Merge pull request #5274 from vkay94/stream-segments

Add stream segments to player controls
This commit is contained in:
Robin 2021-01-15 10:59:34 +01:00 committed by GitHub
commit 98ed80d305
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 406 additions and 49 deletions

View File

@ -27,7 +27,7 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
private boolean allowScroll = true; private boolean allowScroll = true;
private final Rect globalRect = new Rect(); private final Rect globalRect = new Rect();
private final List<Integer> skipInterceptionOfElements = Arrays.asList( private final List<Integer> skipInterceptionOfElements = Arrays.asList(
R.id.playQueuePanel, R.id.playbackSeekBar, R.id.itemsListPanel, R.id.playbackSeekBar,
R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton); R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton);
@Override @Override

View File

@ -2283,7 +2283,7 @@ public final class VideoDetailFragment
// Re-enable clicks // Re-enable clicks
setOverlayElementsClickable(true); setOverlayElementsClickable(true);
if (player != null) { if (player != null) {
player.closeQueue(); player.closeItemsList();
} }
setOverlayLook(appBarLayout, behavior, 0); setOverlayLook(appBarLayout, behavior, 0);
break; break;

View File

@ -0,0 +1,66 @@
package org.schabi.newpipe.info_list
import android.util.Log
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.GroupieViewHolder
import org.schabi.newpipe.extractor.stream.StreamInfo
import kotlin.math.max
/**
* Custom RecyclerView.Adapter/GroupieAdapter for [StreamSegmentItem] for handling selection state.
*/
class StreamSegmentAdapter(
private val listener: StreamSegmentListener
) : GroupAdapter<GroupieViewHolder>() {
var currentIndex: Int = 0
private set
/**
* Returns `true` if the provided [StreamInfo] contains segments, `false` otherwise.
*/
fun setItems(info: StreamInfo): Boolean {
if (info.streamSegments.isNotEmpty()) {
clear()
addAll(info.streamSegments.map { StreamSegmentItem(it, listener) })
return true
}
return false
}
fun selectSegment(segment: StreamSegmentItem) {
unSelectCurrentSegment()
currentIndex = max(0, getAdapterPosition(segment))
segment.isSelected = true
segment.notifyChanged(StreamSegmentItem.PAYLOAD_SELECT)
}
fun selectSegmentAt(position: Int) {
try {
selectSegment(getGroupAtAdapterPosition(position) as StreamSegmentItem)
} catch (e: IndexOutOfBoundsException) {
// Just to make sure that getGroupAtAdapterPosition doesn't close the app
// Shouldn't happen since setItems is always called before select-methods but just in case
currentIndex = 0
Log.e("StreamSegmentAdapter", "selectSegmentAt: ${e.message}")
}
}
private fun unSelectCurrentSegment() {
try {
val segmentItem = getGroupAtAdapterPosition(currentIndex) as StreamSegmentItem
currentIndex = 0
segmentItem.isSelected = false
segmentItem.notifyChanged(StreamSegmentItem.PAYLOAD_SELECT)
} catch (e: IndexOutOfBoundsException) {
// Just to make sure that getGroupAtAdapterPosition doesn't close the app
// Shouldn't happen since setItems is always called before select-methods but just in case
currentIndex = 0
Log.e("StreamSegmentAdapter", "unSelectCurrentSegment: ${e.message}")
}
}
interface StreamSegmentListener {
fun onItemClick(item: StreamSegmentItem, seconds: Int)
}
}

View File

@ -0,0 +1,47 @@
package org.schabi.newpipe.info_list
import android.widget.ImageView
import android.widget.TextView
import com.nostra13.universalimageloader.core.ImageLoader
import com.xwray.groupie.GroupieViewHolder
import com.xwray.groupie.Item
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.stream.StreamSegment
import org.schabi.newpipe.util.ImageDisplayConstants
import org.schabi.newpipe.util.Localization
class StreamSegmentItem(
private val item: StreamSegment,
private val onClick: StreamSegmentAdapter.StreamSegmentListener
) : Item<GroupieViewHolder>() {
companion object {
const val PAYLOAD_SELECT = 1
}
var isSelected = false
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
item.previewUrl?.let {
ImageLoader.getInstance().displayImage(
it, viewHolder.root.findViewById<ImageView>(R.id.previewImage),
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS
)
}
viewHolder.root.findViewById<TextView>(R.id.textViewTitle).text = item.title
viewHolder.root.findViewById<TextView>(R.id.textViewStartSeconds).text =
Localization.getDurationString(item.startTimeSeconds.toLong())
viewHolder.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) }
viewHolder.root.isSelected = isSelected
}
override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.contains(PAYLOAD_SELECT)) {
viewHolder.root.isSelected = isSelected
return
}
super.bind(viewHolder, position, payloads)
}
override fun getLayout() = R.layout.item_stream_segment
}

