From a2d5314cf79d14492a52bfb55e36f21751e7d3fe Mon Sep 17 00:00:00 2001 From: Avently <7953703+avently@users.noreply.github.com> Date: Mon, 6 Jan 2020 13:39:01 +0300 Subject: [PATCH] Fourth block of fixes for review - wrote more methods to PlayQueue. Now it supports internal history of played items with ability to play previous() item. Also it has equals() to check whether queues has the same content or not - backstack in fragment is more powerful now with help of PlayQueue's history and able to work great with playlists' PlayQueue and SinglePlayQueue at the same time - simplified logic inside fragment. Easy to understand. New PlayQueue will be added in backstack from only one place; less number of setInitialData() calls - BasePlayer now able to check PlayQueue and compare it with currently playing. And if it is the same queue it tries to not init() it twice. It gives possibility to have a great backstack in fragment since the same queue will not be played from two different instances and will not be added to backstack twice with duplicated history inside - better support of Player.STATE_IDLE - worked with layouts of player and made them better and more universal - service will be stopped when activity finishes by a user decision - fixed a problem related to ChannelPlayQueue and PlaylistPlayQueue in initial start of fragment - fixed crash in popup --- .../newpipe/fragments/detail/StackItem.java | 6 +- .../fragments/detail/VideoDetailFragment.java | 71 +++++++++---------- .../org/schabi/newpipe/player/BasePlayer.java | 20 +++++- .../org/schabi/newpipe/player/MainPlayer.java | 1 + .../newpipe/player/ServicePlayerActivity.java | 4 ++ .../newpipe/player/VideoPlayerImpl.java | 25 +++++-- .../player/event/PlayerEventListener.java | 2 + .../newpipe/player/playqueue/PlayQueue.java | 58 ++++++++++++++- .../activity_main_player.xml | 31 +++++--- .../main/res/layout/activity_main_player.xml | 31 +++++--- 10 files changed, 184 insertions(+), 65 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/StackItem.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/StackItem.java index 3fb7a7716..2e90a4fc9 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/StackItem.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/StackItem.java @@ -7,7 +7,7 @@ import java.io.Serializable; class StackItem implements Serializable { private final int serviceId; private String title; - private final String url; + private String url; private final PlayQueue playQueue; StackItem(int serviceId, String url, String title, PlayQueue playQueue) { @@ -21,6 +21,10 @@ class StackItem implements Serializable { this.title = title; } + public void setUrl(String url) { + this.url = url; + } + public int getServiceId() { return serviceId; } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 3d42942ba..d35e27c40 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -126,7 +126,7 @@ public class VideoDetailFragment @State protected PlayQueue playQueue; @State - int bottomSheetState = BottomSheetBehavior.STATE_HIDDEN; + int bottomSheetState = BottomSheetBehavior.STATE_EXPANDED; private StreamInfo currentInfo; private Disposable currentWorker; @@ -398,7 +398,8 @@ public class VideoDetailFragment public void onDestroy() { super.onDestroy(); - unbind(); + if (!activity.isFinishing()) unbind(); + else stopService(); PreferenceManager.getDefaultSharedPreferences(activity) .unregisterOnSharedPreferenceChangeListener(this); @@ -850,26 +851,6 @@ public class VideoDetailFragment */ protected final LinkedList stack = new LinkedList<>(); - public void pushToStack(int serviceId, String videoUrl, String name, PlayQueue playQueue) { - if (DEBUG) { - Log.d(TAG, "pushToStack() called with: serviceId = [" - + serviceId + "], videoUrl = [" + videoUrl + "], name = [" + name + "], playQueue = [" + playQueue + "]"); - } - - if (stack.size() > 0 - && stack.peek().getServiceId() == serviceId - && stack.peek().getUrl().equals(videoUrl) - && stack.peek().getPlayQueue().getClass().equals(playQueue.getClass())) { - Log.d(TAG, "pushToStack() called with: serviceId == peek.serviceId = [" - + serviceId + "], videoUrl == peek.getUrl = [" + videoUrl + "]"); - return; - } else { - Log.d(TAG, "pushToStack() wasn't equal"); - } - - stack.push(new StackItem(serviceId, videoUrl, name, playQueue)); - } - public void setTitleToUrl(int serviceId, String videoUrl, String name) { if (name != null && !name.isEmpty()) { for (StackItem stackItem : stack) { @@ -885,12 +866,17 @@ public class VideoDetailFragment public boolean onBackPressed() { if (DEBUG) Log.d(TAG, "onBackPressed() called"); + // If we are in fullscreen mode just exit from it via first back press if (player != null && player.isInFullscreen()) { player.onPause(); restoreDefaultOrientation(); return true; } + // If we have something in history of played items we replay it here + if (player != null && player.getPlayQueue().previous()) { + return true; + } // That means that we are on the start of the stack, // return false to let the MainActivity handle the onBack if (stack.size() <= 1) { @@ -928,15 +914,15 @@ public class VideoDetailFragment } public void selectAndLoadVideo(int serviceId, String videoUrl, String name, PlayQueue playQueue) { - boolean streamIsTheSame = videoUrl.equals(url) && currentInfo != null; - setInitialData(serviceId, videoUrl, name, playQueue); - + boolean streamIsTheSame = this.playQueue != null && this.playQueue.equals(playQueue); // Situation when user switches from players to main player. All needed data is here, we can start watching if (streamIsTheSame) { - handleResult(currentInfo); + //TODO not sure about usefulness of this line in the case when user switches from one player to another + // handleResult(currentInfo); openVideoPlayer(); return; } + setInitialData(serviceId, videoUrl, name, playQueue); startLoading(false); } @@ -944,7 +930,6 @@ public class VideoDetailFragment if (DEBUG) Log.d(TAG, "prepareAndHandleInfo() called with: info = [" + info + "], scrollToTop = [" + scrollToTop + "]"); - setInitialData(info.getServiceId(), info.getUrl(), info.getName(), new SinglePlayQueue(info)); showLoading(); initTabs(); @@ -1390,8 +1375,6 @@ public class VideoDetailFragment setInitialData(info.getServiceId(), info.getOriginalUrl(), info.getName(), playQueue == null ? new SinglePlayQueue(info) : playQueue); - pushToStack(serviceId, url, name, playQueue); - if(showRelatedStreams){ if(null == relatedStreamsLayout){ //phone pageAdapter.updateItem(RELATED_TAB_TAG, RelatedVideosFragment.getInstance(info)); @@ -1627,6 +1610,20 @@ public class VideoDetailFragment // Player event listener //////////////////////////////////////////////////////////////////////////*/ + @Override + public void onQueueUpdate(PlayQueue queue) { + playQueue = queue; + // This should be the only place where we push data to stack. It will allow to have live instance of PlayQueue with actual + // information about deleted/added items inside Channel/Playlist queue and makes possible to have a history of played items + if (stack.isEmpty() || !stack.peek().getPlayQueue().equals(queue)) + stack.push(new StackItem(serviceId, url, name, playQueue)); + + if (DEBUG) { + Log.d(TAG, "onQueueUpdate() called with: serviceId = [" + + serviceId + "], videoUrl = [" + url + "], name = [" + name + "], playQueue = [" + playQueue + "]"); + } + } + @Override public void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters) { setOverlayPlayPauseImage(); @@ -1647,11 +1644,6 @@ public class VideoDetailFragment public void onProgressUpdate(int currentProgress, int duration, int bufferPercent) { // Progress updates every second even if media is paused. It's useless until playing if (!player.getPlayer().isPlaying() || playQueue == null) return; - - // Update current progress in cached playQueue because playQueue in popup and background players - // are different instances - playQueue.setRecovery(playQueue.getIndex(), currentProgress); - showPlaybackProgress(currentProgress, duration); // We don't want to interrupt playback and don't want to see notification if player is stopped @@ -1672,6 +1664,14 @@ public class VideoDetailFragment @Override public void onMetadataUpdate(StreamInfo info) { + if (!stack.isEmpty()) { + // When PlayQueue can have multiple streams (PlaylistPlayQueue or ChannelPlayQueue) every new played stream gives + // new title and url. StackItem contains information about first played stream. Let's update it here + StackItem peek = stack.peek(); + peek.setTitle(info.getName()); + peek.setUrl(info.getUrl()); + } + if (currentInfo == info) return; currentInfo = info; @@ -1865,7 +1865,7 @@ public class VideoDetailFragment case BottomSheetBehavior.STATE_COLLAPSED: // Re-enable clicks setOverlayElementsClickable(true); - if (player != null && player.isInFullscreen() && player.isPlaying()) showSystemUi(); + if (player != null && player.isInFullscreen()) showSystemUi(); break; case BottomSheetBehavior.STATE_DRAGGING: if (player != null && player.isControlsVisible()) player.hideControls(0, 0); @@ -1873,7 +1873,6 @@ public class VideoDetailFragment case BottomSheetBehavior.STATE_SETTLING: break; } - Log.d(TAG, "onStateChanged: " + newState); } @Override public void onSlide(@NonNull View bottomSheet, float slideOffset) { setOverlayLook(appBarLayout, behavior, slideOffset); diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index 2ae822d7f..70ab82fb8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -274,6 +274,16 @@ public abstract class BasePlayer implements return; } + boolean same = playQueue != null && playQueue.equals(queue); + + // Do not re-init the same PlayQueue. Save time + if (same && !playQueue.isDisposed()) { + // Player can have state = IDLE when playback is stopped or failed and we should retry() in this case + if (simpleExoPlayer != null && simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE) + simpleExoPlayer.retry(); + return; + } + final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode()); final float playbackSpeed = intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed()); final float playbackPitch = intent.getFloatExtra(PLAYBACK_PITCH, getPlaybackPitch()); @@ -284,14 +294,17 @@ public abstract class BasePlayer implements if (simpleExoPlayer != null && queue.size() == 1 && playQueue != null + && playQueue.size() == 1 && playQueue.getItem() != null && queue.getItem().getUrl().equals(playQueue.getItem().getUrl()) && queue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET - ) { + && !same) { simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition()); return; - } else if (intent.getBooleanExtra(RESUME_PLAYBACK, false) && isPlaybackResumeEnabled()) { + } else if (intent.getBooleanExtra(RESUME_PLAYBACK, false) + && isPlaybackResumeEnabled() + && !same) { final PlayQueueItem item = queue.getItem(); if (item != null && item.getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) { stateLoader = recordManager.loadStreamState(item) @@ -321,7 +334,8 @@ public abstract class BasePlayer implements } } // Good to go... - initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence, + // In a case of equal PlayQueues we can re-init old one but only when it is disposed + initPlayback(same ? playQueue : queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence, /*playOnInit=*/true); } diff --git a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java index 6e5082494..4e3c070a5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java @@ -126,6 +126,7 @@ public final class MainPlayer extends Service { if (playerImpl.getPlayer() != null) { playerImpl.wasPlaying = playerImpl.getPlayer().getPlayWhenReady(); + // We can't pause the player here because it will make transition from one stream to a new stream not smooth playerImpl.getPlayer().stop(false); playerImpl.setRecovery(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index c8d564557..1c449c77e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -557,6 +557,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity // Binding Service Listener //////////////////////////////////////////////////////////////////////////// + @Override + public void onQueueUpdate(PlayQueue queue) { + } + @Override public void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters) { onStateChanged(state); diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java index d5bb5c86d..21934fb70 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java @@ -281,6 +281,7 @@ public class VideoPlayerImpl extends VideoPlayer private void setupElementsVisibility() { if (popupPlayerSelected()) { fullscreenButton.setVisibility(View.VISIBLE); + getRootView().findViewById(R.id.spaceBeforeControls).setVisibility(View.GONE); getRootView().findViewById(R.id.metadataView).setVisibility(View.GONE); queueButton.setVisibility(View.GONE); moreOptionsButton.setVisibility(View.GONE); @@ -294,10 +295,11 @@ public class VideoPlayerImpl extends VideoPlayer openInBrowser.setVisibility(View.GONE); } else { fullscreenButton.setVisibility(View.GONE); + getRootView().findViewById(R.id.spaceBeforeControls).setVisibility(View.VISIBLE); getRootView().findViewById(R.id.metadataView).setVisibility(View.VISIBLE); moreOptionsButton.setVisibility(View.VISIBLE); getTopControlsRoot().setOrientation(LinearLayout.VERTICAL); - primaryControls.getLayoutParams().width = LinearLayout.LayoutParams.MATCH_PARENT; + primaryControls.getLayoutParams().width = secondaryControls.getLayoutParams().width; secondaryControls.setVisibility(View.GONE); moreOptionsButton.setImageDrawable(service.getResources().getDrawable( R.drawable.ic_expand_more_white_24dp)); @@ -500,7 +502,13 @@ public class VideoPlayerImpl extends VideoPlayer triggerProgressUpdate(); } - /*////////////////////////////////////////////////////////////////////////// + @Override + protected void initPlayback(@NonNull PlayQueue queue, int repeatMode, float playbackSpeed, float playbackPitch, boolean playbackSkipSilence, boolean playOnReady) { + super.initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence, playOnReady); + updateQueue(queue); + } + + /*////////////////////////////////////////////////////////////////////////// // Player Overrides //////////////////////////////////////////////////////////////////////////*/ @@ -1088,7 +1096,7 @@ public class VideoPlayerImpl extends VideoPlayer } private void showSystemUIPartially() { - if (isInFullscreen()) { + if (isInFullscreen() && getParentActivity() != null) { int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_FULLSCREEN; @@ -1106,7 +1114,7 @@ public class VideoPlayerImpl extends VideoPlayer * This method measures width and height of controls visible on screen. It ensures that controls will be side-by-side with * NavigationBar and notches but not under them. Tablets have only bottom NavigationBar * */ - void setControlsSize() { + private void setControlsSize() { Point size = new Point(); Display display = getRootView().getDisplay(); if (display == null) return; @@ -1479,6 +1487,15 @@ public class VideoPlayerImpl extends VideoPlayer } } + private void updateQueue(PlayQueue queue) { + if (fragmentListener != null) { + fragmentListener.onQueueUpdate(queue); + } + if (activityListener != null) { + activityListener.onQueueUpdate(queue); + } + } + private void updateMetadata() { if (fragmentListener != null && getCurrentMetadata() != null) { fragmentListener.onMetadataUpdate(getCurrentMetadata().getMetadata()); diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java index 3a7b29954..37ad9798f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java @@ -4,8 +4,10 @@ package org.schabi.newpipe.player.event; import com.google.android.exoplayer2.PlaybackParameters; import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.player.playqueue.PlayQueue; public interface PlayerEventListener { + void onQueueUpdate(PlayQueue queue); void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters); void onProgressUpdate(int currentProgress, int duration, int bufferPercent); void onMetadataUpdate(StreamInfo info); diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java index fcb1e2819..12454bde9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java @@ -46,17 +46,23 @@ public abstract class PlayQueue implements Serializable { private ArrayList backup; private ArrayList streams; + private ArrayList history; @NonNull private final AtomicInteger queueIndex; private transient BehaviorSubject eventBroadcast; private transient Flowable broadcastReceiver; private transient Subscription reportingReactor; + private transient boolean disposed; + PlayQueue(final int index, final List startWith) { streams = new ArrayList<>(); streams.addAll(startWith); + history = new ArrayList<>(); + history.add(streams.get(index)); queueIndex = new AtomicInteger(index); + disposed = false; } /*////////////////////////////////////////////////////////////////////////// @@ -88,6 +94,7 @@ public abstract class PlayQueue implements Serializable { eventBroadcast = null; broadcastReceiver = null; reportingReactor = null; + disposed = true; } /** @@ -195,6 +202,7 @@ public abstract class PlayQueue implements Serializable { int newIndex = index; if (index < 0) newIndex = 0; if (index >= streams.size()) newIndex = isComplete() ? index % streams.size() : streams.size() - 1; + if (oldIndex != newIndex) history.add(streams.get(newIndex)); queueIndex.set(newIndex); broadcast(new SelectEvent(oldIndex, newIndex)); @@ -267,6 +275,9 @@ public abstract class PlayQueue implements Serializable { if (skippable) { queueIndex.incrementAndGet(); + if (streams.size() > queueIndex.get()) { + history.add(streams.get(queueIndex.get())); + } } else { removeInternal(index); } @@ -292,7 +303,9 @@ public abstract class PlayQueue implements Serializable { final int backupIndex = backup.indexOf(getItem(removeIndex)); backup.remove(backupIndex); } - streams.remove(removeIndex); + + history.remove(streams.remove(removeIndex)); + history.add(streams.get(queueIndex.get())); } /** @@ -366,6 +379,7 @@ public abstract class PlayQueue implements Serializable { streams.add(0, streams.remove(newIndex)); } queueIndex.set(0); + history.add(streams.get(0)); broadcast(new ReorderEvent(originIndex, queueIndex.get())); } @@ -393,10 +407,52 @@ public abstract class PlayQueue implements Serializable { } else { queueIndex.set(0); } + history.add(streams.get(queueIndex.get())); broadcast(new ReorderEvent(originIndex, queueIndex.get())); } + /** + * Selects previous played item + * + * This method removes currently playing item from history and + * starts playing the last item from history if it exists + * + * Returns true if history is not empty and the item can be played + * */ + public synchronized boolean previous() { + if (history.size() <= 1) return false; + + history.remove(history.size() - 1); + + PlayQueueItem last = history.remove(history.size() - 1); + setIndex(indexOf(last)); + + return true; + } + + /* + * Compares two PlayQueues. Useful when a user switches players but queue is the same so + * we don't have to do anything with new queue. This method also gives a chance to track history of items in a queue in + * VideoDetailFragment without duplicating items from two identical queues + * */ + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof PlayQueue) || getStreams().size() != ((PlayQueue) obj).getStreams().size()) + return false; + + PlayQueue other = (PlayQueue) obj; + for (int i = 0; i < getStreams().size(); i++) { + if (!getItem(i).getUrl().equals(other.getItem(i).getUrl())) + return false; + } + + return true; + } + + public boolean isDisposed() { + return disposed; + } /*////////////////////////////////////////////////////////////////////////// // Rx Broadcast //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/res/layout-large-land/activity_main_player.xml b/app/src/main/res/layout-large-land/activity_main_player.xml index ff57bc2bf..d0602ed75 100644 --- a/app/src/main/res/layout-large-land/activity_main_player.xml +++ b/app/src/main/res/layout-large-land/activity_main_player.xml @@ -141,17 +141,29 @@ android:layout_height="match_parent" android:fitsSystemWindows="true"> + + + + + + android:paddingEnd="6dp" + android:layout_toEndOf="@id/spaceBeforeControls" + android:baselineAligned="false"> @@ -274,8 +284,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="top" - android:paddingStart="16dp" - android:paddingEnd="6dp" android:visibility="invisible" tools:ignore="RtlHardcoded" tools:visibility="visible"> @@ -302,6 +310,8 @@ android:layout_marginEnd="8dp" android:gravity="center|left" android:minHeight="35dp" + android:lines="1" + android:ellipsize="end" android:minWidth="50dp" android:textColor="@android:color/white" android:textStyle="bold" @@ -365,10 +375,11 @@ android:id="@+id/fullScreenButton" android:layout_width="40dp" android:layout_height="40dp" - android:paddingEnd="8dp" - android:paddingTop="8dp" + android:layout_marginTop="2dp" + android:layout_marginEnd="2dp" + android:padding="6dp" android:layout_alignParentRight="true" - android:background="@drawable/player_top_controls_bg" + android:background="?attr/selectableItemBackground" android:clickable="true" android:focusable="true" android:scaleType="fitCenter" diff --git a/app/src/main/res/layout/activity_main_player.xml b/app/src/main/res/layout/activity_main_player.xml index 1022d2e95..bf9da748f 100644 --- a/app/src/main/res/layout/activity_main_player.xml +++ b/app/src/main/res/layout/activity_main_player.xml @@ -139,17 +139,29 @@ android:layout_height="match_parent" android:fitsSystemWindows="true"> + + + + + + android:paddingEnd="6dp" + android:layout_toEndOf="@id/spaceBeforeControls" + android:baselineAligned="false"> @@ -272,8 +282,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="top" - android:paddingStart="16dp" - android:paddingEnd="6dp" android:visibility="invisible" tools:ignore="RtlHardcoded" tools:visibility="visible"> @@ -300,6 +308,8 @@ android:layout_marginEnd="8dp" android:gravity="center|left" android:minHeight="35dp" + android:lines="1" + android:ellipsize="end" android:minWidth="50dp" android:textColor="@android:color/white" android:textStyle="bold" @@ -363,10 +373,11 @@ android:id="@+id/fullScreenButton" android:layout_width="40dp" android:layout_height="40dp" - android:paddingEnd="8dp" - android:paddingTop="8dp" + android:layout_marginTop="2dp" + android:layout_marginEnd="2dp" + android:padding="6dp" android:layout_alignParentRight="true" - android:background="@drawable/player_top_controls_bg" + android:background="?attr/selectableItemBackground" android:clickable="true" android:focusable="true" android:scaleType="fitCenter"