From 31da089eb10a3c7a49479722ea78c22a6937c270 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Tue, 25 Jul 2023 21:15:10 +0200 Subject: [PATCH] testing tag for tv focus + player popup fix --- .../lagradost/cloudstream3/MainActivity.kt | 32 ++-- .../cloudstream3/ui/player/CS3IPlayer.kt | 139 +++++++++++------- .../cloudstream3/ui/player/GeneratorPlayer.kt | 1 + app/src/main/res/layout/activity_main_tv.xml | 4 +- app/src/main/res/layout/fragment_player.xml | 4 +- .../main/res/layout/fragment_player_tv.xml | 5 +- .../res/layout/player_custom_layout_tv.xml | 87 ++++++----- app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 7 +- 9 files changed, 174 insertions(+), 108 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 0e16fec9..73664dc0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -744,23 +744,35 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { return ret } - var binding: ActivityMainBinding? = null - var focusOutline: WeakReference = WeakReference(null) - var lastFocus: WeakReference = WeakReference(null) - val layoutListener: View.OnLayoutChangeListener = - View.OnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> + private var binding: ActivityMainBinding? = null + private var focusOutline: WeakReference = WeakReference(null) + private var lastFocus: WeakReference = WeakReference(null) + private val layoutListener: View.OnLayoutChangeListener = + View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ -> updateFocusView( v ) } + private val attachListener : View.OnAttachStateChangeListener = object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + updateFocusView(v) + } + + override fun onViewDetachedFromWindow(v: View) { + // removes the focus view but not the listener as updateFocusView(null) will remove the listener + focusOutline.get()?.isVisible = false + } + } private fun updateFocusView(newFocus: View?) { val focusOutline = focusOutline.get() ?: return - //lastFocus.get()?.removeOnLayoutChangeListener(layoutListener) + lastFocus.get()?.removeOnLayoutChangeListener(layoutListener) + lastFocus.get()?.removeOnAttachStateChangeListener(attachListener) val wasGone = focusOutline.isGone - focusOutline.isVisible = - newFocus != null && newFocus.measuredHeight > 0 && newFocus.measuredWidth > 0 && newFocus.isVisible && newFocus !is MaterialButton + val visible = + newFocus != null && newFocus.measuredHeight > 0 && newFocus.measuredWidth > 0 && newFocus.isVisible && newFocus.tag != "tv_no_focus_tag" + focusOutline.isVisible = visible if (newFocus != null) { lastFocus = WeakReference(newFocus) @@ -781,8 +793,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } }*/ - // newFocus.addOnLayoutChangeListener(layoutListener) - + newFocus.addOnLayoutChangeListener(layoutListener) + newFocus.addOnAttachStateChangeListener(attachListener) // val set = AnimationSet(true) if(!wasGone) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index 9ec18b9c..f491f995 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -52,7 +52,15 @@ import javax.net.ssl.SSLSession const val TAG = "CS3ExoPlayer" const val PREFERRED_AUDIO_LANGUAGE_KEY = "preferred_audio_language" -/** Cache */ +/** toleranceBeforeUs – The maximum time that the actual position seeked to may precede the + * requested seek position, in microseconds. Must be non-negative. */ +const val toleranceBeforeUs = 300_000L + +/** + * toleranceAfterUs – The maximum time that the actual position seeked to may exceed the requested + * seek position, in microseconds. Must be non-negative. + */ +const val toleranceAfterUs = 300_000L class CS3IPlayer : IPlayer { private var isPlaying = false @@ -387,6 +395,7 @@ class CS3IPlayer : IPlayer { Log.i(TAG, "setPreferredSubtitles REQUIRES_RELOAD") return@let true } + SubtitleStatus.IS_ACTIVE -> { Log.i(TAG, "setPreferredSubtitles IS_ACTIVE") @@ -412,6 +421,7 @@ class CS3IPlayer : IPlayer { // }, 1) //} } + SubtitleStatus.NOT_FOUND -> { Log.i(TAG, "setPreferredSubtitles NOT_FOUND") return@let true @@ -678,22 +688,22 @@ class CS3IPlayer : IPlayer { // Enable Ffmpeg extension // setExtensionRendererMode(EXTENSION_RENDERER_MODE_ON) }.createRenderers( - eventHandler, - videoRendererEventListener, - audioRendererEventListener, - textRendererOutput, - metadataRendererOutput - ).map { - if (it is TextRenderer) { - currentTextRenderer = CustomTextRenderer( - subtitleOffset, - textRendererOutput, - eventHandler.looper, - CustomSubtitleDecoderFactory() - ) - currentTextRenderer!! - } else it - }.toTypedArray() + eventHandler, + videoRendererEventListener, + audioRendererEventListener, + textRendererOutput, + metadataRendererOutput + ).map { + if (it is TextRenderer) { + currentTextRenderer = CustomTextRenderer( + subtitleOffset, + textRendererOutput, + eventHandler.looper, + CustomSubtitleDecoderFactory() + ) + currentTextRenderer!! + } else it + }.toTypedArray() } .setTrackSelector( trackSelector ?: getTrackSelector( @@ -702,7 +712,7 @@ class CS3IPlayer : IPlayer { ) ) // Allows any seeking to be +- 0.3s to allow for faster seeking - .setSeekParameters(SeekParameters(300_000, 300_000)) + .setSeekParameters(SeekParameters(toleranceBeforeUs, toleranceAfterUs)) .setLoadControl( DefaultLoadControl.Builder() .setTargetBufferBytes( @@ -769,7 +779,7 @@ class CS3IPlayer : IPlayer { private fun getCurrentTimestamp(writePosition: Long? = null): EpisodeSkip.SkipStamp? { val position = writePosition ?: this@CS3IPlayer.getPosition() ?: return null for (lastTimeStamp in lastTimeStamps) { - if (lastTimeStamp.startMs <= position && position < lastTimeStamp.endMs) { + if (lastTimeStamp.startMs <= position && (position + (toleranceBeforeUs / 1000L) + 1) < lastTimeStamp.endMs) { return lastTimeStamp } } @@ -777,11 +787,12 @@ class CS3IPlayer : IPlayer { } fun updatedTime(writePosition: Long? = null) { - getCurrentTimestamp(writePosition)?.let { timestamp -> + val position = writePosition ?: exoPlayer?.currentPosition + + getCurrentTimestamp(position)?.let { timestamp -> onTimestampInvoked?.invoke(timestamp) } - val position = writePosition ?: exoPlayer?.currentPosition val duration = exoPlayer?.contentDuration if (duration != null && position != null) { playerPositionChanged?.invoke(Pair(position, duration)) @@ -810,9 +821,11 @@ class CS3IPlayer : IPlayer { CSPlayerEvent.Play -> { play() } + CSPlayerEvent.Pause -> { pause() } + CSPlayerEvent.ToggleMute -> { if (volume <= 0) { //is muted @@ -823,6 +836,7 @@ class CS3IPlayer : IPlayer { volume = 0f } } + CSPlayerEvent.PlayPauseToggle -> { if (isPlaying) { pause() @@ -830,6 +844,7 @@ class CS3IPlayer : IPlayer { play() } } + CSPlayerEvent.SeekForward -> seekTime(seekActionTime) CSPlayerEvent.SeekBack -> seekTime(-seekActionTime) CSPlayerEvent.NextEpisode -> nextEpisode?.invoke() @@ -954,6 +969,7 @@ class CS3IPlayer : IPlayer { Player.STATE_READY -> { onRenderFirst() } + else -> {} } @@ -963,6 +979,7 @@ class CS3IPlayer : IPlayer { Player.STATE_READY -> { } + Player.STATE_ENDED -> { // Only play next episode if autoplay is on (default) if (PreferenceManager.getDefaultSharedPreferences(context) @@ -974,12 +991,15 @@ class CS3IPlayer : IPlayer { handleEvent(CSPlayerEvent.NextEpisode) } } + Player.STATE_BUFFERING -> { updatedTime() } + Player.STATE_IDLE -> { // IDLE } + else -> Unit } } @@ -994,11 +1014,13 @@ class CS3IPlayer : IPlayer { && exoPlayer?.duration != TIME_UNSET -> { exoPlayer?.prepare() } + error.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW -> { // Re-initialize player at the current live window default position. exoPlayer?.seekToDefaultPosition() exoPlayer?.prepare() } + else -> { playerError?.invoke(error) } @@ -1025,6 +1047,7 @@ class CS3IPlayer : IPlayer { Player.STATE_READY -> { } + Player.STATE_ENDED -> { // Only play next episode if autoplay is on (default) if (PreferenceManager.getDefaultSharedPreferences(context) @@ -1036,12 +1059,15 @@ class CS3IPlayer : IPlayer { handleEvent(CSPlayerEvent.NextEpisode) } } + Player.STATE_BUFFERING -> { updatedTime() } + Player.STATE_IDLE -> { // IDLE } + else -> Unit } } @@ -1052,9 +1078,9 @@ class CS3IPlayer : IPlayer { } override fun onRenderedFirstFrame() { - updatedTime() super.onRenderedFirstFrame() onRenderFirst() + updatedTime() } }) } catch (e: Exception) { @@ -1082,42 +1108,43 @@ class CS3IPlayer : IPlayer { } fun onRenderFirst() { - if (!hasUsedFirstRender) { // this insures that we only call this once per player load - Log.i(TAG, "Rendered first frame") - val invalid = exoPlayer?.duration?.let { duration -> - // Only errors short playback when not playing downloaded files - duration < 20_000L && currentDownloadedFile == null - // Concatenated sources (non 1 periodCount) bypasses the invalid check as exoPlayer.duration gives only the current period - // If you can get the total time that'd be better, but this is already niche. - && exoPlayer?.currentTimeline?.periodCount == 1 - && exoPlayer?.isCurrentMediaItemLive != true - } ?: false + if (hasUsedFirstRender) { // this insures that we only call this once per player load + return + } + Log.i(TAG, "Rendered first frame") + hasUsedFirstRender = true + val invalid = exoPlayer?.duration?.let { duration -> + // Only errors short playback when not playing downloaded files + duration < 20_000L && currentDownloadedFile == null + // Concatenated sources (non 1 periodCount) bypasses the invalid check as exoPlayer.duration gives only the current period + // If you can get the total time that'd be better, but this is already niche. + && exoPlayer?.currentTimeline?.periodCount == 1 + && exoPlayer?.isCurrentMediaItemLive != true + } ?: false - if (invalid) { - releasePlayer(saveTime = false) - playerError?.invoke(InvalidFileException("Too short playback")) - return - } + if (invalid) { + releasePlayer(saveTime = false) + playerError?.invoke(InvalidFileException("Too short playback")) + return + } - setPreferredSubtitles(currentSubtitles) - hasUsedFirstRender = true - val format = exoPlayer?.videoFormat - val width = format?.width - val height = format?.height - if (height != null && width != null) { - playerDimensionsLoaded?.invoke(Pair(width, height)) - updatedTime() - exoPlayer?.apply { - requestedListeningPercentages?.forEach { percentage -> - createMessage { _, _ -> - updatedTime() - } - .setLooper(Looper.getMainLooper()) - .setPosition( /* positionMs= */contentDuration * percentage / 100) - // .setPayload(customPayloadData) - .setDeleteAfterDelivery(false) - .send() + setPreferredSubtitles(currentSubtitles) + val format = exoPlayer?.videoFormat + val width = format?.width + val height = format?.height + if (height != null && width != null) { + playerDimensionsLoaded?.invoke(Pair(width, height)) + updatedTime() + exoPlayer?.apply { + requestedListeningPercentages?.forEach { percentage -> + createMessage { _, _ -> + updatedTime() } + .setLooper(Looper.getMainLooper()) + .setPosition(contentDuration * percentage / 100) + // .setPayload(customPayloadData) + .setDeleteAfterDelivery(false) + .send() } } } @@ -1169,6 +1196,7 @@ class CS3IPlayer : IPlayer { null } } + SubtitleOrigin.URL -> { if (onlineSourceFactory != null) { activeSubtitles.add(sub) @@ -1181,6 +1209,7 @@ class CS3IPlayer : IPlayer { null } } + SubtitleOrigin.EMBEDDED_IN_VIDEO -> { if (offlineSourceFactory != null) { activeSubtitles.add(sub) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 8a7eefbc..d2656ab5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -1294,6 +1294,7 @@ class GeneratorPlayer : FullScreenPlayer() { override fun onTimestamp(timestamp: EpisodeSkip.SkipStamp?) { if (timestamp != null) { + println("timestamp: $timestamp") playerBinding?.skipChapterButton?.setText(timestamp.uiText) displayTimeStamp(true) val currentIndex = skipIndex diff --git a/app/src/main/res/layout/activity_main_tv.xml b/app/src/main/res/layout/activity_main_tv.xml index 8076eda2..a70a40cd 100644 --- a/app/src/main/res/layout/activity_main_tv.xml +++ b/app/src/main/res/layout/activity_main_tv.xml @@ -90,8 +90,8 @@ android:id="@+id/focus_outline" android:src="@drawable/outline" android:layout_width="100dp" - android:layout_height="100dp"> + android:layout_height="100dp" + android:importantForAccessibility="no"> - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_player.xml b/app/src/main/res/layout/fragment_player.xml index 250551cb..7a9c2e1d 100644 --- a/app/src/main/res/layout/fragment_player.xml +++ b/app/src/main/res/layout/fragment_player.xml @@ -93,7 +93,7 @@ - - + --> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_player_tv.xml b/app/src/main/res/layout/fragment_player_tv.xml index a0826087..bda80bcb 100644 --- a/app/src/main/res/layout/fragment_player_tv.xml +++ b/app/src/main/res/layout/fragment_player_tv.xml @@ -78,6 +78,7 @@ app:tint="@android:color/white" /> - - + --> \ No newline at end of file diff --git a/app/src/main/res/layout/player_custom_layout_tv.xml b/app/src/main/res/layout/player_custom_layout_tv.xml index 4852136d..5af0edfb 100644 --- a/app/src/main/res/layout/player_custom_layout_tv.xml +++ b/app/src/main/res/layout/player_custom_layout_tv.xml @@ -168,24 +168,23 @@ app:layout_constraintTop_toTopOf="parent" /> + android:layout_height="0dp" + android:visibility="gone" /> + android:importantForAccessibility="no" + android:visibility="gone" /> + android:focusable="true" + android:tag="@string/tv_no_focus_tag" /> @@ -323,11 +327,11 @@ tools:visibility="visible"> @@ -582,9 +588,10 @@ android:orientation="horizontal"> + android:layout_height="0dp" + android:visibility="gone"> + + + + + @@ -610,8 +640,10 @@ android:id="@+id/player_speed_btt" style="@style/VideoButtonTV" android:nextFocusLeft="@id/player_resize_btt" + android:nextFocusRight="@id/player_subtitle_offset_btt" android:nextFocusUp="@id/player_pause_play" + android:nextFocusDown="@id/player_subtitle_offset_btt" app:icon="@drawable/ic_baseline_speed_24" tools:text="Speed" /> @@ -620,6 +652,8 @@ style="@style/VideoButtonTV" android:nextFocusLeft="@id/player_speed_btt" android:nextFocusRight="@id/player_sources_btt" + android:nextFocusUp="@id/player_pause_play" + android:nextFocusDown="@id/player_sources_btt" android:text="@string/subtitle_offset" android:visibility="gone" @@ -632,6 +666,8 @@ android:nextFocusLeft="@id/player_subtitle_offset_btt" android:nextFocusRight="@id/player_tracks_btt" android:nextFocusUp="@id/player_pause_play" + android:nextFocusDown="@id/player_tracks_btt" + android:text="@string/video_source" app:icon="@drawable/ic_baseline_playlist_play_24" /> @@ -643,25 +679,6 @@ android:nextFocusUp="@id/player_pause_play" android:text="@string/tracks" app:icon="@drawable/ic_baseline_playlist_play_24" /> - - - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 18d4131b..c101b0b1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -678,4 +678,7 @@ Qualities Profile background UI was unable to be created correctly, this is a MAJOR BUG and should be reported immediately %s + + + tv_no_focus_tag diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 7fe92741..ca6a3c7c 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -495,9 +495,10 @@ 20dp 20dp true - @drawable/outline_drawable_less + ?attr/textColor - ?attr/selectableItemBackgroundBorderless ?android:attr/textAppearanceListItemSmall @drawable/ic_baseline_keyboard_arrow_right_24 @@ -547,6 +548,7 @@ 0dp 0dp @drawable/outline_drawable_less + @string/tv_no_focus_tag