gpt4 book ai didi

android - 如何在播放时下载视频,使用 ExoPlayer?

转载 作者:行者123 更新时间:2023-12-04 19:26:49 28 4
gpt4 key购买 nike

背景

我正在开发一个可以播放一些短视频的应用程序。

我想避免每次用户玩它们时都访问互联网,以使其更快并降低数据使用量。

问题

目前我只找到了如何播放或下载(它只是一个文件,所以我可以像下载任何其他文件一样下载它)。

这是从 URL 播放视频文件的代码(示例可用 这里 ):

毕业

...
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.exoplayer:exoplayer-core:2.8.4'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.8.4'
...

list
<manifest package="com.example.user.myapplication" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

<application
android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity
android:name=".MainActivity" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>

</manifest>

activity_main.xml
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools:context=".MainActivity">

<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/playerView" android:layout_width="match_parent" android:layout_height="match_parent"
app:resize_mode="zoom"/>
</FrameLayout>

MainActivity.kt
class MainActivity : AppCompatActivity() {
private var player: SimpleExoPlayer? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}

override fun onStart() {
super.onStart()
playVideo()
}

private fun playVideo() {
player = ExoPlayerFactory.newSimpleInstance(this@MainActivity, DefaultTrackSelector())
playerView.player = player
player!!.addVideoListener(object : VideoListener {
override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) {
}

override fun onRenderedFirstFrame() {
Log.d("appLog", "onRenderedFirstFrame")
}
})
player!!.addListener(object : PlayerEventListener() {
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
super.onPlayerStateChanged(playWhenReady, playbackState)
when (playbackState) {
Player.STATE_READY -> Log.d("appLog", "STATE_READY")
Player.STATE_BUFFERING -> Log.d("appLog", "STATE_BUFFERING")
Player.STATE_IDLE -> Log.d("appLog", "STATE_IDLE")
Player.STATE_ENDED -> Log.d("appLog", "STATE_ENDED")
}
}
})
player!!.volume = 0f
player!!.playWhenReady = true
player!!.repeatMode = Player.REPEAT_MODE_ALL
player!!.playVideoFromUrl(this@MainActivity, "https://sample-videos.com/video123/mkv/720/big_buck_bunny_720p_1mb.mkv")
}

override fun onStop() {
super.onStop()
playerView.player = null
player!!.release()
player = null
}


abstract class PlayerEventListener : Player.EventListener {
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters?) {}
override fun onSeekProcessed() {}
override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) {}
override fun onPlayerError(error: ExoPlaybackException?) {}
override fun onLoadingChanged(isLoading: Boolean) {}
override fun onPositionDiscontinuity(reason: Int) {}
override fun onRepeatModeChanged(repeatMode: Int) {}
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {}
override fun onTimelineChanged(timeline: Timeline?, manifest: Any?, reason: Int) {}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {}
}

companion object {
@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.playVideoFromUri(context: Context, uri: Uri) {
val dataSourceFactory = DefaultDataSourceFactory(context, MainActivity.getUserAgent(context))
val mediaSource = ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri)
prepare(mediaSource)
}


fun SimpleExoPlayer.playVideoFromUrl(context: Context, url: String) = playVideoFromUri(context, Uri.parse(url))

fun SimpleExoPlayer.playVideoFile(context: Context, file: File) = playVideoFromUri(context, Uri.fromFile(file))
}

我试过的

我已经尝试阅读文档,并获得了这些链接(通过询问 here ):

https://medium.com/google-exoplayer/downloading-streams-6d259eec7f95
https://medium.com/google-exoplayer/downloading-adaptive-streams-37191f9776e

可悲的是,目前我能想出的唯一解决方案是在另一个线程上下载文件,这将导致设备与它有 2 个连接,从而使用两倍的带宽。

