-Refactored player media source resolution into external helpers.
-Baked resolved media metadata into media source for one-way data passing.
This commit is contained in:
parent
3194a2bf2c
commit
bc6fdf81d2
|
@ -42,14 +42,12 @@ import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
|
||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
|
||||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||||
import org.schabi.newpipe.player.helper.LockManager;
|
import org.schabi.newpipe.player.helper.LockManager;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
|
||||||
|
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
|
@ -279,10 +277,18 @@ public final class BackgroundPlayer extends Service {
|
||||||
|
|
||||||
protected class BasePlayerImpl extends BasePlayer {
|
protected class BasePlayerImpl extends BasePlayer {
|
||||||
|
|
||||||
|
@Nullable private AudioPlaybackResolver resolver;
|
||||||
|
|
||||||
BasePlayerImpl(Context context) {
|
BasePlayerImpl(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initPlayer(boolean playOnReady) {
|
||||||
|
super.initPlayer(playOnReady);
|
||||||
|
resolver = new AudioPlaybackResolver(context, dataSource);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleIntent(final Intent intent) {
|
public void handleIntent(final Intent intent) {
|
||||||
super.handleIntent(intent);
|
super.handleIntent(intent);
|
||||||
|
@ -390,11 +396,9 @@ public final class BackgroundPlayer extends Service {
|
||||||
// Playback Listener
|
// Playback Listener
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
|
||||||
@Nullable final StreamInfo info,
|
super.onMetadataChanged(tag);
|
||||||
final int newPlayQueueIndex,
|
if (shouldUpdateOnProgress) {
|
||||||
final boolean hasPlayQueueItemChanged) {
|
|
||||||
if (shouldUpdateOnProgress || hasPlayQueueItemChanged) {
|
|
||||||
resetNotification();
|
resetNotification();
|
||||||
updateNotification(-1);
|
updateNotification(-1);
|
||||||
updateMetadata();
|
updateMetadata();
|
||||||
|
@ -404,15 +408,7 @@ public final class BackgroundPlayer extends Service {
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
||||||
final MediaSource liveSource = super.sourceOf(item, info);
|
return resolver == null ? null : resolver.resolve(info);
|
||||||
if (liveSource != null) return liveSource;
|
|
||||||
|
|
||||||
final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams());
|
|
||||||
if (index < 0 || index >= info.getAudioStreams().size()) return null;
|
|
||||||
|
|
||||||
final AudioStream audio = info.getAudioStreams().get(index);
|
|
||||||
return buildMediaSource(audio.getUrl(), PlayerHelper.cacheKeyOf(info, audio),
|
|
||||||
MediaFormat.getSuffixById(audio.getFormatId()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -439,8 +435,8 @@ public final class BackgroundPlayer extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMetadata() {
|
private void updateMetadata() {
|
||||||
if (activityListener != null && currentInfo != null) {
|
if (activityListener != null && getCurrentMetadata() != null) {
|
||||||
activityListener.onMetadataUpdate(currentInfo);
|
activityListener.onMetadataUpdate(getCurrentMetadata().getMetadata());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,15 +25,12 @@ import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.net.Uri;
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
|
||||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||||
|
@ -49,7 +46,6 @@ import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||||
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||||
|
@ -57,7 +53,6 @@ import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||||
import org.schabi.newpipe.Downloader;
|
import org.schabi.newpipe.Downloader;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.player.helper.AudioReactor;
|
import org.schabi.newpipe.player.helper.AudioReactor;
|
||||||
import org.schabi.newpipe.player.helper.LoadController;
|
import org.schabi.newpipe.player.helper.LoadController;
|
||||||
|
@ -72,6 +67,7 @@ import org.schabi.newpipe.player.playback.PlaybackListener;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
|
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||||
|
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||||
import org.schabi.newpipe.util.SerializedCache;
|
import org.schabi.newpipe.util.SerializedCache;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -130,12 +126,12 @@ public abstract class BasePlayer implements
|
||||||
protected PlayQueue playQueue;
|
protected PlayQueue playQueue;
|
||||||
protected PlayQueueAdapter playQueueAdapter;
|
protected PlayQueueAdapter playQueueAdapter;
|
||||||
|
|
||||||
protected MediaSourceManager playbackManager;
|
@Nullable protected MediaSourceManager playbackManager;
|
||||||
|
|
||||||
protected StreamInfo currentInfo;
|
@Nullable private PlayQueueItem currentItem;
|
||||||
protected PlayQueueItem currentItem;
|
@Nullable private MediaSourceTag currentMetadata;
|
||||||
|
|
||||||
protected Toast errorToast;
|
@Nullable protected Toast errorToast;
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Player
|
// Player
|
||||||
|
@ -329,58 +325,6 @@ public abstract class BasePlayer implements
|
||||||
if (DEBUG) Log.d(TAG, "Thumbnail - onLoadingCancelled() called with: " +
|
if (DEBUG) Log.d(TAG, "Thumbnail - onLoadingCancelled() called with: " +
|
||||||
"imageUri = [" + imageUri + "], view = [" + view + "]");
|
"imageUri = [" + imageUri + "], view = [" + view + "]");
|
||||||
}
|
}
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// MediaSource Building
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
public MediaSource buildLiveMediaSource(@NonNull final String sourceUrl,
|
|
||||||
@C.ContentType final int type) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "buildLiveMediaSource() called with: url = [" + sourceUrl +
|
|
||||||
"], content type = [" + type + "]");
|
|
||||||
}
|
|
||||||
if (dataSource == null) return null;
|
|
||||||
|
|
||||||
final Uri uri = Uri.parse(sourceUrl);
|
|
||||||
switch (type) {
|
|
||||||
case C.TYPE_SS:
|
|
||||||
return dataSource.getLiveSsMediaSourceFactory().createMediaSource(uri);
|
|
||||||
case C.TYPE_DASH:
|
|
||||||
return dataSource.getLiveDashMediaSourceFactory().createMediaSource(uri);
|
|
||||||
case C.TYPE_HLS:
|
|
||||||
return dataSource.getLiveHlsMediaSourceFactory().createMediaSource(uri);
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Unsupported type: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MediaSource buildMediaSource(@NonNull final String sourceUrl,
|
|
||||||
@NonNull final String cacheKey,
|
|
||||||
@NonNull final String overrideExtension) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "buildMediaSource() called with: url = [" + sourceUrl +
|
|
||||||
"], cacheKey = [" + cacheKey + "]" +
|
|
||||||
"], overrideExtension = [" + overrideExtension + "]");
|
|
||||||
}
|
|
||||||
if (dataSource == null) return null;
|
|
||||||
|
|
||||||
final Uri uri = Uri.parse(sourceUrl);
|
|
||||||
@C.ContentType final int type = TextUtils.isEmpty(overrideExtension) ?
|
|
||||||
Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension);
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case C.TYPE_SS:
|
|
||||||
return dataSource.getLiveSsMediaSourceFactory().createMediaSource(uri);
|
|
||||||
case C.TYPE_DASH:
|
|
||||||
return dataSource.getDashMediaSourceFactory().createMediaSource(uri);
|
|
||||||
case C.TYPE_HLS:
|
|
||||||
return dataSource.getHlsMediaSourceFactory().createMediaSource(uri);
|
|
||||||
case C.TYPE_OTHER:
|
|
||||||
return dataSource.getExtractorMediaSourceFactory(cacheKey).createMediaSource(uri);
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Unsupported type: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Broadcast Receiver
|
// Broadcast Receiver
|
||||||
|
@ -614,6 +558,7 @@ public abstract class BasePlayer implements
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Player.STATE_READY: //3
|
case Player.STATE_READY: //3
|
||||||
|
maybeUpdateCurrentMetadata();
|
||||||
maybeCorrectSeekPosition();
|
maybeCorrectSeekPosition();
|
||||||
if (!isPrepared) {
|
if (!isPrepared) {
|
||||||
isPrepared = true;
|
isPrepared = true;
|
||||||
|
@ -630,10 +575,12 @@ public abstract class BasePlayer implements
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeCorrectSeekPosition() {
|
private void maybeCorrectSeekPosition() {
|
||||||
if (playQueue == null || simpleExoPlayer == null || currentInfo == null) return;
|
if (playQueue == null || simpleExoPlayer == null || currentMetadata == null) return;
|
||||||
|
|
||||||
final int currentSourceIndex = playQueue.getIndex();
|
final int currentSourceIndex = playQueue.getIndex();
|
||||||
final PlayQueueItem currentSourceItem = playQueue.getItem();
|
final PlayQueueItem currentSourceItem = playQueue.getItem();
|
||||||
|
final StreamInfo currentInfo = currentMetadata.getMetadata();
|
||||||
|
|
||||||
if (currentSourceItem == null) return;
|
if (currentSourceItem == null) return;
|
||||||
|
|
||||||
final long recoveryPositionMillis = currentSourceItem.getRecoveryPosition();
|
final long recoveryPositionMillis = currentSourceItem.getRecoveryPosition();
|
||||||
|
@ -649,16 +596,15 @@ public abstract class BasePlayer implements
|
||||||
playQueue.unsetRecovery(currentSourceIndex);
|
playQueue.unsetRecovery(currentSourceIndex);
|
||||||
|
|
||||||
} else if (isSynchronizing && isLive()) {
|
} else if (isSynchronizing && isLive()) {
|
||||||
if (DEBUG) Log.d(TAG, "Playback - Synchronizing livestream to default time");
|
|
||||||
// Is still synchronizing?
|
// Is still synchronizing?
|
||||||
|
if (DEBUG) Log.d(TAG, "Playback - Synchronizing livestream to default time");
|
||||||
seekToDefault();
|
seekToDefault();
|
||||||
|
|
||||||
} else if (isSynchronizing && presetStartPositionMillis > 0L) {
|
} else if (isSynchronizing && presetStartPositionMillis > 0L) {
|
||||||
|
// Has another start position?
|
||||||
if (DEBUG) Log.d(TAG, "Playback - Seeking to preset start " +
|
if (DEBUG) Log.d(TAG, "Playback - Seeking to preset start " +
|
||||||
"position=[" + presetStartPositionMillis + "]");
|
"position=[" + presetStartPositionMillis + "]");
|
||||||
// Has another start position?
|
|
||||||
seekTo(presetStartPositionMillis);
|
seekTo(presetStartPositionMillis);
|
||||||
currentInfo.setStartPosition(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isSynchronizing = false;
|
isSynchronizing = false;
|
||||||
|
@ -732,6 +678,9 @@ public abstract class BasePlayer implements
|
||||||
public void onPositionDiscontinuity(@Player.DiscontinuityReason final int reason) {
|
public void onPositionDiscontinuity(@Player.DiscontinuityReason final int reason) {
|
||||||
if (DEBUG) Log.d(TAG, "ExoPlayer - onPositionDiscontinuity() called with " +
|
if (DEBUG) Log.d(TAG, "ExoPlayer - onPositionDiscontinuity() called with " +
|
||||||
"reason = [" + reason + "]");
|
"reason = [" + reason + "]");
|
||||||
|
|
||||||
|
maybeUpdateCurrentMetadata();
|
||||||
|
|
||||||
// Refresh the playback if there is a transition to the next video
|
// Refresh the playback if there is a transition to the next video
|
||||||
final int newPeriodIndex = simpleExoPlayer.getCurrentPeriodIndex();
|
final int newPeriodIndex = simpleExoPlayer.getCurrentPeriodIndex();
|
||||||
|
|
||||||
|
@ -793,7 +742,7 @@ public abstract class BasePlayer implements
|
||||||
if (DEBUG) Log.d(TAG, "Playback - onPlaybackBlock() called");
|
if (DEBUG) Log.d(TAG, "Playback - onPlaybackBlock() called");
|
||||||
|
|
||||||
currentItem = null;
|
currentItem = null;
|
||||||
currentInfo = null;
|
currentMetadata = null;
|
||||||
simpleExoPlayer.stop();
|
simpleExoPlayer.stop();
|
||||||
isPrepared = false;
|
isPrepared = false;
|
||||||
|
|
||||||
|
@ -810,42 +759,21 @@ public abstract class BasePlayer implements
|
||||||
simpleExoPlayer.prepare(mediaSource);
|
simpleExoPlayer.prepare(mediaSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void onPlaybackSynchronize(@NonNull final PlayQueueItem item) {
|
||||||
public void onPlaybackSynchronize(@NonNull final PlayQueueItem item,
|
|
||||||
@Nullable final StreamInfo info) {
|
|
||||||
if (DEBUG) Log.d(TAG, "Playback - onPlaybackSynchronize() called with " +
|
if (DEBUG) Log.d(TAG, "Playback - onPlaybackSynchronize() called with " +
|
||||||
(info != null ? "available" : "null") + " info, " +
|
|
||||||
"item=[" + item.getTitle() + "], url=[" + item.getUrl() + "]");
|
"item=[" + item.getTitle() + "], url=[" + item.getUrl() + "]");
|
||||||
if (simpleExoPlayer == null || playQueue == null) return;
|
if (simpleExoPlayer == null || playQueue == null) return;
|
||||||
|
|
||||||
final boolean onPlaybackInitial = currentItem == null;
|
final boolean onPlaybackInitial = currentItem == null;
|
||||||
final boolean hasPlayQueueItemChanged = currentItem != item;
|
final boolean hasPlayQueueItemChanged = currentItem != item;
|
||||||
final boolean hasStreamInfoChanged = currentInfo != info;
|
|
||||||
|
|
||||||
final int currentPlayQueueIndex = playQueue.indexOf(item);
|
final int currentPlayQueueIndex = playQueue.indexOf(item);
|
||||||
final int currentPlaylistIndex = simpleExoPlayer.getCurrentWindowIndex();
|
final int currentPlaylistIndex = simpleExoPlayer.getCurrentWindowIndex();
|
||||||
final int currentPlaylistSize = simpleExoPlayer.getCurrentTimeline().getWindowCount();
|
final int currentPlaylistSize = simpleExoPlayer.getCurrentTimeline().getWindowCount();
|
||||||
|
|
||||||
// when starting playback on the last item when not repeating, maybe auto queue
|
|
||||||
if (info != null && currentPlayQueueIndex == playQueue.size() - 1 &&
|
|
||||||
getRepeatMode() == Player.REPEAT_MODE_OFF &&
|
|
||||||
PlayerHelper.isAutoQueueEnabled(context)) {
|
|
||||||
final PlayQueue autoQueue = PlayerHelper.autoQueueOf(info, playQueue.getStreams());
|
|
||||||
if (autoQueue != null) playQueue.append(autoQueue.getStreams());
|
|
||||||
}
|
|
||||||
// If nothing to synchronize
|
// If nothing to synchronize
|
||||||
if (!hasPlayQueueItemChanged && !hasStreamInfoChanged) {
|
if (!hasPlayQueueItemChanged) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentItem = item;
|
currentItem = item;
|
||||||
currentInfo = info;
|
|
||||||
if (hasPlayQueueItemChanged) {
|
|
||||||
// updates only to the stream info should not trigger another view count
|
|
||||||
registerView();
|
|
||||||
initThumbnail(info == null ? item.getThumbnailUrl() : info.getThumbnailUrl());
|
|
||||||
}
|
|
||||||
onMetadataChanged(item, info, currentPlayQueueIndex, hasPlayQueueItemChanged);
|
|
||||||
|
|
||||||
// Check if on wrong window
|
// Check if on wrong window
|
||||||
if (currentPlayQueueIndex != playQueue.getIndex()) {
|
if (currentPlayQueueIndex != playQueue.getIndex()) {
|
||||||
|
@ -873,26 +801,21 @@ public abstract class BasePlayer implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
|
||||||
@Nullable final StreamInfo info,
|
Log.d(TAG, "Playback - onMetadataChanged() called, " +
|
||||||
final int newPlayQueueIndex,
|
"playing: " + tag.getMetadata().getName());
|
||||||
final boolean hasPlayQueueItemChanged);
|
final StreamInfo info = tag.getMetadata();
|
||||||
|
|
||||||
@Nullable
|
initThumbnail(info.getThumbnailUrl());
|
||||||
@Override
|
registerView();
|
||||||
public MediaSource sourceOf(PlayQueueItem item, StreamInfo info) {
|
|
||||||
final StreamType streamType = info.getStreamType();
|
// when starting playback on the last item when not repeating, maybe auto queue
|
||||||
if (!(streamType == StreamType.AUDIO_LIVE_STREAM || streamType == StreamType.LIVE_STREAM)) {
|
if (playQueue.getIndex() == playQueue.size() - 1 &&
|
||||||
return null;
|
getRepeatMode() == Player.REPEAT_MODE_OFF &&
|
||||||
|
PlayerHelper.isAutoQueueEnabled(context)) {
|
||||||
|
final PlayQueue autoQueue = PlayerHelper.autoQueueOf(info, playQueue.getStreams());
|
||||||
|
if (autoQueue != null) playQueue.append(autoQueue.getStreams());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!info.getHlsUrl().isEmpty()) {
|
|
||||||
return buildLiveMediaSource(info.getHlsUrl(), C.TYPE_HLS);
|
|
||||||
} else if (!info.getDashMpdUrl().isEmpty()) {
|
|
||||||
return buildLiveMediaSource(info.getDashMpdUrl(), C.TYPE_DASH);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1051,7 +974,8 @@ public abstract class BasePlayer implements
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
private void registerView() {
|
private void registerView() {
|
||||||
if (databaseUpdateReactor == null || currentInfo == null) return;
|
if (databaseUpdateReactor == null || currentMetadata == null) return;
|
||||||
|
final StreamInfo currentInfo = currentMetadata.getMetadata();
|
||||||
databaseUpdateReactor.add(recordManager.onViewed(currentInfo).onErrorComplete()
|
databaseUpdateReactor.add(recordManager.onViewed(currentInfo).onErrorComplete()
|
||||||
.subscribe(
|
.subscribe(
|
||||||
ignored -> {/* successful */},
|
ignored -> {/* successful */},
|
||||||
|
@ -1082,7 +1006,8 @@ public abstract class BasePlayer implements
|
||||||
}
|
}
|
||||||
|
|
||||||
private void savePlaybackState() {
|
private void savePlaybackState() {
|
||||||
if (simpleExoPlayer == null || currentInfo == null) return;
|
if (simpleExoPlayer == null || currentMetadata == null) return;
|
||||||
|
final StreamInfo currentInfo = currentMetadata.getMetadata();
|
||||||
|
|
||||||
if (simpleExoPlayer.getCurrentPosition() > RECOVERY_SKIP_THRESHOLD_MILLIS &&
|
if (simpleExoPlayer.getCurrentPosition() > RECOVERY_SKIP_THRESHOLD_MILLIS &&
|
||||||
simpleExoPlayer.getCurrentPosition() <
|
simpleExoPlayer.getCurrentPosition() <
|
||||||
|
@ -1090,6 +1015,23 @@ public abstract class BasePlayer implements
|
||||||
savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition());
|
savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void maybeUpdateCurrentMetadata() {
|
||||||
|
if (simpleExoPlayer == null) return;
|
||||||
|
|
||||||
|
final MediaSourceTag metadata;
|
||||||
|
try {
|
||||||
|
metadata = (MediaSourceTag) simpleExoPlayer.getCurrentTag();
|
||||||
|
} catch (IndexOutOfBoundsException | ClassCastException error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata == null || currentMetadata == metadata) return;
|
||||||
|
|
||||||
|
currentMetadata = metadata;
|
||||||
|
onMetadataChanged(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Getters and Setters
|
// Getters and Setters
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -1106,19 +1048,28 @@ public abstract class BasePlayer implements
|
||||||
return currentState;
|
return currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public MediaSourceTag getCurrentMetadata() {
|
||||||
|
return currentMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public String getVideoUrl() {
|
public String getVideoUrl() {
|
||||||
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUrl();
|
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public String getVideoTitle() {
|
public String getVideoTitle() {
|
||||||
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getTitle();
|
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public String getUploaderName() {
|
public String getUploaderName() {
|
||||||
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUploader();
|
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUploader();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Checks if the current playback is a livestream AND is playing at or beyond the live edge */
|
/** Checks if the current playback is a livestream AND is playing at or beyond the live edge */
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
public boolean isLiveEdge() {
|
public boolean isLiveEdge() {
|
||||||
if (simpleExoPlayer == null || !isLive()) return false;
|
if (simpleExoPlayer == null || !isLive()) return false;
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,6 @@ import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||||
import com.google.android.exoplayer2.ui.SubtitleView;
|
import com.google.android.exoplayer2.ui.SubtitleView;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
|
||||||
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.player.helper.PlaybackParameterDialog;
|
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
|
||||||
|
@ -67,6 +66,8 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
|
||||||
|
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||||
|
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||||
import org.schabi.newpipe.util.AnimationUtils;
|
import org.schabi.newpipe.util.AnimationUtils;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
|
@ -497,11 +498,8 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
// Playback Listener
|
// Playback Listener
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
protected void onMetadataChanged(@Nullable final MediaSourceTag tag) {
|
||||||
@Nullable final StreamInfo info,
|
super.onMetadataChanged(tag);
|
||||||
final int newPlayQueueIndex,
|
|
||||||
final boolean hasPlayQueueItemChanged) {
|
|
||||||
super.onMetadataChanged(item, info, newPlayQueueIndex, false);
|
|
||||||
|
|
||||||
titleTextView.setText(getVideoTitle());
|
titleTextView.setText(getVideoTitle());
|
||||||
channelTextView.setText(getUploaderName());
|
channelTextView.setText(getUploaderName());
|
||||||
|
@ -686,14 +684,19 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getDefaultResolutionIndex(final List<VideoStream> sortedVideos) {
|
protected VideoPlaybackResolver.QualityResolver getQualityResolver() {
|
||||||
return ListHelper.getDefaultResolutionIndex(context, sortedVideos);
|
return new VideoPlaybackResolver.QualityResolver() {
|
||||||
}
|
@Override
|
||||||
|
public int getDefaultResolutionIndex(List<VideoStream> sortedVideos) {
|
||||||
|
return ListHelper.getDefaultResolutionIndex(context, sortedVideos);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
|
public int getOverrideResolutionIndex(List<VideoStream> sortedVideos,
|
||||||
final String playbackQuality) {
|
String playbackQuality) {
|
||||||
return ListHelper.getResolutionIndex(context, sortedVideos, playbackQuality);
|
return ListHelper.getResolutionIndex(context, sortedVideos, playbackQuality);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -59,13 +59,13 @@ import com.google.android.exoplayer2.ui.SubtitleView;
|
||||||
|
|
||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||||
import org.schabi.newpipe.player.helper.LockManager;
|
import org.schabi.newpipe.player.helper.LockManager;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||||
import org.schabi.newpipe.player.old.PlayVideoActivity;
|
import org.schabi.newpipe.player.old.PlayVideoActivity;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||||
|
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
@ -511,14 +511,20 @@ public final class PopupVideoPlayer extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getDefaultResolutionIndex(final List<VideoStream> sortedVideos) {
|
protected VideoPlaybackResolver.QualityResolver getQualityResolver() {
|
||||||
return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos);
|
return new VideoPlaybackResolver.QualityResolver() {
|
||||||
}
|
@Override
|
||||||
|
public int getDefaultResolutionIndex(List<VideoStream> sortedVideos) {
|
||||||
|
return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
|
public int getOverrideResolutionIndex(List<VideoStream> sortedVideos,
|
||||||
final String playbackQuality) {
|
String playbackQuality) {
|
||||||
return ListHelper.getPopupResolutionIndex(context, sortedVideos, playbackQuality);
|
return ListHelper.getPopupResolutionIndex(context, sortedVideos,
|
||||||
|
playbackQuality);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -539,8 +545,8 @@ public final class PopupVideoPlayer extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMetadata() {
|
private void updateMetadata() {
|
||||||
if (activityListener != null && currentInfo != null) {
|
if (activityListener != null && getCurrentMetadata() != null) {
|
||||||
activityListener.onMetadataUpdate(currentInfo);
|
activityListener.onMetadataUpdate(getCurrentMetadata().getMetadata());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,11 +592,8 @@ public final class PopupVideoPlayer extends Service {
|
||||||
// Playback Listener
|
// Playback Listener
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
protected void onMetadataChanged(@Nullable final MediaSourceTag tag) {
|
||||||
@Nullable final StreamInfo info,
|
super.onMetadataChanged(tag);
|
||||||
final int newPlayQueueIndex,
|
|
||||||
final boolean hasPlayQueueItemChanged) {
|
|
||||||
super.onMetadataChanged(item, info, newPlayQueueIndex, false);
|
|
||||||
updateMetadata();
|
updateMetadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,6 @@ import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
@ -47,11 +46,9 @@ import android.widget.SeekBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
|
||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.MergingMediaSource;
|
|
||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.text.CaptionStyleCompat;
|
import com.google.android.exoplayer2.text.CaptionStyleCompat;
|
||||||
|
@ -62,21 +59,17 @@ import com.google.android.exoplayer2.video.VideoListener;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.Subtitles;
|
|
||||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||||
|
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||||
|
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||||
import org.schabi.newpipe.util.AnimationUtils;
|
import org.schabi.newpipe.util.AnimationUtils;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.C.SELECTION_FLAG_AUTOSELECT;
|
|
||||||
import static com.google.android.exoplayer2.C.TIME_UNSET;
|
|
||||||
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
|
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
|
||||||
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
|
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
|
||||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||||
|
@ -105,13 +98,12 @@ public abstract class VideoPlayer extends BasePlayer
|
||||||
public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis
|
public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis
|
||||||
public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds
|
public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds
|
||||||
|
|
||||||
private ArrayList<VideoStream> availableStreams;
|
private List<VideoStream> availableStreams;
|
||||||
private int selectedStreamIndex;
|
private int selectedStreamIndex;
|
||||||
|
|
||||||
protected String playbackQuality;
|
|
||||||
|
|
||||||
protected boolean wasPlaying = false;
|
protected boolean wasPlaying = false;
|
||||||
|
|
||||||
|
@Nullable private VideoPlaybackResolver resolver;
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Views
|
// Views
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -244,6 +236,8 @@ public abstract class VideoPlayer extends BasePlayer
|
||||||
trackSelector.setParameters(trackSelector.buildUponParameters()
|
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||||
.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)));
|
.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -326,23 +320,18 @@ public abstract class VideoPlayer extends BasePlayer
|
||||||
// Playback Listener
|
// Playback Listener
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
protected abstract int getDefaultResolutionIndex(final List<VideoStream> sortedVideos);
|
protected abstract VideoPlaybackResolver.QualityResolver getQualityResolver();
|
||||||
|
|
||||||
protected abstract int getOverrideResolutionIndex(final List<VideoStream> sortedVideos, final String playbackQuality);
|
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
|
||||||
|
super.onMetadataChanged(tag);
|
||||||
|
|
||||||
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
|
||||||
@Nullable final StreamInfo info,
|
|
||||||
final int newPlayQueueIndex,
|
|
||||||
final boolean hasPlayQueueItemChanged) {
|
|
||||||
qualityTextView.setVisibility(View.GONE);
|
qualityTextView.setVisibility(View.GONE);
|
||||||
playbackSpeedTextView.setVisibility(View.GONE);
|
playbackSpeedTextView.setVisibility(View.GONE);
|
||||||
|
|
||||||
playbackEndTime.setVisibility(View.GONE);
|
playbackEndTime.setVisibility(View.GONE);
|
||||||
playbackLiveSync.setVisibility(View.GONE);
|
playbackLiveSync.setVisibility(View.GONE);
|
||||||
|
|
||||||
final StreamType streamType = info == null ? StreamType.NONE : info.getStreamType();
|
switch (tag.getMetadata().getStreamType()) {
|
||||||
|
|
||||||
switch (streamType) {
|
|
||||||
case AUDIO_STREAM:
|
case AUDIO_STREAM:
|
||||||
surfaceView.setVisibility(View.GONE);
|
surfaceView.setVisibility(View.GONE);
|
||||||
playbackEndTime.setVisibility(View.VISIBLE);
|
playbackEndTime.setVisibility(View.VISIBLE);
|
||||||
|
@ -359,20 +348,14 @@ public abstract class VideoPlayer extends BasePlayer
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case VIDEO_STREAM:
|
case VIDEO_STREAM:
|
||||||
|
final StreamInfo info = tag.getMetadata();
|
||||||
if (info.getVideoStreams().size() + info.getVideoOnlyStreams().size() == 0) break;
|
if (info.getVideoStreams().size() + info.getVideoOnlyStreams().size() == 0) break;
|
||||||
|
|
||||||
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context,
|
availableStreams = tag.getSortedAvailableVideoStreams();
|
||||||
info.getVideoStreams(), info.getVideoOnlyStreams(), false);
|
selectedStreamIndex = tag.getSelectedVideoStreamIndex();
|
||||||
availableStreams = new ArrayList<>(videos);
|
|
||||||
if (playbackQuality == null) {
|
|
||||||
selectedStreamIndex = getDefaultResolutionIndex(videos);
|
|
||||||
} else {
|
|
||||||
selectedStreamIndex = getOverrideResolutionIndex(videos, getPlaybackQuality());
|
|
||||||
}
|
|
||||||
|
|
||||||
buildQualityMenu();
|
buildQualityMenu();
|
||||||
qualityTextView.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
|
qualityTextView.setVisibility(View.VISIBLE);
|
||||||
surfaceView.setVisibility(View.VISIBLE);
|
surfaceView.setVisibility(View.VISIBLE);
|
||||||
default:
|
default:
|
||||||
playbackEndTime.setVisibility(View.VISIBLE);
|
playbackEndTime.setVisibility(View.VISIBLE);
|
||||||
|
@ -386,65 +369,7 @@ public abstract class VideoPlayer extends BasePlayer
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
||||||
final MediaSource liveSource = super.sourceOf(item, info);
|
return resolver == null ? null : resolver.resolve(info);
|
||||||
if (liveSource != null) return liveSource;
|
|
||||||
|
|
||||||
List<MediaSource> mediaSources = new ArrayList<>();
|
|
||||||
|
|
||||||
// Create video stream source
|
|
||||||
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context,
|
|
||||||
info.getVideoStreams(), info.getVideoOnlyStreams(), false);
|
|
||||||
final int index;
|
|
||||||
if (videos.isEmpty()) {
|
|
||||||
index = -1;
|
|
||||||
} else if (playbackQuality == null) {
|
|
||||||
index = getDefaultResolutionIndex(videos);
|
|
||||||
} else {
|
|
||||||
index = getOverrideResolutionIndex(videos, getPlaybackQuality());
|
|
||||||
}
|
|
||||||
final VideoStream video = index >= 0 && index < videos.size() ? videos.get(index) : null;
|
|
||||||
if (video != null) {
|
|
||||||
final MediaSource streamSource = buildMediaSource(video.getUrl(),
|
|
||||||
PlayerHelper.cacheKeyOf(info, video),
|
|
||||||
MediaFormat.getSuffixById(video.getFormatId()));
|
|
||||||
mediaSources.add(streamSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create optional audio stream source
|
|
||||||
final List<AudioStream> audioStreams = info.getAudioStreams();
|
|
||||||
final AudioStream audio = audioStreams.isEmpty() ? null : audioStreams.get(
|
|
||||||
ListHelper.getDefaultAudioFormat(context, audioStreams));
|
|
||||||
// Use the audio stream if there is no video stream, or
|
|
||||||
// Merge with audio stream in case if video does not contain audio
|
|
||||||
if (audio != null && ((video != null && video.isVideoOnly) || video == null)) {
|
|
||||||
final MediaSource audioSource = buildMediaSource(audio.getUrl(),
|
|
||||||
PlayerHelper.cacheKeyOf(info, audio),
|
|
||||||
MediaFormat.getSuffixById(audio.getFormatId()));
|
|
||||||
mediaSources.add(audioSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is no audio or video sources, then this media source cannot be played back
|
|
||||||
if (mediaSources.isEmpty()) return null;
|
|
||||||
// Below are auxiliary media sources
|
|
||||||
|
|
||||||
// Create subtitle sources
|
|
||||||
for (final Subtitles subtitle : info.getSubtitles()) {
|
|
||||||
final String mimeType = PlayerHelper.mimeTypesOf(subtitle.getFileType());
|
|
||||||
if (mimeType == null) continue;
|
|
||||||
|
|
||||||
final Format textFormat = Format.createTextSampleFormat(null, mimeType,
|
|
||||||
SELECTION_FLAG_AUTOSELECT, PlayerHelper.captionLanguageOf(context, subtitle));
|
|
||||||
final MediaSource textSource = dataSource.getSampleMediaSourceFactory()
|
|
||||||
.createMediaSource(Uri.parse(subtitle.getURL()), textFormat, TIME_UNSET);
|
|
||||||
mediaSources.add(textSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaSources.size() == 1) {
|
|
||||||
return mediaSources.get(0);
|
|
||||||
} else {
|
|
||||||
return new MergingMediaSource(mediaSources.toArray(
|
|
||||||
new MediaSource[mediaSources.size()]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -908,11 +833,12 @@ public abstract class VideoPlayer extends BasePlayer
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public void setPlaybackQuality(final String quality) {
|
public void setPlaybackQuality(final String quality) {
|
||||||
this.playbackQuality = quality;
|
if (resolver != null) resolver.setPlaybackQuality(quality);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public String getPlaybackQuality() {
|
public String getPlaybackQuality() {
|
||||||
return playbackQuality;
|
return resolver == null ? null : resolver.getPlaybackQuality();
|
||||||
}
|
}
|
||||||
|
|
||||||
public AspectRatioFrameLayout getAspectRatioFrameLayout() {
|
public AspectRatioFrameLayout getAspectRatioFrameLayout() {
|
||||||
|
|
|
@ -5,12 +5,10 @@ import android.support.annotation.Nullable;
|
||||||
import android.support.v4.util.ArraySet;
|
import android.support.v4.util.ArraySet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource;
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
|
||||||
import org.reactivestreams.Subscriber;
|
import org.reactivestreams.Subscriber;
|
||||||
import org.reactivestreams.Subscription;
|
import org.reactivestreams.Subscription;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
|
||||||
import org.schabi.newpipe.player.mediasource.FailedMediaSource;
|
import org.schabi.newpipe.player.mediasource.FailedMediaSource;
|
||||||
import org.schabi.newpipe.player.mediasource.LoadedMediaSource;
|
import org.schabi.newpipe.player.mediasource.LoadedMediaSource;
|
||||||
import org.schabi.newpipe.player.mediasource.ManagedMediaSource;
|
import org.schabi.newpipe.player.mediasource.ManagedMediaSource;
|
||||||
|
@ -24,10 +22,8 @@ import org.schabi.newpipe.player.playqueue.events.RemoveEvent;
|
||||||
import org.schabi.newpipe.player.playqueue.events.ReorderEvent;
|
import org.schabi.newpipe.player.playqueue.events.ReorderEvent;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
@ -37,8 +33,6 @@ import io.reactivex.Single;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.disposables.SerialDisposable;
|
|
||||||
import io.reactivex.functions.Consumer;
|
|
||||||
import io.reactivex.internal.subscriptions.EmptySubscription;
|
import io.reactivex.internal.subscriptions.EmptySubscription;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import io.reactivex.subjects.PublishSubject;
|
import io.reactivex.subjects.PublishSubject;
|
||||||
|
@ -104,7 +98,6 @@ public class MediaSourceManager {
|
||||||
private final static int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1;
|
private final static int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1;
|
||||||
@NonNull private final CompositeDisposable loaderReactor;
|
@NonNull private final CompositeDisposable loaderReactor;
|
||||||
@NonNull private final Set<PlayQueueItem> loadingItems;
|
@NonNull private final Set<PlayQueueItem> loadingItems;
|
||||||
@NonNull private final SerialDisposable syncReactor;
|
|
||||||
|
|
||||||
@NonNull private final AtomicBoolean isBlocked;
|
@NonNull private final AtomicBoolean isBlocked;
|
||||||
|
|
||||||
|
@ -144,7 +137,6 @@ public class MediaSourceManager {
|
||||||
|
|
||||||
this.playQueueReactor = EmptySubscription.INSTANCE;
|
this.playQueueReactor = EmptySubscription.INSTANCE;
|
||||||
this.loaderReactor = new CompositeDisposable();
|
this.loaderReactor = new CompositeDisposable();
|
||||||
this.syncReactor = new SerialDisposable();
|
|
||||||
|
|
||||||
this.isBlocked = new AtomicBoolean(false);
|
this.isBlocked = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
@ -171,7 +163,6 @@ public class MediaSourceManager {
|
||||||
|
|
||||||
playQueueReactor.cancel();
|
playQueueReactor.cancel();
|
||||||
loaderReactor.dispose();
|
loaderReactor.dispose();
|
||||||
syncReactor.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -310,21 +301,7 @@ public class MediaSourceManager {
|
||||||
final PlayQueueItem currentItem = playQueue.getItem();
|
final PlayQueueItem currentItem = playQueue.getItem();
|
||||||
if (isBlocked.get() || currentItem == null) return;
|
if (isBlocked.get() || currentItem == null) return;
|
||||||
|
|
||||||
final Consumer<StreamInfo> onSuccess = info -> syncInternal(currentItem, info);
|
playbackListener.onPlaybackSynchronize(currentItem);
|
||||||
final Consumer<Throwable> onError = throwable -> syncInternal(currentItem, null);
|
|
||||||
|
|
||||||
final Disposable sync = currentItem.getStream()
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(onSuccess, onError);
|
|
||||||
syncReactor.set(sync);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void syncInternal(@NonNull final PlayQueueItem item,
|
|
||||||
@Nullable final StreamInfo info) {
|
|
||||||
// Ensure the current item is up to date with the play queue
|
|
||||||
if (playQueue.getItem() == item) {
|
|
||||||
playbackListener.onPlaybackSynchronize(item, info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void maybeSynchronizePlayer() {
|
private synchronized void maybeSynchronizePlayer() {
|
||||||
|
@ -423,7 +400,8 @@ public class MediaSourceManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the corresponding MediaSource in {@link DynamicConcatenatingMediaSource}
|
* Checks if the corresponding MediaSource in
|
||||||
|
* {@link com.google.android.exoplayer2.source.ConcatenatingMediaSource}
|
||||||
* for a given {@link PlayQueueItem} needs replacement, either due to gapless playback
|
* for a given {@link PlayQueueItem} needs replacement, either due to gapless playback
|
||||||
* readiness or playlist desynchronization.
|
* readiness or playlist desynchronization.
|
||||||
* <br><br>
|
* <br><br>
|
||||||
|
|
|
@ -45,7 +45,7 @@ public interface PlaybackListener {
|
||||||
*
|
*
|
||||||
* May be called anytime at any amount once unblock is called.
|
* May be called anytime at any amount once unblock is called.
|
||||||
* */
|
* */
|
||||||
void onPlaybackSynchronize(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info);
|
void onPlaybackSynchronize(@NonNull final PlayQueueItem item);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests the listener to resolve a stream info into a media source
|
* Requests the listener to resolve a stream info into a media source
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.schabi.newpipe.player.resolver;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
|
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
|
import org.schabi.newpipe.player.helper.PlayerDataSource;
|
||||||
|
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||||
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
|
|
||||||
|
public class AudioPlaybackResolver implements PlaybackResolver {
|
||||||
|
|
||||||
|
@NonNull private final Context context;
|
||||||
|
@NonNull private final PlayerDataSource dataSource;
|
||||||
|
|
||||||
|
public AudioPlaybackResolver(@NonNull final Context context,
|
||||||
|
@NonNull final PlayerDataSource dataSource) {
|
||||||
|
this.context = context;
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaSource resolve(@NonNull StreamInfo info) {
|
||||||
|
final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info);
|
||||||
|
if (liveSource != null) return liveSource;
|
||||||
|
|
||||||
|
final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams());
|
||||||
|
if (index < 0 || index >= info.getAudioStreams().size()) return null;
|
||||||
|
|
||||||
|
final AudioStream audio = info.getAudioStreams().get(index);
|
||||||
|
final MediaSourceTag tag = new MediaSourceTag(info);
|
||||||
|
return buildMediaSource(dataSource, audio.getUrl(), PlayerHelper.cacheKeyOf(info, audio),
|
||||||
|
MediaFormat.getSuffixById(audio.getFormatId()), tag);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package org.schabi.newpipe.player.resolver;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MediaSourceTag implements Serializable {
|
||||||
|
@NonNull private final StreamInfo metadata;
|
||||||
|
|
||||||
|
@NonNull private final List<VideoStream> sortedAvailableVideoStreams;
|
||||||
|
private final int selectedVideoStreamIndex;
|
||||||
|
|
||||||
|
public MediaSourceTag(@NonNull final StreamInfo metadata,
|
||||||
|
@NonNull final List<VideoStream> sortedAvailableVideoStreams,
|
||||||
|
final int selectedVideoStreamIndex) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
this.sortedAvailableVideoStreams = sortedAvailableVideoStreams;
|
||||||
|
this.selectedVideoStreamIndex = selectedVideoStreamIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaSourceTag(@NonNull final StreamInfo metadata) {
|
||||||
|
this(metadata, Collections.emptyList(), /*indexNotAvailable=*/-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public StreamInfo getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<VideoStream> getSortedAvailableVideoStreams() {
|
||||||
|
return sortedAvailableVideoStreams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSelectedVideoStreamIndex() {
|
||||||
|
return selectedVideoStreamIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public VideoStream getSelectedVideoStream() {
|
||||||
|
return selectedVideoStreamIndex < 0 ||
|
||||||
|
selectedVideoStreamIndex >= sortedAvailableVideoStreams.size() ? null :
|
||||||
|
sortedAvailableVideoStreams.get(selectedVideoStreamIndex);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package org.schabi.newpipe.player.resolver;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
|
import org.schabi.newpipe.player.helper.PlayerDataSource;
|
||||||
|
|
||||||
|
public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
default MediaSource maybeBuildLiveMediaSource(@NonNull final PlayerDataSource dataSource,
|
||||||
|
@NonNull final StreamInfo info) {
|
||||||
|
final StreamType streamType = info.getStreamType();
|
||||||
|
if (!(streamType == StreamType.AUDIO_LIVE_STREAM || streamType == StreamType.LIVE_STREAM)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final MediaSourceTag tag = new MediaSourceTag(info);
|
||||||
|
if (!info.getHlsUrl().isEmpty()) {
|
||||||
|
return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.TYPE_HLS, tag);
|
||||||
|
} else if (!info.getDashMpdUrl().isEmpty()) {
|
||||||
|
return buildLiveMediaSource(dataSource, info.getDashMpdUrl(), C.TYPE_DASH, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
default MediaSource buildLiveMediaSource(@NonNull final PlayerDataSource dataSource,
|
||||||
|
@NonNull final String sourceUrl,
|
||||||
|
@C.ContentType final int type,
|
||||||
|
@NonNull final MediaSourceTag metadata) {
|
||||||
|
final Uri uri = Uri.parse(sourceUrl);
|
||||||
|
switch (type) {
|
||||||
|
case C.TYPE_SS:
|
||||||
|
return dataSource.getLiveSsMediaSourceFactory().setTag(metadata)
|
||||||
|
.createMediaSource(uri);
|
||||||
|
case C.TYPE_DASH:
|
||||||
|
return dataSource.getLiveDashMediaSourceFactory().setTag(metadata)
|
||||||
|
.createMediaSource(uri);
|
||||||
|
case C.TYPE_HLS:
|
||||||
|
return dataSource.getLiveHlsMediaSourceFactory().setTag(metadata)
|
||||||
|
.createMediaSource(uri);
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unsupported type: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
default MediaSource buildMediaSource(@NonNull final PlayerDataSource dataSource,
|
||||||
|
@NonNull final String sourceUrl,
|
||||||
|
@NonNull final String cacheKey,
|
||||||
|
@NonNull final String overrideExtension,
|
||||||
|
@NonNull final MediaSourceTag metadata) {
|
||||||
|
final Uri uri = Uri.parse(sourceUrl);
|
||||||
|
@C.ContentType final int type = TextUtils.isEmpty(overrideExtension) ?
|
||||||
|
Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case C.TYPE_SS:
|
||||||
|
return dataSource.getLiveSsMediaSourceFactory().setTag(metadata)
|
||||||
|
.createMediaSource(uri);
|
||||||
|
case C.TYPE_DASH:
|
||||||
|
return dataSource.getDashMediaSourceFactory().setTag(metadata)
|
||||||
|
.createMediaSource(uri);
|
||||||
|
case C.TYPE_HLS:
|
||||||
|
return dataSource.getHlsMediaSourceFactory().setTag(metadata)
|
||||||
|
.createMediaSource(uri);
|
||||||
|
case C.TYPE_OTHER:
|
||||||
|
return dataSource.getExtractorMediaSourceFactory(cacheKey).setTag(metadata)
|
||||||
|
.createMediaSource(uri);
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unsupported type: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.schabi.newpipe.player.resolver;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
public interface Resolver<Source, Produce> {
|
||||||
|
Produce resolve(@NonNull Source source);
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package org.schabi.newpipe.player.resolver;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.MergingMediaSource;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
|
import org.schabi.newpipe.extractor.Subtitles;
|
||||||
|
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
|
import org.schabi.newpipe.player.helper.PlayerDataSource;
|
||||||
|
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||||
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.C.SELECTION_FLAG_AUTOSELECT;
|
||||||
|
import static com.google.android.exoplayer2.C.TIME_UNSET;
|
||||||
|
|
||||||
|
public class VideoPlaybackResolver implements PlaybackResolver {
|
||||||
|
|
||||||
|
public interface QualityResolver {
|
||||||
|
int getDefaultResolutionIndex(final List<VideoStream> sortedVideos);
|
||||||
|
int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
|
||||||
|
final String playbackQuality);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull private final Context context;
|
||||||
|
@NonNull private final PlayerDataSource dataSource;
|
||||||
|
@NonNull private final QualityResolver qualityResolver;
|
||||||
|
|
||||||
|
@Nullable private String playbackQuality;
|
||||||
|
|
||||||
|
public VideoPlaybackResolver(@NonNull final Context context,
|
||||||
|
@NonNull final PlayerDataSource dataSource,
|
||||||
|
@NonNull final QualityResolver qualityResolver) {
|
||||||
|
this.context = context;
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
this.qualityResolver = qualityResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaSource resolve(@NonNull StreamInfo info) {
|
||||||
|
final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info);
|
||||||
|
if (liveSource != null) return liveSource;
|
||||||
|
|
||||||
|
List<MediaSource> mediaSources = new ArrayList<>();
|
||||||
|
|
||||||
|
// Create video stream source
|
||||||
|
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context,
|
||||||
|
info.getVideoStreams(), info.getVideoOnlyStreams(), false);
|
||||||
|
final int index;
|
||||||
|
if (videos.isEmpty()) {
|
||||||
|
index = -1;
|
||||||
|
} else if (playbackQuality == null) {
|
||||||
|
index = qualityResolver.getDefaultResolutionIndex(videos);
|
||||||
|
} else {
|
||||||
|
index = qualityResolver.getOverrideResolutionIndex(videos, getPlaybackQuality());
|
||||||
|
}
|
||||||
|
final MediaSourceTag tag = new MediaSourceTag(info, videos, index);
|
||||||
|
@Nullable final VideoStream video = tag.getSelectedVideoStream();
|
||||||
|
|
||||||
|
if (video != null) {
|
||||||
|
final MediaSource streamSource = buildMediaSource(dataSource, video.getUrl(),
|
||||||
|
PlayerHelper.cacheKeyOf(info, video),
|
||||||
|
MediaFormat.getSuffixById(video.getFormatId()), tag);
|
||||||
|
mediaSources.add(streamSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create optional audio stream source
|
||||||
|
final List<AudioStream> audioStreams = info.getAudioStreams();
|
||||||
|
final AudioStream audio = audioStreams.isEmpty() ? null : audioStreams.get(
|
||||||
|
ListHelper.getDefaultAudioFormat(context, audioStreams));
|
||||||
|
// Use the audio stream if there is no video stream, or
|
||||||
|
// Merge with audio stream in case if video does not contain audio
|
||||||
|
if (audio != null && ((video != null && video.isVideoOnly) || video == null)) {
|
||||||
|
final MediaSource audioSource = buildMediaSource(dataSource, audio.getUrl(),
|
||||||
|
PlayerHelper.cacheKeyOf(info, audio),
|
||||||
|
MediaFormat.getSuffixById(audio.getFormatId()), tag);
|
||||||
|
mediaSources.add(audioSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no audio or video sources, then this media source cannot be played back
|
||||||
|
if (mediaSources.isEmpty()) return null;
|
||||||
|
// Below are auxiliary media sources
|
||||||
|
|
||||||
|
// Create subtitle sources
|
||||||
|
for (final Subtitles subtitle : info.getSubtitles()) {
|
||||||
|
final String mimeType = PlayerHelper.mimeTypesOf(subtitle.getFileType());
|
||||||
|
if (mimeType == null) continue;
|
||||||
|
|
||||||
|
final Format textFormat = Format.createTextSampleFormat(null, mimeType,
|
||||||
|
SELECTION_FLAG_AUTOSELECT, PlayerHelper.captionLanguageOf(context, subtitle));
|
||||||
|
final MediaSource textSource = dataSource.getSampleMediaSourceFactory()
|
||||||
|
.createMediaSource(Uri.parse(subtitle.getURL()), textFormat, TIME_UNSET);
|
||||||
|
mediaSources.add(textSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaSources.size() == 1) {
|
||||||
|
return mediaSources.get(0);
|
||||||
|
} else {
|
||||||
|
return new MergingMediaSource(mediaSources.toArray(
|
||||||
|
new MediaSource[mediaSources.size()]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getPlaybackQuality() {
|
||||||
|
return playbackQuality;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlaybackQuality(@Nullable String playbackQuality) {
|
||||||
|
this.playbackQuality = playbackQuality;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue