|
|
@@ -9,12 +9,27 @@ import androidx.lifecycle.LifecycleOwner
|
|
|
import androidx.media3.common.MediaItem
|
|
|
import androidx.media3.common.PlaybackException
|
|
|
import androidx.media3.common.Player
|
|
|
+import androidx.media3.common.Player.STATE_BUFFERING
|
|
|
+import androidx.media3.common.Player.STATE_READY
|
|
|
import androidx.media3.common.util.UnstableApi
|
|
|
+import androidx.media3.database.StandaloneDatabaseProvider
|
|
|
+import androidx.media3.datasource.DefaultDataSource
|
|
|
+import androidx.media3.datasource.cache.Cache
|
|
|
+import androidx.media3.datasource.cache.CacheDataSource
|
|
|
+import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor
|
|
|
+import androidx.media3.datasource.cache.SimpleCache
|
|
|
+import androidx.media3.exoplayer.DefaultRenderersFactory
|
|
|
import androidx.media3.exoplayer.ExoPlayer
|
|
|
+import androidx.media3.exoplayer.source.MediaSource
|
|
|
+import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
|
|
import androidx.media3.ui.PlayerView
|
|
|
import com.adealink.frame.log.Log
|
|
|
+import com.adealink.frame.util.md5
|
|
|
+import com.adealink.weparty.storage.file.FilePath
|
|
|
+import java.io.File
|
|
|
import java.lang.ref.WeakReference
|
|
|
|
|
|
+@UnstableApi
|
|
|
class ExoPlayerHelper : DefaultLifecycleObserver {
|
|
|
|
|
|
private var playerViewRef: WeakReference<View?> = WeakReference(null)
|
|
|
@@ -27,10 +42,17 @@ class ExoPlayerHelper : DefaultLifecycleObserver {
|
|
|
private var repeatMode = Player.REPEAT_MODE_OFF
|
|
|
private var playWhenReady = true
|
|
|
private var player: ExoPlayer? = null
|
|
|
+ private var cache: Cache? = null
|
|
|
|
|
|
var doOnReadyCallback: (() -> Unit)? = null
|
|
|
var onErrorCallback: ((error: PlaybackException) -> Unit)? = null
|
|
|
|
|
|
+ //Player初始化回调
|
|
|
+ private var onPlayerInit: ((player: Player) -> Unit)? = null
|
|
|
+
|
|
|
+ //缓冲导致的播放暂停
|
|
|
+ private var onPauseWhenBuffering: ((isPlaying: Boolean) -> Unit)? = null
|
|
|
+
|
|
|
fun observe(lifecycle: Lifecycle) {
|
|
|
lifecycle.addObserver(this)
|
|
|
}
|
|
|
@@ -43,7 +65,7 @@ class ExoPlayerHelper : DefaultLifecycleObserver {
|
|
|
return player
|
|
|
}
|
|
|
|
|
|
- class Builder(val targetView: View) {
|
|
|
+ class Builder(val targetView: View, val onPlayerInit: ((player: Player) -> Unit) ? = null) {
|
|
|
|
|
|
var uri: String? = null
|
|
|
var scaleMode: Int? = null
|
|
|
@@ -51,6 +73,7 @@ class ExoPlayerHelper : DefaultLifecycleObserver {
|
|
|
var playWhenReady = true
|
|
|
var doOnReadyCallback: (() -> Unit)? = null
|
|
|
var onErrorCallback: ((error: PlaybackException) -> Unit)? = null
|
|
|
+ private var onPauseWhenBuffering: ((isPlaying: Boolean) -> Unit)? = null
|
|
|
|
|
|
fun uri(uri: String): Builder {
|
|
|
this.uri = uri
|
|
|
@@ -81,15 +104,23 @@ class ExoPlayerHelper : DefaultLifecycleObserver {
|
|
|
return this
|
|
|
}
|
|
|
|
|
|
+ fun onPauseWhenBuffering(listener: ((isPlaying: Boolean) -> Unit)?): Builder {
|
|
|
+ this.onPauseWhenBuffering = listener
|
|
|
+ return this
|
|
|
+ }
|
|
|
+
|
|
|
fun build(): ExoPlayerHelper {
|
|
|
val helper = ExoPlayerHelper().apply {
|
|
|
this.playerViewRef = WeakReference(targetView)
|
|
|
+ this.onPlayerInit = this@Builder.onPlayerInit
|
|
|
+
|
|
|
this.uri = this@Builder.uri
|
|
|
this.scaleMode = this@Builder.scaleMode
|
|
|
this.repeatMode = this@Builder.repeatMode
|
|
|
this.playWhenReady = this@Builder.playWhenReady
|
|
|
this.doOnReadyCallback = this@Builder.doOnReadyCallback
|
|
|
this.onErrorCallback = this@Builder.onErrorCallback
|
|
|
+ this.onPauseWhenBuffering = this@Builder.onPauseWhenBuffering
|
|
|
}
|
|
|
return helper
|
|
|
}
|
|
|
@@ -99,26 +130,62 @@ class ExoPlayerHelper : DefaultLifecycleObserver {
|
|
|
private fun initializePlayer() {
|
|
|
Log.d(TAG, "initializePlayer")
|
|
|
val playerView = (playerView as? PlayerView) ?: return
|
|
|
- player = ExoPlayer.Builder(playerView.context)
|
|
|
+ player = ExoPlayer.Builder(playerView.context, DefaultRenderersFactory(playerView.context).setEnableDecoderFallback(true))
|
|
|
.build()
|
|
|
.also { player ->
|
|
|
playerView.player = player
|
|
|
- uri?.let { uriString ->
|
|
|
- val mediaItem = MediaItem.fromUri(uriString)
|
|
|
- player.setMediaItem(mediaItem)
|
|
|
+ uri?.let {
|
|
|
+ player.setMediaItem(MediaItem.fromUri(it))
|
|
|
}
|
|
|
scaleMode?.let { mode ->
|
|
|
player.videoScalingMode = mode
|
|
|
}
|
|
|
player.playWhenReady = playWhenReady
|
|
|
player.repeatMode = repeatMode
|
|
|
+
|
|
|
player.addListener(object : Player.Listener {
|
|
|
+ override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
|
|
|
+ super.onPlayWhenReadyChanged(playWhenReady, reason)
|
|
|
+ Log.d(TAG, "onPlayWhenReadyChanged, playWhenReady:$playWhenReady, reason:$reason")
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onIsPlayingChanged(isPlaying: Boolean) {
|
|
|
+ super.onIsPlayingChanged(isPlaying)
|
|
|
+ Log.d(TAG, "onIsPlayingChanged, isPlaying:$isPlaying")
|
|
|
+ when (player.playbackState) {
|
|
|
+ STATE_BUFFERING -> {
|
|
|
+ //缓冲中
|
|
|
+ onPauseWhenBuffering?.invoke(isPlaying)
|
|
|
+ }
|
|
|
+
|
|
|
+ else -> {
|
|
|
+ //缓冲完毕
|
|
|
+ onPauseWhenBuffering?.invoke(true)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onEvents(player: Player, events: Player.Events) {
|
|
|
+ super.onEvents(player, events)
|
|
|
+ Log.d(TAG, "onIsPlayingChanged, player:$player, events:$events")
|
|
|
+ }
|
|
|
|
|
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
|
|
- super.onPlaybackStateChanged(playbackState)
|
|
|
- if (Player.STATE_READY == playbackState) {
|
|
|
- doOnReadyCallback?.invoke()
|
|
|
- doOnReadyCallback = null
|
|
|
+ Log.d(TAG, "onPlaybackStateChanged, playbackState: $playbackState")
|
|
|
+ when (playbackState) {
|
|
|
+ STATE_READY -> {
|
|
|
+ doOnReadyCallback?.invoke()
|
|
|
+ doOnReadyCallback = null
|
|
|
+ }
|
|
|
+ STATE_BUFFERING -> {
|
|
|
+ //缓冲中
|
|
|
+ onPauseWhenBuffering?.invoke(player.isPlaying)
|
|
|
+ }
|
|
|
+
|
|
|
+ else -> {
|
|
|
+ //缓冲完毕
|
|
|
+ onPauseWhenBuffering?.invoke(true)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -127,13 +194,23 @@ class ExoPlayerHelper : DefaultLifecycleObserver {
|
|
|
Log.d(TAG, "onPlayerError, error: $error")
|
|
|
onErrorCallback?.invoke(error)
|
|
|
}
|
|
|
+
|
|
|
+ override fun onPlayerErrorChanged(error: PlaybackException?) {
|
|
|
+ super.onPlayerErrorChanged(error)
|
|
|
+ Log.d(TAG, "onPlayerErrorChanged, error:$error")
|
|
|
+ }
|
|
|
+
|
|
|
})
|
|
|
+
|
|
|
player.prepare()
|
|
|
+ onPlayerInit?.invoke(player)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private fun releasePlayer() {
|
|
|
Log.d(TAG, "releasePlayer")
|
|
|
+ cache?.release()
|
|
|
+ cache = null
|
|
|
player?.release()
|
|
|
player = null
|
|
|
}
|
|
|
@@ -147,6 +224,29 @@ class ExoPlayerHelper : DefaultLifecycleObserver {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private fun getCachePath(url: String): String {
|
|
|
+ return "${FilePath.videoPath}${File.separator}${"${url.md5()}.${url.split(".").last()}"}"
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun buildMediaSource(): Pair<Cache, MediaSource>? {
|
|
|
+ val ctx = playerView?.context ?: return null
|
|
|
+ val uriString = uri ?: return null
|
|
|
+
|
|
|
+ //1. 构建缓存文件
|
|
|
+ val cacheFile = File(getCachePath(uriString))
|
|
|
+ //2. 构建缓存实例
|
|
|
+ val cache = SimpleCache(cacheFile, LeastRecentlyUsedCacheEvictor(MAX_CACHE_BYTE), StandaloneDatabaseProvider(ctx))
|
|
|
+ val mediaItem = MediaItem.fromUri(uriString)
|
|
|
+ //3. 构建 DataSourceFactory
|
|
|
+ val cacheDataSourceFactory = CacheDataSource.Factory()
|
|
|
+ .setCache(cache)
|
|
|
+ .setUpstreamDataSourceFactory(DefaultDataSource.Factory(ctx))
|
|
|
+ .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
|
|
|
+ val mediaSource = ProgressiveMediaSource.Factory(cacheDataSourceFactory)
|
|
|
+ .createMediaSource(mediaItem)
|
|
|
+ return Pair(cache, mediaSource)
|
|
|
+ }
|
|
|
+
|
|
|
override fun onResume(owner: LifecycleOwner) {
|
|
|
Log.d(TAG, "onResume")
|
|
|
super.onResume(owner)
|
|
|
@@ -173,6 +273,8 @@ class ExoPlayerHelper : DefaultLifecycleObserver {
|
|
|
|
|
|
companion object {
|
|
|
private const val TAG = "ExoPlayerHelper"
|
|
|
+
|
|
|
+ private const val MAX_CACHE_BYTE = 50 * 1024 * 1024L
|
|
|
}
|
|
|
|
|
|
}
|