问题
  • 如何使用 ExoPlayer 播放视频文件,同时将其下载到某个文件路径?
  • 有没有办法让 ExoPlayer 上的缓存机制(使用磁盘)被激活以达到完全相同的目的?

  • 注意:为了清楚。我不想下载文件,然后才播放它。

    编辑:我找到了一种从 API 缓存中获取和使用文件的方法(写了 here ),但似乎这被认为是不安全的(写 here )。

    所以,鉴于 ExoPlayer 的 API 支持的简单缓存机制,我目前的问题是:
  • 如果文件被缓存,我怎样才能以安全的方式使用它?
  • 如果一个文件被部分缓存(意味着我们已经下载了它的一部分),我如何继续准备它(没有实际播放它或等待整个播放完成)直到我可以使用它(当然是以安全的方式) ?

  • 我为此创建了一个 Github 存储库 here .你可以试试看。

    最佳答案

    我看了一下erdemguven的示例代码here并且似乎有一些有用的东西。这基本上是 erdemguven 写的,但我写入文件而不是字节数组并创建数据源。我认为,由于 ExoPlayer 专家 erdemguven 将此作为访问缓存的正确方法,因此我的 mod 也是“正确的”并且不违反任何规则。

    这是代码。 getCachedData是新的东西。

    class MainActivity : AppCompatActivity(), CacheDataSource.EventListener, TransferListener {

    private var player: SimpleExoPlayer? = null

    companion object {
    // About 10 seconds and 1 meg.
    // const val VIDEO_URL = "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4"

    // About 1 minute and 5.3 megs
    const val VIDEO_URL = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"

    // The full movie about 355 megs.
    // const val VIDEO_URL = "http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_60fps_normal.mp4"

    // Use to download video other than the one you are viewing. See #3 test of the answer.
    // const val VIDEO_URL_LIE = "http://file-examples.com/wp-content/uploads/2017/04/file_example_MP4_480_1_5MG.mp4"

    // No changes in code deleted here.

    //NOTE: I know I shouldn't use an AsyncTask. It's just a sample...
    @SuppressLint("StaticFieldLeak")
    fun tryShareCacheFile() {
    // file is cached and ready to be used
    object : AsyncTask<Void?, Void?, File>() {
    override fun doInBackground(vararg params: Void?): File {
    val tempFile = FilesPaths.FILE_TO_SHARE.getFile(this@MainActivity, true)
    getCachedData(this@MainActivity, cache, VIDEO_URL, tempFile)
    return tempFile
    }

    override fun onPostExecute(result: File) {
    super.onPostExecute(result)
    val intent = prepareIntentForSharingFile(this@MainActivity, result)
    startActivity(intent)
    }
    }.execute()
    }

    private var mTotalBytesToRead = 0L
    private var mBytesReadFromCache: Long = 0
    private var mBytesReadFromNetwork: Long = 0

    @WorkerThread
    fun getCachedData(
    context: Context, myCache: Cache?, url: String, tempfile: File
    ): Boolean {
    var isSuccessful = false
    val myUpstreamDataSource = DefaultHttpDataSourceFactory(ExoPlayerEx.getUserAgent(context)).createDataSource()
    val dataSource = CacheDataSource(
    myCache,
    // If the cache doesn't have the whole content, the missing data will be read from upstream
    myUpstreamDataSource,
    FileDataSource(),
    // Set this to null if you don't want the downloaded data from upstream to be written to cache
    CacheDataSink(myCache, CacheDataSink.DEFAULT_BUFFER_SIZE.toLong()),
    /* flags= */ 0,
    /* eventListener= */ this
    )

    // Listen to the progress of the reads from cache and the network.
    dataSource.addTransferListener(this)

    var outFile: FileOutputStream? = null
    var bytesRead = 0

    // Total bytes read is the sum of these two variables.
    mTotalBytesToRead = C.LENGTH_UNSET.toLong()
    mBytesReadFromCache = 0
    mBytesReadFromNetwork = 0

    try {
    outFile = FileOutputStream(tempfile)
    mTotalBytesToRead = dataSource.open(DataSpec(Uri.parse(url)))
    // Just read from the data source and write to the file.
    val data = ByteArray(1024)

    Log.d("getCachedData", "<<<<Starting fetch...")
    while (bytesRead != C.RESULT_END_OF_INPUT) {
    bytesRead = dataSource.read(data, 0, data.size)
    if (bytesRead != C.RESULT_END_OF_INPUT) {
    outFile.write(data, 0, bytesRead)
    }
    }
    isSuccessful = true
    } catch (e: IOException) {
    // error processing
    } finally {
    dataSource.close()
    outFile?.flush()
    outFile?.close()
    }

    return isSuccessful
    }

    override fun onCachedBytesRead(cacheSizeBytes: Long, cachedBytesRead: Long) {
    Log.d("onCachedBytesRead", "<<<<Cache read? Yes, (byte read) $cachedBytesRead (cache size) $cacheSizeBytes")
    }

    override fun onCacheIgnored(reason: Int) {
    Log.d("onCacheIgnored", "<<<<Cache ignored. Reason = $reason")
    }

    override fun onTransferInitializing(source: DataSource?, dataSpec: DataSpec?, isNetwork: Boolean) {
    Log.d("TransferListener", "<<<<Initializing isNetwork=$isNetwork")
    }

    override fun onTransferStart(source: DataSource?, dataSpec: DataSpec?, isNetwork: Boolean) {
    Log.d("TransferListener", "<<<<Transfer is starting isNetwork=$isNetwork")
    }

    override fun onTransferEnd(source: DataSource?, dataSpec: DataSpec?, isNetwork: Boolean) {
    reportProgress(0, isNetwork)
    Log.d("TransferListener", "<<<<Transfer has ended isNetwork=$isNetwork")
    }

    override fun onBytesTransferred(
    source: DataSource?,
    dataSpec: DataSpec?,
    isNetwork: Boolean,
    bytesTransferred: Int
    ) {
    // Report progress here.
    if (isNetwork) {
    mBytesReadFromNetwork += bytesTransferred
    } else {
    mBytesReadFromCache += bytesTransferred
    }

    reportProgress(bytesTransferred, isNetwork)
    }

    private fun reportProgress(bytesTransferred: Int, isNetwork: Boolean) {
    val percentComplete =
    100 * (mBytesReadFromNetwork + mBytesReadFromCache).toFloat() / mTotalBytesToRead
    val completed = "%.1f".format(percentComplete)
    Log.d(
    "TransferListener", "<<<<Bytes transferred: $bytesTransferred isNetwork=$isNetwork" +
    " $completed% completed"
    )
    }

    // No changes below here.
    }

    这是我为测试它所做的,这绝不是详尽的:
  • 只需使用 FAB 通过电子邮件共享视频。我收到了视频并且能够播放它。
  • 关闭物理设备上的所有网络访问(飞行模式 = 打开)并通过电子邮件共享视频。当我重新打开网络(飞行模式 = 关闭)时,我收到并能够播放视频。这表明由于网络不可用,视频必须来自缓存。
  • 更改了代码,而不是 VIDEO_URL从缓存中复制,我指定 VIDEO_URL_LIE应该复制。 (该应用程序仍然只播放 VIDEO_URL 。)因为我还没有下载 VIDEO_URL_LIE 的视频。 ,视频不在缓存中,所以应用程序必须去网络获取视频。我通过电子邮件成功收到了正确的视频并且能够播放它。这表明如果缓存不可用,应用程序可以访问基础 Assets 。

  • 我绝不是 ExoPlayer 专家,所以你可以用任何你可能有的问题快速地难倒我。

    以下代码将跟踪视频被读取并存储在本地文件中的进度。
    // Get total bytes if known. This is C.LENGTH_UNSET if the video length is unknown.
    totalBytesToRead = dataSource.open(DataSpec(Uri.parse(url)))

    // Just read from the data source and write to the file.
    val data = ByteArray(1024)
    var bytesRead = 0
    var totalBytesRead = 0L
    while (bytesRead != C.RESULT_END_OF_INPUT) {
    bytesRead = dataSource.read(data, 0, data.size)
    if (bytesRead != C.RESULT_END_OF_INPUT) {
    outFile.write(data, 0, bytesRead)
    if (totalBytesToRead == C.LENGTH_UNSET.toLong()) {
    // Length of video in not known. Do something different here.
    } else {
    totalBytesRead += bytesRead
    Log.d("Progress:", "<<<< Percent read: %.2f".format(totalBytesRead.toFloat() / totalBytesToRead))
    }
    }
    }

    关于android - 如何在播放时下载视频,使用 ExoPlayer?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53692452/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com