mirror of
https://github.com/lizongying/my-tv.git
synced 2026-11-01 09:06:31 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb4fd56845 | ||
|
|
657b8714a0 |
@@ -91,6 +91,8 @@ static def VersionName() {
|
|||||||
dependencies {
|
dependencies {
|
||||||
def media3_version = "1.1.1"
|
def media3_version = "1.1.1"
|
||||||
|
|
||||||
|
implementation "androidx.media3:media3-ui:$media3_version"
|
||||||
|
|
||||||
// For media playback using ExoPlayer
|
// For media playback using ExoPlayer
|
||||||
implementation "androidx.media3:media3-exoplayer:$media3_version"
|
implementation "androidx.media3:media3-exoplayer:$media3_version"
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,10 @@
|
|||||||
<activity
|
<activity
|
||||||
android:keepScreenOn="true"
|
android:keepScreenOn="true"
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:banner="@drawable/tv"
|
android:banner="@drawable/logo"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:icon="@drawable/tv"
|
android:icon="@drawable/logo"
|
||||||
android:logo="@drawable/tv"
|
android:logo="@drawable/logo"
|
||||||
android:screenOrientation="landscape">
|
android:screenOrientation="landscape">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|||||||
Binary file not shown.
@@ -1,23 +0,0 @@
|
|||||||
package com.lizongying.mytv
|
|
||||||
|
|
||||||
import androidx.media3.common.C
|
|
||||||
import androidx.media3.common.util.UnstableApi
|
|
||||||
import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy
|
|
||||||
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
class CustomLoadErrorHandlingPolicy(private val minimumLoadableRetryCount: Int) :
|
|
||||||
DefaultLoadErrorHandlingPolicy(minimumLoadableRetryCount) {
|
|
||||||
|
|
||||||
override fun getMinimumLoadableRetryCount(dataType: Int): Int {
|
|
||||||
return if (minimumLoadableRetryCount == -1) {
|
|
||||||
if (dataType == C.DATA_TYPE_MEDIA_PROGRESSIVE_LIVE) {
|
|
||||||
DEFAULT_MIN_LOADABLE_RETRY_COUNT_PROGRESSIVE_LIVE
|
|
||||||
} else {
|
|
||||||
DEFAULT_MIN_LOADABLE_RETRY_COUNT
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
minimumLoadableRetryCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,7 @@ import android.util.Log
|
|||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
import android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
@@ -26,7 +27,7 @@ import java.security.MessageDigest
|
|||||||
|
|
||||||
class MainActivity : FragmentActivity() {
|
class MainActivity : FragmentActivity() {
|
||||||
|
|
||||||
private val playbackFragment = PlaybackFragment()
|
private val playerFragment = PlayerFragment()
|
||||||
private val mainFragment = MainFragment()
|
private val mainFragment = MainFragment()
|
||||||
private val infoFragment = InfoFragment()
|
private val infoFragment = InfoFragment()
|
||||||
|
|
||||||
@@ -40,10 +41,11 @@ class MainActivity : FragmentActivity() {
|
|||||||
|
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||||
|
window.decorView.systemUiVisibility = SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.add(R.id.main_browse_fragment, playbackFragment)
|
.add(R.id.main_browse_fragment, playerFragment)
|
||||||
.add(R.id.main_browse_fragment, mainFragment)
|
.add(R.id.main_browse_fragment, mainFragment)
|
||||||
.add(R.id.main_browse_fragment, infoFragment)
|
.add(R.id.main_browse_fragment, infoFragment)
|
||||||
.hide(infoFragment)
|
.hide(infoFragment)
|
||||||
@@ -75,7 +77,7 @@ class MainActivity : FragmentActivity() {
|
|||||||
|
|
||||||
fun play(tvViewModel: TVViewModel) {
|
fun play(tvViewModel: TVViewModel) {
|
||||||
Log.i(TAG, "play: ${tvViewModel.getTV()}")
|
Log.i(TAG, "play: ${tvViewModel.getTV()}")
|
||||||
playbackFragment.play(tvViewModel)
|
playerFragment.play(tvViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun prev() {
|
fun prev() {
|
||||||
@@ -134,35 +136,8 @@ class MainActivity : FragmentActivity() {
|
|||||||
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
|
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
|
||||||
|
|
||||||
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
|
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
|
||||||
// 处理单击事件
|
Log.i(TAG, "onSingleTapConfirmed")
|
||||||
val versionName = getPackageInfo().versionName
|
switchMainFragment()
|
||||||
|
|
||||||
val textView = TextView(this@MainActivity)
|
|
||||||
textView.text =
|
|
||||||
"当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/"
|
|
||||||
|
|
||||||
val imageView = ImageView(this@MainActivity)
|
|
||||||
val drawable = ContextCompat.getDrawable(this@MainActivity, R.drawable.appreciate)
|
|
||||||
imageView.setImageDrawable(drawable)
|
|
||||||
|
|
||||||
val linearLayout = LinearLayout(this@MainActivity)
|
|
||||||
linearLayout.orientation = LinearLayout.VERTICAL
|
|
||||||
linearLayout.addView(textView)
|
|
||||||
linearLayout.addView(imageView)
|
|
||||||
|
|
||||||
val layoutParams = LinearLayout.LayoutParams(
|
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
|
||||||
)
|
|
||||||
imageView.layoutParams = layoutParams
|
|
||||||
textView.layoutParams = layoutParams
|
|
||||||
|
|
||||||
val builder: AlertDialog.Builder = AlertDialog.Builder(this@MainActivity)
|
|
||||||
builder
|
|
||||||
.setView(linearLayout)
|
|
||||||
|
|
||||||
val dialog: AlertDialog = builder.create()
|
|
||||||
dialog.show()
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,12 +243,12 @@ class MainActivity : FragmentActivity() {
|
|||||||
if (mainFragment.isHidden) {
|
if (mainFragment.isHidden) {
|
||||||
prev()
|
prev()
|
||||||
} else {
|
} else {
|
||||||
if (mainFragment.selectedPosition == 0) {
|
// if (mainFragment.selectedPosition == 0) {
|
||||||
mainFragment.setSelectedPosition(
|
// mainFragment.setSelectedPosition(
|
||||||
mainFragment.tvListViewModel.maxNum.size - 1,
|
// mainFragment.tvListViewModel.maxNum.size - 1,
|
||||||
false
|
// false
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,11 +256,11 @@ class MainActivity : FragmentActivity() {
|
|||||||
if (mainFragment.isHidden) {
|
if (mainFragment.isHidden) {
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
if (mainFragment.selectedPosition == mainFragment.tvListViewModel.maxNum.size - 1) {
|
// if (mainFragment.selectedPosition == mainFragment.tvListViewModel.maxNum.size - 1) {
|
||||||
// mainFragment.setSelectedPosition(0, false)
|
//// mainFragment.setSelectedPosition(0, false)
|
||||||
hideMainFragment()
|
// hideMainFragment()
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,12 +268,12 @@ class MainActivity : FragmentActivity() {
|
|||||||
if (mainFragment.isHidden) {
|
if (mainFragment.isHidden) {
|
||||||
prevSource()
|
prevSource()
|
||||||
} else {
|
} else {
|
||||||
if (mainFragment.tvListViewModel.getTVViewModelCurrent()
|
// if (mainFragment.tvListViewModel.getTVViewModelCurrent()
|
||||||
?.getItemPosition() == 0
|
// ?.getItemPosition() == 0
|
||||||
) {
|
// ) {
|
||||||
// mainFragment.toLastPosition()
|
//// mainFragment.toLastPosition()
|
||||||
hideMainFragment()
|
// hideMainFragment()
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,11 +281,11 @@ class MainActivity : FragmentActivity() {
|
|||||||
if (mainFragment.isHidden) {
|
if (mainFragment.isHidden) {
|
||||||
nextSource()
|
nextSource()
|
||||||
} else {
|
} else {
|
||||||
if (mainFragment.tvListViewModel.getTVViewModelCurrent()
|
// if (mainFragment.tvListViewModel.getTVViewModelCurrent()
|
||||||
?.getItemPosition() == mainFragment.tvListViewModel.maxNum[mainFragment.selectedPosition] - 1
|
// ?.getItemPosition() == mainFragment.tvListViewModel.maxNum[mainFragment.selectedPosition] - 1
|
||||||
) {
|
// ) {
|
||||||
mainFragment.toFirstPosition()
|
// mainFragment.toFirstPosition()
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import android.content.Context
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.leanback.app.BrowseSupportFragment
|
import androidx.leanback.app.BrowseSupportFragment
|
||||||
import androidx.leanback.widget.ArrayObjectAdapter
|
import androidx.leanback.widget.ArrayObjectAdapter
|
||||||
@@ -51,7 +54,6 @@ class MainFragment : BrowseSupportFragment() {
|
|||||||
// request?.fetchPage()
|
// request?.fetchPage()
|
||||||
// tvListViewModel.getTVViewModel(0)?.let { request?.fetchProgram(it) }
|
// tvListViewModel.getTVViewModel(0)?.let { request?.fetchProgram(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel ->
|
tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel ->
|
||||||
tvViewModel.ready.observe(viewLifecycleOwner) { _ ->
|
tvViewModel.ready.observe(viewLifecycleOwner) { _ ->
|
||||||
if (tvViewModel.ready.value != null) {
|
if (tvViewModel.ready.value != null) {
|
||||||
@@ -96,17 +98,6 @@ class MainFragment : BrowseSupportFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkProgram() {
|
|
||||||
tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel ->
|
|
||||||
if (tvViewModel.program.value!!.isEmpty()) {
|
|
||||||
if (tvViewModel.programId.value != null) {
|
|
||||||
Log.i(TAG, "get program ${tvViewModel.title.value}")
|
|
||||||
request?.fetchProgram(tvViewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toLastPosition() {
|
fun toLastPosition() {
|
||||||
setSelectedPosition(
|
setSelectedPosition(
|
||||||
selectedPosition, false,
|
selectedPosition, false,
|
||||||
@@ -245,6 +236,7 @@ class MainFragment : BrowseSupportFragment() {
|
|||||||
rowViewHolder: RowPresenter.ViewHolder,
|
rowViewHolder: RowPresenter.ViewHolder,
|
||||||
row: Row
|
row: Row
|
||||||
) {
|
) {
|
||||||
|
Log.i(TAG, "onSingleTapConfirmed")
|
||||||
if (item is TVViewModel) {
|
if (item is TVViewModel) {
|
||||||
itemPosition = item.id.value!!
|
itemPosition = item.id.value!!
|
||||||
savePosition(itemPosition)
|
savePosition(itemPosition)
|
||||||
@@ -262,17 +254,7 @@ class MainFragment : BrowseSupportFragment() {
|
|||||||
itemViewHolder: Presenter.ViewHolder?, item: Any?,
|
itemViewHolder: Presenter.ViewHolder?, item: Any?,
|
||||||
rowViewHolder: RowPresenter.ViewHolder, row: Row
|
rowViewHolder: RowPresenter.ViewHolder, row: Row
|
||||||
) {
|
) {
|
||||||
if (itemViewHolder == null) {
|
Log.i(TAG, "onSingleTapConfirmed1111")
|
||||||
view?.post {
|
|
||||||
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
|
|
||||||
if (tvViewModel != null) {
|
|
||||||
setSelectedPosition(
|
|
||||||
tvViewModel.getRowPosition(), false,
|
|
||||||
SelectItemViewHolderTask(tvViewModel.getItemPosition())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (item is TVViewModel) {
|
if (item is TVViewModel) {
|
||||||
tvListViewModel.setItemPositionCurrent(item.id.value!!)
|
tvListViewModel.setItemPositionCurrent(item.id.value!!)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package com.lizongying.mytv
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.leanback.app.VideoSupportFragment
|
|
||||||
import androidx.leanback.app.VideoSupportFragmentGlueHost
|
|
||||||
import androidx.leanback.media.PlaybackTransportControlGlue
|
|
||||||
import androidx.leanback.media.PlayerAdapter
|
|
||||||
import androidx.leanback.widget.PlaybackControlsRow
|
|
||||||
import com.lizongying.mytv.models.TVViewModel
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class PlaybackFragment : VideoSupportFragment() {
|
|
||||||
|
|
||||||
private lateinit var mTransportControlGlue: PlaybackTransportControlGlue<PlayerAdapter>
|
|
||||||
private var playerAdapter: ExoPlayerAdapter? = null
|
|
||||||
private var lastVideoUrl: String = ""
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
playerAdapter = ExoPlayerAdapter(context)
|
|
||||||
playerAdapter?.setRepeatAction(PlaybackControlsRow.RepeatAction.INDEX_NONE)
|
|
||||||
|
|
||||||
view?.isFocusable = false
|
|
||||||
view?.isFocusableInTouchMode = false
|
|
||||||
|
|
||||||
val glueHost = VideoSupportFragmentGlueHost(this@PlaybackFragment)
|
|
||||||
mTransportControlGlue = PlaybackControlGlue(activity, playerAdapter)
|
|
||||||
mTransportControlGlue.host = glueHost
|
|
||||||
mTransportControlGlue.playWhenPrepared()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showControlsOverlay(runAnimation: Boolean) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fun play(tvModel: TVViewModel) {
|
|
||||||
val videoUrl = tvModel.videoIndex.value?.let { tvModel.videoUrl.value?.get(it) }
|
|
||||||
if (videoUrl == null || videoUrl == "") {
|
|
||||||
Log.e(TAG, "${tvModel.title.value} videoUrl is empty")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (videoUrl == lastVideoUrl) {
|
|
||||||
Log.e(TAG, "videoUrl is duplication")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
lastVideoUrl = videoUrl
|
|
||||||
|
|
||||||
playerAdapter?.callback = PlayerCallback(tvModel)
|
|
||||||
if (tvModel.ysp() != null) {
|
|
||||||
playerAdapter?.setMinimumLoadableRetryCount(0)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
playerAdapter?.setDataSource(Uri.parse(videoUrl))
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.e(TAG, "error $e")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hideControlsOverlay(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class PlayerCallback(private var tvModel: TVViewModel) :
|
|
||||||
PlayerAdapter.Callback() {
|
|
||||||
override fun onError(adapter: PlayerAdapter?, errorCode: Int, errorMessage: String?) {
|
|
||||||
Log.e(TAG, "on error: $errorMessage")
|
|
||||||
if (tvModel.ysp() != null && tvModel.videoIndex.value!! > 0 && errorMessage == "Source error") {
|
|
||||||
tvModel.changed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "PlaybackVideoFragment"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.lizongying.mytv
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.media3.common.MediaItem
|
||||||
|
import androidx.media3.common.Player
|
||||||
|
import androidx.media3.common.VideoSize
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
import androidx.media3.ui.PlayerView
|
||||||
|
import com.lizongying.mytv.databinding.PlayerBinding
|
||||||
|
import com.lizongying.mytv.models.TVViewModel
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerFragment : Fragment() {
|
||||||
|
|
||||||
|
private var lastVideoUrl: String = ""
|
||||||
|
|
||||||
|
private var _binding: PlayerBinding? = null
|
||||||
|
private var playerView: PlayerView? = null
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = PlayerBinding.inflate(inflater, container, false)
|
||||||
|
playerView = _binding!!.playerView
|
||||||
|
return _binding!!.root
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
fun play(tvModel: TVViewModel) {
|
||||||
|
val videoUrl = tvModel.videoIndex.value?.let { tvModel.videoUrl.value?.get(it) }
|
||||||
|
if (videoUrl == null || videoUrl == "") {
|
||||||
|
Log.e(TAG, "${tvModel.title.value} videoUrl is empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoUrl == lastVideoUrl) {
|
||||||
|
Log.e(TAG, "videoUrl is duplication")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lastVideoUrl = videoUrl
|
||||||
|
|
||||||
|
if (playerView!!.player == null) {
|
||||||
|
playerView!!.player = activity?.let {
|
||||||
|
ExoPlayer.Builder(it)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
playerView!!.player?.playWhenReady = true
|
||||||
|
playerView!!.player?.addListener(object : Player.Listener {
|
||||||
|
override fun onVideoSizeChanged(videoSize: VideoSize) {
|
||||||
|
val aspectRatio = 16f / 9f
|
||||||
|
val layoutParams = playerView?.layoutParams
|
||||||
|
layoutParams?.width = (playerView?.measuredHeight?.times(aspectRatio))?.toInt()
|
||||||
|
playerView?.layoutParams = layoutParams
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
playerView!!.player?.run {
|
||||||
|
setMediaItem(MediaItem.fromUri(videoUrl))
|
||||||
|
prepare()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "PlaybackVideoFragment"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -84,7 +84,6 @@ CGTN 纪录频道,https://livedoc.cgtn.com/500d/prog_index.m3u8,https://resource
|
|||||||
三沙卫视,https://pullsstv90080111.ssws.tv/live/SSTV20220729.m3u8
|
三沙卫视,https://pullsstv90080111.ssws.tv/live/SSTV20220729.m3u8
|
||||||
延边卫视,http://live.ybtvyun.com/video/s10006-44f040627ca1/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226220/index.m3u8
|
延边卫视,http://live.ybtvyun.com/video/s10006-44f040627ca1/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226220/index.m3u8
|
||||||
浙江少儿,http://hw-m-l.cztv.com/channels/lantian/channel008/1080p.m3u8
|
浙江少儿,http://hw-m-l.cztv.com/channels/lantian/channel008/1080p.m3u8
|
||||||
南京少儿,http://live.nbs.cn/channels/njtv/sepd/500k.m3u8
|
|
||||||
|
|
||||||
移动专区
|
移动专区
|
||||||
CETV1,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225753/index.m3u8
|
CETV1,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225753/index.m3u8
|
||||||
@@ -161,6 +160,9 @@ CHC动作电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226465/in
|
|||||||
}
|
}
|
||||||
if (!i.contains(",")) {
|
if (!i.contains(",")) {
|
||||||
channel = i.trim()
|
channel = i.trim()
|
||||||
|
if (channel == "移动专区") {
|
||||||
|
break
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val p = i.split(",")
|
val p = i.split(",")
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package com.lizongying.mytv.api
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.util.Log
|
||||||
import com.lizongying.mytv.Encryptor
|
import com.lizongying.mytv.Encryptor
|
||||||
import com.lizongying.mytv.MainActivity
|
import com.lizongying.mytv.MainActivity
|
||||||
import com.lizongying.mytv.models.TVViewModel
|
import com.lizongying.mytv.models.TVViewModel
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
class YSP(var context: Context) {
|
class YSP(var context: Context) {
|
||||||
private var cnlid = ""
|
private var cnlid = ""
|
||||||
@@ -87,14 +89,8 @@ class YSP(var context: Context) {
|
|||||||
|
|
||||||
fun generateGuid(): String {
|
fun generateGuid(): String {
|
||||||
val timestamp = (System.currentTimeMillis()).toString(36)
|
val timestamp = (System.currentTimeMillis()).toString(36)
|
||||||
val originalString = Math.random().toString()
|
val randomPart = Random.nextLong().toString(36).take(11)
|
||||||
val resultString = if (originalString.startsWith("0.")) {
|
return timestamp + "_" + "0".repeat(11 - randomPart.length) + randomPart
|
||||||
originalString.substring(2)
|
|
||||||
} else {
|
|
||||||
originalString
|
|
||||||
}
|
|
||||||
val randomPart = resultString.toLong().toString(36)
|
|
||||||
return timestamp + "_" + randomPart
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGuid(): String {
|
fun getGuid(): String {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import androidx.media3.common.MediaItem
|
|||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.datasource.DefaultHttpDataSource
|
import androidx.media3.datasource.DefaultHttpDataSource
|
||||||
import androidx.media3.exoplayer.hls.HlsMediaSource
|
import androidx.media3.exoplayer.hls.HlsMediaSource
|
||||||
import com.lizongying.mytv.CustomLoadErrorHandlingPolicy
|
|
||||||
import com.lizongying.mytv.TV
|
import com.lizongying.mytv.TV
|
||||||
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.programModel.Program
|
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.programModel.Program
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@@ -426,9 +425,7 @@ class TVViewModel(private var tv: TV) : ViewModel() {
|
|||||||
val httpDataSource = DefaultHttpDataSource.Factory()
|
val httpDataSource = DefaultHttpDataSource.Factory()
|
||||||
mHeaders?.let { httpDataSource.setDefaultRequestProperties(it) }
|
mHeaders?.let { httpDataSource.setDefaultRequestProperties(it) }
|
||||||
|
|
||||||
return HlsMediaSource.Factory(httpDataSource).setLoadErrorHandlingPolicy(
|
return HlsMediaSource.Factory(httpDataSource).createMediaSource(
|
||||||
CustomLoadErrorHandlingPolicy(mMinimumLoadableRetryCount)
|
|
||||||
).createMediaSource(
|
|
||||||
MediaItem.fromUri(
|
MediaItem.fromUri(
|
||||||
Uri.parse(videoUrl)
|
Uri.parse(videoUrl)
|
||||||
)
|
)
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,17 @@
|
|||||||
|
<?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:background="@color/black"
|
||||||
|
android:keepScreenOn="true">
|
||||||
|
|
||||||
|
<androidx.media3.ui.PlayerView
|
||||||
|
android:id="@+id/player_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
app:resize_mode="fill"
|
||||||
|
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>
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<color name="fastlane_background">#30000000</color>
|
<color name="fastlane_background">#30000000</color>
|
||||||
|
<color name="black">#000</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">My TV</string>
|
<string name="app_name">我的电视</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -5,5 +5,4 @@
|
|||||||
<item name="browsePaddingEnd">11dp</item>
|
<item name="browsePaddingEnd">11dp</item>
|
||||||
<item name="browseRowsMarginTop">54dp</item>
|
<item name="browseRowsMarginTop">54dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user