NewPipe/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java

218 lines
9.7 KiB
Java

package org.schabi.newpipe.player.helper;
import static org.schabi.newpipe.MainActivity.DEBUG;
import android.content.Context;
import android.util.Log;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.SingleSampleMediaSource;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistTracker;
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeOtfDashManifestCreator;
import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubePostLiveStreamDvrDashManifestCreator;
import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeProgressiveDashManifestCreator;
import org.schabi.newpipe.player.datasource.NonUriHlsDataSourceFactory;
import org.schabi.newpipe.player.datasource.YoutubeHttpDataSource;
import java.io.File;
public class PlayerDataSource {
public static final String TAG = PlayerDataSource.class.getSimpleName();
public static final int LIVE_STREAM_EDGE_GAP_MILLIS = 10000;
/**
* An approximately 4.3 times greater value than the
* {@link DefaultHlsPlaylistTracker#DEFAULT_PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT default}
* to ensure that (very) low latency livestreams which got stuck for a moment don't crash too
* early.
*/
private static final double PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT = 15;
/**
* The maximum number of generated manifests per cache, in
* {@link YoutubeProgressiveDashManifestCreator}, {@link YoutubeOtfDashManifestCreator} and
* {@link YoutubePostLiveStreamDvrDashManifestCreator}.
*/
private static final int MAX_MANIFEST_CACHE_SIZE = 500;
/**
* The folder name in which the ExoPlayer cache will be written.
*/
private static final String CACHE_FOLDER_NAME = "exoplayer";
/**
* The {@link SimpleCache} instance which will be used to build
* {@link com.google.android.exoplayer2.upstream.cache.CacheDataSource}s instances (with
* {@link CacheFactory}).
*/
private static SimpleCache cache;
private final int progressiveLoadIntervalBytes;
// Generic Data Source Factories (without or with cache)
private final DataSource.Factory cachelessDataSourceFactory;
private final CacheFactory cacheDataSourceFactory;
// YouTube-specific Data Source Factories (with cache)
// They use YoutubeHttpDataSource.Factory, with different parameters each
private final CacheFactory ytHlsCacheDataSourceFactory;
private final CacheFactory ytDashCacheDataSourceFactory;
private final CacheFactory ytProgressiveDashCacheDataSourceFactory;
public PlayerDataSource(final Context context,
final TransferListener transferListener) {
progressiveLoadIntervalBytes = PlayerHelper.getProgressiveLoadIntervalBytes(context);
// make sure the static cache was created: needed by CacheFactories below
instantiateCacheIfNeeded(context);
// generic data source factories use DefaultHttpDataSource.Factory
cachelessDataSourceFactory = new DefaultDataSource.Factory(context,
new DefaultHttpDataSource.Factory().setUserAgent(DownloaderImpl.USER_AGENT))
.setTransferListener(transferListener);
cacheDataSourceFactory = new CacheFactory(context, transferListener, cache,
new DefaultHttpDataSource.Factory().setUserAgent(DownloaderImpl.USER_AGENT));
// YouTube-specific data source factories use getYoutubeHttpDataSourceFactory()
ytHlsCacheDataSourceFactory = new CacheFactory(context, transferListener, cache,
getYoutubeHttpDataSourceFactory(false, false));
ytDashCacheDataSourceFactory = new CacheFactory(context, transferListener, cache,
getYoutubeHttpDataSourceFactory(true, true));
ytProgressiveDashCacheDataSourceFactory = new CacheFactory(context, transferListener, cache,
getYoutubeHttpDataSourceFactory(false, true));
// set the maximum size to manifest creators
YoutubeProgressiveDashManifestCreator.getCache().setMaximumSize(MAX_MANIFEST_CACHE_SIZE);
YoutubeOtfDashManifestCreator.getCache().setMaximumSize(MAX_MANIFEST_CACHE_SIZE);
YoutubePostLiveStreamDvrDashManifestCreator.getCache().setMaximumSize(
MAX_MANIFEST_CACHE_SIZE);
}
//region Live media source factories
public SsMediaSource.Factory getLiveSsMediaSourceFactory() {
return getSSMediaSourceFactory().setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS);
}
public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() {
return new HlsMediaSource.Factory(cachelessDataSourceFactory)
.setAllowChunklessPreparation(true)
.setPlaylistTrackerFactory((dataSourceFactory, loadErrorHandlingPolicy,
playlistParserFactory) ->
new DefaultHlsPlaylistTracker(dataSourceFactory, loadErrorHandlingPolicy,
playlistParserFactory,
PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT));
}
public DashMediaSource.Factory getLiveDashMediaSourceFactory() {
return new DashMediaSource.Factory(
getDefaultDashChunkSourceFactory(cachelessDataSourceFactory),
cachelessDataSourceFactory);
}
//endregion
//region Generic media source factories
public HlsMediaSource.Factory getHlsMediaSourceFactory(
@Nullable final NonUriHlsDataSourceFactory.Builder hlsDataSourceFactoryBuilder) {
if (hlsDataSourceFactoryBuilder != null) {
hlsDataSourceFactoryBuilder.setDataSourceFactory(cacheDataSourceFactory);
return new HlsMediaSource.Factory(hlsDataSourceFactoryBuilder.build());
}
return new HlsMediaSource.Factory(cacheDataSourceFactory);
}
public DashMediaSource.Factory getDashMediaSourceFactory() {
return new DashMediaSource.Factory(
getDefaultDashChunkSourceFactory(cacheDataSourceFactory),
cacheDataSourceFactory);
}
public ProgressiveMediaSource.Factory getProgressiveMediaSourceFactory() {
return new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
.setContinueLoadingCheckIntervalBytes(progressiveLoadIntervalBytes);
}
public SsMediaSource.Factory getSSMediaSourceFactory() {
return new SsMediaSource.Factory(
new DefaultSsChunkSource.Factory(cachelessDataSourceFactory),
cachelessDataSourceFactory);
}
public SingleSampleMediaSource.Factory getSingleSampleMediaSourceFactory() {
return new SingleSampleMediaSource.Factory(cacheDataSourceFactory);
}
//endregion
//region YouTube media source factories
public HlsMediaSource.Factory getYoutubeHlsMediaSourceFactory() {
return new HlsMediaSource.Factory(ytHlsCacheDataSourceFactory);
}
public DashMediaSource.Factory getYoutubeDashMediaSourceFactory() {
return new DashMediaSource.Factory(
getDefaultDashChunkSourceFactory(ytDashCacheDataSourceFactory),
ytDashCacheDataSourceFactory);
}
public ProgressiveMediaSource.Factory getYoutubeProgressiveMediaSourceFactory() {
return new ProgressiveMediaSource.Factory(ytProgressiveDashCacheDataSourceFactory)
.setContinueLoadingCheckIntervalBytes(progressiveLoadIntervalBytes);
}
//endregion
//region Static methods
private static DefaultDashChunkSource.Factory getDefaultDashChunkSourceFactory(
final DataSource.Factory dataSourceFactory) {
return new DefaultDashChunkSource.Factory(dataSourceFactory);
}
private static YoutubeHttpDataSource.Factory getYoutubeHttpDataSourceFactory(
final boolean rangeParameterEnabled,
final boolean rnParameterEnabled) {
return new YoutubeHttpDataSource.Factory()
.setRangeParameterEnabled(rangeParameterEnabled)
.setRnParameterEnabled(rnParameterEnabled);
}
private static void instantiateCacheIfNeeded(final Context context) {
if (cache == null) {
final File cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
if (DEBUG) {
Log.d(TAG, "instantiateCacheIfNeeded: cacheDir = " + cacheDir.getAbsolutePath());
}
if (!cacheDir.exists() && !cacheDir.mkdir()) {
Log.w(TAG, "instantiateCacheIfNeeded: could not create cache dir");
}
final LeastRecentlyUsedCacheEvictor evictor
= new LeastRecentlyUsedCacheEvictor(PlayerHelper.getPreferredCacheSize());
cache = new SimpleCache(cacheDir, evictor, new StandaloneDatabaseProvider(context));
}
}
//endregion
}