Skip to content

Commit

Permalink
Add stream segments to VideoPlayerImpl
Browse files Browse the repository at this point in the history
  • Loading branch information
vkay94 committed Dec 26, 2020
1 parent 90d3c9c commit 5b2452b
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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 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()
val list = arrayListOf<StreamSegmentItem>()
info.streamSegments.forEach {
list.add(StreamSegmentItem(it, listener))
}
addAll(list)
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, secondsInMillis: Long)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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 java.util.concurrent.TimeUnit

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 =
secondsToString(item.startTimeSeconds.toLong())
viewHolder.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds * 1000L) }
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)
}

private fun secondsToString(seconds: Long): String {
val hours = TimeUnit.SECONDS.toHours(seconds)
val minutes = TimeUnit.SECONDS.toMinutes(seconds)
.minus(TimeUnit.HOURS.toMinutes(hours))
val sec = seconds
.minus(TimeUnit.HOURS.toSeconds(hours))
.minus(TimeUnit.MINUTES.toSeconds(minutes))

return if (hours == 0L) String.format("%02d:%02d", minutes, sec)
else String.format("%02d:%02d:%02d", hours, minutes, sec)
}

override fun getLayout() = R.layout.item_stream_segment

interface ClickListener {
fun onClick(segmentItem: StreamSegmentItem, positionInMillis: Long)
}
}
92 changes: 91 additions & 1 deletion app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@

import org.schabi.newpipe.R;
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;
Expand All @@ -100,6 +101,7 @@
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.info_list.StreamSegmentAdapter;

import java.util.List;

Expand Down Expand Up @@ -155,6 +157,7 @@ public class VideoPlayerImpl extends VideoPlayer
private ImageView brightnessImageView;
private TextView resizingIndicator;
private ImageButton queueButton;
private ImageButton segmentsButton;
private ImageButton repeatButton;
private ImageButton shuffleButton;
private ImageButton playWithKodi;
Expand All @@ -171,11 +174,13 @@ public class VideoPlayerImpl extends VideoPlayer
private RelativeLayout queueLayout;
private ImageButton itemsListCloseButton;
private RecyclerView itemsList;
private StreamSegmentAdapter segmentAdapter;
private ItemTouchHelper itemTouchHelper;

private RelativeLayout playerOverlays;

private boolean queueVisible;
private boolean segmentsVisible;
private MainPlayer.PlayerType playerType = MainPlayer.PlayerType.VIDEO;

