You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android中使用CacheDataSource.Factory预缓存视频列表的高效实现方案

实现ExoPlayer视频列表预缓存(后台线程版)

核心思路

预缓存本质是把视频文件的部分或全部数据提前下载到ExoPlayer的SimpleCache中,不需要启动播放。用WorkManager做后台任务管理是最优选择——它能自动处理进程被杀、设备重启后的任务恢复,还能基于网络、充电状态调度任务,比普通Service更稳定可靠。

具体实现步骤

1. 封装预缓存工具类

先写一个通用的缓存任务类,负责单个视频的预缓存逻辑:

public class PreCacheTask {
    private final SimpleCache simpleCache;
    private final Context context;

    public PreCacheTask(SimpleCache simpleCache, Context context) {
        this.simpleCache = simpleCache;
        this.context = context;
    }

    // 预缓存指定URI的视频,可设置最大缓存字节数(比如缓存前10分钟内容)
    public void preCacheVideo(String videoUri, long maxBytesToCache) throws IOException {
        DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(context, new DefaultHttpDataSource.Factory());
        CacheDataSource.Factory cacheDataSourceFactory = new CacheDataSource.Factory()
                .setCache(simpleCache)
                .setUpstreamDataSourceFactory(dataSourceFactory)
                .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);

        // 创建MediaSource但不关联播放器
        ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
                .createMediaSource(MediaItem.fromUri(videoUri));

        // 用ExoPlayer内置的CacheUtil主动缓存数据
        CacheUtil.cache(
                mediaSource,
                new DefaultCacheKeyFactory(),
                simpleCache,
                maxBytesToCache,
                CacheUtil.DEFAULT_BUFFER_SIZE,
                (requestLength, cachedLength, newBytesCached) -> {
                    // 监听缓存进度,可用于更新WorkManager任务进度
                    float progress = (float) cachedLength / requestLength;
                    // 这里可以处理进度回调,比如记录日志或通知UI
                },
                null // 取消信号,用于中断任务
        );
    }
}

2. 用WorkManager创建后台缓存任务

创建WorkRequest调度批量缓存任务,支持多视频处理:

public class PreCacheWorker extends CoroutineWorker {
    public static final String KEY_VIDEO_URIS = "video_uris";
    public static final String KEY_MAX_CACHE_BYTES = "max_cache_bytes";

    public PreCacheWorker(@NonNull Context context, @NonNull WorkerParameters params) {
        super(context, params);
    }

    @NonNull
    @Override
    public Result doWork() {
        List<String> videoUris = getInputData().getStringArray(KEY_VIDEO_URIS) != null ?
                Arrays.asList(getInputData().getStringArray(KEY_VIDEO_URIS)) :
                Collections.emptyList();
        // 默认每个视频缓存50MB,可根据需求调整
        long maxCacheBytes = getInputData().getLong(KEY_MAX_CACHE_BYTES, 1024 * 1024 * 50);

        PreCacheTask preCacheTask = new PreCacheTask(SimpleMediaPlayer.simpleCache, getApplicationContext());

        for (String uri : videoUris) {
            try {
                // 先判断是否已缓存足够数据,避免重复下载
                CacheKey cacheKey = new DefaultCacheKeyFactory().buildCacheKey(MediaItem.fromUri(uri));
                long cachedBytes = CacheUtil.getCachedBytes(simpleCache, cacheKey, 0, maxCacheBytes);
                if (cachedBytes >= maxCacheBytes) {
                    continue;
                }
                preCacheTask.preCacheVideo(uri, maxCacheBytes - cachedBytes);
            } catch (IOException e) {
                // 单个视频缓存失败,记录日志后继续处理下一个
                Log.e("PreCacheWorker", "Cache failed for " + uri, e);
            }
        }

        return Result.success();
    }

    // 启动缓存任务的静态方法
    public static void startPreCache(Context context, List<String> videoUris, long maxCacheBytes) {
        Data inputData = new Data.Builder()
                .putStringArray(KEY_VIDEO_URIS, videoUris.toArray(new String[0]))
                .putLong(KEY_MAX_CACHE_BYTES, maxCacheBytes)
                .build();

        OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(PreCacheWorker.class)
                .setInputData(inputData)
                .setConstraints(new Constraints.Builder()
                        .setRequiredNetworkType(NetworkType.UNMETERED) // 可选:仅WiFi下缓存
                        .setRequiresDeviceIdle(false) // 可选:是否设备空闲时执行
                        .build())
                .build();

        WorkManager.getInstance(context).enqueue(workRequest);
    }
}

3. 触发预缓存任务

在需要预缓存的场景(比如进入视频列表页时)调用:

List<String> videoList = Arrays.asList(
        "https://xxxxxx.s3.us-north-2.amazonaws.com/video1.mp4",
        "https://xxxxxx.s3.us-north-2.amazonaws.com/video2.mp4"
);
// 每个视频缓存50MB
PreCacheWorker.startPreCache(this, videoList, 1024 * 1024 * 50);

关键优化点

  • 缓存大小控制:不要缓存整个视频,根据视频时长设置合理的缓存量(比如前5-10分钟),避免占用过多存储空间。
  • 重复缓存判断:用CacheUtil.getCachedBytes检查已缓存数据,避免重复下载。
  • 任务调度策略:通过WorkManager的Constraints设置仅在WiFi、充电时缓存,减少用户流量消耗和设备负载。
  • 进度监听:可通过WorkManager的getWorkInfoByIdLiveData监听任务整体进度,给用户展示缓存状态。

注意事项

  • 确保SimpleCache是全局单例,避免多个实例导致缓存数据混乱。
  • 需申请INTERNET权限,Android 10+需注意后台网络访问限制,WorkManager的Constraints会自动适配系统规则。

内容的提问来源于stack exchange,提问作者ThinQ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 19:31:15