change to exoplayer

This commit is contained in:
Li ZongYing 2023-12-15 15:41:59 +08:00
parent dc5f988baa
commit aef165504f
4 changed files with 266 additions and 15 deletions

View File

@ -90,6 +90,14 @@ static def VersionName() {
} }
dependencies { dependencies {
def media3_version = "1.1.1"
// For media playback using ExoPlayer
implementation "androidx.media3:media3-exoplayer:$media3_version"
// For HLS playback support with ExoPlayer
implementation "androidx.media3:media3-exoplayer-hls:$media3_version"
implementation 'com.google.protobuf:protobuf-kotlin:3.25.1' implementation 'com.google.protobuf:protobuf-kotlin:3.25.1'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:converter-protobuf:2.9.0' implementation 'com.squareup.retrofit2:converter-protobuf:2.9.0'

View File

@ -0,0 +1,251 @@
package com.lizongying.mytv
import android.content.Context
import android.media.MediaPlayer
import android.net.Uri
import android.os.Handler
import android.view.SurfaceHolder
import androidx.annotation.OptIn
import androidx.leanback.media.PlaybackGlueHost
import androidx.leanback.media.PlayerAdapter
import androidx.leanback.media.SurfaceHolderGlueHost
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.hls.HlsMediaSource
import java.io.IOException
open class Custom2MediaPlayerAdapter(private var mContext: Context?) : PlayerAdapter() {
val mPlayer = mContext?.let { ExoPlayer.Builder(it).build() }
var mSurfaceHolderGlueHost: SurfaceHolderGlueHost? = null
val mRunnable: Runnable = object : Runnable {
override fun run() {
callback.onCurrentPositionChanged(this@Custom2MediaPlayerAdapter)
mHandler.postDelayed(this, getProgressUpdatingInterval().toLong())
}
};
val mHandler = Handler()
var mInitialized = false // true when the MediaPlayer is prepared/initialized
var mMediaSourceUri: Uri? = null
var mHasDisplay = false
var mBufferedProgress: Long = 0
var mBufferingStart = false
open fun notifyBufferingStartEnd() {
callback.onBufferingStateChanged(
this@Custom2MediaPlayerAdapter,
mBufferingStart || !mInitialized
)
}
override fun onAttachedToHost(host: PlaybackGlueHost?) {
if (host is SurfaceHolderGlueHost) {
mSurfaceHolderGlueHost = host
mSurfaceHolderGlueHost!!.setSurfaceHolderCallback(VideoPlayerSurfaceHolderCallback(this))
}
}
/**
* Will reset the [MediaPlayer] and the glue such that a new file can be played. You are
* not required to call this method before playing the first file. However you have to call it
* before playing a second one.
*/
open fun reset() {
changeToUnitialized()
// mPlayer.reset()
}
open fun changeToUnitialized() {
if (mInitialized) {
mInitialized = false
notifyBufferingStartEnd()
if (mHasDisplay) {
callback.onPreparedStateChanged(this@Custom2MediaPlayerAdapter)
}
}
}
/**
* Release internal MediaPlayer. Should not use the object after call release().
*/
open fun release() {
changeToUnitialized()
mHasDisplay = false
mPlayer?.release()
}
override fun onDetachedFromHost() {
if (mSurfaceHolderGlueHost != null) {
mSurfaceHolderGlueHost!!.setSurfaceHolderCallback(null)
mSurfaceHolderGlueHost = null
}
reset()
release()
}
/**
* @see MediaPlayer.setDisplay
*/
fun setDisplay(surfaceHolder: SurfaceHolder?) {
val hadDisplay = mHasDisplay
mHasDisplay = surfaceHolder != null
if (hadDisplay == mHasDisplay) {
return
}
mPlayer?.setVideoSurfaceHolder(surfaceHolder)
if (mHasDisplay) {
if (mInitialized) {
callback.onPreparedStateChanged(this@Custom2MediaPlayerAdapter)
}
} else {
if (mInitialized) {
callback.onPreparedStateChanged(this@Custom2MediaPlayerAdapter)
}
}
}
override fun setProgressUpdatingEnabled(enabled: Boolean) {
mHandler.removeCallbacks(mRunnable)
if (!enabled) {
return
}
mHandler.postDelayed(mRunnable, getProgressUpdatingInterval().toLong())
}
/**
* Return updating interval of progress UI in milliseconds. Subclass may override.
* @return Update interval of progress UI in milliseconds.
*/
open fun getProgressUpdatingInterval(): Int {
return 16
}
override fun isPlaying(): Boolean {
return mInitialized && mPlayer?.isPlaying ?: false
}
override fun getDuration(): Long {
if (mInitialized) {
val duration = mPlayer?.duration
if (duration != null) {
return duration.toLong()
}
}
return -1
}
override fun getCurrentPosition(): Long {
if (mInitialized) {
val currentPosition = mPlayer?.currentPosition
if (currentPosition != null) {
return currentPosition.toLong()
}
}
return -1
}
override fun play() {
if (!mInitialized || mPlayer?.isPlaying == true) {
return
}
mPlayer?.play()
callback.onPlayStateChanged(this@Custom2MediaPlayerAdapter)
callback.onCurrentPositionChanged(this@Custom2MediaPlayerAdapter)
}
override fun pause() {
if (isPlaying) {
mPlayer?.pause()
callback.onPlayStateChanged(this@Custom2MediaPlayerAdapter)
}
}
override fun seekTo(newPosition: Long) {
if (!mInitialized) {
return
}
mPlayer?.seekTo(newPosition.toInt().toLong())
}
override fun getBufferedPosition(): Long {
return mBufferedProgress
}
/**
* Sets the media source of the player witha given URI.
*
* @return Returns `true` if uri represents a new media; `false`
* otherwise.
* @see MediaPlayer.setDataSource
*/
open fun setDataSource(uri: Uri?): Boolean {
if (if (mMediaSourceUri != null) mMediaSourceUri == uri else uri == null) {
return false
}
mMediaSourceUri = uri
prepareMediaForPlaying()
return true
}
private var mHeaders: Map<String, String>? = mapOf()
fun setHeaders(headers: Map<String, String>) {
mHeaders = headers
}
@OptIn(UnstableApi::class)
private fun prepareMediaForPlaying() {
reset()
try {
if (mMediaSourceUri != null) {
val httpDataSource = DefaultHttpDataSource.Factory()
mHeaders?.let { httpDataSource.setDefaultRequestProperties(it) }
val hlsMediaSource =
HlsMediaSource.Factory(httpDataSource).createMediaSource(
MediaItem.fromUri(
mMediaSourceUri!!
)
)
mPlayer?.setMediaSource(hlsMediaSource)
} else {
return
}
} catch (e: IOException) {
e.printStackTrace()
throw RuntimeException(e)
}
mPlayer?.prepare()
mPlayer?.playWhenReady = true
callback.onPlayStateChanged(this@Custom2MediaPlayerAdapter)
}
/**
* @return True if MediaPlayer OnPreparedListener is invoked and got a SurfaceHolder if
* [PlaybackGlueHost] provides SurfaceHolder.
*/
override fun isPrepared(): Boolean {
return mInitialized && (mSurfaceHolderGlueHost == null || mHasDisplay)
}
}
internal class VideoPlayerSurfaceHolderCallback(private val playerAdapter: Custom2MediaPlayerAdapter) :
SurfaceHolder.Callback {
override fun surfaceCreated(surfaceHolder: SurfaceHolder) {
playerAdapter.setDisplay(surfaceHolder)
}
override fun surfaceChanged(surfaceHolder: SurfaceHolder, i: Int, i1: Int, i2: Int) {
// Handle surface changes if needed
}
override fun surfaceDestroyed(surfaceHolder: SurfaceHolder) {
playerAdapter.setDisplay(null)
}
}

View File

@ -5,12 +5,13 @@ import android.view.KeyEvent
import android.view.View import android.view.View
import androidx.leanback.media.MediaPlayerAdapter import androidx.leanback.media.MediaPlayerAdapter
import androidx.leanback.media.PlaybackTransportControlGlue import androidx.leanback.media.PlaybackTransportControlGlue
import androidx.leanback.media.PlayerAdapter
class PlaybackControlGlue( class PlaybackControlGlue(
context: Context?, context: Context?,
playerAdapter: MediaPlayerAdapter?, playerAdapter: PlayerAdapter?,
) : ) :
PlaybackTransportControlGlue<MediaPlayerAdapter>(context, playerAdapter) { PlaybackTransportControlGlue<PlayerAdapter>(context, playerAdapter) {
override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean { override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean {
if (event!!.action == KeyEvent.ACTION_DOWN) { if (event!!.action == KeyEvent.ACTION_DOWN) {

View File

@ -5,21 +5,21 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.leanback.app.VideoSupportFragment import androidx.leanback.app.VideoSupportFragment
import androidx.leanback.app.VideoSupportFragmentGlueHost import androidx.leanback.app.VideoSupportFragmentGlueHost
import androidx.leanback.media.MediaPlayerAdapter import androidx.leanback.media.PlayerAdapter
import androidx.leanback.media.PlaybackTransportControlGlue import androidx.leanback.media.PlaybackTransportControlGlue
import androidx.leanback.widget.PlaybackControlsRow import androidx.leanback.widget.PlaybackControlsRow
import java.io.IOException import java.io.IOException
class PlaybackFragment : VideoSupportFragment() { class PlaybackFragment : VideoSupportFragment() {
private lateinit var mTransportControlGlue: PlaybackTransportControlGlue<MediaPlayerAdapter> private lateinit var mTransportControlGlue: PlaybackTransportControlGlue<PlayerAdapter>
private var playerAdapter: MediaPlayerAdapter? = null private var playerAdapter: Custom2MediaPlayerAdapter? = null
private var lastVideoUrl: String = "" private var lastVideoUrl: String = ""
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
playerAdapter = MediaPlayerAdapter(context) playerAdapter = Custom2MediaPlayerAdapter(context)
playerAdapter?.setRepeatAction(PlaybackControlsRow.RepeatAction.INDEX_NONE) playerAdapter?.setRepeatAction(PlaybackControlsRow.RepeatAction.INDEX_NONE)
view?.isFocusable = false view?.isFocusable = false
@ -60,15 +60,6 @@ class PlaybackFragment : VideoSupportFragment() {
hideControlsOverlay(false) hideControlsOverlay(false)
} }
override fun onDestroy() {
if (playerAdapter?.mediaPlayer != null) {
playerAdapter?.release()
Log.d(TAG, "playerAdapter released")
}
super.onDestroy()
}
companion object { companion object {
private const val TAG = "PlaybackVideoFragment" private const val TAG = "PlaybackVideoFragment"
} }