View File

@ -151,7 +151,7 @@ public final class MainPlayer extends Service {
// Android TV will handle back button in case controls will be visible // Android TV will handle back button in case controls will be visible
// (one more additional unneeded click while the player is hidden) // (one more additional unneeded click while the player is hidden)
player.hideControls(0, 0); player.hideControls(0, 0);
player.closeQueue(); player.closeItemsList();
// Notification shows information about old stream but if a user selects // Notification shows information about old stream but if a user selects
// a stream from backStack it's not actual anymore // a stream from backStack it's not actual anymore
// So we should hide the notification at all. // So we should hide the notification at all.

View File

@ -87,9 +87,11 @@ import org.schabi.newpipe.databinding.PlayerBinding;
import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding; import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamSegment;
import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.info_list.StreamSegmentAdapter;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.MainPlayer.PlayerType; import org.schabi.newpipe.player.MainPlayer.PlayerType;
import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.event.PlayerEventListener;
@ -245,6 +247,7 @@ public final class Player implements
private PlayQueue playQueue; private PlayQueue playQueue;
private PlayQueueAdapter playQueueAdapter; private PlayQueueAdapter playQueueAdapter;
private StreamSegmentAdapter segmentAdapter;
@Nullable private MediaSourceManager playQueueManager; @Nullable private MediaSourceManager playQueueManager;
@ -301,6 +304,7 @@ public final class Player implements
// fullscreen player // fullscreen player
private boolean isQueueVisible = false; private boolean isQueueVisible = false;
private boolean areSegmentsVisible = false;
private ItemTouchHelper itemTouchHelper; private ItemTouchHelper itemTouchHelper;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -454,7 +458,7 @@ public final class Player implements
binding.channelTextView.setSelected(true); binding.channelTextView.setSelected(true);
// Prevent hiding of bottom sheet via swipe inside queue // Prevent hiding of bottom sheet via swipe inside queue
binding.playQueue.setNestedScrollingEnabled(false); binding.itemsList.setNestedScrollingEnabled(false);
} }
private void initPlayer(final boolean playOnReady) { private void initPlayer(final boolean playOnReady) {
@ -505,6 +509,7 @@ public final class Player implements
binding.getRoot().setOnTouchListener(listener); binding.getRoot().setOnTouchListener(listener);
binding.queueButton.setOnClickListener(this); binding.queueButton.setOnClickListener(this);
binding.segmentsButton.setOnClickListener(this);
binding.repeatButton.setOnClickListener(this); binding.repeatButton.setOnClickListener(this);
binding.shuffleButton.setOnClickListener(this); binding.shuffleButton.setOnClickListener(this);
@ -533,7 +538,7 @@ public final class Player implements
settingsContentObserver); settingsContentObserver);
binding.getRoot().addOnLayoutChangeListener(this::onLayoutChange); binding.getRoot().addOnLayoutChangeListener(this::onLayoutChange);
ViewCompat.setOnApplyWindowInsetsListener(binding.playQueuePanel, (view, windowInsets) -> { ViewCompat.setOnApplyWindowInsetsListener(binding.itemsListPanel, (view, windowInsets) -> {
final DisplayCutoutCompat cutout = windowInsets.getDisplayCutout(); final DisplayCutoutCompat cutout = windowInsets.getDisplayCutout();
if (cutout != null) { if (cutout != null) {
view.setPadding(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(), view.setPadding(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
@ -665,11 +670,11 @@ public final class Player implements
playbackSkipSilence, playWhenReady, isMuted); playbackSkipSilence, playWhenReady, isMuted);
}, },
() -> { () -> {
// Completed but not found in history // Completed but not found in history
initPlayback(newQueue, repeatMode, playbackSpeed, playbackPitch, initPlayback(newQueue, repeatMode, playbackSpeed, playbackPitch,
playbackSkipSilence, playWhenReady, isMuted); playbackSkipSilence, playWhenReady, isMuted);
} }
)); ));
} else { } else {
// Good to go... // Good to go...
// In a case of equal PlayQueues we can re-init old one but only when it is disposed // In a case of equal PlayQueues we can re-init old one but only when it is disposed
@ -697,7 +702,7 @@ public final class Player implements
} else { } else {
binding.getRoot().setVisibility(View.VISIBLE); binding.getRoot().setVisibility(View.VISIBLE);
initVideoPlayer(); initVideoPlayer();
closeQueue(); closeItemsList();
// Android TV: without it focus will frame the whole player // Android TV: without it focus will frame the whole player
binding.playPauseButton.requestFocus(); binding.playPauseButton.requestFocus();
@ -730,6 +735,7 @@ public final class Player implements
playQueueAdapter.dispose(); playQueueAdapter.dispose();
} }
playQueueAdapter = new PlayQueueAdapter(context, playQueue); playQueueAdapter = new PlayQueueAdapter(context, playQueue);
segmentAdapter = new StreamSegmentAdapter(getStreamSegmentListener());
simpleExoPlayer.setVolume(isMuted ? 0 : 1); simpleExoPlayer.setVolume(isMuted ? 0 : 1);
notifyQueueUpdateToListeners(); notifyQueueUpdateToListeners();
@ -923,6 +929,7 @@ public final class Player implements
binding.resizeTextView.setVisibility(View.GONE); binding.resizeTextView.setVisibility(View.GONE);
binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.GONE); binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.GONE);
binding.queueButton.setVisibility(View.GONE); binding.queueButton.setVisibility(View.GONE);
binding.segmentsButton.setVisibility(View.GONE);
binding.moreOptionsButton.setVisibility(View.GONE); binding.moreOptionsButton.setVisibility(View.GONE);
binding.topControls.setOrientation(LinearLayout.HORIZONTAL); binding.topControls.setOrientation(LinearLayout.HORIZONTAL);
binding.primaryControls.getLayoutParams().width binding.primaryControls.getLayoutParams().width
@ -939,7 +946,7 @@ public final class Player implements
binding.topControls.setClickable(false); binding.topControls.setClickable(false);
binding.topControls.setFocusable(false); binding.topControls.setFocusable(false);
binding.bottomControls.bringToFront(); binding.bottomControls.bringToFront();
closeQueue(); closeItemsList();
} else if (videoPlayerSelected()) { } else if (videoPlayerSelected()) {
binding.fullScreenButton.setVisibility(View.GONE); binding.fullScreenButton.setVisibility(View.GONE);
setupScreenRotationButton(); setupScreenRotationButton();
@ -1123,7 +1130,7 @@ public final class Player implements
} }
// Close it because when changing orientation from portrait // Close it because when changing orientation from portrait
// (in fullscreen mode) the size of queue layout can be larger than the screen size // (in fullscreen mode) the size of queue layout can be larger than the screen size
closeQueue(); closeItemsList();
break; break;
case Intent.ACTION_SCREEN_ON: case Intent.ACTION_SCREEN_ON:
// Interrupt playback only when screen turns on // Interrupt playback only when screen turns on
@ -1484,6 +1491,10 @@ public final class Player implements
notifyProgressUpdateToListeners(currentProgress, duration, bufferPercent); notifyProgressUpdateToListeners(currentProgress, duration, bufferPercent);
if (areSegmentsVisible) {
segmentAdapter.selectSegmentAt(getNearestStreamSegmentPosition(currentProgress));
}
final boolean showThumbnail = prefs.getBoolean( final boolean showThumbnail = prefs.getBoolean(
context.getString(R.string.show_thumbnail_key), true); context.getString(R.string.show_thumbnail_key), true);
// setMetadata only updates the metadata when any of the metadata keys are null // setMetadata only updates the metadata when any of the metadata keys are null
@ -1696,10 +1707,10 @@ public final class Player implements
controlsVisibilityHandler.removeCallbacksAndMessages(null); controlsVisibilityHandler.removeCallbacksAndMessages(null);
controlsVisibilityHandler.postDelayed(() -> { controlsVisibilityHandler.postDelayed(() -> {
showHideShadow(false, duration); showHideShadow(false, duration);
animateView(binding.playbackControlRoot, false, duration, 0, animateView(binding.playbackControlRoot, false, duration, 0,
this::hideSystemUIIfNeeded); this::hideSystemUIIfNeeded);
}, delay); }, delay);
} }
private void showHideShadow(final boolean show, final long duration) { private void showHideShadow(final boolean show, final long duration) {
@ -1715,6 +1726,11 @@ public final class Player implements
final boolean showPrev = playQueue.getIndex() != 0; final boolean showPrev = playQueue.getIndex() != 0;
final boolean showNext = playQueue.getIndex() + 1 != playQueue.getStreams().size(); final boolean showNext = playQueue.getIndex() + 1 != playQueue.getStreams().size();
final boolean showQueue = playQueue.getStreams().size() > 1 && !popupPlayerSelected(); final boolean showQueue = playQueue.getStreams().size() > 1 && !popupPlayerSelected();
boolean showSegment = false;
if (currentMetadata != null) {
showSegment = !currentMetadata.getMetadata().getStreamSegments().isEmpty()
&& !popupPlayerSelected();
}
binding.playPreviousButton.setVisibility(showPrev ? View.VISIBLE : View.INVISIBLE); binding.playPreviousButton.setVisibility(showPrev ? View.VISIBLE : View.INVISIBLE);
binding.playPreviousButton.setAlpha(showPrev ? 1.0f : 0.0f); binding.playPreviousButton.setAlpha(showPrev ? 1.0f : 0.0f);
@ -1722,6 +1738,8 @@ public final class Player implements
binding.playNextButton.setAlpha(showNext ? 1.0f : 0.0f); binding.playNextButton.setAlpha(showNext ? 1.0f : 0.0f);
binding.queueButton.setVisibility(showQueue ? View.VISIBLE : View.GONE); binding.queueButton.setVisibility(showQueue ? View.VISIBLE : View.GONE);
binding.queueButton.setAlpha(showQueue ? 1.0f : 0.0f); binding.queueButton.setAlpha(showQueue ? 1.0f : 0.0f);
binding.segmentsButton.setVisibility(showSegment ? View.VISIBLE : View.GONE);
binding.segmentsButton.setAlpha(showSegment ? 1.0f : 0.0f);
} }
private void showSystemUIPartially() { private void showSystemUIPartially() {
@ -2725,6 +2743,17 @@ public final class Player implements
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
notifyMetadataUpdateToListeners(); notifyMetadataUpdateToListeners();
if (areSegmentsVisible) {
if (segmentAdapter.setItems(info)) {
final int adapterPosition = getNearestStreamSegmentPosition(
simpleExoPlayer.getCurrentPosition());
segmentAdapter.selectSegmentAt(adapterPosition);
binding.itemsList.scrollToPosition(adapterPosition);
} else {
closeItemsList();
}
}
} }
private void maybeUpdateCurrentMetadata() { private void maybeUpdateCurrentMetadata() {
@ -2787,7 +2816,7 @@ public final class Player implements
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Play queue and streams // Play queue, segments and streams
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
//region //region
@ -2835,41 +2864,90 @@ public final class Player implements
hideSystemUIIfNeeded(); hideSystemUIIfNeeded();
buildQueue(); buildQueue();
//updatePlaybackButtons();//TODO verify this can be removed
binding.itemsListHeaderTitle.setVisibility(View.GONE);
binding.shuffleButton.setVisibility(View.VISIBLE);
binding.repeatButton.setVisibility(View.VISIBLE);
hideControls(0, 0); hideControls(0, 0);
binding.playQueuePanel.requestFocus(); binding.itemsListPanel.requestFocus();
animateView(binding.playQueuePanel, SLIDE_AND_ALPHA, true, animateView(binding.itemsListPanel, SLIDE_AND_ALPHA, true,
DEFAULT_CONTROLS_DURATION); DEFAULT_CONTROLS_DURATION);
binding.playQueue.scrollToPosition(playQueue.getIndex()); binding.itemsList.scrollToPosition(playQueue.getIndex());
} }
private void buildQueue() { private void buildQueue() {
binding.playQueue.setAdapter(playQueueAdapter); binding.itemsList.setAdapter(playQueueAdapter);
binding.playQueue.setClickable(true); binding.itemsList.setClickable(true);
binding.playQueue.setLongClickable(true); binding.itemsList.setLongClickable(true);
binding.playQueue.clearOnScrollListeners(); binding.itemsList.clearOnScrollListeners();
binding.playQueue.addOnScrollListener(getQueueScrollListener()); binding.itemsList.addOnScrollListener(getQueueScrollListener());
itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
itemTouchHelper.attachToRecyclerView(binding.playQueue); itemTouchHelper.attachToRecyclerView(binding.itemsList);
playQueueAdapter.setSelectedListener(getOnSelectedListener()); playQueueAdapter.setSelectedListener(getOnSelectedListener());
binding.playQueueClose.setOnClickListener(view -> closeQueue()); binding.itemsListClose.setOnClickListener(view -> closeItemsList());
} }
public void closeQueue() { private void onSegmentsClicked() {
if (isQueueVisible) { areSegmentsVisible = true;
hideSystemUIIfNeeded();
buildSegments();
binding.itemsListHeaderTitle.setVisibility(View.VISIBLE);
binding.shuffleButton.setVisibility(View.GONE);
binding.repeatButton.setVisibility(View.GONE);
hideControls(0, 0);
binding.itemsListPanel.requestFocus();
animateView(binding.itemsListPanel, SLIDE_AND_ALPHA, true,
DEFAULT_CONTROLS_DURATION);
final int adapterPosition = getNearestStreamSegmentPosition(simpleExoPlayer
.getCurrentPosition());
segmentAdapter.selectSegmentAt(adapterPosition);
binding.itemsList.scrollToPosition(adapterPosition);
}
private void buildSegments() {
binding.itemsList.setAdapter(segmentAdapter);
binding.itemsList.setClickable(true);
binding.itemsList.setLongClickable(false);
binding.itemsList.clearOnScrollListeners();
if (itemTouchHelper != null) {
itemTouchHelper.attachToRecyclerView(null);
}
if (currentMetadata != null) {
segmentAdapter.setItems(currentMetadata.getMetadata());
}
binding.shuffleButton.setVisibility(View.GONE);
binding.repeatButton.setVisibility(View.GONE);
binding.itemsListClose.setOnClickListener(view -> closeItemsList());
}
public void closeItemsList() {
if (isQueueVisible || areSegmentsVisible) {
isQueueVisible = false; isQueueVisible = false;
animateView(binding.playQueuePanel, SLIDE_AND_ALPHA, false, areSegmentsVisible = false;
if (itemTouchHelper != null) {
itemTouchHelper.attachToRecyclerView(null);
}
animateView(binding.itemsListPanel, SLIDE_AND_ALPHA, false,
DEFAULT_CONTROLS_DURATION, 0, () -> { DEFAULT_CONTROLS_DURATION, 0, () -> {
// Even when queueLayout is GONE it receives touch events // Even when queueLayout is GONE it receives touch events
// and ruins normal behavior of the app. This line fixes it // and ruins normal behavior of the app. This line fixes it
binding.playQueuePanel.setTranslationY( binding.itemsListPanel.setTranslationY(
-binding.playQueuePanel.getHeight() * 5); -binding.itemsListPanel.getHeight() * 5);
}); });
binding.playPauseButton.requestFocus(); binding.playPauseButton.requestFocus();
} }
@ -2882,12 +2960,33 @@ public final class Player implements
if (playQueue != null && !playQueue.isComplete()) { if (playQueue != null && !playQueue.isComplete()) {
playQueue.fetch(); playQueue.fetch();
} else if (binding != null) { } else if (binding != null) {
binding.playQueue.clearOnScrollListeners(); binding.itemsList.clearOnScrollListeners();
} }
} }
}; };
} }
private StreamSegmentAdapter.StreamSegmentListener getStreamSegmentListener() {
return (item, seconds) -> {
segmentAdapter.selectSegment(item);
seekTo(seconds * 1000);
triggerProgressUpdate();
};
}
private int getNearestStreamSegmentPosition(final long playbackPosition) {
int nearestPosition = 0;
final List<StreamSegment> segments = currentMetadata.getMetadata().getStreamSegments();
for (int i = 0; i < segments.size(); i++) {
if (segments.get(i).getStartTimeSeconds() * 1000 > playbackPosition) {
break;
}
nearestPosition++;
}
return Math.max(0, nearestPosition - 1);
}
private ItemTouchHelper.SimpleCallback getItemTouchCallback() { private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
return new PlayQueueItemTouchCallback() { return new PlayQueueItemTouchCallback() {
@Override @Override
@ -3313,6 +3412,9 @@ public final class Player implements
} else if (v.getId() == binding.queueButton.getId()) { } else if (v.getId() == binding.queueButton.getId()) {
onQueueClicked(); onQueueClicked();
return; return;
} else if (v.getId() == binding.segmentsButton.getId()) {
onSegmentsClicked();
return;
} else if (v.getId() == binding.repeatButton.getId()) { } else if (v.getId() == binding.repeatButton.getId()) {
onRepeatClicked(); onRepeatClicked();
return; return;
@ -3610,8 +3712,8 @@ public final class Player implements
binding.brightnessProgressBar.setMax(maxGestureLength); binding.brightnessProgressBar.setMax(maxGestureLength);
setInitialGestureValues(); setInitialGestureValues();
binding.playQueuePanel.getLayoutParams().height binding.itemsListPanel.getLayoutParams().height
= height - binding.playQueuePanel.getTop(); = height - binding.itemsListPanel.getTop();
} }
} }
@ -3663,7 +3765,7 @@ public final class Player implements
if (!isFullscreen) { if (!isFullscreen) {
binding.playbackControlRoot.setPadding(0, 0, 0, 0); binding.playbackControlRoot.setPadding(0, 0, 0, 0);
} }
binding.playQueuePanel.setPadding(0, 0, 0, 0); binding.itemsListPanel.setPadding(0, 0, 0, 0);
notifyQueueUpdateToListeners(); notifyQueueUpdateToListeners();
notifyMetadataUpdateToListeners(); notifyMetadataUpdateToListeners();
notifyPlaybackUpdateToListeners(); notifyPlaybackUpdateToListeners();

