class VideoPlayerAdapter( private val videoList: List, private val preloadingStrategy: String, private val context: Context ) : RecyclerView.Adapter() { private var currentPlayerPosition: Int = 0 // Index of currently playing video val bandwidthMeter = DefaultBandwidthMeter.Builder(context) .setInitialBitrateEstimate(100_000L) .build() val exoPlayer = ExoPlayer.Builder(context) .setLoadControl(createAggressiveLoadControl()) .setBandwidthMeter(bandwidthMeter) .build() val cache = SimpleCache( File(context.cacheDir, "exo_cache"), NoOpCacheEvictor(), StandaloneDatabaseProvider(context) ) val cacheKeyFactory = object : CacheKeyFactory { override fun buildCacheKey(dataSpec: DataSpec): String { val url = dataSpec.uri.toString() return "video_cache_key_${dataSpec.uri.hashCode()}" } } val cacheDataSourceFactory = CacheDataSource.Factory() .setCache(cache) .setCacheKeyFactory(cacheKeyFactory) .setUpstreamDataSourceFactory(Trusting(context)) // Trusting manages security due to HTTPs .setCacheWriteDataSinkFactory (CacheDataSink.Factory().setCache(cache)) .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR) init { val videoUri = videoList[0].toUri() val mediaItem = buildMediaItem(videoUri, 0) val mediaSource = DashMediaSource.Factory(cacheDataSourceFactory) .createMediaSource(mediaItem) exoPlayer.setMediaSource(mediaSource) } private fun buildMediaItem(uri: Uri, index: Int): MediaItem { return MediaItem.Builder() .setUri(uri) .setCustomCacheKey("video_cache_key_${uri.hashCode()}") .build() } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideoViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_video, parent, false) return VideoViewHolder(view, this) } override fun onBindViewHolder(holder: VideoViewHolder, position: Int) { holder.bind(position) } override fun getItemCount() = videoList.size private fun createAggressiveLoadControl(): LoadControl { return DefaultLoadControl.Builder() .setBufferDurationsMs( 4000, 100000, 4000, 4000 ) .build() } class VideoViewHolder(itemView: View, private val adapter: VideoPlayerAdapter) : RecyclerView.ViewHolder(itemView) { val playerView: PlayerView = itemView.findViewById(R.id.playerView) private var exoPlayer: ExoPlayer? = null fun bind(position: Int) { playerView.player = null // Bind the player to the view only for the current item ... } fun onPageSelected(position: Int) { // Clear exoplayer from current video and switch to next video exoPlayer.stop() val mediaItem = buildMediaItem(videoList[position].toUri(), position) val mediaSource = DashMediaSource.Factory(cacheDataSourceFactory) .createMediaSource(mediaItem) exoPlayer.setMediaSource(mediaSource) exoPlayer.prepare() exoPlayer.play() // Update index and refresh views val oldPosition = currentPlayerPosition currentPlayerPosition = position notifyItemChanged(oldPosition) notifyItemChanged(position) // Preload the next two videos preloadNextVideos( context = context, cache = cache, cacheDataSourceFactory = cacheDataSourceFactory, videoUris = videoList.map { it.toUri() }, currentIndex = position, count = 2, preloadBytesLimit = 2 * 1024 * 1024 ) } fun preloadNextVideos( context: Context, cache: Cache, cacheDataSourceFactory: CacheDataSource.Factory, videoUris: List, currentIndex: Int, count: Int = 1, preloadBytesLimit: Long = 2 * 1024 * 1024 ) { // Intended to preload 2 videos for (i in 1..count) { val preloadIndex = currentIndex + i if (preloadIndex >= videoUris.size) break val uri = videoUris[preloadIndex] val dataSpec = DataSpec.Builder() .setUri(uri) .setLength(preloadBytesLimit) .build() Thread { try { val dataSource = cacheDataSourceFactory.createDataSource() val writer = CacheWriter(dataSource, dataSpec, null, null) writer.cache() Log.d("Preloader", "Preloaded: $uri") } catch (e: Exception) { Log.w("Preloader", "Failed to preload: $uri", e) } }.start() } } }