我正在开发一款可以播放一些短视频的应用。
我想避免每次用户播放Internet时都访问Internet,以使其速度更快并降低数据使用量。
目前,我只找到了如何播放或下载(这只是一个文件,因此我可以像下载其他文件一样下载它)。
以下是从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'
...
清单
<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个连接,从而使用两倍的带宽。
注意:为了清楚起见。我不想下载文件,只能播放它。
编辑:我已经找到了一种从API的缓存中获取和使用文件的方法(写为here),但是看来这被认为是不安全的(写为here)。
因此,鉴于ExoPlayer API支持的简单缓存机制,我目前的问题是:
我为此here创建了一个Github存储库。您可以尝试一下。
答案 0 :(得分:1)
我看了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.
}
这是我所做的测试,这绝不是详尽无遗的:
VIDEO_URL
,我指定应复制VIDEO_URL_LIE
。 (该应用程序仍仅播放VIDEO_URL
。)由于我没有下载VIDEO_URL_LIE
的视频,因此该视频不在缓存中,因此该应用程序必须将视频发送到网络。我通过电子邮件成功收到了正确的视频,并能够播放。这表明如果缓存不可用,则应用程序可以访问基础资产。我绝不是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))
}
}
}
答案 1 :(得分:0)
答案 2 :(得分:0)
您可以将exoplayer的SimpleCache与LeastRecentlyUsedCacheEvictor一起使用,以在流式传输时进行缓存。代码看起来像这样。
temporaryCache = new SimpleCache(new File(context.getExternalCacheDir(), "player"), new LeastRecentlyUsedCacheEvictor(bytesToCache));
cacheSourceFactory = new CacheDataSourceFactory(temporaryCache, buildDataSourceFactory(), CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);