mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-30 23:03:00 +00:00 
			
		
		
		
	Add stream segments to player
This commit is contained in:
		| @@ -27,7 +27,7 @@ public final class FlingBehavior extends AppBarLayout.Behavior { | ||||
|     private boolean allowScroll = true; | ||||
|     private final Rect globalRect = new Rect(); | ||||
|     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); | ||||
|  | ||||
|     @Override | ||||
|   | ||||
| @@ -2283,7 +2283,7 @@ public final class VideoDetailFragment | ||||
|                         // Re-enable clicks | ||||
|                         setOverlayElementsClickable(true); | ||||
|                         if (player != null) { | ||||
|                             player.closeQueue(); | ||||
|                             player.closeItemsList(); | ||||
|                         } | ||||
|                         setOverlayLook(appBarLayout, behavior, 0); | ||||
|                         break; | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
| @@ -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 | ||||
| } | ||||
| @@ -151,7 +151,7 @@ public final class MainPlayer extends Service { | ||||
|             // Android TV will handle back button in case controls will be visible | ||||
|             // (one more additional unneeded click while the player is hidden) | ||||
|             player.hideControls(0, 0); | ||||
|             player.closeQueue(); | ||||
|             player.closeItemsList(); | ||||
|             // Notification shows information about old stream but if a user selects | ||||
|             // a stream from backStack it's not actual anymore | ||||
|             // So we should hide the notification at all. | ||||
|   | ||||
| @@ -87,9 +87,11 @@ import org.schabi.newpipe.databinding.PlayerBinding; | ||||
| import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding; | ||||
| import org.schabi.newpipe.extractor.MediaFormat; | ||||
| 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.fragments.OnScrollBelowItemsListener; | ||||
| 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.player.MainPlayer.PlayerType; | ||||
| import org.schabi.newpipe.player.event.PlayerEventListener; | ||||
| @@ -245,6 +247,7 @@ public final class Player implements | ||||
|  | ||||
|     private PlayQueue playQueue; | ||||
|     private PlayQueueAdapter playQueueAdapter; | ||||
|     private StreamSegmentAdapter segmentAdapter; | ||||
|  | ||||
|     @Nullable private MediaSourceManager playQueueManager; | ||||
|  | ||||
| @@ -301,6 +304,7 @@ public final class Player implements | ||||
|  | ||||
|     // fullscreen player | ||||
|     private boolean isQueueVisible = false; | ||||
|     private boolean areSegmentsVisible = false; | ||||
|     private ItemTouchHelper itemTouchHelper; | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
| @@ -454,7 +458,7 @@ public final class Player implements | ||||
|         binding.channelTextView.setSelected(true); | ||||
|  | ||||
|         // Prevent hiding of bottom sheet via swipe inside queue | ||||
|         binding.playQueue.setNestedScrollingEnabled(false); | ||||
|         binding.itemsList.setNestedScrollingEnabled(false); | ||||
|     } | ||||
|  | ||||
|     private void initPlayer(final boolean playOnReady) { | ||||
| @@ -505,6 +509,7 @@ public final class Player implements | ||||
|         binding.getRoot().setOnTouchListener(listener); | ||||
|  | ||||
|         binding.queueButton.setOnClickListener(this); | ||||
|         binding.segmentsButton.setOnClickListener(this); | ||||
|         binding.repeatButton.setOnClickListener(this); | ||||
|         binding.shuffleButton.setOnClickListener(this); | ||||
|  | ||||
| @@ -533,7 +538,7 @@ public final class Player implements | ||||
|                 settingsContentObserver); | ||||
|         binding.getRoot().addOnLayoutChangeListener(this::onLayoutChange); | ||||
|  | ||||
|         ViewCompat.setOnApplyWindowInsetsListener(binding.playQueuePanel, (view, windowInsets) -> { | ||||
|         ViewCompat.setOnApplyWindowInsetsListener(binding.itemsListPanel, (view, windowInsets) -> { | ||||
|             final DisplayCutoutCompat cutout = windowInsets.getDisplayCutout(); | ||||
|             if (cutout != null) { | ||||
|                 view.setPadding(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(), | ||||
| @@ -665,11 +670,11 @@ public final class Player implements | ||||
|                                         playbackSkipSilence, playWhenReady, isMuted); | ||||
|                             }, | ||||
|                             () -> { | ||||
|                                     // Completed but not found in history | ||||
|                                     initPlayback(newQueue, repeatMode, playbackSpeed, playbackPitch, | ||||
|                                             playbackSkipSilence, playWhenReady, isMuted); | ||||
|                                 } | ||||
|                         )); | ||||
|                                 // Completed but not found in history | ||||
|                                 initPlayback(newQueue, repeatMode, playbackSpeed, playbackPitch, | ||||
|                                         playbackSkipSilence, playWhenReady, isMuted); | ||||
|                             } | ||||
|                     )); | ||||
|         } else { | ||||
|             // Good to go... | ||||
|             // 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 { | ||||
|             binding.getRoot().setVisibility(View.VISIBLE); | ||||
|             initVideoPlayer(); | ||||
|             closeQueue(); | ||||
|             closeItemsList(); | ||||
|             // Android TV: without it focus will frame the whole player | ||||
|             binding.playPauseButton.requestFocus(); | ||||
|  | ||||
| @@ -730,6 +735,7 @@ public final class Player implements | ||||
|             playQueueAdapter.dispose(); | ||||
|         } | ||||
|         playQueueAdapter = new PlayQueueAdapter(context, playQueue); | ||||
|         segmentAdapter = new StreamSegmentAdapter(getStreamSegmentListener()); | ||||
|  | ||||
|         simpleExoPlayer.setVolume(isMuted ? 0 : 1); | ||||
|         notifyQueueUpdateToListeners(); | ||||
| @@ -923,6 +929,7 @@ public final class Player implements | ||||
|             binding.resizeTextView.setVisibility(View.GONE); | ||||
|             binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.GONE); | ||||
|             binding.queueButton.setVisibility(View.GONE); | ||||
|             binding.segmentsButton.setVisibility(View.GONE); | ||||
|             binding.moreOptionsButton.setVisibility(View.GONE); | ||||
|             binding.topControls.setOrientation(LinearLayout.HORIZONTAL); | ||||
|             binding.primaryControls.getLayoutParams().width | ||||
| @@ -939,7 +946,7 @@ public final class Player implements | ||||
|             binding.topControls.setClickable(false); | ||||
|             binding.topControls.setFocusable(false); | ||||
|             binding.bottomControls.bringToFront(); | ||||
|             closeQueue(); | ||||
|             closeItemsList(); | ||||
|         } else if (videoPlayerSelected()) { | ||||
|             binding.fullScreenButton.setVisibility(View.GONE); | ||||
|             setupScreenRotationButton(); | ||||
| @@ -1123,7 +1130,7 @@ public final class Player implements | ||||
|                 } | ||||
|                 // Close it because when changing orientation from portrait | ||||
|                 // (in fullscreen mode) the size of queue layout can be larger than the screen size | ||||
|                 closeQueue(); | ||||
|                 closeItemsList(); | ||||
|                 break; | ||||
|             case Intent.ACTION_SCREEN_ON: | ||||
|                 // Interrupt playback only when screen turns on | ||||
| @@ -1484,6 +1491,10 @@ public final class Player implements | ||||
|  | ||||
|         notifyProgressUpdateToListeners(currentProgress, duration, bufferPercent); | ||||
|  | ||||
|         if (areSegmentsVisible) { | ||||
|             segmentAdapter.selectSegmentAt(getNearestStreamSegmentPosition(currentProgress)); | ||||
|         } | ||||
|  | ||||
|         final boolean showThumbnail = prefs.getBoolean( | ||||
|                 context.getString(R.string.show_thumbnail_key), true); | ||||
|         // 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.postDelayed(() -> { | ||||
|                     showHideShadow(false, duration); | ||||
|                     animateView(binding.playbackControlRoot, false, duration, 0, | ||||
|                             this::hideSystemUIIfNeeded); | ||||
|                 }, delay); | ||||
|             showHideShadow(false, duration); | ||||
|             animateView(binding.playbackControlRoot, false, duration, 0, | ||||
|                     this::hideSystemUIIfNeeded); | ||||
|         }, delay); | ||||
|     } | ||||
|  | ||||
|     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 showNext = playQueue.getIndex() + 1 != playQueue.getStreams().size(); | ||||
|         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.setAlpha(showPrev ? 1.0f : 0.0f); | ||||
| @@ -1722,6 +1738,8 @@ public final class Player implements | ||||
|         binding.playNextButton.setAlpha(showNext ? 1.0f : 0.0f); | ||||
|         binding.queueButton.setVisibility(showQueue ? View.VISIBLE : View.GONE); | ||||
|         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() { | ||||
| @@ -2725,6 +2743,17 @@ public final class Player implements | ||||
|  | ||||
|         NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); | ||||
|         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() { | ||||
| @@ -2787,7 +2816,7 @@ public final class Player implements | ||||
|  | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Play queue and streams | ||||
|     // Play queue, segments and streams | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
|     //region | ||||
|  | ||||
| @@ -2835,41 +2864,90 @@ public final class Player implements | ||||
|  | ||||
|         hideSystemUIIfNeeded(); | ||||
|         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); | ||||
|         binding.playQueuePanel.requestFocus(); | ||||
|         animateView(binding.playQueuePanel, SLIDE_AND_ALPHA, true, | ||||
|         binding.itemsListPanel.requestFocus(); | ||||
|         animateView(binding.itemsListPanel, SLIDE_AND_ALPHA, true, | ||||
|                 DEFAULT_CONTROLS_DURATION); | ||||
|  | ||||
|         binding.playQueue.scrollToPosition(playQueue.getIndex()); | ||||
|         binding.itemsList.scrollToPosition(playQueue.getIndex()); | ||||
|     } | ||||
|  | ||||
|     private void buildQueue() { | ||||
|         binding.playQueue.setAdapter(playQueueAdapter); | ||||
|         binding.playQueue.setClickable(true); | ||||
|         binding.playQueue.setLongClickable(true); | ||||
|         binding.itemsList.setAdapter(playQueueAdapter); | ||||
|         binding.itemsList.setClickable(true); | ||||
|         binding.itemsList.setLongClickable(true); | ||||
|  | ||||
|         binding.playQueue.clearOnScrollListeners(); | ||||
|         binding.playQueue.addOnScrollListener(getQueueScrollListener()); | ||||
|         binding.itemsList.clearOnScrollListeners(); | ||||
|         binding.itemsList.addOnScrollListener(getQueueScrollListener()); | ||||
|  | ||||
|         itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); | ||||
|         itemTouchHelper.attachToRecyclerView(binding.playQueue); | ||||
|         itemTouchHelper.attachToRecyclerView(binding.itemsList); | ||||
|  | ||||
|         playQueueAdapter.setSelectedListener(getOnSelectedListener()); | ||||
|  | ||||
|         binding.playQueueClose.setOnClickListener(view -> closeQueue()); | ||||
|         binding.itemsListClose.setOnClickListener(view -> closeItemsList()); | ||||
|     } | ||||
|  | ||||
|     public void closeQueue() { | ||||
|         if (isQueueVisible) { | ||||
|     private void onSegmentsClicked() { | ||||
|         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; | ||||
|             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, () -> { | ||||
|                         // Even when queueLayout is GONE it receives touch events | ||||
|                         // and ruins normal behavior of the app. This line fixes it | ||||
|                         binding.playQueuePanel.setTranslationY( | ||||
|                                 -binding.playQueuePanel.getHeight() * 5); | ||||
|                         binding.itemsListPanel.setTranslationY( | ||||
|                                 -binding.itemsListPanel.getHeight() * 5); | ||||
|                     }); | ||||
|             binding.playPauseButton.requestFocus(); | ||||
|         } | ||||
| @@ -2882,12 +2960,33 @@ public final class Player implements | ||||
|                 if (playQueue != null && !playQueue.isComplete()) { | ||||
|                     playQueue.fetch(); | ||||
|                 } 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() { | ||||
|         return new PlayQueueItemTouchCallback() { | ||||
|             @Override | ||||
| @@ -3313,6 +3412,9 @@ public final class Player implements | ||||
|         } else if (v.getId() == binding.queueButton.getId()) { | ||||
|             onQueueClicked(); | ||||
|             return; | ||||
|         } else if (v.getId() == binding.segmentsButton.getId()) { | ||||
|             onSegmentsClicked(); | ||||
|             return; | ||||
|         } else if (v.getId() == binding.repeatButton.getId()) { | ||||
|             onRepeatClicked(); | ||||
|             return; | ||||
| @@ -3610,8 +3712,8 @@ public final class Player implements | ||||
|             binding.brightnessProgressBar.setMax(maxGestureLength); | ||||
|  | ||||
|             setInitialGestureValues(); | ||||
|             binding.playQueuePanel.getLayoutParams().height | ||||
|                     = height - binding.playQueuePanel.getTop(); | ||||
|             binding.itemsListPanel.getLayoutParams().height | ||||
|                     = height - binding.itemsListPanel.getTop(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -3663,7 +3765,7 @@ public final class Player implements | ||||
|         if (!isFullscreen) { | ||||
|             binding.playbackControlRoot.setPadding(0, 0, 0, 0); | ||||
|         } | ||||
|         binding.playQueuePanel.setPadding(0, 0, 0, 0); | ||||
|         binding.itemsListPanel.setPadding(0, 0, 0, 0); | ||||
|         notifyQueueUpdateToListeners(); | ||||
|         notifyMetadataUpdateToListeners(); | ||||
|         notifyPlaybackUpdateToListeners(); | ||||
|   | ||||
| @@ -24,7 +24,7 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior<FrameLayout> | ||||
|     private boolean skippingInterception = false; | ||||
|     private final List<Integer> skipInterceptionOfElements = Arrays.asList( | ||||
|             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); | ||||
|  | ||||
|     @Override | ||||
|   | ||||
| @@ -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> | ||||
| @@ -191,6 +191,24 @@ | ||||
|                         tools:ignore="ContentDescription,RtlHardcoded" | ||||
|                         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 | ||||
|                         android:id="@+id/moreOptionsButton" | ||||
|                         android:layout_width="wrap_content" | ||||
| @@ -452,7 +470,7 @@ | ||||
|     </RelativeLayout> | ||||
|  | ||||
|     <RelativeLayout | ||||
|         android:id="@+id/playQueuePanel" | ||||
|         android:id="@+id/itemsListPanel" | ||||
|         android:layout_width="380dp" | ||||
|         android:layout_height="match_parent" | ||||
|         android:layout_alignParentEnd="true" | ||||
| @@ -461,14 +479,30 @@ | ||||
|         tools:visibility="visible"> | ||||
|  | ||||
|         <RelativeLayout | ||||
|             android:id="@+id/playQueueControl" | ||||
|             android:id="@+id/itemsListControl" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="60dp" | ||||
|             android:clickable="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 | ||||
|                 android:id="@+id/playQueueClose" | ||||
|                 android:id="@+id/itemsListClose" | ||||
|                 android:layout_width="50dp" | ||||
|                 android:layout_height="50dp" | ||||
|                 android:layout_alignParentEnd="true" | ||||
| @@ -517,10 +551,10 @@ | ||||
|         </RelativeLayout> | ||||
|  | ||||
|         <androidx.recyclerview.widget.RecyclerView | ||||
|             android:id="@+id/playQueue" | ||||
|             android:id="@+id/itemsList" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="match_parent" | ||||
|             android:layout_below="@id/playQueueControl" | ||||
|             android:layout_below="@id/itemsListControl" | ||||
|             android:scrollbars="vertical" | ||||
|             app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" | ||||
|             tools:listitem="@layout/play_queue_item" /> | ||||
|   | ||||
							
								
								
									
										64
									
								
								app/src/main/res/layout/item_stream_segment.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								app/src/main/res/layout/item_stream_segment.xml
									
									
									
									
									
										Normal 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> | ||||
| @@ -192,6 +192,23 @@ | ||||
|                         app:srcCompat="@drawable/ic_list_white_24dp" | ||||
|                         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 | ||||
|                         android:id="@+id/moreOptionsButton" | ||||
|                         android:layout_width="wrap_content" | ||||
| @@ -450,7 +467,7 @@ | ||||
|     </RelativeLayout> | ||||
|  | ||||
|     <RelativeLayout | ||||
|         android:id="@+id/playQueuePanel" | ||||
|         android:id="@+id/itemsListPanel" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         android:background="?attr/queue_background_color" | ||||
| @@ -458,14 +475,30 @@ | ||||
|         tools:visibility="visible"> | ||||
|  | ||||
|         <RelativeLayout | ||||
|             android:id="@+id/playQueueControl" | ||||
|             android:id="@+id/itemsListControl" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="60dp" | ||||
|             android:clickable="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 | ||||
|                 android:id="@+id/playQueueClose" | ||||
|                 android:id="@+id/itemsListClose" | ||||
|                 android:layout_width="50dp" | ||||
|                 android:layout_height="50dp" | ||||
|                 android:layout_alignParentEnd="true" | ||||
| @@ -514,10 +547,10 @@ | ||||
|         </RelativeLayout> | ||||
|  | ||||
|         <androidx.recyclerview.widget.RecyclerView | ||||
|             android:id="@+id/playQueue" | ||||
|             android:id="@+id/itemsList" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="match_parent" | ||||
|             android:layout_below="@id/playQueueControl" | ||||
|             android:layout_below="@id/itemsListControl" | ||||
|             android:scrollbars="vertical" | ||||
|             app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" | ||||
|             tools:listitem="@layout/play_queue_item" /> | ||||
|   | ||||
| @@ -692,4 +692,5 @@ | ||||
|     <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="recent">Recent</string> | ||||
|     <string name="chapters">Chapters</string> | ||||
| </resources> | ||||
		Reference in New Issue
	
	Block a user
	 vkay94
					vkay94