change to exoplayer
This commit is contained in:
parent
dc5f988baa
commit
aef165504f
|
|
@ -90,6 +90,14 @@ static def VersionName() {
|
|||
}
|
||||
|
||||
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.squareup.retrofit2:converter-gson:2.9.0'
|
||||
implementation 'com.squareup.retrofit2:converter-protobuf:2.9.0'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,12 +5,13 @@ import android.view.KeyEvent
|
|||
import android.view.View
|
||||
import androidx.leanback.media.MediaPlayerAdapter
|
||||
import androidx.leanback.media.PlaybackTransportControlGlue
|
||||
import androidx.leanback.media.PlayerAdapter
|
||||
|
||||
class PlaybackControlGlue(
|
||||
context: Context?,
|
||||
playerAdapter: MediaPlayerAdapter?,
|
||||
playerAdapter: PlayerAdapter?,
|
||||
) :
|
||||
PlaybackTransportControlGlue<MediaPlayerAdapter>(context, playerAdapter) {
|
||||
PlaybackTransportControlGlue<PlayerAdapter>(context, playerAdapter) {
|
||||
|
||||
override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean {
|
||||
if (event!!.action == KeyEvent.ACTION_DOWN) {
|
||||
|
|
|
|||
|
|
@ -5,21 +5,21 @@ import android.os.Bundle
|
|||
import android.util.Log
|
||||
import androidx.leanback.app.VideoSupportFragment
|
||||
import androidx.leanback.app.VideoSupportFragmentGlueHost
|
||||
import androidx.leanback.media.MediaPlayerAdapter
|
||||
import androidx.leanback.media.PlayerAdapter
|
||||
import androidx.leanback.media.PlaybackTransportControlGlue
|
||||
import androidx.leanback.widget.PlaybackControlsRow
|
||||
import java.io.IOException
|
||||
|
||||
class PlaybackFragment : VideoSupportFragment() {
|
||||
|
||||
private lateinit var mTransportControlGlue: PlaybackTransportControlGlue<MediaPlayerAdapter>
|
||||
private var playerAdapter: MediaPlayerAdapter? = null
|
||||
private lateinit var mTransportControlGlue: PlaybackTransportControlGlue<PlayerAdapter>
|
||||
private var playerAdapter: Custom2MediaPlayerAdapter? = null
|
||||
private var lastVideoUrl: String = ""
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
playerAdapter = MediaPlayerAdapter(context)
|
||||
playerAdapter = Custom2MediaPlayerAdapter(context)
|
||||
playerAdapter?.setRepeatAction(PlaybackControlsRow.RepeatAction.INDEX_NONE)
|
||||
|
||||
view?.isFocusable = false
|
||||
|
|
@ -60,15 +60,6 @@ class PlaybackFragment : VideoSupportFragment() {
|
|||
hideControlsOverlay(false)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (playerAdapter?.mediaPlayer != null) {
|
||||
playerAdapter?.release()
|
||||
Log.d(TAG, "playerAdapter released")
|
||||
}
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "PlaybackVideoFragment"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue