mirror of https://github.com/lizongying/my-tv.git
parent
3b62d5e00b
commit
657b8714a0
Binary file not shown.
@ -1,290 +0,0 @@ |
||||
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.PlaybackException |
||||
import androidx.media3.common.Player |
||||
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 ExoPlayerAdapter(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@ExoPlayerAdapter) |
||||
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 |
||||
|
||||
|
||||
private var mMinimumLoadableRetryCount = 3 |
||||
|
||||
|
||||
private var mPlayerErrorListener: PlayerErrorListener? = null |
||||
|
||||
init { |
||||
mPlayer?.playWhenReady = true |
||||
|
||||
if (mPlayerErrorListener == null) { |
||||
mPlayerErrorListener = PlayerErrorListener() |
||||
mPlayer?.addListener(mPlayerErrorListener!!) |
||||
} |
||||
} |
||||
|
||||
|
||||
open fun notifyBufferingStartEnd() { |
||||
callback.onBufferingStateChanged( |
||||
this@ExoPlayerAdapter, |
||||
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@ExoPlayerAdapter) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 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@ExoPlayerAdapter) |
||||
} |
||||
} else { |
||||
if (mInitialized) { |
||||
callback.onPreparedStateChanged(this@ExoPlayerAdapter) |
||||
} |
||||
} |
||||
} |
||||
|
||||
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@ExoPlayerAdapter) |
||||
callback.onCurrentPositionChanged(this@ExoPlayerAdapter) |
||||
} |
||||
|
||||
override fun pause() { |
||||
if (isPlaying) { |
||||
mPlayer?.pause() |
||||
callback.onPlayStateChanged(this@ExoPlayerAdapter) |
||||
} |
||||
} |
||||
|
||||
override fun seekTo(newPosition: Long) { |
||||
if (!mInitialized) { |
||||
return |
||||
} |
||||
mPlayer?.seekTo(newPosition.toInt().toLong()) |
||||
} |
||||
|
||||
override fun getBufferedPosition(): Long { |
||||
return mBufferedProgress |
||||
} |
||||
|
||||
private inner class PlayerErrorListener : Player.Listener { |
||||
override fun onPlayerError(error: PlaybackException) { |
||||
callback.onError(this@ExoPlayerAdapter, error.errorCode, error.message) |
||||
} |
||||
} |
||||
|
||||
|
||||
private var mHeaders: Map<String, String>? = mapOf() |
||||
|
||||
fun setHeaders(headers: Map<String, String>) { |
||||
mHeaders = headers |
||||
} |
||||
|
||||
/** |
||||
* Sets the media source of the player witha given URI. |
||||
* |
||||
* @return Returns `true` if uri represents a new media; `false` |
||||
* otherwise. |
||||
* @see MediaPlayer.setDataSource |
||||
*/ |
||||
|
||||
@OptIn(UnstableApi::class) |
||||
open fun setDataSource(uri: Uri?): Boolean { |
||||
if (if (mMediaSourceUri != null) mMediaSourceUri == uri else uri == null) { |
||||
return false |
||||
} |
||||
mMediaSourceUri = uri |
||||
|
||||
val httpDataSource = DefaultHttpDataSource.Factory() |
||||
mHeaders?.let { httpDataSource.setDefaultRequestProperties(it) } |
||||
|
||||
val hlsMediaSource = |
||||
HlsMediaSource.Factory(httpDataSource).setLoadErrorHandlingPolicy( |
||||
CustomLoadErrorHandlingPolicy(mMinimumLoadableRetryCount) |
||||
).createMediaSource( |
||||
MediaItem.fromUri( |
||||
mMediaSourceUri!! |
||||
) |
||||
) |
||||
prepareMediaForPlaying(hlsMediaSource) |
||||
return true |
||||
} |
||||
|
||||
@OptIn(UnstableApi::class) |
||||
open fun setDataSource(hlsMediaSource: HlsMediaSource): Boolean { |
||||
prepareMediaForPlaying(hlsMediaSource) |
||||
return true |
||||
} |
||||
|
||||
fun setMinimumLoadableRetryCount(minimumLoadableRetryCount: Int) { |
||||
mMinimumLoadableRetryCount = minimumLoadableRetryCount |
||||
} |
||||
|
||||
@OptIn(UnstableApi::class) |
||||
private fun prepareMediaForPlaying(hlsMediaSource: HlsMediaSource) { |
||||
try { |
||||
mPlayer?.setMediaSource(hlsMediaSource) |
||||
} catch (e: IOException) { |
||||
e.printStackTrace() |
||||
throw RuntimeException(e) |
||||
} |
||||
mPlayer?.prepare() |
||||
|
||||
callback.onPlayStateChanged(this@ExoPlayerAdapter) |
||||
} |
||||
|
||||
/** |
||||
* @return True if MediaPlayer OnPreparedListener is invoked and got a SurfaceHolder if |
||||
* [PlaybackGlueHost] provides SurfaceHolder. |
||||
*/ |
||||
override fun isPrepared(): Boolean { |
||||
return mInitialized && (mSurfaceHolderGlueHost == null || mHasDisplay) |
||||
} |
||||
|
||||
companion object { |
||||
private const val TAG = "ExoPlayerAdapter" |
||||
} |
||||
} |
||||
|
||||
internal class VideoPlayerSurfaceHolderCallback(private val playerAdapter: ExoPlayerAdapter) : |
||||
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) |
||||
} |
||||
} |
||||
@ -1,57 +0,0 @@ |
||||
package com.lizongying.mytv |
||||
|
||||
import android.content.Context |
||||
import android.util.Log |
||||
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: PlayerAdapter?, |
||||
) : |
||||
PlaybackTransportControlGlue<PlayerAdapter>(context, playerAdapter) { |
||||
|
||||
override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean { |
||||
if (event!!.action == KeyEvent.ACTION_DOWN) { |
||||
when (keyCode) { |
||||
KeyEvent.KEYCODE_DPAD_CENTER -> { |
||||
Log.i(TAG, "KEYCODE_DPAD_CENTER") |
||||
(context as? MainActivity)?.switchMainFragment() |
||||
} |
||||
|
||||
KeyEvent.KEYCODE_DPAD_UP -> { |
||||
if ((context as? MainActivity)?.mainFragmentIsHidden() == true) { |
||||
(context as? MainActivity)?.prev() |
||||
} |
||||
} |
||||
|
||||
KeyEvent.KEYCODE_DPAD_DOWN -> { |
||||
if ((context as? MainActivity)?.mainFragmentIsHidden() == true) { |
||||
(context as? MainActivity)?.next() |
||||
} |
||||
} |
||||
|
||||
KeyEvent.KEYCODE_DPAD_LEFT -> { |
||||
if ((context as? MainActivity)?.mainFragmentIsHidden() == true) { |
||||
(context as? MainActivity)?.prevSource() |
||||
} |
||||
} |
||||
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT -> { |
||||
if ((context as? MainActivity)?.mainFragmentIsHidden() == true) { |
||||
(context as? MainActivity)?.nextSource() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return super.onKey(v, keyCode, event) |
||||
} |
||||
|
||||
companion object { |
||||
private const val TAG = "PlaybackControlGlue" |
||||
} |
||||
} |
||||
@ -0,0 +1,14 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
android:id="@+id/player_fragment" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:keepScreenOn="true"> |
||||
|
||||
<androidx.media3.ui.PlayerView |
||||
android:id="@+id/player_view" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
app:use_controller="false" /> |
||||
</FrameLayout> |
||||
@ -0,0 +1,202 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
<!-- Must be kept in sync with AspectRatioFrameLayout --> |
||||
<attr name="resize_mode" format="enum"> |
||||
<enum name="fit" value="0"/> |
||||
<enum name="fixed_width" value="1"/> |
||||
<enum name="fixed_height" value="2"/> |
||||
<enum name="fill" value="3"/> |
||||
<enum name="zoom" value="4"/> |
||||
</attr> |
||||
|
||||
<!-- Must be kept in sync with LegacyPlayerView and PlayerView --> |
||||
<attr name="surface_type" format="enum"> |
||||
<enum name="none" value="0"/> |
||||
<enum name="surface_view" value="1"/> |
||||
<enum name="texture_view" value="2"/> |
||||
<enum name="spherical_gl_surface_view" value="3"/> |
||||
<enum name="video_decoder_gl_surface_view" value="4"/> |
||||
</attr> |
||||
|
||||
<!-- Must be kept in sync with RepeatModeUtil --> |
||||
<attr name="repeat_toggle_modes"> |
||||
<flag name="none" value="0"/> |
||||
<flag name="one" value="1"/> |
||||
<flag name="all" value="2"/> |
||||
</attr> |
||||
|
||||
<!-- LegacyPlayerView and PlayerView attributes --> |
||||
<attr name="use_artwork" format="boolean"/> |
||||
<attr name="artwork_display_mode" format="enum"> |
||||
<enum name="off" value="0"/> |
||||
<enum name="fit" value="1"/> |
||||
<enum name="fill" value="2"/> |
||||
</attr> |
||||
<attr name="shutter_background_color" format="color"/> |
||||
<attr name="default_artwork" format="reference"/> |
||||
<attr name="use_controller" format="boolean"/> |
||||
<attr name="hide_on_touch" format="boolean"/> |
||||
<attr name="hide_during_ads" format="boolean"/> |
||||
<attr name="auto_show" format="boolean"/> |
||||
<attr name="show_buffering" format="enum"> |
||||
<enum name="never" value="0"/> |
||||
<enum name="when_playing" value="1"/> |
||||
<enum name="always" value="2"/> |
||||
</attr> |
||||
<attr name="keep_content_on_player_reset" format="boolean"/> |
||||
<attr name="player_layout_id" format="reference"/> |
||||
|
||||
<!-- LegacyPlayerControlView and PlayerControlView attributes --> |
||||
<attr name="show_timeout" format="integer"/> |
||||
<attr name="show_rewind_button" format="boolean"/> |
||||
<attr name="show_fastforward_button" format="boolean"/> |
||||
<attr name="show_previous_button" format="boolean"/> |
||||
<attr name="show_next_button" format="boolean"/> |
||||
<attr name="show_shuffle_button" format="boolean"/> |
||||
<attr name="show_subtitle_button" format="boolean"/> |
||||
<attr name="show_vr_button" format="boolean"/> |
||||
<attr name="time_bar_min_update_interval" format="integer"/> |
||||
<attr name="controller_layout_id" format="reference"/> |
||||
<attr name="animation_enabled" format="boolean"/> |
||||
|
||||
<!-- Needed for https://github.com/google/ExoPlayer/issues/7898 --> |
||||
<attr name="backgroundTint" format="color"/> |
||||
|
||||
<!-- DefaultTimeBar attributes --> |
||||
<attr name="bar_height" format="dimension"/> |
||||
<attr name="bar_gravity" format="enum"> |
||||
<enum name="center" value="0"/> |
||||
<enum name="bottom" value="1"/> |
||||
</attr> |
||||
<attr name="touch_target_height" format="dimension"/> |
||||
<attr name="ad_marker_width" format="dimension"/> |
||||
<attr name="scrubber_enabled_size" format="dimension"/> |
||||
<attr name="scrubber_disabled_size" format="dimension"/> |
||||
<attr name="scrubber_dragged_size" format="dimension"/> |
||||
<attr name="scrubber_drawable" format="reference"/> |
||||
<attr name="played_color" format="color"/> |
||||
<attr name="scrubber_color" format="color"/> |
||||
<attr name="buffered_color" format="color"/> |
||||
<attr name="unplayed_color" format="color"/> |
||||
<attr name="ad_marker_color" format="color"/> |
||||
<attr name="played_ad_marker_color" format="color"/> |
||||
|
||||
<declare-styleable name="PlayerView"> |
||||
<attr name="use_artwork"/> |
||||
<attr name="artwork_display_mode"/> |
||||
<attr name="shutter_background_color"/> |
||||
<attr name="default_artwork"/> |
||||
<attr name="use_controller"/> |
||||
<attr name="hide_on_touch"/> |
||||
<attr name="hide_during_ads"/> |
||||
<attr name="auto_show"/> |
||||
<attr name="show_buffering"/> |
||||
<attr name="keep_content_on_player_reset"/> |
||||
<attr name="player_layout_id"/> |
||||
<attr name="surface_type"/> |
||||
<!-- AspectRatioFrameLayout attributes --> |
||||
<attr name="resize_mode"/> |
||||
<!-- PlayerControlView attributes --> |
||||
<attr name="show_timeout"/> |
||||
<attr name="repeat_toggle_modes"/> |
||||
<attr name="show_shuffle_button"/> |
||||
<attr name="show_subtitle_button"/> |
||||
<attr name="show_vr_button"/> |
||||
<attr name="time_bar_min_update_interval"/> |
||||
<attr name="controller_layout_id"/> |
||||
<attr name="animation_enabled"/> |
||||
<!-- DefaultTimeBar attributes --> |
||||
<attr name="bar_height"/> |
||||
<attr name="bar_gravity"/> |
||||
<attr name="touch_target_height"/> |
||||
<attr name="ad_marker_width"/> |
||||
<attr name="scrubber_enabled_size"/> |
||||
<attr name="scrubber_disabled_size"/> |
||||
<attr name="scrubber_dragged_size"/> |
||||
<attr name="scrubber_drawable"/> |
||||
<attr name="played_color"/> |
||||
<attr name="scrubber_color"/> |
||||
<attr name="buffered_color" /> |
||||
<attr name="unplayed_color"/> |
||||
<attr name="ad_marker_color"/> |
||||
<attr name="played_ad_marker_color"/> |
||||
</declare-styleable> |
||||
|
||||
<declare-styleable name="AspectRatioFrameLayout"> |
||||
<attr name="resize_mode"/> |
||||
</declare-styleable> |
||||
|
||||
<declare-styleable name="LegacyPlayerControlView"> |
||||
<attr name="show_timeout"/> |
||||
<attr name="repeat_toggle_modes"/> |
||||
<attr name="show_rewind_button"/> |
||||
<attr name="show_fastforward_button"/> |
||||
<attr name="show_previous_button"/> |
||||
<attr name="show_next_button"/> |
||||
<attr name="show_shuffle_button"/> |
||||
<attr name="time_bar_min_update_interval"/> |
||||
<attr name="controller_layout_id"/> |
||||
<!-- DefaultTimeBar attributes --> |
||||
<attr name="bar_height"/> |
||||
<attr name="bar_gravity"/> |
||||
<attr name="touch_target_height"/> |
||||
<attr name="ad_marker_width"/> |
||||
<attr name="scrubber_enabled_size"/> |
||||
<attr name="scrubber_disabled_size"/> |
||||
<attr name="scrubber_dragged_size"/> |
||||
<attr name="scrubber_drawable"/> |
||||
<attr name="played_color"/> |
||||
<attr name="scrubber_color"/> |
||||
<attr name="buffered_color" /> |
||||
<attr name="unplayed_color"/> |
||||
<attr name="ad_marker_color"/> |
||||
<attr name="played_ad_marker_color"/> |
||||
</declare-styleable> |
||||
|
||||
<declare-styleable name="PlayerControlView"> |
||||
<attr name="show_timeout"/> |
||||
<attr name="repeat_toggle_modes"/> |
||||
<attr name="show_rewind_button"/> |
||||
<attr name="show_fastforward_button"/> |
||||
<attr name="show_previous_button"/> |
||||
<attr name="show_next_button"/> |
||||
<attr name="show_shuffle_button"/> |
||||
<attr name="show_subtitle_button"/> |
||||
<attr name="show_vr_button"/> |
||||
<attr name="time_bar_min_update_interval"/> |
||||
<attr name="controller_layout_id"/> |
||||
<attr name="animation_enabled"/> |
||||
<!-- DefaultTimeBar attributes --> |
||||
<attr name="bar_height"/> |
||||
<attr name="bar_gravity"/> |
||||
<attr name="touch_target_height"/> |
||||
<attr name="ad_marker_width"/> |
||||
<attr name="scrubber_enabled_size"/> |
||||
<attr name="scrubber_disabled_size"/> |
||||
<attr name="scrubber_dragged_size"/> |
||||
<attr name="scrubber_drawable"/> |
||||
<attr name="played_color"/> |
||||
<attr name="scrubber_color"/> |
||||
<attr name="buffered_color" /> |
||||
<attr name="unplayed_color"/> |
||||
<attr name="ad_marker_color"/> |
||||
<attr name="played_ad_marker_color"/> |
||||
</declare-styleable> |
||||
|
||||
<declare-styleable name="DefaultTimeBar"> |
||||
<attr name="bar_height"/> |
||||
<attr name="bar_gravity"/> |
||||
<attr name="touch_target_height"/> |
||||
<attr name="ad_marker_width"/> |
||||
<attr name="scrubber_enabled_size"/> |
||||
<attr name="scrubber_disabled_size"/> |
||||
<attr name="scrubber_dragged_size"/> |
||||
<attr name="scrubber_drawable"/> |
||||
<attr name="played_color"/> |
||||
<attr name="scrubber_color"/> |
||||
<attr name="buffered_color" /> |
||||
<attr name="unplayed_color"/> |
||||
<attr name="ad_marker_color"/> |
||||
<attr name="played_ad_marker_color"/> |
||||
</declare-styleable> |
||||
</resources> |
||||
Loading…
Reference in new issue