private ImageButton moreOptionsButton;
Expand Down Expand Up @@ -291,6 +296,7 @@ public void initViews(final View view) {
this.brightnessImageView = view.findViewById(R.id.brightnessImageView);
this.resizingIndicator = view.findViewById(R.id.resizing_indicator);
this.queueButton = view.findViewById(R.id.queueButton);
this.segmentsButton = view.findViewById(R.id.segmentsButton);
this.repeatButton = view.findViewById(R.id.repeatButton);
this.shuffleButton = view.findViewById(R.id.shuffleButton);
this.playWithKodi = view.findViewById(R.id.playWithKodi);
Expand Down Expand Up @@ -320,10 +326,20 @@ public void initViews(final View view) {
titleTextView.setSelected(true);
channelTextView.setSelected(true);

segmentAdapter = new StreamSegmentAdapter(getStreamSegmentListener());

// Prevent hiding of bottom sheet via swipe inside queue
this.itemsList.setNestedScrollingEnabled(false);
}

private StreamSegmentAdapter.StreamSegmentListener getStreamSegmentListener() {
return (item, secondsInMillis) -> {
segmentAdapter.selectSegment(item);
getPlayer().seekTo(secondsInMillis);
triggerProgressUpdate();
};
}

@Override
protected void setupSubtitleView(final @NonNull SubtitleView view,
final float captionScale,
Expand Down Expand Up @@ -354,6 +370,7 @@ private void setupElementsVisibility() {
getResizeView().setVisibility(View.GONE);
getRootView().findViewById(R.id.metadataView).setVisibility(View.GONE);
queueButton.setVisibility(View.GONE);
segmentsButton.setVisibility(View.GONE);
moreOptionsButton.setVisibility(View.GONE);
getTopControlsRoot().setOrientation(LinearLayout.HORIZONTAL);
primaryControls.getLayoutParams().width = LinearLayout.LayoutParams.WRAP_CONTENT;
Expand Down Expand Up @@ -453,6 +470,7 @@ public void initListeners() {
getRootView().setOnTouchListener(listener);

queueButton.setOnClickListener(this);
segmentsButton.setOnClickListener(this);
repeatButton.setOnClickListener(this);
shuffleButton.setOnClickListener(this);

Expand Down Expand Up @@ -775,6 +793,9 @@ public void onClick(final View v) {
} else if (v.getId() == queueButton.getId()) {
onQueueClicked();
return;
} else if (v.getId() == segmentsButton.getId()) {
onSegmentsClicked();
return;
} else if (v.getId() == repeatButton.getId()) {
onRepeatClicked();
return;
Expand Down Expand Up @@ -850,8 +871,25 @@ private void onQueueClicked() {
itemsList.scrollToPosition(playQueue.getIndex());
}

private void onSegmentsClicked() {
segmentsVisible = true;

hideSystemUIIfNeeded();
buildSegments();

hideControls(0, 0);
queueLayout.requestFocus();
animateView(queueLayout, SLIDE_AND_ALPHA, true,
DEFAULT_CONTROLS_DURATION);

final int adapterPosition = getNearestStreamSegmentPosition(getPlayer()
.getCurrentPosition());
segmentAdapter.selectSegmentAt(adapterPosition);
itemsList.scrollToPosition(adapterPosition);
}

public void onQueueClosed() {
if (!queueVisible) {
if (!queueVisible && !segmentsVisible) {
return;
}

Expand All @@ -862,6 +900,7 @@ public void onQueueClosed() {
queueLayout.setTranslationY(-queueLayout.getHeight() * 5);
});
queueVisible = false;
segmentsVisible = false;
playPauseButton.requestFocus();
}

Expand Down Expand Up @@ -1463,6 +1502,14 @@ private void showOrHideButtons() {
playNextButton.setAlpha(showNext ? 1.0f : 0.0f);
queueButton.setVisibility(showQueue ? View.VISIBLE : View.GONE);
queueButton.setAlpha(showQueue ? 1.0f : 0.0f);

boolean showSegment = false;
if (getCurrentMetadata() != null) {
showSegment = getCurrentMetadata().getMetadata().getStreamSegments().size() > 0
&& !popupPlayerSelected();
}
segmentsButton.setVisibility(showSegment ? View.VISIBLE : View.GONE);
segmentsButton.setAlpha(showSegment ? 1.0f : 0.0f);
}

private void showSystemUIPartially() {
Expand Down Expand Up @@ -1548,9 +1595,40 @@ private void buildQueue() {

playQueueAdapter.setSelectedListener(getOnSelectedListener());

shuffleButton.setVisibility(View.VISIBLE);
repeatButton.setVisibility(View.VISIBLE);
itemsListCloseButton.setOnClickListener(view -> onQueueClosed());
}

private void buildSegments() {
itemsList.setAdapter(segmentAdapter);
itemsList.setClickable(true);
itemsList.clearOnScrollListeners();

if (getCurrentMetadata() != null) {
segmentAdapter.setItems(getCurrentMetadata().getMetadata());
}

shuffleButton.setVisibility(View.GONE);
repeatButton.setVisibility(View.GONE);
itemsListCloseButton.setOnClickListener(view -> onQueueClosed());
}

private int getNearestStreamSegmentPosition(final long playbackPosition) {
int nearestPosition = 0;
final List<StreamSegment> segments = getCurrentMetadata().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);
}

public void useVideoSource(final boolean video) {
if (playQueue == null || audioOnly == !video || audioPlayerSelected()) {
return;
Expand Down Expand Up @@ -1976,6 +2054,15 @@ private void updateMetadata() {
if (activityListener != null && getCurrentMetadata() != null) {
activityListener.onMetadataUpdate(getCurrentMetadata().getMetadata(), playQueue);
}
if (getCurrentMetadata() != null && segmentsVisible
&& segmentAdapter.setItems(getCurrentMetadata().getMetadata())) {
final int adapterPosition = getNearestStreamSegmentPosition(getPlayer()
.getCurrentPosition());
segmentAdapter.selectSegmentAt(adapterPosition);
itemsList.scrollToPosition(adapterPosition);
} else if (segmentsVisible) {
onQueueClosed();
}
}

private void updatePlayback() {
Expand All @@ -1997,6 +2084,9 @@ private void updateProgress(final int currentProgress, final int duration,
if (activityListener != null) {
activityListener.onProgressUpdate(currentProgress, duration, bufferPercent);
}
if (segmentsVisible) {
segmentAdapter.selectSegmentAt(getNearestStreamSegmentPosition(currentProgress));
}
}

void stopActivityBinding() {
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_format_list_numbered_white_24.xml
Original file line number Diff line number Diff line change
@@ -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>
18 changes: 18 additions & 0 deletions app/src/main/res/layout-large-land/player.xml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,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"
tools:visibility="visible"
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"
Expand Down
Loading

0 comments on commit 5b2452b

Please sign in to comment.