背景
我正在开发一个可以播放一些短视频的应用程序。
我想避免用户每次播放视频时都访问互联网,以使其更快并降低数据使用量。
问题
目前我只找到了如何播放或下载(它只是一个文件,所以我可以像下载其他文件一样下载它)。
以下是从URL播放视频文件的代码(样例可在此处找到):
gradle
...
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'
...
清单文件
<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))
}
我尝试过的
我已经阅读了文档,并通过询问这里获得了以下链接:
https://medium.com/google-exoplayer/downloading-streams-6d259eec7f95 https://medium.com/google-exoplayer/downloading-adaptive-streams-37191f9776e
所以很遗憾,目前我能想到的唯一解决办法是在另一个线程上下载文件,这将导致设备连接两次,从而使用了两倍的带宽。
问题
- 如何在使用ExoPlayer播放视频文件的同时,将其下载到某个文件路径中?
- 是否有一种方式可以启用ExoPlayer上的缓存机制(使用磁盘)来实现完全相同的目的?
注意:为了明确。我不想下载文件然后再播放它。
编辑:我找到了一种从API缓存中获取和使用文件的方法(在这里中写了),但似乎这被视为不安全的(在这里写了)。
因此,考虑到ExoPlayer API支持的简单缓存机制,我的当前问题是:
- 如果文件已缓存,如何以安全的方式使用它?
- 如果文件部分缓存(意味着我们已经下载了一部分),如何继续准备它(而无需实际播放它或等待整个播放完成),直到可以以安全的方式使用它为止?
我为此创建了一个Github存储库这里。您可以尝试一下。
Exo
的专家。但是有一段时间我在研究它,然后偶然发现了这个。整个想法是使用代理。你可以看看实现方式。 - ADM