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

如何修改ExoPlayer代码将DASH视频下载为单MP4文件?

问题描述

我想用ExoPlayer下载DASH格式视频,但当前代码执行后会生成多个文件。我怀疑问题出在dataSourceFactory上,试过替换成DefaultDataSourceFactory但没成功。现有代码如下,请问怎么修改才能让保存的文件是MP4格式?

import android.app.Notification
import android.os.Environment
import android.util.Log
import ar.com.flow.iot.R
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
import com.google.android.exoplayer2.offline.Download
import com.google.android.exoplayer2.offline.DownloadManager
import com.google.android.exoplayer2.offline.DownloadService
import com.google.android.exoplayer2.scheduler.Scheduler
import com.google.android.exoplayer2.ui.DownloadNotificationHelper
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
import com.google.android.exoplayer2.upstream.cache.SimpleCache
import java.io.File
import java.util.concurrent.Executor


class MyDownloadService : DownloadService(
    FOREGROUND_NOTIFICATION_ID,
    1000,
    DOWNLOAD_NOTIFICATION_CHANNEL_ID,
    R.string.exo_download_notification_channel_name,
    0
) {

    override fun getDownloadManager(): DownloadManager {

        Log.w("TAGGG_DOWNLOAD", "getDownloadManager")

        val databaseProvider = StandaloneDatabaseProvider(this)

        val downloadsDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)

        val cacheDirectory = File(downloadsDirectory, "media_cache")

        val cacheEvictionPolicy = LeastRecentlyUsedCacheEvictor(50 * 1024 * 1024) // 50 MB

        val downloadCache = SimpleCache(
            cacheDirectory,
            cacheEvictionPolicy,
            databaseProvider
        )

        val dataSourceFactory = DefaultHttpDataSource.Factory()

        val downloadExecutor = Executor { obj: Runnable -> obj.run() }

        val downloadManager = DownloadManager(
            this,
            databaseProvider,
            downloadCache,
            dataSourceFactory,
            downloadExecutor
        )

        // Optionally, setters can be called to configure the download manager.
        //downloadManager.requirements = requirements;
        downloadManager.maxParallelDownloads = 3;

        return downloadManager

    }

    override fun getScheduler(): Scheduler? {
        Log.e("TAGGG_DOWNLOAD", "Falta implementar getScheduler ")
        return null
    }

    override fun getForegroundNotification(
        downloads: MutableList<Download>,
        notMetRequirements: Int
    ): Notification {
        Log.w("TAGGG_DOWNLOAD", "getForegroundNotification $downloads $notMetRequirements ")

        return DownloadNotificationHelper(this, DOWNLOAD_NOTIFICATION_CHANNEL_ID).buildProgressNotification(
            this,
            R.drawable.ic_icon_help,
            null,
            null,
            downloads,
            notMetRequirements
        )

    }

    companion object {

        private const val FOREGROUND_NOTIFICATION_ID = 1

        const val DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download channel id"

    }
}

private fun downloadVideo(uri:Uri) {
    val downloadRequest = DownloadRequest.Builder(id, uri).build()

    DownloadService.sendAddDownload(
        this,
        MyDownloadService::class.java,
        downloadRequest,
        false
    )
}
解决方案

ExoPlayer默认对DASH这类自适应流媒体采用分片缓存机制,会生成多个小文件。要输出单MP4文件,需从数据源配置、下载请求、缓存管理三个维度修改:

1. 替换数据源工厂为CacheDataSourceFactory

DefaultHttpDataSource.Factory仅处理HTTP请求,无法支持分片合并。需改用DefaultDataSourceFactory结合CacheDataSourceFactory,实现缓存与合并的完整流程:

override fun getDownloadManager(): DownloadManager {
    Log.w("TAGGG_DOWNLOAD", "getDownloadManager")

    val databaseProvider = StandaloneDatabaseProvider(this)
    // 优先用应用私有目录,避免权限问题;若需公共目录可替换回原路径
    val downloadsDirectory = getExternalFilesDir(Environment.DIRECTORY_MOVIES) ?: 
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
    
    // 缓存目录用于临时存储分片,最终合并后的MP4会输出到downloadsDirectory
    val cacheDirectory = File(downloadsDirectory, "exo_cache")
    val cacheEvictionPolicy = LeastRecentlyUsedCacheEvictor(50 * 1024 * 1024) // 50 MB
    val downloadCache = SimpleCache(cacheDirectory, cacheEvictionPolicy, databaseProvider)

    // 创建完整的数据源工厂
    val userAgent = com.google.android.exoplayer2.util.Util.getUserAgent(this, "YourAppName")
    val defaultDataSourceFactory = com.google.android.exoplayer2.upstream.DefaultDataSourceFactory(
        this,
        userAgent
    )
    val cacheDataSourceFactory = com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory(
        downloadCache,
        defaultDataSourceFactory,
        com.google.android.exoplayer2.upstream.cache.CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR
    )

    val downloadExecutor = Executor { obj: Runnable -> obj.run() }

    val downloadManager = DownloadManager(
        this,
        databaseProvider,
        downloadCache,
        cacheDataSourceFactory, // 替换为CacheDataSourceFactory
        downloadExecutor
    )

    downloadManager.maxParallelDownloads = 3;

    return downloadManager
}

2. 配置DownloadRequest指定MP4输出格式

创建下载请求时,需明确指定输出格式为MP4,并选择MP4兼容的媒体轨道(H.264视频+AAC音频):

private fun downloadVideo(uri: Uri) {
    // 构建轨道选择参数,指定MP4兼容的编码格式
    val trackSelector = com.google.android.exoplayer2.trackselection.DefaultTrackSelector(this)
    val trackSelectionParameters = trackSelector.parameters
        .buildUpon()
        .setPreferredVideoMimeType(com.google.android.exoplayer2.util.MimeTypes.VIDEO_H264)
        .setPreferredAudioMimeType(com.google.android.exoplayer2.util.MimeTypes.AUDIO_AAC)
        .build()

    val downloadRequest = com.google.android.exoplayer2.offline.DownloadRequest.Builder(id, uri)
        .setTrackSelectionParameters(trackSelectionParameters)
        .setOutputFileFormat(com.google.android.exoplayer2.offline.DownloadRequest.OUTPUT_FILE_FORMAT_MP4)
        .build()

    DownloadService.sendAddDownload(
        this,
        MyDownloadService::class.java,
        downloadRequest,
        false
    )
}

3. 权限适配

  • Android 10及以下:需申请WRITE_EXTERNAL_STORAGE权限
  • Android 11+:建议使用应用私有目录(getExternalFilesDir)避免权限申请;若需存储到公共目录,需申请MANAGE_EXTERNAL_STORAGE权限

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 21:14:59