View File

@ -24,7 +24,7 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior<FrameLayout>
private boolean skippingInterception = false; private boolean skippingInterception = false;
private final List<Integer> skipInterceptionOfElements = Arrays.asList( private final List<Integer> skipInterceptionOfElements = Arrays.asList(
R.id.detail_content_root_layout, R.id.relatedStreamsLayout, R.id.detail_content_root_layout, R.id.relatedStreamsLayout,
R.id.playQueuePanel, R.id.viewpager, R.id.bottomControls, R.id.itemsListPanel, R.id.viewpager, R.id.bottomControls,
R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton); R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton);
@Override @Override

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M2,17h2v0.5L3,17.5v1h1v0.5L2,19v1h3v-4L2,16v1zM3,8h1L4,4L2,4v1h1v3zM2,11h1.8L2,13.1v0.9h3v-1L3.2,13L5,10.9L5,10L2,10v1zM7,5v2h14L21,5L7,5zM7,19h14v-2L7,17v2zM7,13h14v-2L7,11v2z" />
</vector>

View File

@ -191,6 +191,24 @@
tools:ignore="ContentDescription,RtlHardcoded" tools:ignore="ContentDescription,RtlHardcoded"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/segmentsButton"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginEnd="8dp"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingStart="3dp"
android:paddingTop="5dp"
android:paddingEnd="3dp"
android:paddingBottom="3dp"
android:scaleType="fitCenter"
android:visibility="gone"
app:srcCompat="@drawable/ic_format_list_numbered_white_24"
tools:ignore="ContentDescription,RtlHardcoded"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/moreOptionsButton" android:id="@+id/moreOptionsButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -452,7 +470,7 @@
</RelativeLayout> </RelativeLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/playQueuePanel" android:id="@+id/itemsListPanel"
android:layout_width="380dp" android:layout_width="380dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
@ -461,14 +479,30 @@
tools:visibility="visible"> tools:visibility="visible">
<RelativeLayout <RelativeLayout
android:id="@+id/playQueueControl" android:id="@+id/itemsListControl"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="60dp" android:layout_height="60dp"
android:clickable="true" android:clickable="true"
android:focusable="true"> android:focusable="true">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/itemsListHeaderTitle"
style="@style/TextAppearance.AppCompat.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="@id/itemsListClose"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginStart="16dp"
android:layout_marginEnd="56dp"
android:ellipsize="end"
android:maxLines="2"
android:text="@string/chapters"
android:textColor="@android:color/white"
android:visibility="gone" />
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/playQueueClose" android:id="@+id/itemsListClose"
android:layout_width="50dp" android:layout_width="50dp"
android:layout_height="50dp" android:layout_height="50dp"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
@ -517,10 +551,10 @@
</RelativeLayout> </RelativeLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/playQueue" android:id="@+id/itemsList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@id/playQueueControl" android:layout_below="@id/itemsListControl"
android:scrollbars="vertical" android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/play_queue_item" /> tools:listitem="@layout/play_queue_item" />

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selector"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
android:paddingStart="16dp"
android:paddingTop="4dp"
android:paddingEnd="16dp"
android:paddingBottom="4dp">
<ImageView
android:id="@+id/previewImage"
android:layout_width="0dp"
android:layout_height="@dimen/play_queue_thumbnail_width"
android:scaleType="centerCrop"
android:src="@drawable/dummy_thumbnail"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<LinearLayout
android:id="@+id/textContainer"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
android:paddingStart="8dp"
android:paddingEnd="0dp"
app:layout_constraintBottom_toBottomOf="@id/previewImage"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/previewImage"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textViewTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_search_title_text_size"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Lorem ipusum is widely used to create long sample text which is used here too" />
<TextView
android:id="@+id/textViewStartSeconds"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textViewTitle"
tools:text="04:26" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -192,6 +192,23 @@
app:srcCompat="@drawable/ic_list_white_24dp" app:srcCompat="@drawable/ic_list_white_24dp"
tools:ignore="ContentDescription,RtlHardcoded" /> tools:ignore="ContentDescription,RtlHardcoded" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/segmentsButton"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginEnd="8dp"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingStart="6dp"
android:paddingTop="5dp"
android:paddingEnd="6dp"
android:paddingBottom="3dp"
android:scaleType="fitCenter"
android:visibility="gone"
app:srcCompat="@drawable/ic_format_list_numbered_white_24"
tools:ignore="ContentDescription,RtlHardcoded" />
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/moreOptionsButton" android:id="@+id/moreOptionsButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -450,7 +467,7 @@
</RelativeLayout> </RelativeLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/playQueuePanel" android:id="@+id/itemsListPanel"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/queue_background_color" android:background="?attr/queue_background_color"
@ -458,14 +475,30 @@
tools:visibility="visible"> tools:visibility="visible">
<RelativeLayout <RelativeLayout
android:id="@+id/playQueueControl" android:id="@+id/itemsListControl"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="60dp" android:layout_height="60dp"
android:clickable="true" android:clickable="true"
android:focusable="true"> android:focusable="true">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/itemsListHeaderTitle"
style="@style/TextAppearance.AppCompat.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="@id/itemsListClose"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginStart="16dp"
android:layout_marginEnd="56dp"
android:ellipsize="end"
android:maxLines="2"
android:text="@string/chapters"
android:textColor="@android:color/white"
android:visibility="gone" />
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/playQueueClose" android:id="@+id/itemsListClose"
android:layout_width="50dp" android:layout_width="50dp"
android:layout_height="50dp" android:layout_height="50dp"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
@ -514,10 +547,10 @@
</RelativeLayout> </RelativeLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/playQueue" android:id="@+id/itemsList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@id/playQueueControl" android:layout_below="@id/itemsListControl"
android:scrollbars="vertical" android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/play_queue_item" /> tools:listitem="@layout/play_queue_item" />

View File

@ -692,4 +692,5 @@
<string name="show_thumbnail_title">Show thumbnail</string> <string name="show_thumbnail_title">Show thumbnail</string>
<string name="show_thumbnail_summary">Use thumbnail for both lock screen background and notifications</string> <string name="show_thumbnail_summary">Use thumbnail for both lock screen background and notifications</string>
<string name="recent">Recent</string> <string name="recent">Recent</string>
<string name="chapters">Chapters</string>
</resources> </resources>