From 657b8714a02bfc09c3bbb5b3fafb4dd34493f749 Mon Sep 17 00:00:00 2001 From: Li ZongYing Date: Mon, 25 Dec 2023 19:23:45 +0800 Subject: [PATCH] support phone --- app/build.gradle | 2 + app/src/main/cpp/armeabi-v7a/libnative.so | Bin 34192 -> 34192 bytes .../com/lizongying/mytv/ExoPlayerAdapter.kt | 290 ------------------ .../java/com/lizongying/mytv/MainActivity.kt | 30 +- .../java/com/lizongying/mytv/MainFragment.kt | 1 - .../lizongying/mytv/PlaybackControlGlue.kt | 57 ---- .../com/lizongying/mytv/PlaybackFragment.kt | 76 ++--- app/src/main/res/layout/player.xml | 14 + app/src/main/res/values/attrs.xml | 202 ++++++++++++ 9 files changed, 251 insertions(+), 421 deletions(-) delete mode 100644 app/src/main/java/com/lizongying/mytv/ExoPlayerAdapter.kt delete mode 100644 app/src/main/java/com/lizongying/mytv/PlaybackControlGlue.kt create mode 100644 app/src/main/res/layout/player.xml create mode 100644 app/src/main/res/values/attrs.xml diff --git a/app/build.gradle b/app/build.gradle index 0194a1e..6e7f451 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -91,6 +91,8 @@ static def VersionName() { dependencies { def media3_version = "1.1.1" + implementation "androidx.media3:media3-ui:$media3_version" + // For media playback using ExoPlayer implementation "androidx.media3:media3-exoplayer:$media3_version" diff --git a/app/src/main/cpp/armeabi-v7a/libnative.so b/app/src/main/cpp/armeabi-v7a/libnative.so index 21148dfe0ecb523446fe759ac6f6359fa25c9916..b7a757940bb1f0da47f1ba6b7d43828019e27513 100755 GIT binary patch delta 3491 zcmYk83sjV48pq$~{XT}_KEQ|rGr;)Gi(Cc<c97Kv*^&G5rGj_AP0)A~KPM;;5+9qzRFQ(>?f5RpzoBqBW{!xpa{9uKY8Ii94R zt?*NUOw5b*FresvaIA|spUHYQ7z&ecE6e-D>EK zp1#RW$kdPoeY~u-pGRO?#cn}n7yYZ}asPE(Oc>+1QJP+xnnEZo^`Sv$LWMYt z0JaJq7?<-HW^AkY1jQ!HT30J(0Il;@Bx|`bAcuho+A4THD9c-|E4_*wdY;!!QF@Ix z^Y&mK?i_`u2{GuAPI8s$lin;^t!>=~q;242J)_}Oq%MmSViyD&S;vDZxc_!6*LwX_ zH#a@57k3-v+Hc~dggqslhIw8vftB+%=yTl)>OSHyzRocALrjWYCr!p0-Zrf@gC4Tj zNC?jk98?EGjzU2vu+_G*&NjG$Z7Az(gR>P@cM31-EZkGgtt$U8A=n1}lI4nd0h-YD zojydY{=o84tUhB^B^II7Yw#lfXj%!FU{G$fTV6fP*EK35Mu>&D-Q-%Qm8fU=`pVM| zz15dcRfVPA(FF%@6V>k)do zN~k-B!0Y(9Wwx~eF3z*uI@h`n$@yK7_taRauo=Me-g>J5a~RF?#|y0{*w176lO@(` z2)UZ&{mU#7E<`iS2O6yYDCTpP53aWQqi2^`KGbN%VF4o(g$_xJr5xQgEw?TWn!hLF z^z$9o4tV>T<)gc-3V685a{F%UDm-*ef_$vS%0^Z_S^i?Lbr5Z;VEOod>xHgDOlP^H z&Dsk;D_H*Wi1j6G-embiyLA-$11z8Hu!<1(Ur9oLoE94ukBTfe^ry~QCFMBV)v{*ytzlb!A% zHC`vByQ|7z@iOb8X*j9*sOLi_2UU`K1Z(^aQyh&VCGp5IQbA4{L#h%9r86baSW+gQ z*kw#EswU;tFQ&W8CnB%StV^Nkq{bo6Ato=)CN&41e_-;_TvFd*Tf)7dG^!!B2019E z?o?0etkjp%d*XLUWekcrjb~m~Hyrh#O{Avjxf#X&{#ey*tm{d;N!1}}8&fg0kXjB3 zu5oYLOX@j&BE#+9guVvXzaJeTbqOB7WBa?Pom3;zk{15^QwOPa+MDU7e!3*n-8&h@ z|A|iq(M3{25pxIAy>x}t6|C7&rZW1OR15t7#8gfhW;!_!^s31CDkg7w2`Tr#z<9a3bCDOB8`=5BhK2GCQ&s= zhx^^-dy&7VJ9JOdbgABhMKRM;G+U~7F{uZcrqEnufXC8Qrm0jTl?Po|!t^xNOLaU- zPxWW?K8Ke6%F0>PAk}D4Tsmkrtwu{jgy_Na3^huXh&!T;X%4+2)${sOe`Z|`39*cI z^XV;JEY>5oPRPcpj~JN)<9%{>S?!Bhmo|G={ahVY7(Lr zF+ERvrMerIRZK6?eiVzHeuilwwP6%l==U1NMRY`}3wS!b!}NPgD2te0C8weKz;YeaI&v9m4`%*8)9d6h)C5F0$+VHY zhWY~UBWrV#`Wy}yqOTv?&6I7Z|Jj9jgz0U{Gt@h1=X|CnDm2t)?AFUn+o+eJF30Gr zIX&zCfX)rehVla%VyFu?{N6BqNW%?v1yAGGnOdpRP}5QFVWvGa%23_$dvSwlAB{28 zT1>Gr2ehBY8Y%?0#}iBksM=7cBRlCJO*T{&3S7WvhiHnS`sp_UZhsug+#Z1L2+cLr z0VLhV^cmF{>LRWVZr4$&H`H)_F;MKEg!yLV!r~;YM$X9VZlXkEu#jiXz_>nV( z^)YUTZ&*J|Sf>nKnV)`d8j6+Ph>g(lG8uP8Y=w9YMHuO}JajNrMtlTKDKBr;^{Q6o6YGv*#BxRW+h}mec6{WtK;e~< zW?&oYxW=|orU=}5wTd>)z@2xi7}Fm30of^TeykZ8_&1=CA`+cYBWqskkg|a0?Pa9k%uZ1`78@!mBUmY^Vp@$ABasIByto aA1}-uH*@O5apRxJnNvKjIqcEJ8UF{xl9l%W delta 3491 zcmYM03sjV48pq$~{XT|)L72f22WEibo0sdra1q1{7G(;RrK}*PwyT0>7xS7&k0P|8 zc2>8jms)nUj$LePwNk;fqE$Api7lH{l$Ny`t-E%!ytJF`{-2TKdXCKVd*0`J?%zAp zvR<~Vm)qx&eTWcZaZ$-^y@*EZtyJY+IWuEsh3m69n_n8Wa?JFeO>Hk3+uEJ-tS5T< zAlTxcleFQ@b53;#VW-F|5%`Fd!qny71gg@t-ohf6O$bx2h$?l%JnBcM5EMmmLddAS zQV2uX?!>c^X7n)}JNocvuYSYplNom1=S?4+9}SywMVHyd0oE6vU zN0WW7u*cWJK8%K5aa*MBO!mfkP&U2YmLK+<^@#3I9$ymWg6CY}$<^6%2senqqN3cr_rsh{v? zUmeUH1EcT>ZuIm4^0es_{v7&3+tSL@FL1KHY49piH^d9E2Ljdhu>G)Gug3D6*Y~D* zX^&o)HqMiXZKX2yl=KuHb4~KjS1qEYJV#PokJmEG}479=B1*>a?R}2*H+r_P_+?xF(TvwkKIOLV(MtlOWuZ%N z4InSzy(F;{V zV+l+*^YOl=)&;oeXSr*+bu*F+dLX~O%36r_mb1LS$!d-iVj9Z_nk|81R1QQ9%cEX9xDene`5LLUQ0oLgyj=`)>_2PSPl5e9n3336hf0G!X#Q!ZKsj14k>Okn1)hZ01q@$1DewT?YKCv! z6!-|cxtrm8HJcrI*&+zXo3YKJ~i z&7}a}wds=O~k(mXbOI&;Mcy&~j2=Vq2p9pmbVAsvkKh zrVMH#Rcz?5GKb@PNM((SE5x0blZK;0dXdyVy*#Tl*n(Bv!MfqpNva1yyO~O9AE{HI z@EVV#{iNR0A7^=kKccVU^&dk$q^^ULqm+X-NzJuctn=Qv0*L^oTCY_Kv(C z#s8j9#?d)aPa|dr(|G!p)D2j(<4hIw11T5I&NEfgWl}SAOSZQz4SsJ8KzA#dQcXe3 zxl9wtF4gn+T3*9+8^uWVI_P<(+sP?at$}ecO(eHe|HVpu$~1{QQoV&`xx!RMKB>BO zY>v0)w;00|1zj~|Np&ycEMlsmT&X&cR}0e=Dv)XdZkbn^YN=SNr?HX;nWoYRsoq5n zpEKP-Wl}w)SLb9?w|+4vd!)n=SEu8oj;2a=8q~&APcx)yHHFy8G>2wNbrEOXOmnFo zq@#o0${~p2%Yg14S}2tRQA(NarKM6uqnckZ-ABu%YQVKLpJ_g=l4?G>@G#T;)Ff3_ ztezjt8d-vtUSZ`DYLRLyHak8Ow3J%W(g-06nI51vsS@!PpaLB zTEg^e+Ao!cWew9qbQr~AbPq5!Q#VGDgHAlbxQ2S9I*Z$37t>nmm1+a3eV=I^^+|O` zZ_f3`1raDa8@ly$Mymb#om?+<>Hgf(#uw4`pZTPfgrPR#*~A>slVlodD1sIUU))KBFljY`2RM+c$ZO1XxbgwafAdYK9gwF~WB$@D4}8|o)mZe-d)BMepU)ZgU| zZ~PdYo0tpbTQtE?qcDvXOnYgPp}xg!{2WskRU4`cID?In$M2XeTEvNUkG`Fbx3+=2)Z6xZm1(j zx|`{5vV3lkJrSb>zxK9roDlLHeA=|X}vqc^!?p!&xSdNR*BgU?#CcS%X zD)qO2IJU&}6kv+7IX6Z&Svd%Kk$)jQD6DU^s+`lP;6y$e{W2OHu@istTu}IBj2YU2 zIxev7AX9{{{j`cT&Cs>qR&l01REB~SH{WH(gl+*8GsK(D&>?JJ_++r@4h_fjC-aHh z^n^Y@L&Ii6wEgDK=$t-#bF*~W^nzPz!CTgmetkxnyLjRK#d8ZwhUL#%Jb%ut W*>~qHE1lIoVZwZ8d-R=av;GI~5|(fP diff --git a/app/src/main/java/com/lizongying/mytv/ExoPlayerAdapter.kt b/app/src/main/java/com/lizongying/mytv/ExoPlayerAdapter.kt deleted file mode 100644 index 433b1ad..0000000 --- a/app/src/main/java/com/lizongying/mytv/ExoPlayerAdapter.kt +++ /dev/null @@ -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? = mapOf() - - fun setHeaders(headers: Map) { - 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) - } -} diff --git a/app/src/main/java/com/lizongying/mytv/MainActivity.kt b/app/src/main/java/com/lizongying/mytv/MainActivity.kt index a7c6d39..ea2f4ba 100644 --- a/app/src/main/java/com/lizongying/mytv/MainActivity.kt +++ b/app/src/main/java/com/lizongying/mytv/MainActivity.kt @@ -134,35 +134,7 @@ class MainActivity : FragmentActivity() { private inner class GestureListener : GestureDetector.SimpleOnGestureListener() { override fun onSingleTapConfirmed(e: MotionEvent): Boolean { - // 处理单击事件 - val versionName = getPackageInfo().versionName - - 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() + switchMainFragment() return true } diff --git a/app/src/main/java/com/lizongying/mytv/MainFragment.kt b/app/src/main/java/com/lizongying/mytv/MainFragment.kt index db074f1..4687d72 100644 --- a/app/src/main/java/com/lizongying/mytv/MainFragment.kt +++ b/app/src/main/java/com/lizongying/mytv/MainFragment.kt @@ -51,7 +51,6 @@ class MainFragment : BrowseSupportFragment() { // request?.fetchPage() // tvListViewModel.getTVViewModel(0)?.let { request?.fetchProgram(it) } } - tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel -> tvViewModel.ready.observe(viewLifecycleOwner) { _ -> if (tvViewModel.ready.value != null) { diff --git a/app/src/main/java/com/lizongying/mytv/PlaybackControlGlue.kt b/app/src/main/java/com/lizongying/mytv/PlaybackControlGlue.kt deleted file mode 100644 index ba85436..0000000 --- a/app/src/main/java/com/lizongying/mytv/PlaybackControlGlue.kt +++ /dev/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(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" - } -} diff --git a/app/src/main/java/com/lizongying/mytv/PlaybackFragment.kt b/app/src/main/java/com/lizongying/mytv/PlaybackFragment.kt index 3f98c42..449ece3 100644 --- a/app/src/main/java/com/lizongying/mytv/PlaybackFragment.kt +++ b/app/src/main/java/com/lizongying/mytv/PlaybackFragment.kt @@ -1,40 +1,37 @@ 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 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.util.UnstableApi +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.ui.PlayerView +import com.lizongying.mytv.databinding.PlayerBinding import com.lizongying.mytv.models.TVViewModel -import java.io.IOException -class PlaybackFragment : VideoSupportFragment() { - private lateinit var mTransportControlGlue: PlaybackTransportControlGlue - private var playerAdapter: ExoPlayerAdapter? = null +class PlaybackFragment : Fragment() { + private var lastVideoUrl: String = "" - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + private var _binding: PlayerBinding? = null + private var playerView: PlayerView? = null - 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) { + 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 == "") { @@ -49,26 +46,17 @@ class PlaybackFragment : VideoSupportFragment() { 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() + if (playerView!!.player == null) { + playerView!!.player = activity?.let { + ExoPlayer.Builder(it) + .build() } + playerView!!.player?.playWhenReady = true + } + + playerView!!.player?.run { + setMediaItem(MediaItem.fromUri(videoUrl)) + prepare() } } diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml new file mode 100644 index 0000000..3c8dbe6 --- /dev/null +++ b/app/src/main/res/layout/player.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..1ff9bfe --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file