android - mediasource - Usar caché en ExoPlayer
hls mediasource exoplayer (9)
Estoy buscando algún ejemplo de implementación de caché en ExoPlayer.
ExoPlayer tiene en su biblioteca diferentes clases sobre caché y Google explica en este video que podemos implementarlo con la clase CacheDataSource, pero Google no proporciona ninguna demostración. Desafortunadamente, esto parece bastante complicado de usar, por lo que actualmente estoy buscando ejemplos (sin éxito en Google).
¿Alguien tiene éxito o tiene alguna información que pueda ayudar? Gracias.
Además de la respuesta de Bao Le, aquí está listo para usar la versión Kotlin de
CacheDataSourceFactory
que mantiene una instancia de
SimpleCache
para resolver el problema de múltiples objetos Cache que se escriben en el mismo directorio.
object VideoCache {
private var sDownloadCache: SimpleCache? = null
private const val maxCacheSize: Long = 100 * 1024 * 1024
fun getInstance(context: Context): SimpleCache {
val evictor = LeastRecentlyUsedCacheEvictor(maxCacheSize)
if (sDownloadCache == null) sDownloadCache = SimpleCache(File(context.cacheDir, "koko-media"), evictor)
return sDownloadCache as SimpleCache
}
}
Aquí está la solución para ExoPlayer 2. +
Crear una fábrica de fuente de datos de caché personalizada
class CacheDataSourceFactory implements DataSource.Factory {
private final Context context;
private final DefaultDataSourceFactory defaultDatasourceFactory;
private final long maxFileSize, maxCacheSize;
CacheDataSourceFactory(Context context, long maxCacheSize, long maxFileSize) {
super();
this.context = context;
this.maxCacheSize = maxCacheSize;
this.maxFileSize = maxFileSize;
String userAgent = Util.getUserAgent(context, context.getString(R.string.app_name));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
defaultDatasourceFactory = new DefaultDataSourceFactory(this.context,
bandwidthMeter,
new DefaultHttpDataSourceFactory(userAgent, bandwidthMeter));
}
@Override
public DataSource createDataSource() {
LeastRecentlyUsedCacheEvictor evictor = new LeastRecentlyUsedCacheEvictor(maxCacheSize);
SimpleCache simpleCache = new SimpleCache(new File(context.getCacheDir(), "media"), evictor);
return new CacheDataSource(simpleCache, defaultDatasourceFactory.createDataSource(),
new FileDataSource(), new CacheDataSink(simpleCache, maxFileSize),
CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR, null);
}
}
Y el jugador
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
SimpleExoPlayer exoPlayer = ExoPlayerFactory.newSimpleInstance(this, trackSelector);
MediaSource audioSource = new ExtractorMediaSource(Uri.parse(url),
new CacheDataSourceFactory(context, 100 * 1024 * 1024, 5 * 1024 * 1024), new DefaultExtractorsFactory(), null, null);
exoPlayer.setPlayWhenReady(true);
exoPlayer.prepare(audioSource);
Funciona bastante bien
Aquí está mi muestra en Kotlin (proyecto disponible here ):
private val simpleCache: SimpleCache by lazy {
VideoCache.getInstance(context)
}
Aquí hay un ejemplo que reemplaza la fuente de datos de demostración con OkHttp, el valor predeterminado es sin caché https://github.com/b95505017/ExoPlayer/commit/ebfdda8e7848a2e2e275f5c0525f614b56ef43a6 https://github.com/b95505017/ExoPlayer/tree/okhttp_http_data_source , youtube necesita configurar el caché OkHttp correctamente y las solicitudes deben almacenarse en caché.
La lista de documentación de Exoplayer es la clase DashDownloader y tiene un código de ejemplo para ese tipo de fuente. (Haga clic en [Marcos] para volver a navegar por la documentación. Tuve que eliminarla para obtener el enlace profundo).
Lo he implementado así en el generador de renderizadores
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int BUFFER_SEGMENT_COUNT = 160;
final String userAgent = Util.getUserAgent(mContext, appName);
final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
final Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE);*
Cache cache = new SimpleCache(context.getCacheDir(), new LeastRecentlyUsedCacheEvictor(1024 * 1024 * 10));
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
CacheDataSource cacheDataSource = new CacheDataSource(cache, dataSource, false, false);
ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri
, cacheDataSource
, allocator
, BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE
, new Mp4Extractor());
Para resolver el problema de múltiples videos o procesos que intentan acceder al mismo caché, necesita un Singleton verdadero. Una forma confiable sería hacerlo de esta manera:
class CacheDataSourceFactory(private val context: Context,
private val maxCacheSize: Long,
private val maxFileSize: Long) : DataSource.Factory {
private val defaultDatasourceFactory: DefaultDataSourceFactory
private val simpleCache: SimpleCache by lazy {
val evictor = LeastRecentlyUsedCacheEvictor(maxCacheSize)
SimpleCache(File(context.cacheDir, "media"), evictor)
}
init {
val userAgent = Util.getUserAgent(context, context.packageName)
val bandwidthMeter = DefaultBandwidthMeter()
defaultDatasourceFactory = DefaultDataSourceFactory(context,
bandwidthMeter,
DefaultHttpDataSourceFactory(userAgent, bandwidthMeter))
}
override fun createDataSource(): DataSource {
return CacheDataSource(simpleCache,
defaultDatasourceFactory.createDataSource(),
FileDataSource(),
CacheDataSink(simpleCache, maxFileSize),
CacheDataSource.FLAG_BLOCK_ON_CACHE or CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,
null)
}
}
que ahora puedes usar:
class MainActivity : AppCompatActivity() {
private var player: SimpleExoPlayer? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (cache == null) {
cache = SimpleCache(File(cacheDir, "media"), LeastRecentlyUsedCacheEvictor(MAX_PREVIEW_CACHE_SIZE_IN_BYTES))
}
setContentView(R.layout.activity_main)
}
override fun onStart() {
super.onStart()
playVideo()
}
private fun playVideo() {
player = ExoPlayerFactory.newSimpleInstance(this@MainActivity, DefaultTrackSelector())
playerView.player = player
player!!.volume = 1f
player!!.playWhenReady = true
player!!.repeatMode = Player.REPEAT_MODE_ALL
player!!.playVideoFromUrl(this, "https://sample-videos.com/video123/mkv/240/big_buck_bunny_240p_20mb.mkv", cache!!)
// player!!.playVideoFromUrl(this, "https://sample-videos.com/video123/mkv/720/big_buck_bunny_720p_1mb.mkv", cache!!)
// player!!.playVideoFromUrl(this, "https://sample-videos.com/video123/mkv/720/big_buck_bunny_720p_1mb.mkv")
// player!!.playRawVideo(this,R.raw.videoplayback)
}
override fun onStop() {
super.onStop()
playerView.player = null
player!!.release()
player = null
}
companion object {
const val MAX_PREVIEW_CACHE_SIZE_IN_BYTES = 20L * 1024L * 1024L
var cache: com.google.android.exoplayer2.upstream.cache.Cache? = null
@JvmStatic
fun getUserAgent(context: Context): String {
val packageManager = context.packageManager
val info = packageManager.getPackageInfo(context.packageName, 0)
val appName = info.applicationInfo.loadLabel(packageManager).toString()
return Util.getUserAgent(context, appName)
}
}
fun SimpleExoPlayer.playRawVideo(context: Context, @RawRes rawVideoRes: Int) {
val dataSpec = DataSpec(RawResourceDataSource.buildRawResourceUri(rawVideoRes))
val rawResourceDataSource = RawResourceDataSource(context)
rawResourceDataSource.open(dataSpec)
val factory: DataSource.Factory = DataSource.Factory { rawResourceDataSource }
prepare(LoopingMediaSource(ExtractorMediaSource.Factory(factory).createMediaSource(rawResourceDataSource.uri)))
}
fun SimpleExoPlayer.playVideoFromUrl(context: Context, url: String, cache: Cache? = null) = playVideoFromUri(context, Uri.parse(url), cache)
fun SimpleExoPlayer.playVideoFile(context: Context, file: File) = playVideoFromUri(context, Uri.fromFile(file))
fun SimpleExoPlayer.playVideoFromUri(context: Context, uri: Uri, cache: Cache? = null) {
val factory = if (cache != null)
CacheDataSourceFactory(cache, DefaultHttpDataSourceFactory(getUserAgent(context)))
else
DefaultDataSourceFactory(context, MainActivity.getUserAgent(context))
val mediaSource = ExtractorMediaSource.Factory(factory).createMediaSource(uri)
prepare(mediaSource)
}
}
Por defecto, ExoPlayer no almacena en caché los medios (video, audio, etc.) Por ejemplo, si desea reproducir un archivo de video en línea, cada vez que ExoPlayer abra una conexión, lea los datos y luego reprodúzcalos.
Afortunadamente, nos proporciona algunas interfaces y clases de implementación para admitir el almacenamiento en caché de medios en nuestra aplicación.
Puede escribir su propia caché que implemente interfaces dadas de ExoPlayer. Para simplificarlo, te guiaré sobre cómo habilitar el caché usando clases de implementación.
Paso 1: especifique una carpeta que contenga sus archivos multimedia, en Android para una carpeta de caché más pequeña (menos de 1 MB), debe usar getCacheDir , de lo contrario puede especificar su carpeta de caché preferida, getFileDir , por ejemplo.
Paso 2: especifique un tamaño para la carpeta de caché y las políticas cuando se alcanza el tamaño. Hay 2 API
- NoOpCacheEvictor que nunca desaloja / elimina archivos de caché. Según la ubicación de su carpeta de caché, si está en almacenamiento interno, la carpeta se eliminará cuando los usuarios borren los datos de la aplicación o la desinstalen.
- LeastRecentlyUsedCacheEvictor que desalojará / eliminará primero los archivos de caché utilizados menos recientemente. Por ejemplo, si el tamaño de su caché es de 10 MB, cuando se alcanza el tamaño, automáticamente buscará y eliminará los archivos que se usaron menos recientemente.
Ponlo junto
val renderersFactory = DefaultRenderersFactory(context.applicationContext)
val trackSelector = DefaultTrackSelector()
val loadControl = DefaultLoadControl()
val player = ExoPlayerFactory.newSimpleInstance(context, renderersFactory, trackSelector, loadControl)
player.addListener(this)
// Specify cache folder, my cache folder named media which is inside getCacheDir.
val cacheFolder = File(context.cacheDir, "media")
// Specify cache size and removing policies
val cacheEvictor = LeastRecentlyUsedCacheEvictor(1 * 1024 * 1024) // My cache size will be 1MB and it will automatically remove least recently used files if the size is reached out.
// Build cache
val cache = SimpleCache(cacheFolder, cacheEvictor)
// Build data source factory with cache enabled, if data is available in cache it will return immediately, otherwise it will open a new connection to get the data.
val cacheDataSourceFactory = CacheDataSourceFactory(cache, DefaultHttpDataSourceFactory("ExoplayerDemo"))
val uri = Uri.parse("Put your media url here")
val mediaSource = ExtractorMediaSource.Factory(cacheDataSourceFactory).createMediaSource(uri)
player.prepare(mediaSource)
Respondí esta pregunta similar aquí: https://.com/a/58678192/2029134
Básicamente, utilizo esta biblioteca: https://github.com/danikula/AndroidVideoCache Para almacenar en caché el archivo desde la URL Luego, póngalo en ExoPlayer.
Aquí está el código de ejemplo:
String mediaURL = "https://my_cool_vid.com/vi.mp4"; SimpleExoPlayer exoPlayer = ExoPlayerFactory.newSimpleInstance(getContext()); HttpProxyCacheServer proxyServer = HttpProxyCacheServer.Builder(getContext()).maxCacheSize(1024 * 1024 * 1024).build(); String proxyURL = proxyServer.getProxyUrl(mediaURL); DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getContext(), Util.getUserAgent(getContext(), getActivity().getApplicationContext().getPackageName())); exoPlayer.prepare(new ProgressiveMediaSource.Factory(dataSourceFactory) .createMediaSource(Uri.parse(proxyURL)););
Espero que ayude.