From c333300906b732356868eb6ddab53172735cb27e Mon Sep 17 00:00:00 2001 From: Kai Date: Thu, 5 Jun 2025 11:08:19 -0500 Subject: [PATCH 01/39] fix graphical glitches with quality selector Changelog: changed --- .../overlays/slideup/SlideUpMenuOverlay.kt | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt index 58850998..72500a49 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt @@ -13,6 +13,7 @@ import android.widget.RelativeLayout import android.widget.TextView import androidx.core.animation.doOnEnd import androidx.core.view.children +import androidx.core.view.isVisible import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event0 @@ -42,10 +43,14 @@ class SlideUpMenuOverlay : RelativeLayout { constructor(context: Context, parent: ViewGroup, titleText: String, okText: String?, animated: Boolean, items: List, hideButtons: Boolean = false): super(context){ init(animated, okText); _container = parent; - if(!_container!!.children.contains(this)) { - _container!!.removeAllViews(); - _container!!.addView(this); + _container!!.removeAllViews(); + _container!!.addView(this); + if (_container!!.isVisible) { + isVisible = true + _viewBackground.alpha = 1.0f; + _viewOverlayContainer.translationY = 0.0f; } + _textTitle.text = titleText; groupItems = items; @@ -56,6 +61,12 @@ class SlideUpMenuOverlay : RelativeLayout { } setItems(items); + + if (!isVisible) { + _viewOverlayContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + _viewOverlayContainer.translationY = _viewOverlayContainer.measuredHeight.toFloat() + _viewBackground.alpha = 0f; + } } @@ -146,16 +157,9 @@ class SlideUpMenuOverlay : RelativeLayout { } isVisible = true; - _container?.post { - _container?.visibility = View.VISIBLE; - _container?.bringToFront(); - } + _container?.visibility = View.VISIBLE; if (_animated) { - _viewOverlayContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - _viewOverlayContainer.translationY = _viewOverlayContainer.measuredHeight.toFloat() - _viewBackground.alpha = 0f; - val animations = arrayListOf(); animations.add(ObjectAnimator.ofFloat(_viewBackground, "alpha", 0.0f, 1.0f).setDuration(ANIMATION_DURATION_MS)); animations.add(ObjectAnimator.ofFloat(_viewOverlayContainer, "translationY", _viewOverlayContainer.measuredHeight.toFloat(), 0.0f).setDuration(ANIMATION_DURATION_MS)); From 19861fe8129d2a38b784e0bf43a65bddf87e131b Mon Sep 17 00:00:00 2001 From: Kai Date: Fri, 6 Jun 2025 13:40:20 -0500 Subject: [PATCH 02/39] fix https://github.com/futo-org/grayjay-android/issues/2316 Changelog: changed --- app/src/main/res/layout/fragment_sources.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/fragment_sources.xml b/app/src/main/res/layout/fragment_sources.xml index 80940400..ccd87712 100644 --- a/app/src/main/res/layout/fragment_sources.xml +++ b/app/src/main/res/layout/fragment_sources.xml @@ -8,7 +8,7 @@ android:orientation="vertical" android:paddingTop="10dp" android:animateLayoutChanges="true"> - - + From 623c47fa2e6a2165443b0d996d1fc66e8bcb66aa Mon Sep 17 00:00:00 2001 From: Kai Date: Fri, 6 Jun 2025 15:25:46 -0500 Subject: [PATCH 03/39] fix https://github.com/futo-org/grayjay-android/issues/2210 Changelog: changed --- .../fragment/mainactivity/main/VideoDetailFragment.kt | 7 +------ app/src/main/res/values/dimensions.xml | 1 + 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt index 53886862..3404de15 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt @@ -101,7 +101,7 @@ class VideoDetailFragment() : MainFragment() { } private fun isSmallWindow(): Boolean { - return resources.configuration.smallestScreenWidthDp < resources.getInteger(R.integer.column_width_dp) * 2 + return resources.configuration.smallestScreenWidthDp < resources.getInteger(R.integer.smallest_width_dp) } private fun isAutoRotateEnabled(): Boolean { @@ -627,11 +627,6 @@ class VideoDetailFragment() : MainFragment() { showSystemUI() } - // temporarily force the device to portrait if auto-rotate is disabled to prevent landscape when exiting full screen on a small device -// @SuppressLint("SourceLockedOrientationActivity") -// if (!isFullscreen && isSmallWindow() && !isAutoRotateEnabled() && !isMinimizingFromFullScreen) { -// activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT -// } updateOrientation(); _view?.allowMotion = !fullscreen; } diff --git a/app/src/main/res/values/dimensions.xml b/app/src/main/res/values/dimensions.xml index 2e4468b5..5aab83f2 100644 --- a/app/src/main/res/values/dimensions.xml +++ b/app/src/main/res/values/dimensions.xml @@ -3,4 +3,5 @@ 500dp 200dp 400 + 600 From 6598dff6dfba1f232ebc69b809c9b6d3aa52c055 Mon Sep 17 00:00:00 2001 From: Kai Date: Fri, 6 Jun 2025 23:35:59 -0500 Subject: [PATCH 04/39] add add to watch later setting add https://github.com/futo-org/grayjay-android/issues/2173 Changelog: added --- .../java/com/futo/platformplayer/Settings.kt | 7 +++-- .../futo/platformplayer/UISlideOverlays.kt | 2 ++ .../main/ArticleDetailFragment.kt | 2 ++ .../mainactivity/main/ChannelFragment.kt | 2 ++ .../mainactivity/main/ContentFeedView.kt | 2 ++ .../mainactivity/main/VideoDetailView.kt | 2 ++ .../platformplayer/states/StatePlaylists.kt | 27 +++++++++---------- app/src/main/res/values/strings.xml | 3 +++ 8 files changed, 31 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index e8f4f70a..c265fc40 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -999,10 +999,13 @@ class Settings : FragmentedStorageFileJson() { @FormField(R.string.playlist_allow_dups, FieldForm.TOGGLE, R.string.playlist_allow_dups_description, 3) var playlistAllowDups: Boolean = true; - @FormField(R.string.enable_polycentric, FieldForm.TOGGLE, R.string.can_be_disabled_when_you_are_experiencing_issues, 4) + @FormField(R.string.add_to_beginning_of_watch_later, FieldForm.TOGGLE, R.string.add_to_beginning_description, 4) + var addToBeginning: Boolean = true; + + @FormField(R.string.enable_polycentric, FieldForm.TOGGLE, R.string.can_be_disabled_when_you_are_experiencing_issues, 5) var polycentricEnabled: Boolean = true; - @FormField(R.string.polycentric_local_cache, FieldForm.TOGGLE, R.string.polycentric_local_cache_description, 5) + @FormField(R.string.polycentric_local_cache, FieldForm.TOGGLE, R.string.polycentric_local_cache_description, 7) var polycentricLocalCache: Boolean = true; } diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt index 3db07410..83387081 100644 --- a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -1151,6 +1151,8 @@ class UISlideOverlays { call = { if(StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video), true)) UIDialogs.appToast("Added to watch later", false); + else + UIDialogs.toast(container.context.getString(R.string.already_in_watch_later)) }), ) ); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ArticleDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ArticleDetailFragment.kt index 989a19e1..d052e0f1 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ArticleDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ArticleDetailFragment.kt @@ -778,6 +778,8 @@ class ArticleDetailFragment : MainFragment { view.onAddToWatchLaterClicked.subscribe { a -> if(StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(content), true)) UIDialogs.toast("Added to watch later\n[${content.name}]") + else + UIDialogs.toast(context.getString(R.string.already_in_watch_later)) } } else if(content is IPlatformPost) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt index 63b60c1f..74116069 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt @@ -226,6 +226,8 @@ class ChannelFragment : MainFragment() { if (content is IPlatformVideo) { if(StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(content), true)) UIDialogs.toast("Added to watch later\n[${content.name}]") + else + UIDialogs.toast(context.getString(R.string.already_in_watch_later)) } } adapter.onUrlClicked.subscribe { url -> diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt index cc528a2b..fbb85dac 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt @@ -86,6 +86,8 @@ abstract class ContentFeedView : FeedViewShow confirmation dialog when deleting media from a playlist Allow duplicate playlist videos Allow adding duplicate videos to playlists + Save new videos to start of watch later + When adding videos to watch later add them to the beginning of the list instead of the end + Already in watch later Enable Polycentric Enable Polycentric Local Caching Caches polycentric results on-device to reduce load times, changing requires app reboot From 99dc50894c849239d560ad1d3f5e8c04bb6adf58 Mon Sep 17 00:00:00 2001 From: Kai Date: Mon, 9 Jun 2025 16:54:24 -0500 Subject: [PATCH 05/39] update text Changelog: changed --- app/src/main/res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 03a21f85..de5148c2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -466,8 +466,8 @@ Show confirmation dialog when deleting media from a playlist Allow duplicate playlist videos Allow adding duplicate videos to playlists - Save new videos to start of watch later - When adding videos to watch later add them to the beginning of the list instead of the end + Add new videos to the beginning of Watch Later + When adding videos to Watch Later add them to the beginning of the list instead of the end Already in watch later Enable Polycentric Enable Polycentric Local Caching From 9944842a2f9a16c167e22a08196b7c07a6b3116e Mon Sep 17 00:00:00 2001 From: Kai Date: Mon, 9 Jun 2025 17:02:55 -0500 Subject: [PATCH 06/39] Change adaptive streaming (HLS and Dash) quality to sort in descending quality to align with YouTube and the rest of Grayjay Changelog: changed --- .../fragment/mainactivity/main/VideoDetailView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index 45ccf042..1a446b4a 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -1897,8 +1897,8 @@ class VideoDetailView : ConstraintLayout { } updateQualityFormatsOverlay( - videoTrackFormats.distinctBy { it.height }.sortedBy { it.height }, - audioTrackFormats.distinctBy { it.bitrate }.sortedBy { it.bitrate }); + videoTrackFormats.distinctBy { it.height }.sortedByDescending { it.height }, + audioTrackFormats.distinctBy { it.bitrate }.sortedByDescending { it.bitrate }); } } From 9e7b936663ed3eab47b8547e6ab8fa6b68f4071d Mon Sep 17 00:00:00 2001 From: Koen J Date: Wed, 11 Jun 2025 17:03:53 +0200 Subject: [PATCH 07/39] Implemented hold to play video at 2x speed gesture. --- .../views/behavior/GestureControlView.kt | 22 ++++++++++++++++++- .../views/video/FutoVideoPlayer.kt | 17 ++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt index f90102b3..bae761fb 100644 --- a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt @@ -92,6 +92,7 @@ class GestureControlView : LinearLayout { private var _surfaceView: View? = null private var _layoutIndicatorFill: FrameLayout; private var _layoutIndicatorFit: FrameLayout; + private var _speedHolding = false private val _gestureController: GestureDetectorCompat; @@ -103,6 +104,8 @@ class GestureControlView : LinearLayout { val onZoom = Event1(); val onSoundAdjusted = Event1(); val onToggleFullscreen = Event0(); + val onSpeedHoldStart = Event0() + val onSpeedHoldEnd = Event0() var fullScreenGestureEnabled = true @@ -216,7 +219,19 @@ class GestureControlView : LinearLayout { return true; } - override fun onLongPress(p0: MotionEvent) = Unit + override fun onLongPress(p0: MotionEvent) { + if (!_isControlsLocked + && !_skipping + && !_adjustingBrightness + && !_adjustingSound + && !_adjustingFullscreenUp + && !_adjustingFullscreenDown + && !_isPanning + && !_isZooming) { + _speedHolding = true + onSpeedHoldStart.emit() + } + } }); _gestureController.setOnDoubleTapListener(object : GestureDetector.OnDoubleTapListener { @@ -309,6 +324,11 @@ class GestureControlView : LinearLayout { override fun onTouchEvent(event: MotionEvent?): Boolean { val ev = event ?: return super.onTouchEvent(event); + if (ev.action == MotionEvent.ACTION_UP && _speedHolding) { + _speedHolding = false + onSpeedHoldEnd.emit() + } + cancelHideJob(); if (_skipping) { diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt index e209f937..4be38cd3 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt @@ -117,6 +117,9 @@ class FutoVideoPlayer : FutoVideoPlayerBase { private var _isControlsLocked: Boolean = false; + private var _speedHoldPrevRate = 1f + private var _speedHoldWasPlaying = false + private val _time_bar_listener: TimeBar.OnScrubListener; var isFitMode : Boolean = false @@ -254,6 +257,20 @@ class FutoVideoPlayer : FutoVideoPlayerBase { gestureControl = findViewById(R.id.gesture_control); gestureControl.setupTouchArea(_layoutControls, background); + gestureControl.onSpeedHoldStart.subscribe { + exoPlayer?.player?.let { player -> + _speedHoldWasPlaying = player.isPlaying + _speedHoldPrevRate = getPlaybackRate() + setPlaybackRate(2f) + player.play() + } + } + gestureControl.onSpeedHoldEnd.subscribe { + exoPlayer?.player?.let { player -> + if (!_speedHoldWasPlaying) player.pause() + setPlaybackRate(_speedHoldPrevRate) + } + } gestureControl.onSeek.subscribe { seekFromCurrent(it); }; gestureControl.onSoundAdjusted.subscribe { if (Settings.instance.gestureControls.useSystemVolume) { From 9bea1563ca53b811dc4771f39734a26233042f82 Mon Sep 17 00:00:00 2001 From: zvonimir Date: Wed, 11 Jun 2025 18:36:05 +0200 Subject: [PATCH 08/39] Revert downloads patch which broke downloads --- .../java/com/futo/platformplayer/downloads/VideoDownload.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt b/app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt index c0d9d0bf..5e64c3e3 100644 --- a/app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt +++ b/app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt @@ -724,7 +724,7 @@ class VideoDownload { val t = cue.groupValues[1]; val d = cue.groupValues[2]; - val url = foundTemplateUrl.replace("\$Number\$", (indexCounter + 1).toString()); + val url = foundTemplateUrl.replace("\$Number\$", (indexCounter).toString()); val data = if(executor != null) executor.executeRequest("GET", url, null, mapOf()); From 522704139857ada51c65e22b9006344973c878fd Mon Sep 17 00:00:00 2001 From: Koen J Date: Thu, 12 Jun 2025 10:33:05 +0200 Subject: [PATCH 09/39] Added setting for hold playback speed increase. Implemented chromecast playback rate adjustment in range [1, 2]. Implemented hold playback speed increase pill. --- .../futo/platformplayer/SyncServerTests.kt | 4 +-- .../java/com/futo/platformplayer/SyncTests.kt | 4 +-- .../java/com/futo/platformplayer/Settings.kt | 17 +++++++++ .../casting/ChomecastCastingDevice.kt | 19 +++++++++- .../views/behavior/GestureControlView.kt | 26 ++++++++++++++ .../platformplayer/views/casting/CastView.kt | 17 +++++++++ .../views/video/FutoVideoPlayer.kt | 2 +- .../main/res/layout/view_gesture_controls.xml | 35 +++++++++++++++++++ app/src/main/res/values/strings.xml | 12 +++++++ 9 files changed, 130 insertions(+), 6 deletions(-) diff --git a/app/src/androidTest/java/com/futo/platformplayer/SyncServerTests.kt b/app/src/androidTest/java/com/futo/platformplayer/SyncServerTests.kt index 7607a2c9..f3e12645 100644 --- a/app/src/androidTest/java/com/futo/platformplayer/SyncServerTests.kt +++ b/app/src/androidTest/java/com/futo/platformplayer/SyncServerTests.kt @@ -11,7 +11,7 @@ import java.nio.ByteBuffer import kotlin.random.Random import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds - +/* class SyncServerTests { //private val relayHost = "relay.grayjay.app" @@ -335,4 +335,4 @@ class SyncServerTests { class AlwaysAuthorized : IAuthorizable { override val isAuthorized: Boolean get() = true -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/app/src/androidTest/java/com/futo/platformplayer/SyncTests.kt b/app/src/androidTest/java/com/futo/platformplayer/SyncTests.kt index 1b9f19cd..d34bfad4 100644 --- a/app/src/androidTest/java/com/futo/platformplayer/SyncTests.kt +++ b/app/src/androidTest/java/com/futo/platformplayer/SyncTests.kt @@ -13,7 +13,7 @@ import kotlin.random.Random import java.io.InputStream import java.io.OutputStream import kotlin.time.Duration.Companion.seconds - +/* data class PipeStreams( val initiatorInput: LittleEndianDataInputStream, val initiatorOutput: LittleEndianDataOutputStream, @@ -509,4 +509,4 @@ class Authorized : IAuthorizable { class Unauthorized : IAuthorizable { override val isAuthorized: Boolean = false -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index e8f4f70a..372501e7 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -584,6 +584,23 @@ class Settings : FragmentedStorageFileJson() { playbackSpeeds.sort(); return playbackSpeeds; } + + @FormField(R.string.hold_playback_speed, FieldForm.DROPDOWN, R.string.hold_playback_speed_description, 27) + @DropdownFieldOptionsId(R.array.hold_playback_speeds) + var holdPlaybackSpeed: Int = 3; + + fun getHoldPlaybackSpeed(): Double { + return when(holdPlaybackSpeed) { + 0 -> 1.25 + 1 -> 1.5 + 2 -> 1.75 + 3 -> 2.0 + 4 -> 2.25 + 5 -> 2.5 + 6 -> 2.75 + else -> 3.0 + } + } } @FormField(R.string.comments, "group", R.string.comments_description, 6) diff --git a/app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt b/app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt index 3d362efd..226a0a66 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt @@ -35,7 +35,7 @@ class ChromecastCastingDevice : CastingDevice { override var usedRemoteAddress: InetAddress? = null; override var localAddress: InetAddress? = null; override val canSetVolume: Boolean get() = true; - override val canSetSpeed: Boolean get() = false; //TODO: Implement + override val canSetSpeed: Boolean get() = true; var addresses: Array? = null; var port: Int = 0; @@ -144,6 +144,23 @@ class ChromecastCastingDevice : CastingDevice { sendChannelMessage("sender-0", transportId, "urn:x-cast:com.google.cast.media", json); } + override fun changeSpeed(speed: Double) { + if (invokeInIOScopeIfRequired { changeSpeed(speed) }) return + + val speedClamped = speed.coerceAtLeast(1.0).coerceAtLeast(1.0).coerceAtMost(2.0) + setSpeed(speedClamped) + val mediaSessionId = _mediaSessionId ?: return + val transportId = _transportId ?: return + val setSpeedObject = JSONObject().apply { + put("type", "SET_PLAYBACK_RATE") + put("mediaSessionId", mediaSessionId) + put("playbackRate", speedClamped) + put("requestId", _requestId++) + } + + sendChannelMessage(sourceId = "sender-0", destinationId = transportId, namespace = "urn:x-cast:com.google.cast.media", json = setSpeedObject.toString()) + } + override fun changeVolume(volume: Double) { if (invokeInIOScopeIfRequired({ changeVolume(volume) })) { return; diff --git a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt index bae761fb..4bb864da 100644 --- a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt @@ -39,6 +39,9 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols +import java.util.Locale class GestureControlView : LinearLayout { @@ -79,6 +82,9 @@ class GestureControlView : LinearLayout { private var _adjustingFullscreenDown: Boolean = false; private var _fullScreenFactorUp = 1.0f; private var _fullScreenFactorDown = 1.0f; + private val _layoutHoldSpeed: LinearLayout + private val _textHoldFastForward: TextView + private val _imageHoldFastForward: ImageView private var _scaleGestureDetector: ScaleGestureDetector private var _scaleFactor = 1.0f @@ -94,6 +100,10 @@ class GestureControlView : LinearLayout { private var _layoutIndicatorFit: FrameLayout; private var _speedHolding = false + private val _speedFormatter = DecimalFormat("#.##", DecimalFormatSymbols(Locale.US)).apply { + roundingMode = java.math.RoundingMode.HALF_UP + } + private val _gestureController: GestureDetectorCompat; val isUserGesturing get() = _rewinding || _skipping || _adjustingBrightness || _adjustingSound || _adjustingFullscreenUp || _adjustingFullscreenDown || _isPanning || _isZooming; @@ -127,6 +137,9 @@ class GestureControlView : LinearLayout { _layoutControlsFullscreen = findViewById(R.id.layout_controls_fullscreen); _layoutIndicatorFill = findViewById(R.id.layout_indicator_fill); _layoutIndicatorFit = findViewById(R.id.layout_indicator_fit); + _layoutHoldSpeed = findViewById(R.id.layout_controls_increased_speed) + _textHoldFastForward = findViewById(R.id.text_holdFastForward) + _imageHoldFastForward = findViewById(R.id.image_holdFastForward) _scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() { override fun onScale(detector: ScaleGestureDetector): Boolean { @@ -229,6 +242,7 @@ class GestureControlView : LinearLayout { && !_isPanning && !_isZooming) { _speedHolding = true + showHoldSpeedControls() onSpeedHoldStart.emit() } } @@ -316,6 +330,17 @@ class GestureControlView : LinearLayout { onPan.emit(_translationX, _translationY) } + private fun showHoldSpeedControls() { + _layoutHoldSpeed.visibility = View.VISIBLE + _textHoldFastForward.text = _speedFormatter.format(Settings.instance.playback.getHoldPlaybackSpeed()) + "x" + (_imageHoldFastForward.drawable as? Animatable)?.start() + } + + private fun hideHoldSpeedControls() { + _layoutHoldSpeed.visibility = View.GONE + (_imageHoldFastForward.drawable as? Animatable)?.stop() + } + fun setupTouchArea(layoutControls: ViewGroup? = null, background: View? = null) { _layoutControls = layoutControls; _background = background; @@ -326,6 +351,7 @@ class GestureControlView : LinearLayout { if (ev.action == MotionEvent.ACTION_UP && _speedHolding) { _speedHolding = false + hideHoldSpeedControls() onSpeedHoldEnd.emit() } diff --git a/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt b/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt index fff853a8..fe941fea 100644 --- a/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt @@ -18,6 +18,7 @@ import androidx.media3.ui.DefaultTimeBar import androidx.media3.ui.TimeBar import com.bumptech.glide.Glide import com.futo.platformplayer.R +import com.futo.platformplayer.Settings import com.futo.platformplayer.api.media.models.chapters.IChapter import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.casting.AirPlayCastingDevice @@ -58,6 +59,8 @@ class CastView : ConstraintLayout { private var _inPictureInPicture: Boolean = false; private var _chapters: List? = null; private var _currentChapter: IChapter? = null; + private var _speedHoldPrevRate = 1.0 + private var _speedHoldWasPlaying = false val onChapterChanged = Event2(); val onMinimizeClick = Event0(); @@ -87,6 +90,20 @@ class CastView : ConstraintLayout { _gestureControlView = findViewById(R.id.gesture_control); _gestureControlView.fullScreenGestureEnabled = false _gestureControlView.setupTouchArea(); + _gestureControlView.onSpeedHoldStart.subscribe { + val d = StateCasting.instance.activeDevice ?: return@subscribe; + _speedHoldWasPlaying = d.isPlaying + _speedHoldPrevRate = d.speed + if (d.canSetSpeed) + d.changeSpeed(Settings.instance.playback.getHoldPlaybackSpeed()) + d.resumeVideo() + } + _gestureControlView.onSpeedHoldEnd.subscribe { + val d = StateCasting.instance.activeDevice ?: return@subscribe; + if (!_speedHoldWasPlaying) d.pauseVideo() + d.changeSpeed(_speedHoldPrevRate) + } + _gestureControlView.onSeek.subscribe { val d = StateCasting.instance.activeDevice ?: return@subscribe; StateCasting.instance.videoSeekTo(d.expectedCurrentTime + it / 1000); diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt index 4be38cd3..b1ba431a 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt @@ -261,7 +261,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase { exoPlayer?.player?.let { player -> _speedHoldWasPlaying = player.isPlaying _speedHoldPrevRate = getPlaybackRate() - setPlaybackRate(2f) + setPlaybackRate(Settings.instance.playback.getHoldPlaybackSpeed().toFloat()) player.play() } } diff --git a/app/src/main/res/layout/view_gesture_controls.xml b/app/src/main/res/layout/view_gesture_controls.xml index ea175d31..ccf8efcd 100644 --- a/app/src/main/res/layout/view_gesture_controls.xml +++ b/app/src/main/res/layout/view_gesture_controls.xml @@ -195,4 +195,39 @@ app:layout_constraintEnd_toEndOf="parent" android:visibility="gone"/> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b2513e21..f4a51bd0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -433,6 +433,8 @@ Minimum Available Speed Maximum Playback Speed Maximum Available Speed + Hold playback speed + Playback speed when pressing down on the video Playback Speed Step Size The step size of playback speeds, may not affect higher playback speeds. Fast-Forward / Fast-Rewind duration @@ -1106,6 +1108,16 @@ 4.0 5.0 + + 1.25 + 1.5 + 1.75 + 2.0 + 2.25 + 2.5 + 2.75 + 3.0 + 0.25 0.5 From 13100dc38d9ee70dc8c6caf865054f12d87cc13b Mon Sep 17 00:00:00 2001 From: Koen J Date: Thu, 12 Jun 2025 11:21:00 +0200 Subject: [PATCH 10/39] Minor fix in playback speed setting. --- app/src/main/java/com/futo/platformplayer/Settings.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index 372501e7..421c37be 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -598,7 +598,8 @@ class Settings : FragmentedStorageFileJson() { 4 -> 2.25 5 -> 2.5 6 -> 2.75 - else -> 3.0 + 7 -> 3.0 + else -> 2.0 } } } From 47027877845161663e129aa9938f431ad532d06f Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Fri, 13 Jun 2025 17:47:22 +0200 Subject: [PATCH 11/39] WIP --- .../platforms/js/internal/JSHttpClient.kt | 2 +- .../engine/packages/PackageBridge.kt | 4 ++++ .../engine/packages/PackageHttp.kt | 22 ++++++++++++++----- .../SubscriptionsTaskFetchAlgorithm.kt | 2 +- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt index 6f835304..eec4414a 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt @@ -69,7 +69,7 @@ class JSHttpClient : ManagedHttpClient { override fun clone(): ManagedHttpClient { val newClient = JSHttpClient(_jsClient, _auth); - newClient._currentCookieMap = HashMap(_currentCookieMap.toList().associate { Pair(it.first, HashMap(it.second)) }) + //newClient._currentCookieMap = HashMap(_currentCookieMap.toList().associate { Pair(it.first, HashMap(it.second)) }) return newClient; } diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt index d2d7cf04..a5f18705 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt @@ -141,6 +141,10 @@ class PackageBridge : V8Package { timeoutMap.remove(id); } } + @V8Function + fun sleep(length: Int) { + Thread.sleep(length.toLong()); + } @V8Function fun toast(str: String) { diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt index 900eb6f0..aa78a184 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt @@ -429,8 +429,23 @@ class PackageHttp: V8Package { }; } @V8Function - fun POST(url: String, body: String, headers: MutableMap = HashMap(), useBytes: Boolean = false) : IBridgeHttpResponse - = POSTInternal(url, body, headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING) + fun POST(url: String, body: Any, headers: MutableMap = HashMap(), useBytes: Boolean = false) : IBridgeHttpResponse { + if(body is V8ValueString) + return POSTInternal(url, body.value, headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING); + else if(body is String) + return POSTInternal(url, body, headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING); + else if(body is V8ValueTypedArray) + return POSTInternal(url, body.toBytes(), headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING); + else if(body is ByteArray) + return POSTInternal(url, body, headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING); + else if(body is ArrayList<*>) //Avoid this case, used purely for testing + return POSTInternal(url, body.map { (it as Double).toInt().toByte() }.toByteArray(), headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING); + else + throw NotImplementedError("Body type " + body?.javaClass?.name?.toString() + " not implemented for POST"); + } + + + // = POSTInternal(url, body, headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING) fun POSTInternal(url: String, body: String, headers: MutableMap = HashMap(), returnType: ReturnType = ReturnType.STRING) : IBridgeHttpResponse { applyDefaultHeaders(headers); return logExceptions { @@ -452,9 +467,6 @@ class PackageHttp: V8Package { } }; } - @V8Function - fun POST(url: String, body: ByteArray, headers: MutableMap = HashMap(), useBytes: Boolean = false) : IBridgeHttpResponse - = POSTInternal(url, body, headers, if(useBytes) ReturnType.BYTES else ReturnType.STRING) fun POSTInternal(url: String, body: ByteArray, headers: MutableMap = HashMap(), returnType: ReturnType = ReturnType.STRING) : IBridgeHttpResponse { applyDefaultHeaders(headers); return logExceptions { diff --git a/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt b/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt index b72e840c..2740ca8b 100644 --- a/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt +++ b/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt @@ -174,7 +174,7 @@ abstract class SubscriptionsTaskFetchAlgorithm( if (resolve != null) { resolveCount = resolves.size; - UIDialogs.appToast("SubsExchange (Res: ${resolves.size}, Prov: ${resolve.size}") + UIDialogs.appToast("SubsExchange (Res: ${resolves.size}, Prov: ${resolve.size})") for(result in resolve){ val task = providedTasks?.find { it.url == result.channelUrl }; if(task != null) { From 58c9aeb1a2ee64ffec1c255fafc2d29b1bfd33fe Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Sat, 14 Jun 2025 15:51:31 +0200 Subject: [PATCH 12/39] WIP: V8 update, package http fixes, ReloadRequiredException support, other fixes. Currently broken in situations where setTimeout is used --- app/build.gradle | 3 +- app/src/main/assets/scripts/source.js | 6 +++ .../api/media/platforms/js/JSClient.kt | 20 ++++++- .../sources/JSDashManifestRawAudioSource.kt | 8 ++- .../models/sources/JSDashManifestRawSource.kt | 8 ++- .../platforms/js/models/sources/JSSource.kt | 2 + .../futo/platformplayer/engine/V8Plugin.kt | 52 ++++++++++++------- .../ScriptReloadRequiredException.kt | 20 +++++++ .../engine/packages/PackageBridge.kt | 7 +++ .../engine/packages/PackageHttp.kt | 38 +++++++++----- .../mainactivity/main/VideoDetailView.kt | 10 ++++ .../platformplayer/states/StatePlatform.kt | 36 ++++++++++++- .../views/video/FutoVideoPlayerBase.kt | 39 +++++++++++--- 13 files changed, 205 insertions(+), 44 deletions(-) create mode 100644 app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptReloadRequiredException.kt diff --git a/app/build.gradle b/app/build.gradle index fcbd422c..8c30e58d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -179,7 +179,8 @@ dependencies { implementation 'com.google.code.gson:gson:2.10.1' //Used for complex/anonymous cases like during development conversions (eg. V8RemoteObject) //JS - implementation("com.caoccao.javet:javet-android:3.0.2") + //implementation("com.caoccao.javet:javet-android:3.0.2") + implementation 'com.caoccao.javet:javet-v8-android:4.1.4' //Exoplayer implementation 'androidx.media3:media3-exoplayer:1.2.1' diff --git a/app/src/main/assets/scripts/source.js b/app/src/main/assets/scripts/source.js index 0638f079..9f38404d 100644 --- a/app/src/main/assets/scripts/source.js +++ b/app/src/main/assets/scripts/source.js @@ -103,6 +103,12 @@ class UnavailableException extends ScriptException { super("UnavailableException", msg); } } +class ReloadRequiredException extends ScriptException { + constructor(msg, reloadData) { + super("ReloadRequiredException", msg); + this.reloadData = reloadData; + } +} class AgeException extends ScriptException { constructor(msg) { super("AgeException", msg); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt index 476bad8a..6ec1eae2 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt @@ -62,6 +62,7 @@ import com.futo.platformplayer.states.StatePlugins import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.time.OffsetDateTime +import java.util.Random import kotlin.Exception import kotlin.reflect.full.findAnnotations import kotlin.reflect.jvm.kotlinFunction @@ -106,6 +107,8 @@ open class JSClient : IPlatformClient { return _busyAction; } + val declareOnEnable = HashMap(); + val settings: HashMap get() = descriptor.settings; val flags: Array; @@ -213,6 +216,10 @@ open class JSClient : IPlatformClient { return plugin.httpClientOthers[id]; } + fun setReloadData(data: String?) { + declareOnEnable.put("__reloadData", data ?: ""); + } + override fun initialize() { if (_initialized) return @@ -263,7 +270,13 @@ open class JSClient : IPlatformClient { fun enable() { if(!_initialized) initialize(); + for(toDeclare in declareOnEnable) { + plugin.execute("var ${toDeclare.key} = " + Json.encodeToString(toDeclare.value)); + } plugin.execute("source.enable(${Json.encodeToString(config)}, parseSettings(${Json.encodeToString(descriptor.getSettingsWithDefaults())}), ${Json.encodeToString(_injectedSaveState)})"); + + if(declareOnEnable.containsKey("__reloadData")) + declareOnEnable.remove("__reloadData"); _enabled = true; } @JSDocs(1, "source.saveState()", "Provide a string that is passed to enable for quicker startup of multiple instances") @@ -735,8 +748,12 @@ open class JSClient : IPlatformClient { } - private fun isBusyWith(actionName: String, handle: ()->T): T { + + fun isBusyWith(actionName: String, handle: ()->T): T { + val busyId = kotlin.random.Random.nextInt(9999); try { + + Logger.v(TAG, "Busy with [${actionName}] (${busyId})") synchronized(_busyLock) { _busyCounter++; } @@ -748,6 +765,7 @@ open class JSClient : IPlatformClient { synchronized(_busyLock) { _busyCounter--; } + Logger.v(TAG, "Busy done [${actionName}] (${busyId})") } } private fun isBusyWith(handle: ()->T): T { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt index ae35207b..2c6d4b35 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt @@ -62,12 +62,16 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS if(_plugin is DevJSClient) result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRaw", false) { _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") { - _obj.invokeString("generate"); + _plugin.isBusyWith("dashAudio.generate") { + _obj.invokeString("generate"); + } } } else result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") { - _obj.invokeString("generate"); + _plugin.isBusyWith("dashAudio.generate") { + _obj.invokeString("generate"); + } } if(result != null){ diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt index d6ff7455..a9c070f7 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt @@ -67,13 +67,17 @@ open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSo if(_plugin is DevJSClient) { result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRawSource.generate()") { _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", { - _obj.invokeString("generate"); + _plugin.isBusyWith("dashVideo.generate") { + _obj.invokeString("generate"); + } }); } } else result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", { - _obj.invokeString("generate"); + _plugin.isBusyWith("dashVideo.generate") { + _obj.invokeString("generate"); + } }); if(result != null){ diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt index 3c76e23d..ee586083 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt @@ -75,9 +75,11 @@ abstract class JSSource { if (!hasRequestExecutor || _obj.isClosed) return null; + Logger.v("JSSource", "Request executor for [${type}] requesting"); val result = V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") { _obj.invoke("getRequestExecutor", arrayOf()); }; + Logger.v("JSSource", "Request executor for [${type}] received"); if (result !is V8ValueObject) return null; diff --git a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt index 15412fd9..96593f48 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt @@ -15,6 +15,7 @@ import com.caoccao.javet.values.primitive.V8ValueString import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient +import com.futo.platformplayer.assume import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.engine.exceptions.NoInternetException import com.futo.platformplayer.engine.exceptions.PluginEngineStoppedException @@ -26,6 +27,7 @@ import com.futo.platformplayer.engine.exceptions.ScriptException import com.futo.platformplayer.engine.exceptions.ScriptExecutionException import com.futo.platformplayer.engine.exceptions.ScriptImplementationException import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException +import com.futo.platformplayer.engine.exceptions.ScriptReloadRequiredException import com.futo.platformplayer.engine.exceptions.ScriptTimeoutException import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException import com.futo.platformplayer.engine.internal.V8Converter @@ -186,6 +188,7 @@ class V8Plugin { Logger.i(TAG, "Stopping plugin [${config.name}]"); isStopped = true; whenNotBusy { + Logger.i(TAG, "Plugin stopping"); synchronized(_runtimeLock) { isStopped = true; @@ -200,7 +203,7 @@ class V8Plugin { _runtime = null; if(!it.isClosed && !it.isDead) { try { - it.close(); + it.close(true); } catch(ex: JavetException) { //In case race conditions are going on, already closed runtimes are fine. @@ -211,6 +214,7 @@ class V8Plugin { Logger.i(TAG, "Stopped plugin [${config.name}]"); }; } + Logger.i(TAG, "Plugin stopped"); onStopped.emit(this); } } @@ -327,26 +331,38 @@ class V8Plugin { throw ScriptCompilationException(config, "Compilation: [${context}]: ${scriptEx.message}\n(${scriptEx.scriptingError.lineNumber})[${scriptEx.scriptingError.startColumn}-${scriptEx.scriptingError.endColumn}]: ${scriptEx.scriptingError.sourceLine}", null, codeStripped); } catch(executeEx: JavetExecutionException) { - if(executeEx.scriptingError?.context?.containsKey("plugin_type") == true) { - val pluginType = executeEx.scriptingError.context["plugin_type"].toString(); + if(executeEx.scriptingError?.context is V8ValueObject) { + val obj = executeEx.scriptingError?.context as V8ValueObject + if(obj.has("plugin_type") == true) { + val pluginType = obj.get("plugin_type").toString(); - //Captcha - if (pluginType == "CaptchaRequiredException") { - throw ScriptCaptchaRequiredException(config, - executeEx.scriptingError.context["url"]?.toString(), - executeEx.scriptingError.context["body"]?.toString(), - executeEx, executeEx.scriptingError?.stack, codeStripped); + //Captcha + if (pluginType == "CaptchaRequiredException") { + throw ScriptCaptchaRequiredException(config, + obj.get("url")?.toString(), + obj.get("body")?.toString(), + executeEx, executeEx.scriptingError?.stack, codeStripped); + } + + //Reload Required + if (pluginType == "ReloadRequiredException") { + throw ScriptReloadRequiredException(config, + obj.get("message")?.toString(), + obj.get("reloadData")?.toString(), + executeEx, executeEx.scriptingError?.stack, codeStripped); + } + + //Others + throwExceptionFromV8( + config, + pluginType, + (extractJSExceptionMessage(executeEx) ?: ""), + executeEx, + executeEx.scriptingError?.stack, + codeStripped + ); } - //Others - throwExceptionFromV8( - config, - pluginType, - (extractJSExceptionMessage(executeEx) ?: ""), - executeEx, - executeEx.scriptingError?.stack, - codeStripped - ); } throw ScriptExecutionException(config, extractJSExceptionMessage(executeEx) ?: "", null, executeEx.scriptingError?.stack, codeStripped); } diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptReloadRequiredException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptReloadRequiredException.kt new file mode 100644 index 00000000..98c0635d --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptReloadRequiredException.kt @@ -0,0 +1,20 @@ +package com.futo.platformplayer.engine.exceptions + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.engine.V8PluginConfig +import com.futo.platformplayer.getOrDefault +import com.futo.platformplayer.getOrThrow + +class ScriptReloadRequiredException(config: IV8PluginConfig, val msg: String?, val reloadData: String?, ex: Exception? = null, stack: String? = null, code: String? = null) : ScriptException(config, msg ?: "ReloadRequired", ex, stack, code) { + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptException { + val contextName = "ScriptReloadRequiredException"; + return ScriptReloadRequiredException(config, + obj.getOrThrow(config, "message", contextName), + obj.getOrDefault(config, "reloadData", contextName, null)); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt index a5f18705..5450c5eb 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt @@ -78,6 +78,13 @@ class PackageBridge : V8Package { return "android"; } + @V8Property + fun supportedFeatures(): Array { + return arrayOf( + "ReloadRequiredException" + ); + } + @V8Property fun supportedContent(): Array { return arrayOf( diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt index aa78a184..0b049ecb 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt @@ -44,6 +44,17 @@ class PackageHttp: V8Package { private val aliveSockets = mutableListOf(); private var _cleanedUp = false; + private val _clients = mutableMapOf() + + fun getClient(id: String?): PackageHttpClient { + if(id == null) + throw IllegalArgumentException("Http client ${id} doesn't exist"); + if(_packageClient.clientId() == id) + return _packageClient; + if(_packageClientAuth.clientId() == id) + return _packageClientAuth; + return _clients.getOrDefault(id, null) ?: throw IllegalArgumentException("Http client ${id} doesn't exist"); + } constructor(plugin: V8Plugin, config: IV8PluginConfig): super(plugin) { _config = config; @@ -112,6 +123,8 @@ class PackageHttp: V8Package { _plugin.registerHttpClient(httpClient); val client = PackageHttpClient(this, httpClient); + _clients.put(client.clientId() ?: "", client); + return client; } @V8Function @@ -246,18 +259,18 @@ class PackageHttp: V8Package { @V8Function fun request(method: String, url: String, headers: MutableMap = HashMap(), useAuth: Boolean = false) : BatchBuilder { - return clientRequest(_package.getDefaultClient(useAuth), method, url, headers); + return clientRequest(_package.getDefaultClient(useAuth).clientId(), method, url, headers); } @V8Function fun requestWithBody(method: String, url: String, body:String, headers: MutableMap = HashMap(), useAuth: Boolean = false) : BatchBuilder { - return clientRequestWithBody(_package.getDefaultClient(useAuth), method, url, body, headers); + return clientRequestWithBody(_package.getDefaultClient(useAuth).clientId(), method, url, body, headers); } @V8Function fun GET(url: String, headers: MutableMap = HashMap(), useAuth: Boolean = false) : BatchBuilder - = clientGET(_package.getDefaultClient(useAuth), url, headers); + = clientGET(_package.getDefaultClient(useAuth).clientId(), url, headers); @V8Function fun POST(url: String, body: String, headers: MutableMap = HashMap(), useAuth: Boolean = false) : BatchBuilder - = clientPOST(_package.getDefaultClient(useAuth), url, body, headers); + = clientPOST(_package.getDefaultClient(useAuth).clientId(), url, body, headers); @V8Function fun DUMMY(): BatchBuilder { @@ -268,21 +281,21 @@ class PackageHttp: V8Package { //Client-specific @V8Function - fun clientRequest(client: PackageHttpClient, method: String, url: String, headers: MutableMap = HashMap()) : BatchBuilder { - _reqs.add(Pair(client, RequestDescriptor(method, url, headers))); + fun clientRequest(clientId: String?, method: String, url: String, headers: MutableMap = HashMap()) : BatchBuilder { + _reqs.add(Pair(_package.getClient(clientId), RequestDescriptor(method, url, headers))); return BatchBuilder(_package, _reqs); } @V8Function - fun clientRequestWithBody(client: PackageHttpClient, method: String, url: String, body:String, headers: MutableMap = HashMap()) : BatchBuilder { - _reqs.add(Pair(client, RequestDescriptor(method, url, headers, body))); + fun clientRequestWithBody(clientId: String?, method: String, url: String, body:String, headers: MutableMap = HashMap()) : BatchBuilder { + _reqs.add(Pair(_package.getClient(clientId), RequestDescriptor(method, url, headers, body))); return BatchBuilder(_package, _reqs); } @V8Function - fun clientGET(client: PackageHttpClient, url: String, headers: MutableMap = HashMap()) : BatchBuilder - = clientRequest(client, "GET", url, headers); + fun clientGET(clientId: String?, url: String, headers: MutableMap = HashMap()) : BatchBuilder + = clientRequest(clientId, "GET", url, headers); @V8Function - fun clientPOST(client: PackageHttpClient, url: String, body: String, headers: MutableMap = HashMap()) : BatchBuilder - = clientRequestWithBody(client, "POST", url, body, headers); + fun clientPOST(clientId: String?, url: String, body: String, headers: MutableMap = HashMap()) : BatchBuilder + = clientRequestWithBody(clientId, "POST", url, body, headers); //Finalizer @@ -321,6 +334,7 @@ class PackageHttp: V8Package { @Transient private val _clientId: String?; + @V8Property fun clientId(): String? { return _clientId; diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index a6241c29..08e04730 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -93,6 +93,7 @@ import com.futo.platformplayer.engine.exceptions.ScriptAgeException import com.futo.platformplayer.engine.exceptions.ScriptException import com.futo.platformplayer.engine.exceptions.ScriptImplementationException import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException +import com.futo.platformplayer.engine.exceptions.ScriptReloadRequiredException import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException import com.futo.platformplayer.exceptions.UnsupportedCastException import com.futo.platformplayer.fixHtmlLinks @@ -608,6 +609,10 @@ class VideoDetailView : ConstraintLayout { } } + _player.onReloadRequired.subscribe { + fetchVideo(); + } + _player.onPlayChanged.subscribe { if (StateCasting.instance.activeDevice == null) { handlePlayChanged(it); @@ -3025,6 +3030,11 @@ class VideoDetailView : ConstraintLayout { return@TaskHandler result; }) .success { setVideoDetails(it, true) } + .exception { + StatePlatform.instance.handleReloadRequired(it, { + fetchVideo(); + }); + } .exception { Logger.w(TAG, "exception", it) diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt index c843ea9f..8cf8f080 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.states import android.content.Context import androidx.collection.LruCache +import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.R import com.futo.platformplayer.Settings import com.futo.platformplayer.UIDialogs @@ -38,6 +39,7 @@ import com.futo.platformplayer.awaitFirstNotNullDeferred import com.futo.platformplayer.constructs.BatchedTaskHandler import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.engine.exceptions.ScriptReloadRequiredException import com.futo.platformplayer.fromPool import com.futo.platformplayer.getNowDiffDays import com.futo.platformplayer.getNowDiffSeconds @@ -316,7 +318,18 @@ class StatePlatform { _platformOrderPersistent.save(); } - suspend fun reloadClient(context: Context, id: String) : JSClient? { + fun handleReloadRequired(reloadRequiredException: ScriptReloadRequiredException, afterReload: (() -> Unit)? = null) { + val id = if(reloadRequiredException.config is SourcePluginConfig) reloadRequiredException.config.id else ""; + UIDialogs.appToast("Reloading [${reloadRequiredException.config.name}] by plugin request"); + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { + if(!reloadRequiredException.reloadData.isNullOrEmpty()) + reEnableClientWithData(id, reloadRequiredException.reloadData, afterReload); + else + reEnableClient(id, afterReload); + } + } + + suspend fun reloadClient(context: Context, id: String, afterReload: (()->Unit)? = null) : JSClient? { return withContext(Dispatchers.IO) { val client = getClient(id); if (client !is JSClient) @@ -347,10 +360,27 @@ class StatePlatform { _availableClients.removeIf { it.id == id }; _availableClients.add(newClient); } + afterReload?.invoke(); return@withContext newClient; }; } + suspend fun reEnableClientWithData(id: String, data: String? = null, afterReload: (()->Unit)? = null) { + val enabledBefore = getEnabledClients().map { it.id }; + if(data != null) { + val client = getClientOrNull(id); + if(client != null && client is JSClient) + client.setReloadData(data); + } + selectClients({ + _scope.launch(Dispatchers.IO) { + selectClients({ + afterReload?.invoke(); + }, *(enabledBefore).distinct().toTypedArray()); + } + }, *(enabledBefore.filter { it != id }).distinct().toTypedArray()) + } + suspend fun reEnableClient(id: String, afterReload: (()->Unit)? = null) = reEnableClientWithData(id, null, afterReload); suspend fun enableClient(ids: List) { val currentClients = getEnabledClients().map { it.id }; @@ -361,6 +391,9 @@ class StatePlatform { * If a client is disabled, NO requests are made to said client */ suspend fun selectClients(vararg ids: String) { + selectClients(null, *ids); + } + suspend fun selectClients(afterLoad: (() -> Unit)?, vararg ids: String) { withContext(Dispatchers.IO) { synchronized(_clientsLock) { val removed = _enabledClients.toMutableList(); @@ -385,6 +418,7 @@ class StatePlatform { onSourceDisabled.emit(oldClient); } } + afterLoad?.invoke(); }; } diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt index 60c5dbf2..ab0bf383 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt @@ -52,10 +52,13 @@ import com.futo.platformplayer.api.media.platforms.js.models.sources.JSDashManif import com.futo.platformplayer.api.media.platforms.js.models.sources.JSHLSManifestAudioSource import com.futo.platformplayer.api.media.platforms.js.models.sources.JSSource import com.futo.platformplayer.api.media.platforms.js.models.sources.JSVideoUrlRangeSource +import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.engine.exceptions.ScriptReloadRequiredException import com.futo.platformplayer.helpers.VideoHelper import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.video.PlayerManager import com.futo.platformplayer.views.video.datasources.PluginMediaDrmCallback import com.futo.platformplayer.views.video.datasources.JSHttpDataSource @@ -108,6 +111,8 @@ abstract class FutoVideoPlayerBase : RelativeLayout { val onPositionDiscontinuity = Event1(); val onDatasourceError = Event1(); + val onReloadRequired = Event0(); + private var _didCallSourceChange = false; private var _lastState: Int = -1; @@ -585,6 +590,12 @@ abstract class FutoVideoPlayerBase : RelativeLayout { } } } + catch(reloadRequired: ScriptReloadRequiredException) { + Logger.i(TAG, "Reload required detected"); + StatePlatform.instance.handleReloadRequired(reloadRequired, { + onReloadRequired.emit(); + }); + } catch(ex: Throwable) { Logger.e(TAG, "DashRaw generator failed", ex); } @@ -677,15 +688,29 @@ abstract class FutoVideoPlayerBase : RelativeLayout { DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT); if(audioSource.hasGenerate) { findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) { - val generated = audioSource.generate(); - if(generated != null) { - withContext(Dispatchers.Main) { - _lastVideoMediaSource = DashMediaSource.Factory(dataSource) - .createMediaSource(DashManifestParser().parse(Uri.parse(audioSource.url), - ByteArrayInputStream(generated?.toByteArray() ?: ByteArray(0)))); - loadSelectedSources(play, resume); + try { + val generated = audioSource.generate(); + if(generated != null) { + withContext(Dispatchers.Main) { + _lastVideoMediaSource = DashMediaSource.Factory(dataSource) + .createMediaSource(DashManifestParser().parse(Uri.parse(audioSource.url), + ByteArrayInputStream(generated?.toByteArray() ?: ByteArray(0)))); + loadSelectedSources(play, resume); + } } } + catch(reloadRequired: ScriptReloadRequiredException) { + Logger.i(TAG, "Reload required detected"); + val plugin = audioSource.getUnderlyingPlugin(); + if(plugin == null) + return@launch; + StatePlatform.instance.reEnableClient(plugin.id, { + onReloadRequired.emit(); + }); + } + catch(ex: Throwable) { + + } } return false; } From bcab3bccbc467d5b48d719ad109e0867c283ad37 Mon Sep 17 00:00:00 2001 From: Koen J Date: Mon, 16 Jun 2025 10:43:57 +0200 Subject: [PATCH 13/39] Fixed crash when signature fields are wrongly populated. --- .../media/platforms/js/SourcePluginConfig.kt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt index a637e89d..e318b5c2 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt @@ -4,6 +4,7 @@ import android.net.Uri import com.futo.platformplayer.SignatureProvider import com.futo.platformplayer.api.media.Serializer import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.matchesDomain import com.futo.platformplayer.states.StatePlugins import kotlinx.serialization.Contextual @@ -168,12 +169,17 @@ class SourcePluginConfig( } fun validate(text: String): Boolean { - if(scriptPublicKey.isNullOrEmpty()) - throw IllegalStateException("No public key present"); - if(scriptSignature.isNullOrEmpty()) - throw IllegalStateException("No signature present"); + try { + if (scriptPublicKey.isNullOrEmpty()) + throw IllegalStateException("No public key present"); + if (scriptSignature.isNullOrEmpty()) + throw IllegalStateException("No signature present"); - return SignatureProvider.verify(text, scriptSignature, scriptPublicKey); + return SignatureProvider.verify(text, scriptSignature, scriptPublicKey); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to verify due to an unhandled exception", e) + return false + } } fun isUrlAllowed(url: String): Boolean { @@ -204,6 +210,8 @@ class SourcePluginConfig( obj.sourceUrl = sourceUrl; return obj; } + + private val TAG = "SourcePluginConfig" } @kotlinx.serialization.Serializable From 2fca7e9a01e6c7e42840463015501032892bd9c2 Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Mon, 16 Jun 2025 14:13:47 +0200 Subject: [PATCH 14/39] Locking of most known v8 interactions, fix returning previously returned jvm objects, Related fixes --- .../api/media/platforms/js/JSClient.kt | 36 +++--- .../api/media/platforms/js/models/JSPager.kt | 38 +++--- .../platforms/js/models/JSPlaybackTracker.kt | 66 +++++++---- .../platforms/js/models/JSRequestExecutor.kt | 110 +++++++++--------- .../platforms/js/models/JSVideoDetails.kt | 4 +- .../platforms/js/models/sources/JSSource.kt | 6 +- .../futo/platformplayer/engine/V8Plugin.kt | 99 +++++++++++++--- .../engine/internal/V8BindObject.kt | 4 +- .../engine/packages/PackageBridge.kt | 25 +++- .../engine/packages/PackageHttp.kt | 20 +++- .../mainactivity/main/VideoDetailView.kt | 4 +- .../views/video/FutoVideoPlayerBase.kt | 5 +- 12 files changed, 274 insertions(+), 143 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt index 6ec1eae2..144faa2c 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt @@ -99,10 +99,8 @@ open class JSClient : IPlatformClient { override val icon: ImageVariable; override var capabilities: PlatformClientCapabilities = PlatformClientCapabilities(); - private val _busyLock = Object(); - private var _busyCounter = 0; private var _busyAction = ""; - val isBusy: Boolean get() = _busyCounter > 0; + val isBusy: Boolean get() = _plugin.isBusy; val isBusyAction: String get() { return _busyAction; } @@ -225,9 +223,12 @@ open class JSClient : IPlatformClient { Logger.i(TAG, "Plugin [${config.name}] initializing"); plugin.start(); + Logger.i(TAG, "Plugin [${config.name}] started"); plugin.execute("plugin.config = ${Json.encodeToString(config)}"); plugin.execute("plugin.settings = parseSettings(${Json.encodeToString(descriptor.getSettingsWithDefaults())})"); + Logger.i(TAG, "Plugin [${config.name}] configs set"); + descriptor.appSettings.loadDefaults(descriptor.config); _initialized = true; @@ -254,6 +255,7 @@ open class JSClient : IPlatformClient { hasGetChannelPlaylists = plugin.executeBoolean("!!source.getChannelPlaylists") ?: false, hasGetContentRecommendations = plugin.executeBoolean("!!source.getContentRecommendations") ?: false ); + Logger.i(TAG, "Plugin [${config.name}] capabilities retrieved"); try { if (capabilities.hasGetChannelTemplateByClaimMap) @@ -565,7 +567,7 @@ open class JSClient : IPlatformClient { Logger.i(TAG, "JSClient.getPlaybackTracker(${url})"); val tracker = plugin.executeTyped("source.getPlaybackTracker(${Json.encodeToString(url)})"); if(tracker is V8ValueObject) - return@isBusyWith JSPlaybackTracker(config, tracker); + return@isBusyWith JSPlaybackTracker(this, tracker); else return@isBusyWith null; } @@ -747,25 +749,23 @@ open class JSClient : IPlatformClient { return urls; } - + fun busy(handle: ()->T): T { + return _plugin.busy { + return@busy handle(); + } + } fun isBusyWith(actionName: String, handle: ()->T): T { - val busyId = kotlin.random.Random.nextInt(9999); - try { + //val busyId = kotlin.random.Random.nextInt(9999); + return busy { + try { + _busyAction = actionName; + return@busy handle(); - Logger.v(TAG, "Busy with [${actionName}] (${busyId})") - synchronized(_busyLock) { - _busyCounter++; } - _busyAction = actionName; - return handle(); - } - finally { - _busyAction = ""; - synchronized(_busyLock) { - _busyCounter--; + finally { + _busyAction = ""; } - Logger.v(TAG, "Busy done [${actionName}] (${busyId})") } } private fun isBusyWith(handle: ()->T): T { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPager.kt index 8782b742..e81a288d 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPager.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPager.kt @@ -29,7 +29,9 @@ abstract class JSPager : IPager { this.pager = pager; this.config = config; - _hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false; + plugin.busy { + _hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false; + } getResults(); } @@ -44,11 +46,14 @@ abstract class JSPager : IPager { override fun nextPage() { warnIfMainThread("JSPager.nextPage"); - pager = plugin.getUnderlyingPlugin().catchScriptErrors("[${plugin.config.name}] JSPager", "pager.nextPage()") { - pager.invoke("nextPage", arrayOf()); - }; - _hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false; - _resultChanged = true; + val pluginV8 = plugin.getUnderlyingPlugin(); + pluginV8.busy { + pager = pluginV8.catchScriptErrors("[${plugin.config.name}] JSPager", "pager.nextPage()") { + pager.invoke("nextPage", arrayOf()); + }; + _hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false; + _resultChanged = true; + } /* try { } @@ -70,15 +75,18 @@ abstract class JSPager : IPager { return previousResults; warnIfMainThread("JSPager.getResults"); - val items = pager.getOrThrow(config, "results", "JSPager"); - if(items.v8Runtime.isDead || items.v8Runtime.isClosed) - throw IllegalStateException("Runtime closed"); - val newResults = items.toArray() - .map { convertResult(it as V8ValueObject) } - .toList(); - _lastResults = newResults; - _resultChanged = false; - return newResults; + + return plugin.getUnderlyingPlugin().busy { + val items = pager.getOrThrow(config, "results", "JSPager"); + if (items.v8Runtime.isDead || items.v8Runtime.isClosed) + throw IllegalStateException("Runtime closed"); + val newResults = items.toArray() + .map { convertResult(it as V8ValueObject) } + .toList(); + _lastResults = newResults; + _resultChanged = false; + return@busy newResults; + } } abstract fun convertResult(obj: V8ValueObject): T; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaybackTracker.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaybackTracker.kt index e5ee7b68..15a7d854 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaybackTracker.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaybackTracker.kt @@ -2,37 +2,50 @@ package com.futo.platformplayer.api.media.platforms.js.models import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker +import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.engine.IV8PluginConfig import com.futo.platformplayer.engine.exceptions.ScriptImplementationException import com.futo.platformplayer.getOrThrow import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.warnIfMainThread class JSPlaybackTracker: IPlaybackTracker { - private val _config: IV8PluginConfig; - private val _obj: V8ValueObject; + private lateinit var _client: JSClient; + private lateinit var _config: IV8PluginConfig; + private lateinit var _obj: V8ValueObject; private var _hasCalledInit: Boolean = false; - private val _hasInit: Boolean; + private var _hasInit: Boolean = false; private var _lastRequest: Long = Long.MIN_VALUE; - private val _hasOnConcluded: Boolean; + private var _hasOnConcluded: Boolean = false; override var nextRequest: Int = 1000 private set; - constructor(config: IV8PluginConfig, obj: V8ValueObject) { + constructor(client: JSClient, obj: V8ValueObject) { warnIfMainThread("JSPlaybackTracker.constructor"); - if(!obj.has("onProgress")) - throw ScriptImplementationException(config, "Missing onProgress on PlaybackTracker"); - if(!obj.has("nextRequest")) - throw ScriptImplementationException(config, "Missing nextRequest on PlaybackTracker"); - _hasOnConcluded = obj.has("onConcluded"); - this._config = config; - this._obj = obj; - this._hasInit = obj.has("onInit"); + client.busy { + if (!obj.has("onProgress")) + throw ScriptImplementationException( + client.config, + "Missing onProgress on PlaybackTracker" + ); + if (!obj.has("nextRequest")) + throw ScriptImplementationException( + client.config, + "Missing nextRequest on PlaybackTracker" + ); + _hasOnConcluded = obj.has("onConcluded"); + + this._client = client; + this._config = client.config; + this._obj = obj; + this._hasInit = obj.has("onInit"); + } } override fun onInit(seconds: Double) { @@ -40,12 +53,15 @@ class JSPlaybackTracker: IPlaybackTracker { synchronized(_obj) { if(_hasCalledInit) return; - if (_hasInit) { - Logger.i("JSPlaybackTracker", "onInit (${seconds})"); - _obj.invokeVoid("onInit", seconds); + + _client.busy { + if (_hasInit) { + Logger.i("JSPlaybackTracker", "onInit (${seconds})"); + _obj.invokeVoid("onInit", seconds); + } + nextRequest = Math.max(100, _obj.getOrThrow(_config, "nextRequest", "PlaybackTracker", false)); + _hasCalledInit = true; } - nextRequest = Math.max(100, _obj.getOrThrow(_config, "nextRequest", "PlaybackTracker", false)); - _hasCalledInit = true; } } @@ -55,10 +71,12 @@ class JSPlaybackTracker: IPlaybackTracker { if(!_hasCalledInit && _hasInit) onInit(seconds); else { - Logger.i("JSPlaybackTracker", "onProgress (${seconds}, ${isPlaying})"); - _obj.invokeVoid("onProgress", Math.floor(seconds), isPlaying); - nextRequest = Math.max(100, _obj.getOrThrow(_config, "nextRequest", "PlaybackTracker", false)); - _lastRequest = System.currentTimeMillis(); + _client.busy { + Logger.i("JSPlaybackTracker", "onProgress (${seconds}, ${isPlaying})"); + _obj.invokeVoid("onProgress", Math.floor(seconds), isPlaying); + nextRequest = Math.max(100, _obj.getOrThrow(_config, "nextRequest", "PlaybackTracker", false)); + _lastRequest = System.currentTimeMillis(); + } } } } @@ -67,7 +85,9 @@ class JSPlaybackTracker: IPlaybackTracker { if(_hasOnConcluded) { synchronized(_obj) { Logger.i("JSPlaybackTracker", "onConcluded"); - _obj.invokeVoid("onConcluded", -1); + _client.busy { + _obj.invokeVoid("onConcluded", -1); + } } } } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestExecutor.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestExecutor.kt index 70dfecfd..36cfc7db 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestExecutor.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestExecutor.kt @@ -46,16 +46,18 @@ class JSRequestExecutor { if (_executor.isClosed) throw IllegalStateException("Executor object is closed"); - val result = if(_plugin is DevJSClient) - StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") { - V8Plugin.catchScriptErrors( - _config, - "[${_config.name}] JSRequestExecutor", - "builder.modifyRequest()" - ) { - _executor.invoke("executeRequest", url, headers, method, body); - } as V8Value; - } + return _plugin.getUnderlyingPlugin().busy { + + val result = if(_plugin is DevJSClient) + StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") { + V8Plugin.catchScriptErrors( + _config, + "[${_config.name}] JSRequestExecutor", + "builder.modifyRequest()" + ) { + _executor.invoke("executeRequest", url, headers, method, body); + } as V8Value; + } else V8Plugin.catchScriptErrors( _config, "[${_config.name}] JSRequestExecutor", @@ -64,34 +66,35 @@ class JSRequestExecutor { _executor.invoke("executeRequest", url, headers, method, body); } as V8Value; - try { - if(result is V8ValueString) { - val base64Result = Base64.getDecoder().decode(result.value); - return base64Result; - } - if(result is V8ValueTypedArray) { - val buffer = result.buffer; - val byteBuffer = buffer.byteBuffer; - val bytesResult = ByteArray(result.byteLength); - byteBuffer.get(bytesResult, 0, result.byteLength); - buffer.close(); - return bytesResult; - } - if(result is V8ValueObject && result.has("type")) { - val type = result.getOrThrow(_config, "type", "JSRequestModifier"); - when(type) { - //TODO: Buffer type? + try { + if(result is V8ValueString) { + val base64Result = Base64.getDecoder().decode(result.value); + return@busy base64Result; } + if(result is V8ValueTypedArray) { + val buffer = result.buffer; + val byteBuffer = buffer.byteBuffer; + val bytesResult = ByteArray(result.byteLength); + byteBuffer.get(bytesResult, 0, result.byteLength); + buffer.close(); + return@busy bytesResult; + } + if(result is V8ValueObject && result.has("type")) { + val type = result.getOrThrow(_config, "type", "JSRequestModifier"); + when(type) { + //TODO: Buffer type? + } + } + if(result is V8ValueUndefined) { + if(_plugin is DevJSClient) + StateDeveloper.instance.logDevException(_plugin.devID, "JSRequestExecutor.executeRequest returned illegal undefined"); + throw ScriptImplementationException(_config, "JSRequestExecutor.executeRequest returned illegal undefined", null); + } + throw NotImplementedError("Executor result type not implemented? " + result.javaClass.name); } - if(result is V8ValueUndefined) { - if(_plugin is DevJSClient) - StateDeveloper.instance.logDevException(_plugin.devID, "JSRequestExecutor.executeRequest returned illegal undefined"); - throw ScriptImplementationException(_config, "JSRequestExecutor.executeRequest returned illegal undefined", null); + finally { + result.close(); } - throw NotImplementedError("Executor result type not implemented? " + result.javaClass.name); - } - finally { - result.close(); } } @@ -99,24 +102,25 @@ class JSRequestExecutor { open fun cleanup() { if (!hasCleanup || _executor.isClosed) return; - - if(_plugin is DevJSClient) - StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") { - V8Plugin.catchScriptErrors( - _config, - "[${_config.name}] JSRequestExecutor", - "builder.modifyRequest()" - ) { - _executor.invokeVoid("cleanup", null); - }; - } - else V8Plugin.catchScriptErrors( - _config, - "[${_config.name}] JSRequestExecutor", - "builder.modifyRequest()" - ) { - _executor.invokeVoid("cleanup", null); - }; + _plugin.busy { + if(_plugin is DevJSClient) + StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") { + V8Plugin.catchScriptErrors( + _config, + "[${_config.name}] JSRequestExecutor", + "builder.modifyRequest()" + ) { + _executor.invokeVoid("cleanup", null); + }; + } + else V8Plugin.catchScriptErrors( + _config, + "[${_config.name}] JSRequestExecutor", + "builder.modifyRequest()" + ) { + _executor.invokeVoid("cleanup", null); + }; + } } protected fun finalize() { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt index da495498..799c737f 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt @@ -27,6 +27,7 @@ import com.futo.platformplayer.getOrThrowNullable import com.futo.platformplayer.states.StateDeveloper class JSVideoDetails : JSVideo, IPlatformVideoDetails { + private val _plugin: JSClient; private val _hasGetComments: Boolean; private val _hasGetContentRecommendations: Boolean; private val _hasGetPlaybackTracker: Boolean; @@ -48,6 +49,7 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails { constructor(plugin: JSClient, obj: V8ValueObject) : super(plugin.config, obj) { val contextName = "VideoDetails"; + _plugin = plugin; val config = plugin.config; description = _content.getOrThrow(config, "description", contextName); video = JSVideoSourceDescriptor.fromV8(plugin, _content.getOrThrow(config, "video", contextName)); @@ -86,7 +88,7 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails { val tracker = _content.invoke("getPlaybackTracker", arrayOf()) ?: return@catchScriptErrors null; if(tracker is V8ValueObject) - return@catchScriptErrors JSPlaybackTracker(_pluginConfig, tracker); + return@catchScriptErrors JSPlaybackTracker(_plugin, tracker); else return@catchScriptErrors null; }; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt index ee586083..649c74cf 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt @@ -77,7 +77,11 @@ abstract class JSSource { Logger.v("JSSource", "Request executor for [${type}] requesting"); val result = V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") { - _obj.invoke("getRequestExecutor", arrayOf()); + _plugin.isBusyWith("getRequestExecutor") { + _plugin.getUnderlyingPlugin().busy { + _obj.invoke("getRequestExecutor", arrayOf()); + } + } }; Logger.v("JSSource", "Request executor for [${type}] received"); diff --git a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt index 96593f48..462fb192 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt @@ -1,9 +1,11 @@ package com.futo.platformplayer.engine import android.content.Context +import com.caoccao.javet.entities.JavetEntityError import com.caoccao.javet.exceptions.JavetCompilationException import com.caoccao.javet.exceptions.JavetException import com.caoccao.javet.exceptions.JavetExecutionException +import com.caoccao.javet.interfaces.IJavetEntityError import com.caoccao.javet.interop.V8Host import com.caoccao.javet.interop.V8Runtime import com.caoccao.javet.interop.options.V8Flags @@ -42,6 +44,9 @@ import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StateAssets import com.futo.platformplayer.warnIfMainThread import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Semaphore +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock class V8Plugin { val config: IV8PluginConfig; @@ -70,9 +75,10 @@ class V8Plugin { val onStopped = Event1(); //TODO: Implement a more universal isBusy system for plugins + JSClient + pooling? TBD if propagation would be beneficial - private val _busyCounterLock = Object(); - private var _busyCounter = 0; - val isBusy get() = synchronized(_busyCounterLock) { _busyCounter > 0 }; + //private val _busyCounterLock = Object(); + //private var _busyCounter = 0; + private val _busyLock = ReentrantLock()//Semaphore(1); + val isBusy get() = _busyLock.isLocked;//synchronized(_busyCounterLock) { _busyCounter > 0 }; var allowDevSubmit: Boolean = false private set(value) { @@ -146,14 +152,19 @@ class V8Plugin { val host = V8Host.getV8Instance(); val options = host.jsRuntimeType.getRuntimeOptions(); + Logger.i(TAG, "Plugin [${config.name}] start: Creating runtime") + _runtime = host.createV8Runtime(options); if (!host.isIsolateCreated) throw IllegalStateException("Isolate not created"); + Logger.i(TAG, "Plugin [${config.name}] start: Created runtime") + //Setup bridge _runtime?.let { it.converter = V8Converter(); + Logger.i(TAG, "Plugin [${config.name}] start: Loading packages") for (pack in _depsPackages) { if (pack.variableName != null) it.createV8ValueObject().use { v8valueObject -> @@ -166,6 +177,8 @@ class V8Plugin { } } + Logger.i(TAG, "Plugin [${config.name}] start: Loading deps") + //Load deps for (dep in _deps) catchScriptErrors("Dep[${dep.key}]") { @@ -176,20 +189,23 @@ class V8Plugin { if (config.allowEval) it.allowEval(true); + Logger.i(TAG, "Plugin [${config.name}] start: Loading script") //Load plugin catchScriptErrors("Plugin[${config.name}]") { it.getExecutor(script).executeVoid() }; isStopped = false; + Logger.i(TAG, "Plugin [${config.name}] start: Script loaded") } } } fun stop(){ Logger.i(TAG, "Stopping plugin [${config.name}]"); - isStopped = true; - whenNotBusy { + busy { Logger.i(TAG, "Plugin stopping"); synchronized(_runtimeLock) { + if(isStopped) + return@busy; isStopped = true; //Cleanup http @@ -203,7 +219,7 @@ class V8Plugin { _runtime = null; if(!it.isClosed && !it.isDead) { try { - it.close(true); + it.close(); } catch(ex: JavetException) { //In case race conditions are going on, already closed runtimes are fine. @@ -219,6 +235,12 @@ class V8Plugin { } } + fun busy(handle: ()->T): T { + _busyLock.withLock { + //Logger.i(TAG, "Entered busy: " + Thread.currentThread().stackTrace.drop(3)?.firstOrNull()?.toString() + ", " + Thread.currentThread().stackTrace.drop(4)?.firstOrNull()?.toString()); + return handle(); + } + } fun execute(js: String) : V8Value { return executeTyped(js); } @@ -227,6 +249,14 @@ class V8Plugin { if(isStopped) throw PluginEngineStoppedException(config, "Instance is stopped", js); + return busy { + + val runtime = _runtime ?: throw IllegalStateException("JSPlugin not started yet"); + return@busy catchScriptErrors("Plugin[${config.name}]", js) { + runtime.getExecutor(js).execute() + }; + } + /* synchronized(_busyCounterLock) { _busyCounter++; } @@ -249,11 +279,26 @@ class V8Plugin { _busyCounter--; } } + */ } - fun executeBoolean(js: String) : Boolean? = catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value }; - fun executeString(js: String) : String? = catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value }; - fun executeInteger(js: String) : Int? = catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value }; + fun executeBoolean(js: String) : Boolean? = busy { catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value } } + fun executeString(js: String) : String? = busy { catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value } } + fun executeInteger(js: String) : Int? = busy { catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value } } + /* + fun whenNotBusyBlocking(handler: (V8Plugin)->T): T { + while(true) { + synchronized(_busyCounterLock) { + if(_busyCounter == 0) + { + return handler(this); + } + } + Thread.sleep(1); + } + } + */ + /* fun whenNotBusy(handler: (V8Plugin)->Unit) { synchronized(_busyCounterLock) { if(_busyCounter == 0) @@ -264,12 +309,25 @@ class V8Plugin { if(it == 0) { Logger.w(TAG, "V8Plugin afterBusy handled"); afterBusy.remove(tag); - handler(this); + + var failed = false; + synchronized(_busyCounterLock) { + if(_busyCounter > 0) { + failed = true; + return@synchronized + } + handler(this); + } + if(failed) + busy { + handler(this); + } } } } } } + */ private fun getPackage(packageName: String, allowNull: Boolean = false): V8Package? { //TODO: Auto get all package types? @@ -331,24 +389,29 @@ class V8Plugin { throw ScriptCompilationException(config, "Compilation: [${context}]: ${scriptEx.message}\n(${scriptEx.scriptingError.lineNumber})[${scriptEx.scriptingError.startColumn}-${scriptEx.scriptingError.endColumn}]: ${scriptEx.scriptingError.sourceLine}", null, codeStripped); } catch(executeEx: JavetExecutionException) { - if(executeEx.scriptingError?.context is V8ValueObject) { - val obj = executeEx.scriptingError?.context as V8ValueObject - if(obj.has("plugin_type") == true) { - val pluginType = obj.get("plugin_type").toString(); + if(executeEx.scriptingError?.context is IJavetEntityError) { + val obj = executeEx.scriptingError?.context as IJavetEntityError + if(obj.context.containsKey("plugin_type") == true) { + val pluginType = obj.context["plugin_type"].toString(); + //val pluginType = obj.get("plugin_type").toString(); //Captcha if (pluginType == "CaptchaRequiredException") { throw ScriptCaptchaRequiredException(config, - obj.get("url")?.toString(), - obj.get("body")?.toString(), + obj.context["url"]?.toString(), + obj.context["body"]?.toString(), + //obj.get("url")?.toString(), + //obj.get("body")?.toString(), executeEx, executeEx.scriptingError?.stack, codeStripped); } //Reload Required if (pluginType == "ReloadRequiredException") { throw ScriptReloadRequiredException(config, - obj.get("message")?.toString(), - obj.get("reloadData")?.toString(), + obj.context["msg"]?.toString(), + obj.context["reloadData"]?.toString(), + //obj.get("message")?.toString(), + //obj.get("reloadData")?.toString(), executeEx, executeEx.scriptingError?.stack, codeStripped); } diff --git a/app/src/main/java/com/futo/platformplayer/engine/internal/V8BindObject.kt b/app/src/main/java/com/futo/platformplayer/engine/internal/V8BindObject.kt index 4e861b72..fd30af6f 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/internal/V8BindObject.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/internal/V8BindObject.kt @@ -13,8 +13,8 @@ open class V8BindObject : IV8Convertable { override fun toV8(runtime: V8Runtime): V8Value? { synchronized(this) { - if(_runtimeObj != null) - return _runtimeObj; + //if(_runtimeObj != null) + // return _runtimeObj; val v8Obj = runtime.createV8ValueObject(); v8Obj.bind(this); diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt index 5450c5eb..b4cc821d 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt @@ -4,6 +4,7 @@ import android.media.MediaCodec import android.media.MediaCodecList import com.caoccao.javet.annotations.V8Function import com.caoccao.javet.annotations.V8Property +import com.caoccao.javet.interop.callback.JavetCallbackContext import com.caoccao.javet.utils.JavetResourceUtils import com.caoccao.javet.values.V8Value import com.caoccao.javet.values.reference.V8ValueFunction @@ -112,28 +113,42 @@ class PackageBridge : V8Package { @V8Function fun setTimeout(func: V8ValueFunction, timeout: Long): Int { val id = timeoutCounter++; - val funcClone = func.toClone() StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { delay(timeout); + if(_plugin.isStopped) + return@launch; synchronized(timeoutMap) { if(!timeoutMap.contains(id)) { - JavetResourceUtils.safeClose(funcClone); + _plugin.busy { + if(!_plugin.isStopped) + JavetResourceUtils.safeClose(funcClone); + } return@launch; } timeoutMap.remove(id); } try { - _plugin.whenNotBusy { - funcClone.callVoid(null, arrayOf()); + Logger.v(TAG, "Timeout started [${id}]"); + _plugin.busy { + Logger.v(TAG, "Timeout call started [${id}]"); + if(!_plugin.isStopped) + funcClone.callVoid(null, arrayOf()); + Logger.v(TAG, "Timeout call ended [${id}]"); } + Logger.v(TAG, "Timeout resolved [${id}]"); } catch(ex: Throwable) { Logger.e(TAG, "Failed timeout callback", ex); } finally { - JavetResourceUtils.safeClose(funcClone); + _plugin.busy { + if(!_plugin.isStopped) + JavetResourceUtils.safeClose(funcClone); + } + //_plugin.whenNotBusy { + //} } }; synchronized(timeoutMap) { diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt index 0b049ecb..2930a476 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt @@ -656,7 +656,9 @@ class PackageHttp: V8Package { _isOpen = true; if(hasOpen && _listeners?.isClosed != true) { try { - _listeners?.invokeVoid("open", arrayOf()); + _package._plugin.busy { + _listeners?.invokeVoid("open", arrayOf()); + } } catch(ex: Throwable){ Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] open failed: " + ex.message, ex); @@ -666,7 +668,9 @@ class PackageHttp: V8Package { override fun message(msg: String) { if(hasMessage && _listeners?.isClosed != true) { try { - _listeners?.invokeVoid("message", msg); + _package._plugin.busy { + _listeners?.invokeVoid("message", msg); + } } catch(ex: Throwable) {} } @@ -675,7 +679,9 @@ class PackageHttp: V8Package { if(hasClosing && _listeners?.isClosed != true) { try { - _listeners?.invokeVoid("closing", code, reason); + _package._plugin.busy { + _listeners?.invokeVoid("closing", code, reason); + } } catch(ex: Throwable){ Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closing failed: " + ex.message, ex); @@ -686,7 +692,9 @@ class PackageHttp: V8Package { _isOpen = false; if(hasClosed && _listeners?.isClosed != true) { try { - _listeners?.invokeVoid("closed", code, reason); + _package._plugin.busy { + _listeners?.invokeVoid("closed", code, reason); + } } catch(ex: Throwable){ Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closed failed: " + ex.message, ex); @@ -702,7 +710,9 @@ class PackageHttp: V8Package { Logger.e(TAG, "Websocket failure: ${exception.message} (${_url})", exception); if(hasFailure && _listeners?.isClosed != true) { try { - _listeners?.invokeVoid("failure", exception.message); + _package._plugin.busy { + _listeners?.invokeVoid("failure", exception.message); + } } catch(ex: Throwable){ Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closed failed: " + ex.message, ex); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index 08e04730..452af019 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -2497,7 +2497,9 @@ class VideoDetailView : ConstraintLayout { val url = _url; if (!url.isNullOrBlank()) { - setLoading(true); + fragment.lifecycleScope.launch(Dispatchers.Main) { + setLoading(true); + } _taskLoadVideo.run(url); } } diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt index ab0bf383..b0afd83f 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt @@ -570,7 +570,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout { if (generated != null) { withContext(Dispatchers.Main) { val dataSource = if(videoSource is JSSource && (videoSource.requiresCustomDatasource)) - videoSource.getHttpDataSourceFactory() + withContext(Dispatchers.IO) { videoSource.getHttpDataSourceFactory() } else DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT); @@ -593,6 +593,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout { catch(reloadRequired: ScriptReloadRequiredException) { Logger.i(TAG, "Reload required detected"); StatePlatform.instance.handleReloadRequired(reloadRequired, { + Logger.i(TAG, "ReloadRequired started reloading video"); onReloadRequired.emit(); }); } @@ -704,9 +705,11 @@ abstract class FutoVideoPlayerBase : RelativeLayout { val plugin = audioSource.getUnderlyingPlugin(); if(plugin == null) return@launch; + /* StatePlatform.instance.reEnableClient(plugin.id, { onReloadRequired.emit(); }); + */ } catch(ex: Throwable) { From 86bd71b89c2e0f21d893baf87a113cdada86201f Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Mon, 16 Jun 2025 14:19:23 +0200 Subject: [PATCH 15/39] Fix edgecase --- .../main/java/com/futo/platformplayer/engine/V8Plugin.kt | 4 ++++ .../futo/platformplayer/views/video/FutoVideoPlayerBase.kt | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt index 462fb192..5b6ffd91 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt @@ -58,6 +58,8 @@ class V8Plugin { val httpClientAuth: ManagedHttpClient get() = _clientAuth; val httpClientOthers: Map get() = _clientOthers; + var startId: Int = 0; + fun registerHttpClient(client: JSHttpClient) { synchronized(_clientOthers) { _clientOthers.put(client.clientId, client); @@ -148,6 +150,7 @@ class V8Plugin { synchronized(_runtimeLock) { if (_runtime != null) return; + startId + 1; //V8RuntimeOptions.V8_FLAGS.setUseStrict(true); val host = V8Host.getV8Instance(); val options = host.jsRuntimeType.getRuntimeOptions(); @@ -207,6 +210,7 @@ class V8Plugin { if(isStopped) return@busy; isStopped = true; + startId = -1; //Cleanup http for(pack in _depsPackages) { diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt index b0afd83f..22650adb 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt @@ -593,7 +593,6 @@ abstract class FutoVideoPlayerBase : RelativeLayout { catch(reloadRequired: ScriptReloadRequiredException) { Logger.i(TAG, "Reload required detected"); StatePlatform.instance.handleReloadRequired(reloadRequired, { - Logger.i(TAG, "ReloadRequired started reloading video"); onReloadRequired.emit(); }); } @@ -689,7 +688,9 @@ abstract class FutoVideoPlayerBase : RelativeLayout { DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT); if(audioSource.hasGenerate) { findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) { + var startId = -1; try { + startId = audioSource.getUnderlyingPlugin()?.getUnderlyingPlugin()?.startId ?: -1; val generated = audioSource.generate(); if(generated != null) { withContext(Dispatchers.Main) { @@ -705,11 +706,11 @@ abstract class FutoVideoPlayerBase : RelativeLayout { val plugin = audioSource.getUnderlyingPlugin(); if(plugin == null) return@launch; - /* + if(startId != -1 && plugin.getUnderlyingPlugin()?.startId != startId) + return@launch; StatePlatform.instance.reEnableClient(plugin.id, { onReloadRequired.emit(); }); - */ } catch(ex: Throwable) { From b3f9de3b832726cd231a9ed6041ae2dea1c6edc6 Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Mon, 16 Jun 2025 14:23:34 +0200 Subject: [PATCH 16/39] edgecase fix --- .../futo/platformplayer/views/video/FutoVideoPlayerBase.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt index 22650adb..b5e29075 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt @@ -565,7 +565,9 @@ abstract class FutoVideoPlayerBase : RelativeLayout { if(videoSource.hasGenerate) { findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) { + var startId = -1; try { + startId = videoSource?.getUnderlyingPlugin()?.getUnderlyingPlugin()?.startId ?: -1; val generated = videoSource.generate(); if (generated != null) { withContext(Dispatchers.Main) { @@ -592,6 +594,11 @@ abstract class FutoVideoPlayerBase : RelativeLayout { } catch(reloadRequired: ScriptReloadRequiredException) { Logger.i(TAG, "Reload required detected"); + val plugin = videoSource.getUnderlyingPlugin(); + if(plugin == null) + return@launch; + if(startId != -1 && plugin.getUnderlyingPlugin()?.startId != startId) + return@launch; StatePlatform.instance.handleReloadRequired(reloadRequired, { onReloadRequired.emit(); }); From ff531b5e77b3ef12d80862ce2a5ce17ad3b6e0b3 Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Mon, 16 Jun 2025 17:46:00 +0200 Subject: [PATCH 17/39] Cleanup, fixes, clearCookies support on httpClients --- .../api/media/platforms/js/DevJSClient.kt | 1 + .../api/media/platforms/js/JSClient.kt | 27 ++++- .../platforms/js/internal/JSHttpClient.kt | 21 +++- .../platforms/js/models/JSRequestModifier.kt | 25 +++-- .../platforms/js/models/sources/JSSource.kt | 21 ++-- .../futo/platformplayer/engine/V8Plugin.kt | 105 +----------------- .../engine/packages/PackageBridge.kt | 9 +- .../engine/packages/PackageHttp.kt | 11 ++ .../views/video/FutoVideoPlayerBase.kt | 20 ++-- 9 files changed, 99 insertions(+), 141 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt index b26abe45..1f29bf2a 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt @@ -56,6 +56,7 @@ class DevJSClient : JSClient { override fun getCopy(privateCopy: Boolean, noSaveState: Boolean): JSClient { val client = DevJSClient(_context, descriptor, _script, if(!privateCopy) _auth else null, _captcha, if (noSaveState) null else saveState(), devID); + client.setReloadData(getReloadData(true)); if (noSaveState) client.initialize() return client diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt index 144faa2c..4422dd28 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt @@ -84,6 +84,8 @@ open class JSClient : IPlatformClient { private var _channelCapabilities: ResultCapabilities? = null; private var _peekChannelTypes: List? = null; + private var _usedReloadData: String? = null; + protected val _script: String; private var _initialized: Boolean = false; @@ -198,6 +200,7 @@ open class JSClient : IPlatformClient { open fun getCopy(withoutCredentials: Boolean = false, noSaveState: Boolean = false): JSClient { val client = JSClient(_context, descriptor, if (noSaveState) null else saveState(), _script, withoutCredentials); + client.setReloadData(getReloadData(true)); if (noSaveState) client.initialize() return client @@ -215,19 +218,29 @@ open class JSClient : IPlatformClient { } fun setReloadData(data: String?) { - declareOnEnable.put("__reloadData", data ?: ""); + if(data == null) { + if(declareOnEnable.containsKey("__reloadData")) + declareOnEnable.remove("__reloadData"); + } + else + declareOnEnable.put("__reloadData", data ?: ""); + } + fun getReloadData(orLast: Boolean): String? { + if(declareOnEnable.containsKey("__reloadData")) + return declareOnEnable["__reloadData"]; + else if(orLast) + return _usedReloadData; + return null; } override fun initialize() { if (_initialized) return - Logger.i(TAG, "Plugin [${config.name}] initializing"); plugin.start(); - Logger.i(TAG, "Plugin [${config.name}] started"); + plugin.execute("plugin.config = ${Json.encodeToString(config)}"); plugin.execute("plugin.settings = parseSettings(${Json.encodeToString(descriptor.getSettingsWithDefaults())})"); - Logger.i(TAG, "Plugin [${config.name}] configs set"); descriptor.appSettings.loadDefaults(descriptor.config); @@ -255,7 +268,6 @@ open class JSClient : IPlatformClient { hasGetChannelPlaylists = plugin.executeBoolean("!!source.getChannelPlaylists") ?: false, hasGetContentRecommendations = plugin.executeBoolean("!!source.getContentRecommendations") ?: false ); - Logger.i(TAG, "Plugin [${config.name}] capabilities retrieved"); try { if (capabilities.hasGetChannelTemplateByClaimMap) @@ -277,8 +289,11 @@ open class JSClient : IPlatformClient { } plugin.execute("source.enable(${Json.encodeToString(config)}, parseSettings(${Json.encodeToString(descriptor.getSettingsWithDefaults())}), ${Json.encodeToString(_injectedSaveState)})"); - if(declareOnEnable.containsKey("__reloadData")) + if(declareOnEnable.containsKey("__reloadData")) { + Logger.i(TAG, "Plugin [${config.name}] enabled with reload data: ${declareOnEnable["__reloadData"]}"); + _usedReloadData = declareOnEnable["__reloadData"]; declareOnEnable.remove("__reloadData"); + } _enabled = true; } @JSDocs(1, "source.saveState()", "Provide a string that is passed to enable for quicker startup of multiple instances") diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt index eec4414a..03c5c2c6 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt @@ -67,9 +67,28 @@ class JSHttpClient : ManagedHttpClient { } + fun resetAuthCookies() { + _currentCookieMap.clear(); + if(!_auth?.cookieMap.isNullOrEmpty()) { + for(domainCookies in _auth!!.cookieMap!!) + _currentCookieMap.put(domainCookies.key, HashMap(domainCookies.value)); + } + if(!_captcha?.cookieMap.isNullOrEmpty()) { + for(domainCookies in _captcha!!.cookieMap!!) { + if(_currentCookieMap.containsKey(domainCookies.key)) + _currentCookieMap[domainCookies.key]?.putAll(domainCookies.value); + else + _currentCookieMap.put(domainCookies.key, HashMap(domainCookies.value)); + } + } + } + fun clearOtherCookies() { + _otherCookieMap.clear(); + } + override fun clone(): ManagedHttpClient { val newClient = JSHttpClient(_jsClient, _auth); - //newClient._currentCookieMap = HashMap(_currentCookieMap.toList().associate { Pair(it.first, HashMap(it.second)) }) + newClient._currentCookieMap = HashMap(_currentCookieMap.toList().associate { Pair(it.first, HashMap(it.second)) }) return newClient; } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestModifier.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestModifier.kt index 150189e7..f7d169af 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestModifier.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestModifier.kt @@ -16,7 +16,7 @@ class JSRequestModifier: IRequestModifier { private val _plugin: JSClient; private val _config: IV8PluginConfig; private var _modifier: V8ValueObject; - override var allowByteSkip: Boolean; + override var allowByteSkip: Boolean = false; constructor(plugin: JSClient, modifier: V8ValueObject) { this._plugin = plugin; @@ -24,10 +24,13 @@ class JSRequestModifier: IRequestModifier { this._config = plugin.config; val config = plugin.config; - allowByteSkip = modifier.getOrNull(config, "allowByteSkip", "JSRequestModifier") ?: true; + plugin.busy { + allowByteSkip = modifier.getOrNull(config, "allowByteSkip", "JSRequestModifier") ?: true; + + if(!modifier.has("modifyRequest")) + throw ScriptImplementationException(config, "RequestModifier is missing modifyRequest", null); + } - if(!modifier.has("modifyRequest")) - throw ScriptImplementationException(config, "RequestModifier is missing modifyRequest", null); } override fun modifyRequest(url: String, headers: Map): IRequest { @@ -35,13 +38,15 @@ class JSRequestModifier: IRequestModifier { return Request(url, headers); } - val result = V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSRequestModifier", "builder.modifyRequest()") { - _modifier.invoke("modifyRequest", url, headers); - } as V8ValueObject; + return _plugin.busy { + val result = V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSRequestModifier", "builder.modifyRequest()") { + _modifier.invoke("modifyRequest", url, headers); + } as V8ValueObject; - val req = JSRequest(_plugin, result, url, headers); - result.close(); - return req; + val req = JSRequest(_plugin, result, url, headers); + result.close(); + return@busy req; + } } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt index 649c74cf..00906239 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt @@ -62,9 +62,11 @@ abstract class JSSource { if (!hasRequestModifier || _obj.isClosed) return null; - val result = V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSVideoUrlSource", "obj.getRequestModifier()") { - _obj.invoke("getRequestModifier", arrayOf()); - }; + val result = _plugin.isBusyWith("getRequestModifier") { + V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSVideoUrlSource", "obj.getRequestModifier()") { + _obj.invoke("getRequestModifier", arrayOf()); + }; + } if (result !is V8ValueObject) return null; @@ -76,13 +78,12 @@ abstract class JSSource { return null; Logger.v("JSSource", "Request executor for [${type}] requesting"); - val result = V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") { - _plugin.isBusyWith("getRequestExecutor") { - _plugin.getUnderlyingPlugin().busy { - _obj.invoke("getRequestExecutor", arrayOf()); - } - } - }; + val result =_plugin.isBusyWith("getRequestExecutor") { + V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") { + _obj.invoke("getRequestExecutor", arrayOf()); + }; + } + Logger.v("JSSource", "Request executor for [${type}] received"); if (result !is V8ValueObject) diff --git a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt index 5b6ffd91..323aa5e1 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt @@ -1,15 +1,12 @@ package com.futo.platformplayer.engine import android.content.Context -import com.caoccao.javet.entities.JavetEntityError import com.caoccao.javet.exceptions.JavetCompilationException import com.caoccao.javet.exceptions.JavetException import com.caoccao.javet.exceptions.JavetExecutionException import com.caoccao.javet.interfaces.IJavetEntityError import com.caoccao.javet.interop.V8Host import com.caoccao.javet.interop.V8Runtime -import com.caoccao.javet.interop.options.V8Flags -import com.caoccao.javet.interop.options.V8RuntimeOptions import com.caoccao.javet.values.V8Value import com.caoccao.javet.values.primitive.V8ValueBoolean import com.caoccao.javet.values.primitive.V8ValueInteger @@ -17,7 +14,6 @@ import com.caoccao.javet.values.primitive.V8ValueString import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient -import com.futo.platformplayer.assume import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.engine.exceptions.NoInternetException import com.futo.platformplayer.engine.exceptions.PluginEngineStoppedException @@ -44,7 +40,6 @@ import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StateAssets import com.futo.platformplayer.warnIfMainThread import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.Semaphore import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock @@ -58,7 +53,7 @@ class V8Plugin { val httpClientAuth: ManagedHttpClient get() = _clientAuth; val httpClientOthers: Map get() = _clientOthers; - var startId: Int = 0; + var runtimeId: Int = 0; fun registerHttpClient(client: JSHttpClient) { synchronized(_clientOthers) { @@ -76,11 +71,8 @@ class V8Plugin { var isStopped = true; val onStopped = Event1(); - //TODO: Implement a more universal isBusy system for plugins + JSClient + pooling? TBD if propagation would be beneficial - //private val _busyCounterLock = Object(); - //private var _busyCounter = 0; - private val _busyLock = ReentrantLock()//Semaphore(1); - val isBusy get() = _busyLock.isLocked;//synchronized(_busyCounterLock) { _busyCounter > 0 }; + private val _busyLock = ReentrantLock() + val isBusy get() = _busyLock.isLocked; var allowDevSubmit: Boolean = false private set(value) { @@ -150,24 +142,19 @@ class V8Plugin { synchronized(_runtimeLock) { if (_runtime != null) return; - startId + 1; + runtimeId = runtimeId + 1; //V8RuntimeOptions.V8_FLAGS.setUseStrict(true); val host = V8Host.getV8Instance(); val options = host.jsRuntimeType.getRuntimeOptions(); - Logger.i(TAG, "Plugin [${config.name}] start: Creating runtime") - _runtime = host.createV8Runtime(options); if (!host.isIsolateCreated) throw IllegalStateException("Isolate not created"); - Logger.i(TAG, "Plugin [${config.name}] start: Created runtime") - //Setup bridge _runtime?.let { it.converter = V8Converter(); - Logger.i(TAG, "Plugin [${config.name}] start: Loading packages") for (pack in _depsPackages) { if (pack.variableName != null) it.createV8ValueObject().use { v8valueObject -> @@ -180,8 +167,6 @@ class V8Plugin { } } - Logger.i(TAG, "Plugin [${config.name}] start: Loading deps") - //Load deps for (dep in _deps) catchScriptErrors("Dep[${dep.key}]") { @@ -192,13 +177,11 @@ class V8Plugin { if (config.allowEval) it.allowEval(true); - Logger.i(TAG, "Plugin [${config.name}] start: Loading script") //Load plugin catchScriptErrors("Plugin[${config.name}]") { it.getExecutor(script).executeVoid() }; isStopped = false; - Logger.i(TAG, "Plugin [${config.name}] start: Script loaded") } } } @@ -210,7 +193,7 @@ class V8Plugin { if(isStopped) return@busy; isStopped = true; - startId = -1; + runtimeId = runtimeId + 1; //Cleanup http for(pack in _depsPackages) { @@ -260,79 +243,11 @@ class V8Plugin { runtime.getExecutor(js).execute() }; } - /* - synchronized(_busyCounterLock) { - _busyCounter++; - } - - val runtime = _runtime ?: throw IllegalStateException("JSPlugin not started yet"); - try { - return catchScriptErrors("Plugin[${config.name}]", js) { - runtime.getExecutor(js).execute() - }; - } - finally { - synchronized(_busyCounterLock) { - //Free busy *after* afterBusy calls are done to prevent calls on dead runtimes - try { - afterBusy.emit(_busyCounter - 1); - } - catch(ex: Throwable) { - Logger.e(TAG, "Unhandled V8Plugin.afterBusy", ex); - } - _busyCounter--; - } - } - */ } fun executeBoolean(js: String) : Boolean? = busy { catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value } } fun executeString(js: String) : String? = busy { catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value } } fun executeInteger(js: String) : Int? = busy { catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value } } - /* - fun whenNotBusyBlocking(handler: (V8Plugin)->T): T { - while(true) { - synchronized(_busyCounterLock) { - if(_busyCounter == 0) - { - return handler(this); - } - } - Thread.sleep(1); - } - } - */ - /* - fun whenNotBusy(handler: (V8Plugin)->Unit) { - synchronized(_busyCounterLock) { - if(_busyCounter == 0) - handler(this); - else { - val tag = Object(); - afterBusy.subscribe(tag) { - if(it == 0) { - Logger.w(TAG, "V8Plugin afterBusy handled"); - afterBusy.remove(tag); - - var failed = false; - synchronized(_busyCounterLock) { - if(_busyCounter > 0) { - failed = true; - return@synchronized - } - handler(this); - } - if(failed) - busy { - handler(this); - } - } - } - } - } - } - */ - private fun getPackage(packageName: String, allowNull: Boolean = false): V8Package? { //TODO: Auto get all package types? return when(packageName) { @@ -397,15 +312,12 @@ class V8Plugin { val obj = executeEx.scriptingError?.context as IJavetEntityError if(obj.context.containsKey("plugin_type") == true) { val pluginType = obj.context["plugin_type"].toString(); - //val pluginType = obj.get("plugin_type").toString(); //Captcha if (pluginType == "CaptchaRequiredException") { throw ScriptCaptchaRequiredException(config, obj.context["url"]?.toString(), obj.context["body"]?.toString(), - //obj.get("url")?.toString(), - //obj.get("body")?.toString(), executeEx, executeEx.scriptingError?.stack, codeStripped); } @@ -414,8 +326,6 @@ class V8Plugin { throw ScriptReloadRequiredException(config, obj.context["msg"]?.toString(), obj.context["reloadData"]?.toString(), - //obj.get("message")?.toString(), - //obj.get("reloadData")?.toString(), executeEx, executeEx.scriptingError?.stack, codeStripped); } @@ -481,9 +391,4 @@ class V8Plugin { return StateAssets.readAsset(context, path) ?: throw java.lang.IllegalStateException("script ${path} not found"); } } - - - /** - * Methods available for scripts (bridge object) - */ } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt index b4cc821d..858b020b 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt @@ -82,7 +82,8 @@ class PackageBridge : V8Package { @V8Property fun supportedFeatures(): Array { return arrayOf( - "ReloadRequiredException" + "ReloadRequiredException", + "HttpBatchClient" ); } @@ -130,14 +131,10 @@ class PackageBridge : V8Package { timeoutMap.remove(id); } try { - Logger.v(TAG, "Timeout started [${id}]"); _plugin.busy { - Logger.v(TAG, "Timeout call started [${id}]"); if(!_plugin.isStopped) funcClone.callVoid(null, arrayOf()); - Logger.v(TAG, "Timeout call ended [${id}]"); } - Logger.v(TAG, "Timeout resolved [${id}]"); } catch(ex: Throwable) { Logger.e(TAG, "Failed timeout callback", ex); @@ -173,7 +170,7 @@ class PackageBridge : V8Package { Logger.i(TAG, "Plugin toast [${_config.name}]: ${str}"); StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { try { - UIDialogs.toast(str); + UIDialogs.appToast(str); } catch (e: Throwable) { Logger.e(TAG, "Failed to show toast.", e); } diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt index 2930a476..82edb023 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt @@ -347,6 +347,17 @@ class PackageHttp: V8Package { _clientId = if(_client is JSHttpClient) _client.clientId else null; } + @V8Function + fun resetAuthCookies(){ + if(_client is JSHttpClient) + _client.resetAuthCookies(); + } + @V8Function + fun clearOtherCookies(){ + if(_client is JSHttpClient) + _client.clearOtherCookies(); + } + @V8Function fun setDefaultHeaders(defaultHeaders: Map) { for(pair in defaultHeaders) diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt index b5e29075..7f361105 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt @@ -567,7 +567,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout { findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) { var startId = -1; try { - startId = videoSource?.getUnderlyingPlugin()?.getUnderlyingPlugin()?.startId ?: -1; + startId = videoSource?.getUnderlyingPlugin()?.getUnderlyingPlugin()?.runtimeId ?: -1; val generated = videoSource.generate(); if (generated != null) { withContext(Dispatchers.Main) { @@ -597,7 +597,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout { val plugin = videoSource.getUnderlyingPlugin(); if(plugin == null) return@launch; - if(startId != -1 && plugin.getUnderlyingPlugin()?.startId != startId) + if(startId != -1 && plugin.getUnderlyingPlugin()?.runtimeId != startId) return@launch; StatePlatform.instance.handleReloadRequired(reloadRequired, { onReloadRequired.emit(); @@ -689,17 +689,17 @@ abstract class FutoVideoPlayerBase : RelativeLayout { @OptIn(UnstableApi::class) private fun swapAudioSourceDashRaw(audioSource: JSDashManifestRawAudioSource, play: Boolean, resume: Boolean): Boolean { Logger.i(TAG, "Loading AudioSource [DashRaw]"); - val dataSource = if(audioSource is JSSource && (audioSource.requiresCustomDatasource)) - audioSource.getHttpDataSourceFactory() - else - DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT); if(audioSource.hasGenerate) { findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) { var startId = -1; try { - startId = audioSource.getUnderlyingPlugin()?.getUnderlyingPlugin()?.startId ?: -1; + startId = audioSource.getUnderlyingPlugin()?.getUnderlyingPlugin()?.runtimeId ?: -1; val generated = audioSource.generate(); if(generated != null) { + val dataSource = if(audioSource is JSSource && (audioSource.requiresCustomDatasource)) + audioSource.getHttpDataSourceFactory() + else + DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT); withContext(Dispatchers.Main) { _lastVideoMediaSource = DashMediaSource.Factory(dataSource) .createMediaSource(DashManifestParser().parse(Uri.parse(audioSource.url), @@ -713,7 +713,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout { val plugin = audioSource.getUnderlyingPlugin(); if(plugin == null) return@launch; - if(startId != -1 && plugin.getUnderlyingPlugin()?.startId != startId) + if(startId != -1 && plugin.getUnderlyingPlugin()?.runtimeId != startId) return@launch; StatePlatform.instance.reEnableClient(plugin.id, { onReloadRequired.emit(); @@ -726,6 +726,10 @@ abstract class FutoVideoPlayerBase : RelativeLayout { return false; } else { + val dataSource = if(audioSource is JSSource && (audioSource.requiresCustomDatasource)) + audioSource.getHttpDataSourceFactory() + else + DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT); _lastVideoMediaSource = DashMediaSource.Factory(dataSource) .createMediaSource( DashManifestParser().parse( From 6ba9ec8bc26752684cdcba72b3760f6241f9e4bc Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Mon, 16 Jun 2025 17:56:04 +0200 Subject: [PATCH 18/39] Clearer name setting --- app/src/main/java/com/futo/platformplayer/Settings.kt | 4 ++-- .../java/com/futo/platformplayer/states/StatePlaylists.kt | 7 +------ app/src/main/res/values/strings.xml | 4 ++-- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index 6cb27e3f..b000d586 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -1017,8 +1017,8 @@ class Settings : FragmentedStorageFileJson() { @FormField(R.string.playlist_allow_dups, FieldForm.TOGGLE, R.string.playlist_allow_dups_description, 3) var playlistAllowDups: Boolean = true; - @FormField(R.string.add_to_beginning_of_watch_later, FieldForm.TOGGLE, R.string.add_to_beginning_description, 4) - var addToBeginning: Boolean = true; + @FormField(R.string.watch_later_add_start, FieldForm.TOGGLE, R.string.watch_later_add_start_description, 4) + var watchLaterAddStart: Boolean = true; @FormField(R.string.enable_polycentric, FieldForm.TOGGLE, R.string.can_be_disabled_when_you_are_experiencing_issues, 5) var polycentricEnabled: Boolean = true; diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt index 996c3f9a..cbe1c518 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt @@ -3,7 +3,6 @@ package com.futo.platformplayer.states import android.content.Context import android.net.Uri import androidx.core.content.FileProvider -import androidx.fragment.app.Fragment import com.futo.platformplayer.R import com.futo.platformplayer.Settings import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException @@ -21,7 +20,6 @@ import com.futo.platformplayer.models.ImportCache import com.futo.platformplayer.models.Playlist import com.futo.platformplayer.sToOffsetDateTimeUTC import com.futo.platformplayer.smartMerge -import com.futo.platformplayer.states.StateSubscriptionGroups.Companion import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.StringArrayStorage import com.futo.platformplayer.stores.StringDateMapStorage @@ -30,15 +28,12 @@ import com.futo.platformplayer.stores.v2.ManagedStore import com.futo.platformplayer.stores.v2.ReconstructStore import com.futo.platformplayer.sync.internal.GJSyncOpcodes import com.futo.platformplayer.sync.models.SyncPlaylistsPackage -import com.futo.platformplayer.sync.models.SyncSubscriptionGroupsPackage -import com.futo.platformplayer.sync.models.SyncSubscriptionsPackage import com.futo.platformplayer.sync.models.SyncWatchLaterPackage import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.io.File -import java.time.Instant import java.time.OffsetDateTime import java.time.ZoneOffset @@ -185,7 +180,7 @@ class StatePlaylists { } _watchlistStore.saveAsync(video) - if (Settings.instance.other.addToBeginning) { + if (Settings.instance.other.watchLaterAddStart) { _watchlistOrderStore.set(*(listOf(video.url) + _watchlistOrderStore.values).toTypedArray()) } else { _watchlistOrderStore.set(*(_watchlistOrderStore.values + listOf(video.url)).toTypedArray()) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b2831cec..4dd158e0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -468,8 +468,8 @@ Show confirmation dialog when deleting media from a playlist Allow duplicate playlist videos Allow adding duplicate videos to playlists - Add new videos to the beginning of Watch Later - When adding videos to Watch Later add them to the beginning of the list instead of the end + Add new videos to the beginning of Watch Later + When adding videos to Watch Later add them to the beginning of the list instead of the end Already in watch later Enable Polycentric Enable Polycentric Local Caching From 7e83793586e2f556b347b1b6ce283e8c2d04d1a0 Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Mon, 16 Jun 2025 18:34:37 +0200 Subject: [PATCH 19/39] Submods --- app/src/stable/assets/sources/youtube | 2 +- app/src/unstable/assets/sources/youtube | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/stable/assets/sources/youtube b/app/src/stable/assets/sources/youtube index 2e258294..0167dfb4 160000 --- a/app/src/stable/assets/sources/youtube +++ b/app/src/stable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 2e25829494addebbf9292b7c68b2d6130e1fc461 +Subproject commit 0167dfb471e6f90ab08e997ac7151072576c42db diff --git a/app/src/unstable/assets/sources/youtube b/app/src/unstable/assets/sources/youtube index 2e258294..0167dfb4 160000 --- a/app/src/unstable/assets/sources/youtube +++ b/app/src/unstable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 2e25829494addebbf9292b7c68b2d6130e1fc461 +Subproject commit 0167dfb471e6f90ab08e997ac7151072576c42db From 33d3d9a29c22beabd05e2808d56a7b0c7ff2275a Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Mon, 16 Jun 2025 19:30:52 +0200 Subject: [PATCH 20/39] Improved locking --- .../api/media/platforms/js/JSClient.kt | 32 +++++++++-------- .../platforms/js/models/JSVideoDetails.kt | 34 +++++++++++-------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt index 4422dd28..6beb6894 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt @@ -281,7 +281,7 @@ open class JSClient : IPlatformClient { } @JSDocs(0, "source.enable()", "Called when the plugin is enabled/started") - fun enable() { + fun enable() = isBusyWith("enable") { if(!_initialized) initialize(); for(toDeclare in declareOnEnable) { @@ -297,12 +297,12 @@ open class JSClient : IPlatformClient { _enabled = true; } @JSDocs(1, "source.saveState()", "Provide a string that is passed to enable for quicker startup of multiple instances") - fun saveState(): String? { + fun saveState(): String? = isBusyWith("saveState") { ensureEnabled(); if(!capabilities.hasSaveState) - return null; + return@isBusyWith null; val resp = plugin.executeTyped("source.saveState()").value; - return resp; + return@isBusyWith resp; } @JSDocs(1, "source.disable()", "Called before the plugin is disabled/stopped") @@ -405,14 +405,14 @@ open class JSClient : IPlatformClient { @JSDocs(6, "source.isChannelUrl(url)", "Validates if an channel url is for this platform") @JSDocsParameter("url", "A channel url (May not be your platform)") - override fun isChannelUrl(url: String): Boolean { + override fun isChannelUrl(url: String): Boolean = isBusyWith("isChannelUrl") { try { - return plugin.executeTyped("source.isChannelUrl(${Json.encodeToString(url)})") + return@isBusyWith plugin.executeTyped("source.isChannelUrl(${Json.encodeToString(url)})") .value; } catch(ex: Throwable) { announcePluginUnhandledException("isChannelUrl", ex); - return false; + return@isBusyWith false; } } @JSDocs(7, "source.getChannel(channelUrl)", "Gets a channel by its url") @@ -543,14 +543,14 @@ open class JSClient : IPlatformClient { @JSDocs(13, "source.isContentDetailsUrl(url)", "Validates if an content url is for this platform") @JSDocsParameter("url", "A content url (May not be your platform)") - override fun isContentDetailsUrl(url: String): Boolean { + override fun isContentDetailsUrl(url: String): Boolean = isBusyWith("isContentDetailsUrl") { try { - return plugin.executeTyped("source.isContentDetailsUrl(${Json.encodeToString(url)})") + return@isBusyWith plugin.executeTyped("source.isContentDetailsUrl(${Json.encodeToString(url)})") .value; } catch(ex: Throwable) { announcePluginUnhandledException("isContentDetailsUrl", ex); - return false; + return@isBusyWith false; } } @JSDocs(14, "source.getContentDetails(url)", "Gets content details by its url") @@ -652,17 +652,19 @@ open class JSClient : IPlatformClient { @JSOptional @JSDocs(20, "source.isPlaylistUrl(url)", "Validates if a playlist url is for this platform") @JSDocsParameter("url", "Url of playlist") - override fun isPlaylistUrl(url: String): Boolean { + override fun isPlaylistUrl(url: String): Boolean = isBusyWith("isPlaylistUrl") { if (!capabilities.hasGetPlaylist) - return false; + return@isBusyWith false; try { - return plugin.executeTyped("source.isPlaylistUrl(${Json.encodeToString(url)})") - .value; + return@isBusyWith busy { + return@busy plugin.executeTyped("source.isPlaylistUrl(${Json.encodeToString(url)})") + .value; + } } catch(ex: Throwable) { announcePluginUnhandledException("isPlaylistUrl", ex); - return false; + return@isBusyWith false; } } @JSOptional diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt index 799c737f..cecb2913 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt @@ -84,14 +84,16 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails { return getPlaybackTrackerJS(); } private fun getPlaybackTrackerJS(): IPlaybackTracker? { - return V8Plugin.catchScriptErrors(_pluginConfig, "VideoDetails", "videoDetails.getPlaybackTracker()") { - val tracker = _content.invoke("getPlaybackTracker", arrayOf()) - ?: return@catchScriptErrors null; - if(tracker is V8ValueObject) - return@catchScriptErrors JSPlaybackTracker(_plugin, tracker); - else - return@catchScriptErrors null; - }; + return _plugin.busy { + V8Plugin.catchScriptErrors(_pluginConfig, "VideoDetails", "videoDetails.getPlaybackTracker()") { + val tracker = _content.invoke("getPlaybackTracker", arrayOf()) + ?: return@catchScriptErrors null; + if(tracker is V8ValueObject) + return@catchScriptErrors JSPlaybackTracker(_plugin, tracker); + else + return@catchScriptErrors null; + } + } } override fun getContentRecommendations(client: IPlatformClient): IPager? { @@ -108,8 +110,10 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails { return null; } private fun getContentRecommendationsJS(client: JSClient): JSContentPager { - val contentPager = _content.invoke("getContentRecommendations", arrayOf()); - return JSContentPager(_pluginConfig, client, contentPager); + return _plugin.busy { + val contentPager = _content.invoke("getContentRecommendations", arrayOf()); + return@busy JSContentPager(_pluginConfig, client, contentPager); + } } override fun getComments(client: IPlatformClient): IPager? { @@ -125,10 +129,12 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails { } private fun getCommentsJS(client: JSClient): IPager? { - val commentPager = _content.invoke("getComments", arrayOf()); - if (commentPager !is V8ValueObject) //TODO: Maybe handle this better? - return null; + return _plugin.busy { + val commentPager = _content.invoke("getComments", arrayOf()); + if (commentPager !is V8ValueObject) //TODO: Maybe handle this better? + return@busy null; - return JSCommentPager(_pluginConfig, client, commentPager); + return@busy JSCommentPager(_pluginConfig, client, commentPager); + } } } \ No newline at end of file From c14378b534150673c3e52a038fc5a94d4b9b1bba Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Tue, 17 Jun 2025 11:45:02 +0200 Subject: [PATCH 21/39] Improved V8 locking, comment section on diff thread than video, global mapping of v8runtimes to plugins --- .../com/futo/platformplayer/Extensions_V8.kt | 20 +++++++++++ .../api/media/platforms/js/JSClient.kt | 10 ++++++ .../models/sources/JSDashManifestRawSource.kt | 2 +- .../platforms/js/models/sources/JSSource.kt | 34 ++++++++----------- .../futo/platformplayer/engine/V8Plugin.kt | 13 +++++++ .../platformplayer/states/StatePlatform.kt | 4 +-- .../views/video/FutoVideoPlayerBase.kt | 11 +++--- 7 files changed, 68 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt b/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt index e31d3dac..b350d153 100644 --- a/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt +++ b/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt @@ -5,7 +5,9 @@ import com.caoccao.javet.values.primitive.* import com.caoccao.javet.values.reference.V8ValueArray import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.engine.V8Plugin import com.futo.platformplayer.engine.exceptions.ScriptImplementationException +import com.futo.platformplayer.logging.Logger //V8 @@ -24,6 +26,10 @@ fun V8Value?.orDefault(default: R, handler: (V8Value)->R): R { return handler(this); } +inline fun V8Value.getSourcePlugin(): V8Plugin? { + return V8Plugin.getPluginFromRuntime(this.v8Runtime); +} + inline fun V8Value.expectOrThrow(config: IV8PluginConfig, contextName: String): T { if(this !is T) throw ScriptImplementationException(config, "Expected ${contextName} to be of type ${T::class.simpleName}, but found ${this::class.simpleName}"); @@ -90,6 +96,20 @@ inline fun V8ValueArray.expectV8Variants(config: IV8PluginConfig, co } inline fun V8Value.expectV8Variant(config: IV8PluginConfig, contextName: String): T { + if(false) + { + this?.getSourcePlugin()?.let { + if (!it.isThreadAlreadyBusy()) { + val stacktrace = Thread.currentThread().stackTrace; + Logger.w("Extensions_V8", + "V8 USE OUTSIDE BUSY: " + stacktrace.drop(3)?.firstOrNull().toString() + + ", " + stacktrace.drop(4)?.firstOrNull().toString() + + ", " + stacktrace.drop(5)?.firstOrNull()?.toString() + + ", " + stacktrace.drop(6)?.firstOrNull()?.toString() + ) + } + } + } return when(T::class) { String::class -> this.expectOrThrow(config, contextName).value as T; Int::class -> { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt index 6beb6894..7dd027f7 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt @@ -59,6 +59,9 @@ import com.futo.platformplayer.states.AnnouncementType import com.futo.platformplayer.states.StateAnnouncement import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePlugins +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.runBlocking import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.time.OffsetDateTime @@ -771,6 +774,13 @@ open class JSClient : IPlatformClient { return@busy handle(); } } + fun busyBlockingSuspended(handle: suspend ()->T): T { + return _plugin.busy { + return@busy runBlocking { + return@runBlocking handle(); + } + } + } fun isBusyWith(actionName: String, handle: ()->T): T { //val busyId = kotlin.random.Random.nextInt(9999); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt index a9c070f7..7f0de0af 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt @@ -32,7 +32,7 @@ open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSo override val duration: Long; override val priority: Boolean; - var url: String?; + val url: String?; override var manifest: String?; override val hasGenerate: Boolean; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt index 00906239..320a918f 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt @@ -53,43 +53,39 @@ abstract class JSSource { hasRequestExecutor = _requestExecutor != null || obj.has("getRequestExecutor"); } - fun getRequestModifier(): IRequestModifier? { + fun getRequestModifier(): IRequestModifier? = _plugin.isBusyWith("getRequestModifier") { if(_requestModifier != null) - return AdhocRequestModifier { url, headers -> + return@isBusyWith AdhocRequestModifier { url, headers -> return@AdhocRequestModifier _requestModifier.modify(_plugin, url, headers); }; if (!hasRequestModifier || _obj.isClosed) - return null; + return@isBusyWith null; - val result = _plugin.isBusyWith("getRequestModifier") { - V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSVideoUrlSource", "obj.getRequestModifier()") { - _obj.invoke("getRequestModifier", arrayOf()); - }; - } + val result = V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSVideoUrlSource", "obj.getRequestModifier()") { + _obj.invoke("getRequestModifier", arrayOf()); + }; if (result !is V8ValueObject) - return null; + return@isBusyWith null; - return JSRequestModifier(_plugin, result) + return@isBusyWith JSRequestModifier(_plugin, result) } - open fun getRequestExecutor(): JSRequestExecutor? { + open fun getRequestExecutor(): JSRequestExecutor? = _plugin.isBusyWith("getRequestExecutor") { if (!hasRequestExecutor || _obj.isClosed) - return null; + return@isBusyWith null; Logger.v("JSSource", "Request executor for [${type}] requesting"); - val result =_plugin.isBusyWith("getRequestExecutor") { - V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") { - _obj.invoke("getRequestExecutor", arrayOf()); - }; - } + val result = V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSSource", "obj.getRequestExecutor()") { + _obj.invoke("getRequestExecutor", arrayOf()); + }; Logger.v("JSSource", "Request executor for [${type}] received"); if (result !is V8ValueObject) - return null; + return@isBusyWith null; - return JSRequestExecutor(_plugin, result) + return@isBusyWith JSRequestExecutor(_plugin, result) } fun getUnderlyingPlugin(): JSClient? { diff --git a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt index 323aa5e1..170c2d56 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt @@ -49,6 +49,7 @@ class V8Plugin { private val _clientAuth: ManagedHttpClient; private val _clientOthers: ConcurrentHashMap = ConcurrentHashMap(); + val httpClient: ManagedHttpClient get() = _client; val httpClientAuth: ManagedHttpClient get() = _clientAuth; val httpClientOthers: Map get() = _clientOthers; @@ -151,6 +152,8 @@ class V8Plugin { if (!host.isIsolateCreated) throw IllegalStateException("Isolate not created"); + _runtimeMap.put(_runtime!!, this); + //Setup bridge _runtime?.let { it.converter = V8Converter(); @@ -203,6 +206,7 @@ class V8Plugin { } _runtime?.let { + _runtimeMap.remove(it); _runtime = null; if(!it.isClosed && !it.isDead) { try { @@ -222,6 +226,9 @@ class V8Plugin { } } + fun isThreadAlreadyBusy(): Boolean { + return _busyLock.isHeldByCurrentThread; + } fun busy(handle: ()->T): T { _busyLock.withLock { //Logger.i(TAG, "Entered busy: " + Thread.currentThread().stackTrace.drop(3)?.firstOrNull()?.toString() + ", " + Thread.currentThread().stackTrace.drop(4)?.firstOrNull()?.toString()); @@ -273,8 +280,14 @@ class V8Plugin { private val REGEX_EX_FALLBACK = Regex(".*throw.*?[\"](.*)[\"].*"); private val REGEX_EX_FALLBACK2 = Regex(".*throw.*?['](.*)['].*"); + private val _runtimeMap = ConcurrentHashMap(); + val TAG = "V8Plugin"; + fun getPluginFromRuntime(runtime: V8Runtime): V8Plugin? { + return _runtimeMap.getOrDefault(runtime, null); + } + fun catchScriptErrors(config: IV8PluginConfig, context: String, code: String? = null, handle: ()->T): T { var codeStripped = code; if(codeStripped != null) { //TODO: Improve code stripped diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt index 8cf8f080..9376644f 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt @@ -958,7 +958,7 @@ class StatePlatform { //Comments fun getComments(content: IPlatformContentDetails): IPager { val client = getContentClient(content.url); - val pager = content.getComments(client); + val pager = null;//content.getComments(client); return pager ?: getComments(content.url); } @@ -969,7 +969,7 @@ class StatePlatform { return EmptyPager(); if(!StateApp.instance.privateMode) - return client.fromPool(_mainClientPool).getComments(url); + return client.fromPool(_pagerClientPool).getComments(url); else return client.fromPool(_privateClientPool).getComments(url); } diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt index 7f361105..6bd66ccb 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt @@ -353,8 +353,10 @@ abstract class FutoVideoPlayerBase : RelativeLayout { var videoSourceUsed = videoSource; var audioSourceUsed = audioSource; if(videoSource is JSDashManifestRawSource && audioSource is JSDashManifestRawAudioSource){ - videoSourceUsed = JSDashManifestMergingRawSource(videoSource, audioSource); - audioSourceUsed = null; + videoSource.getUnderlyingPlugin()?.busy { + videoSourceUsed = JSDashManifestMergingRawSource(videoSource, audioSource); + audioSourceUsed = null; + } } val didSetVideo = swapSourceInternal(videoSourceUsed, play, resume); @@ -567,8 +569,9 @@ abstract class FutoVideoPlayerBase : RelativeLayout { findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) { var startId = -1; try { - startId = videoSource?.getUnderlyingPlugin()?.getUnderlyingPlugin()?.runtimeId ?: -1; - val generated = videoSource.generate(); + val plugin = videoSource.getUnderlyingPlugin() ?: return@launch; + startId = plugin.getUnderlyingPlugin()?.runtimeId ?: -1; + val generated = plugin.busy { videoSource.generate(); }; if (generated != null) { withContext(Dispatchers.Main) { val dataSource = if(videoSource is JSSource && (videoSource.requiresCustomDatasource)) From b953ff21e7c2604a599de37a064da78b0686bb4f Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Tue, 17 Jun 2025 11:52:26 +0200 Subject: [PATCH 22/39] Lock on subtitle fetch --- .../api/media/platforms/js/models/JSSubtitleSource.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSSubtitleSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSSubtitleSource.kt index bb4650f6..259a89e4 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSSubtitleSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSSubtitleSource.kt @@ -6,6 +6,7 @@ import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.getSourcePlugin import com.futo.platformplayer.states.StateApp import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -35,8 +36,11 @@ class JSSubtitleSource : ISubtitleSource { override fun getSubtitles(): String { if(!hasFetch) throw IllegalStateException("This subtitle doesn't support getSubtitles.."); - val v8String = _obj.invoke("getSubtitles", arrayOf()); - return v8String.value; + + return _obj.getSourcePlugin()?.busy { + val v8String = _obj.invoke("getSubtitles", arrayOf()); + return@busy v8String.value; + } ?: ""; } override suspend fun getSubtitlesURI(): Uri? { From c0bbe5d4914e81061e4ec85021f545212f860b11 Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Tue, 17 Jun 2025 15:21:46 +0200 Subject: [PATCH 23/39] Additional locking --- .../api/media/platforms/js/JSClient.kt | 19 ++++++++++++------- .../platforms/js/models/JSLiveEventPager.kt | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt index 7dd027f7..d61ebc0b 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt @@ -346,8 +346,10 @@ open class JSClient : IPlatformClient { return _searchCapabilities!!; } - _searchCapabilities = ResultCapabilities.fromV8(config, plugin.executeTyped("source.getSearchCapabilities()")); - return _searchCapabilities!!; + return busy { + _searchCapabilities = ResultCapabilities.fromV8(config, plugin.executeTyped("source.getSearchCapabilities()")); + return@busy _searchCapabilities!!; + } } catch(ex: Throwable) { announcePluginUnhandledException("getSearchCapabilities", ex); @@ -375,8 +377,10 @@ open class JSClient : IPlatformClient { if (_searchChannelContentsCapabilities != null) return _searchChannelContentsCapabilities!!; - _searchChannelContentsCapabilities = ResultCapabilities.fromV8(config, plugin.executeTyped("source.getSearchChannelContentsCapabilities()")); - return _searchChannelContentsCapabilities!!; + return busy { + _searchChannelContentsCapabilities = ResultCapabilities.fromV8(config, plugin.executeTyped("source.getSearchChannelContentsCapabilities()")); + return@busy _searchChannelContentsCapabilities!!; + } } @JSDocs(5, "source.searchChannelContents(query)", "Searches for videos on the platform") @JSDocsParameter("channelUrl", "Channel url to search") @@ -433,9 +437,10 @@ open class JSClient : IPlatformClient { if (_channelCapabilities != null) { return _channelCapabilities!!; } - - _channelCapabilities = ResultCapabilities.fromV8(config, plugin.executeTyped("source.getChannelCapabilities()")); - return _channelCapabilities!!; + return busy { + _channelCapabilities = ResultCapabilities.fromV8(config, plugin.executeTyped("source.getChannelCapabilities()")); + return@busy _channelCapabilities!!; + }; } catch(ex: Throwable) { announcePluginUnhandledException("getChannelCapabilities", ex); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSLiveEventPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSLiveEventPager.kt index 27731fea..dc2ba7b2 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSLiveEventPager.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSLiveEventPager.kt @@ -15,7 +15,7 @@ class JSLiveEventPager : JSPager, IPlatformLiveEventPager { nextRequest = pager.getOrThrow(config, "nextRequest", "LiveEventPager"); } - override fun nextPage() { + override fun nextPage() = plugin.isBusyWith("JSLiveEventPager.nextPage") { super.nextPage(); nextRequest = pager.getOrThrow(config, "nextRequest", "LiveEventPager"); } From ab07288ba037d22872eebd9215a4c4ece1550af5 Mon Sep 17 00:00:00 2001 From: zvonimir Date: Tue, 17 Jun 2025 17:25:34 +0200 Subject: [PATCH 24/39] fix: timeoutMap being deadlocked --- .../engine/packages/PackageBridge.kt | 39 ++++++++----------- .../views/video/FutoVideoPlayerBase.kt | 2 +- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt index 858b020b..72bdf34f 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.launch import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import java.util.concurrent.ConcurrentHashMap class PackageBridge : V8Package { @Transient @@ -110,7 +111,7 @@ class PackageBridge : V8Package { } var timeoutCounter = 0; - var timeoutMap = HashSet(); + var timeoutMap = ConcurrentHashMap(); @V8Function fun setTimeout(func: V8ValueFunction, timeout: Long): Int { val id = timeoutCounter++; @@ -118,47 +119,39 @@ class PackageBridge : V8Package { StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { delay(timeout); - if(_plugin.isStopped) + if (_plugin.isStopped) return@launch; - synchronized(timeoutMap) { - if(!timeoutMap.contains(id)) { - _plugin.busy { - if(!_plugin.isStopped) - JavetResourceUtils.safeClose(funcClone); - } - return@launch; + if (!timeoutMap.containsKey(id)) { + _plugin.busy { + if (!_plugin.isStopped) + JavetResourceUtils.safeClose(funcClone); } - timeoutMap.remove(id); + return@launch; } + timeoutMap.remove(id); try { _plugin.busy { - if(!_plugin.isStopped) + if (!_plugin.isStopped) funcClone.callVoid(null, arrayOf()); } - } - catch(ex: Throwable) { + } catch (ex: Throwable) { Logger.e(TAG, "Failed timeout callback", ex); - } - finally { + } finally { _plugin.busy { - if(!_plugin.isStopped) + if (!_plugin.isStopped) JavetResourceUtils.safeClose(funcClone); } //_plugin.whenNotBusy { //} } }; - synchronized(timeoutMap) { - timeoutMap.add(id); - } + timeoutMap.put(id, true); return id; } @V8Function fun clearTimeout(id: Int) { - synchronized(timeoutMap) { - if(timeoutMap.contains(id)) - timeoutMap.remove(id); - } + if (timeoutMap.containsKey(id)) + timeoutMap.remove(id); } @V8Function fun sleep(length: Int) { diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt index 6bd66ccb..43ed541d 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt @@ -580,7 +580,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout { DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT); if(dataSource is JSHttpDataSource.Factory && videoSource is JSDashManifestMergingRawSource) - dataSource.setRequestExecutor2(videoSource.audio.getRequestExecutor()); + dataSource.setRequestExecutor2(withContext(Dispatchers.IO){videoSource.audio.getRequestExecutor()}); _lastVideoMediaSource = DashMediaSource.Factory(dataSource) .createMediaSource( DashManifestParser().parse( From 48a96140a74f7fb121a6b6ef06ef57f9ff1fd66e Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Tue, 17 Jun 2025 17:28:10 +0200 Subject: [PATCH 25/39] isBusy checks and locking improvements --- .../com/futo/platformplayer/Extensions_V8.kt | 36 ++++++++++++------- .../platformplayer/api/media/PlatformID.kt | 2 ++ .../api/media/models/PlatformAuthorLink.kt | 2 ++ .../models/PlatformAuthorMembershipLink.kt | 2 ++ .../api/media/models/ResultCapabilities.kt | 4 +++ .../api/media/models/Thumbnails.kt | 2 ++ .../media/models/live/IPlatformLiveEvent.kt | 2 ++ .../api/media/models/live/LiveEventComment.kt | 3 ++ .../media/models/live/LiveEventDonation.kt | 2 ++ .../api/media/models/live/LiveEventEmojis.kt | 2 ++ .../api/media/models/live/LiveEventRaid.kt | 2 ++ .../media/models/live/LiveEventViewCount.kt | 2 ++ .../api/media/models/ratings/IRating.kt | 7 +++- .../models/ratings/RatingLikeDislikes.kt | 2 ++ .../api/media/models/ratings/RatingLikes.kt | 2 ++ .../api/media/models/ratings/RatingScaler.kt | 2 ++ .../media/platforms/js/models/IJSContent.kt | 2 ++ .../platforms/js/models/IJSContentDetails.kt | 2 ++ .../sources/JSHLSManifestAudioSource.kt | 11 ++++-- .../platforms/js/models/sources/JSSource.kt | 28 ++++++++++++--- .../models/sources/JSVideoSourceDescriptor.kt | 2 ++ .../engine/exceptions/NoInternetException.kt | 2 ++ .../engine/exceptions/ScriptAgeException.kt | 2 ++ .../ScriptCaptchaRequiredException.kt | 2 ++ .../exceptions/ScriptCompilationException.kt | 2 ++ .../exceptions/ScriptCriticalException.kt | 2 ++ .../engine/exceptions/ScriptException.kt | 2 ++ .../exceptions/ScriptExecutionException.kt | 2 ++ .../ScriptImplementationException.kt | 2 ++ .../ScriptLoginRequiredException.kt | 2 ++ .../ScriptReloadRequiredException.kt | 2 ++ .../exceptions/ScriptTimeoutException.kt | 2 ++ .../exceptions/ScriptUnavailableException.kt | 2 ++ .../futo/platformplayer/models/Playlist.kt | 2 ++ 34 files changed, 124 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt b/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt index b350d153..7a43f078 100644 --- a/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt +++ b/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt @@ -95,21 +95,31 @@ inline fun V8ValueArray.expectV8Variants(config: IV8PluginConfig, co .map { kv-> kv.second.orNull { it.expectV8Variant(config, contextName + "[${kv.first}]", ) } as T }; } -inline fun V8Value.expectV8Variant(config: IV8PluginConfig, contextName: String): T { - if(false) - { - this?.getSourcePlugin()?.let { - if (!it.isThreadAlreadyBusy()) { - val stacktrace = Thread.currentThread().stackTrace; - Logger.w("Extensions_V8", - "V8 USE OUTSIDE BUSY: " + stacktrace.drop(3)?.firstOrNull().toString() + - ", " + stacktrace.drop(4)?.firstOrNull().toString() + - ", " + stacktrace.drop(5)?.firstOrNull()?.toString() + - ", " + stacktrace.drop(6)?.firstOrNull()?.toString() - ) - } +inline fun V8Plugin.ensureIsBusy() { + this.let { + if (!it.isThreadAlreadyBusy()) { + throw IllegalStateException("Tried to access V8Plugin without busy"); + /* + val stacktrace = Thread.currentThread().stackTrace; + Logger.w("Extensions_V8", + "V8 USE OUTSIDE BUSY: " + stacktrace.drop(3)?.firstOrNull().toString() + + ", " + stacktrace.drop(4)?.firstOrNull().toString() + + ", " + stacktrace.drop(5)?.firstOrNull()?.toString() + + ", " + stacktrace.drop(6)?.firstOrNull()?.toString() + ) + */ } } +} +inline fun V8Value.ensureIsBusy() { + this?.getSourcePlugin()?.let { + it.ensureIsBusy(); + } +} + +inline fun V8Value.expectV8Variant(config: IV8PluginConfig, contextName: String): T { + if(true) + ensureIsBusy(); return when(T::class) { String::class -> this.expectOrThrow(config, contextName).value as T; Int::class -> { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/PlatformID.kt b/app/src/main/java/com/futo/platformplayer/api/media/PlatformID.kt index 9b063c9b..4ff3a549 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/PlatformID.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/PlatformID.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.api.media import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrDefault import com.futo.platformplayer.getOrThrow import com.futo.platformplayer.getOrThrowNullable @@ -44,6 +45,7 @@ class PlatformID { val NONE = PlatformID("Unknown", null); fun fromV8(config: SourcePluginConfig, value: V8ValueObject): PlatformID { + value.ensureIsBusy(); val contextName = "PlatformID"; return PlatformID( value.getOrThrow(config, "platform", contextName), diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorLink.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorLink.kt index 330597a4..831f8ef7 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorLink.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorLink.kt @@ -6,6 +6,7 @@ import com.futo.platformplayer.api.media.models.contents.ContentType import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.api.media.platforms.js.models.JSContent +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrDefault import com.futo.platformplayer.getOrThrow @@ -33,6 +34,7 @@ open class PlatformAuthorLink { val UNKNOWN = PlatformAuthorLink(PlatformID.NONE, "Unknown", "", null, null); fun fromV8(config: SourcePluginConfig, value: V8ValueObject): PlatformAuthorLink { + value.ensureIsBusy(); if(value.has("membershipUrl")) return PlatformAuthorMembershipLink.fromV8(config, value); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorMembershipLink.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorMembershipLink.kt index 03abad1a..6b73842f 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorMembershipLink.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorMembershipLink.kt @@ -3,6 +3,7 @@ package com.futo.platformplayer.api.media.models import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrDefault import com.futo.platformplayer.getOrThrow @@ -20,6 +21,7 @@ class PlatformAuthorMembershipLink: PlatformAuthorLink { companion object { fun fromV8(config: SourcePluginConfig, value: V8ValueObject): PlatformAuthorMembershipLink { + value.ensureIsBusy(); val context = "AuthorMembershipLink" return PlatformAuthorMembershipLink(PlatformID.fromV8(config, value.getOrThrow(config, "id", context, false)), value.getOrThrow(config ,"name", context), diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/ResultCapabilities.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/ResultCapabilities.kt index fd24de30..e95b3fe0 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/ResultCapabilities.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/ResultCapabilities.kt @@ -5,6 +5,7 @@ import com.caoccao.javet.values.primitive.V8ValueInteger import com.caoccao.javet.values.reference.V8ValueArray import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.expectV8Variant import com.futo.platformplayer.getOrDefault import com.futo.platformplayer.getOrThrow @@ -46,6 +47,7 @@ class ResultCapabilities( fun fromV8(config: IV8PluginConfig, value: V8ValueObject): ResultCapabilities { val contextName = "ResultCapabilities"; + value.ensureIsBusy(); return ResultCapabilities( value.getOrThrow(config, "types", contextName).toArray().map { it.expectV8Variant(config, "Capabilities.types") }, value.getOrThrow(config, "sorts", contextName).toArray().map { it.expectV8Variant(config, "Capabilities.sorts"); }, @@ -69,6 +71,7 @@ class FilterGroup( companion object { fun fromV8(config: IV8PluginConfig, value: V8ValueObject): FilterGroup { + value.ensureIsBusy(); return FilterGroup( value.getString("name"), value.getOrDefault(config, "filters", "FilterGroup", null) @@ -90,6 +93,7 @@ class FilterCapability( companion object { fun fromV8(obj: V8ValueObject): FilterCapability { + obj.ensureIsBusy(); val value = obj.get("value") as V8Value; return FilterCapability( obj.getString("name"), diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/Thumbnails.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/Thumbnails.kt index a30d31c9..b25936a0 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/Thumbnails.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/Thumbnails.kt @@ -4,6 +4,7 @@ import com.caoccao.javet.values.reference.V8ValueArray import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig import com.futo.platformplayer.engine.V8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrDefault import com.futo.platformplayer.getOrThrow @@ -31,6 +32,7 @@ class Thumbnails { companion object { fun fromV8(config: IV8PluginConfig, value: V8ValueObject): Thumbnails { + value.ensureIsBusy(); return Thumbnails((value.getOrThrow(config, "sources", "Thumbnails")) .toArray() .map { Thumbnail.fromV8(config, it as V8ValueObject) } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/IPlatformLiveEvent.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/IPlatformLiveEvent.kt index 89826b01..19b4bbb9 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/live/IPlatformLiveEvent.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/IPlatformLiveEvent.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.api.media.models.live import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow interface IPlatformLiveEvent { @@ -10,6 +11,7 @@ interface IPlatformLiveEvent { companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject, contextName: String = "LiveEvent") : IPlatformLiveEvent { + obj.ensureIsBusy(); val t = LiveEventType.fromInt(obj.getOrThrow(config, "type", contextName)); return when(t) { LiveEventType.COMMENT -> LiveEventComment.fromV8(config, obj); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventComment.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventComment.kt index 28bbe15a..8b9883ef 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventComment.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventComment.kt @@ -4,6 +4,7 @@ import com.caoccao.javet.values.reference.V8ValueArray import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.api.media.models.ratings.RatingLikes import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrDefault import com.futo.platformplayer.getOrThrow @@ -27,6 +28,8 @@ class LiveEventComment: IPlatformLiveEvent, ILiveEventChatMessage { companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : LiveEventComment { + obj.ensureIsBusy(); + val contextName = "LiveEventComment" val colorName = obj.getOrDefault(config, "colorName", contextName, null); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventDonation.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventDonation.kt index a4ac5d47..f8cbafe6 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventDonation.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventDonation.kt @@ -3,6 +3,7 @@ package com.futo.platformplayer.api.media.models.live import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.api.media.models.ratings.RatingLikes import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrDefault import com.futo.platformplayer.getOrThrow @@ -37,6 +38,7 @@ class LiveEventDonation: IPlatformLiveEvent, ILiveEventChatMessage { companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : LiveEventDonation { + obj.ensureIsBusy(); val contextName = "LiveEventDonation" return LiveEventDonation( obj.getOrThrow(config, "name", contextName), diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventEmojis.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventEmojis.kt index 6e29bac5..7028d59d 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventEmojis.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventEmojis.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.api.media.models.live import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow class LiveEventEmojis: IPlatformLiveEvent { @@ -15,6 +16,7 @@ class LiveEventEmojis: IPlatformLiveEvent { companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : LiveEventEmojis { + obj.ensureIsBusy(); val contextName = "LiveEventEmojis" return LiveEventEmojis( obj.getOrThrow(config, "emojis", contextName)); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventRaid.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventRaid.kt index ff5dd36f..f43a7c5b 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventRaid.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventRaid.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.api.media.models.live import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow class LiveEventRaid: IPlatformLiveEvent { @@ -19,6 +20,7 @@ class LiveEventRaid: IPlatformLiveEvent { companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : LiveEventRaid { + obj.ensureIsBusy(); val contextName = "LiveEventRaid" return LiveEventRaid( obj.getOrThrow(config, "targetName", contextName), diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventViewCount.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventViewCount.kt index adcfb883..5e48e984 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventViewCount.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventViewCount.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.api.media.models.live import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow class LiveEventViewCount: IPlatformLiveEvent { @@ -15,6 +16,7 @@ class LiveEventViewCount: IPlatformLiveEvent { companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : LiveEventViewCount { + obj.ensureIsBusy(); val contextName = "LiveEventViewCount" return LiveEventViewCount( obj.getOrThrow(config, "viewCount", contextName)); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/IRating.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/IRating.kt index 75286b44..1fdbb442 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/IRating.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/IRating.kt @@ -3,6 +3,7 @@ package com.futo.platformplayer.api.media.models.ratings import com.caoccao.javet.values.V8Value import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow import com.futo.platformplayer.orDefault import com.futo.platformplayer.serializers.IRatingSerializer @@ -13,8 +14,12 @@ interface IRating { companion object { - fun fromV8OrDefault(config: IV8PluginConfig, obj: V8Value?, default: IRating) = obj.orDefault(default) { fromV8(config, it as V8ValueObject) }; + fun fromV8OrDefault(config: IV8PluginConfig, obj: V8Value?, default: IRating): IRating { + obj?.ensureIsBusy(); + return obj.orDefault(default) { fromV8(config, it as V8ValueObject) } + }; fun fromV8(config: IV8PluginConfig, obj: V8ValueObject, contextName: String = "Rating") : IRating { + obj.ensureIsBusy(); val t = RatingType.fromInt(obj.getOrThrow(config, "type", contextName)); return when(t) { RatingType.LIKES -> RatingLikes.fromV8(config, obj); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikeDislikes.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikeDislikes.kt index 6d0e787b..8ccc6b2e 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikeDislikes.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikeDislikes.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.api.media.models.ratings import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow /** @@ -14,6 +15,7 @@ class RatingLikeDislikes(val likes: Long, val dislikes: Long) : IRating { companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : RatingLikeDislikes { + obj.ensureIsBusy(); return RatingLikeDislikes(obj.getOrThrow(config, "likes", "RatingLikeDislikes"), obj.getOrThrow(config, "dislikes", "RatingLikeDislikes")); } } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikes.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikes.kt index e40169f2..0a45f15b 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikes.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikes.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.api.media.models.ratings import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow /** @@ -13,6 +14,7 @@ class RatingLikes(val likes: Long) : IRating { companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : RatingLikes { + obj.ensureIsBusy(); return RatingLikes(obj.getOrThrow(config, "likes", "RatingLikes")); } } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingScaler.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingScaler.kt index 7646cf24..d656df5f 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingScaler.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingScaler.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.api.media.models.ratings import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow /** @@ -13,6 +14,7 @@ class RatingScaler(val value: Float) : IRating { companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : RatingScaler { + obj.ensureIsBusy() return RatingScaler(obj.getOrThrow(config, "value", "RatingScaler")); } } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContent.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContent.kt index 777981bf..326b4086 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContent.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContent.kt @@ -6,6 +6,7 @@ import com.futo.platformplayer.api.media.models.contents.ContentType import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrDefault import com.futo.platformplayer.getOrThrow @@ -13,6 +14,7 @@ interface IJSContent: IPlatformContent { companion object { fun fromV8(plugin: JSClient, obj: V8ValueObject): IPlatformContent { + obj.ensureIsBusy(); val config = plugin.config; val type: Int = obj.getOrThrow(config, "contentType", "ContentItem"); val pluginType: String? = obj.getOrDefault(config, "plugin_type", "ContentItem", null); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContentDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContentDetails.kt index 21b475ff..16470c17 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContentDetails.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContentDetails.kt @@ -6,12 +6,14 @@ import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow interface IJSContentDetails: IPlatformContent { companion object { fun fromV8(plugin: JSClient, obj: V8ValueObject): IPlatformContentDetails { + obj.ensureIsBusy(); val type: Int = obj.getOrThrow(plugin.config, "contentType", "ContentDetails"); return when(ContentType.fromInt(type)) { ContentType.MEDIA -> JSVideoDetails(plugin, obj); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestAudioSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestAudioSource.kt index 9e328df3..18cd71fc 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestAudioSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestAudioSource.kt @@ -7,6 +7,7 @@ import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestAudi import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.engine.IV8PluginConfig import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrNull import com.futo.platformplayer.getOrThrow import com.futo.platformplayer.orNull @@ -38,7 +39,13 @@ class JSHLSManifestAudioSource : IHLSManifestAudioSource, JSSource { companion object { - fun fromV8HLSNullable(plugin: JSClient, obj: V8Value?) : JSHLSManifestAudioSource? = obj.orNull { fromV8HLS(plugin, it as V8ValueObject) }; - fun fromV8HLS(plugin: JSClient, obj: V8ValueObject) : JSHLSManifestAudioSource = JSHLSManifestAudioSource(plugin, obj); + fun fromV8HLSNullable(plugin: JSClient, obj: V8Value?) : JSHLSManifestAudioSource? { + obj?.ensureIsBusy(); + return obj.orNull { fromV8HLS(plugin, it as V8ValueObject) } + }; + fun fromV8HLS(plugin: JSClient, obj: V8ValueObject) : JSHLSManifestAudioSource { + obj.ensureIsBusy(); + return JSHLSManifestAudioSource(plugin, obj) + }; } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt index 320a918f..22bf2a60 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt @@ -14,6 +14,7 @@ import com.futo.platformplayer.api.media.platforms.js.models.JSRequestExecutor import com.futo.platformplayer.api.media.platforms.js.models.JSRequestModifier import com.futo.platformplayer.engine.IV8PluginConfig import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrDefault import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.orNull @@ -108,8 +109,12 @@ abstract class JSSource { const val TYPE_AUDIOURL_WIDEVINE = "AudioUrlWidevineSource" const val TYPE_VIDEOURL_WIDEVINE = "VideoUrlWidevineSource" - fun fromV8VideoNullable(plugin: JSClient, obj: V8Value?) : IVideoSource? = obj.orNull { fromV8Video(plugin, it as V8ValueObject) }; + fun fromV8VideoNullable(plugin: JSClient, obj: V8Value?) : IVideoSource? { + obj?.ensureIsBusy(); + return obj.orNull { fromV8Video(plugin, it as V8ValueObject) } + }; fun fromV8Video(plugin: JSClient, obj: V8ValueObject) : IVideoSource? { + obj.ensureIsBusy() val type = obj.getString("plugin_type"); return when(type) { TYPE_VIDEOURL -> JSVideoUrlSource(plugin, obj); @@ -126,13 +131,26 @@ abstract class JSSource { } } fun fromV8DashNullable(plugin: JSClient, obj: V8Value?) : JSDashManifestSource? = obj.orNull { fromV8Dash(plugin, it as V8ValueObject) }; - fun fromV8Dash(plugin: JSClient, obj: V8ValueObject) : JSDashManifestSource = JSDashManifestSource(plugin, obj); - fun fromV8DashRaw(plugin: JSClient, obj: V8ValueObject) : JSDashManifestRawSource = JSDashManifestRawSource(plugin, obj); - fun fromV8DashRawAudio(plugin: JSClient, obj: V8ValueObject) : JSDashManifestRawAudioSource = JSDashManifestRawAudioSource(plugin, obj); + fun fromV8Dash(plugin: JSClient, obj: V8ValueObject) : JSDashManifestSource{ + obj.ensureIsBusy(); + return JSDashManifestSource(plugin, obj) + }; + fun fromV8DashRaw(plugin: JSClient, obj: V8ValueObject) : JSDashManifestRawSource{ + obj.ensureIsBusy() + return JSDashManifestRawSource(plugin, obj); + } + fun fromV8DashRawAudio(plugin: JSClient, obj: V8ValueObject) : JSDashManifestRawAudioSource { + obj?.ensureIsBusy(); + return JSDashManifestRawAudioSource(plugin, obj) + }; fun fromV8HLSNullable(plugin: JSClient, obj: V8Value?) : JSHLSManifestSource? = obj.orNull { fromV8HLS(plugin, it as V8ValueObject) }; - fun fromV8HLS(plugin: JSClient, obj: V8ValueObject) : JSHLSManifestSource = JSHLSManifestSource(plugin, obj); + fun fromV8HLS(plugin: JSClient, obj: V8ValueObject) : JSHLSManifestSource { + obj.ensureIsBusy(); + return JSHLSManifestSource(plugin, obj) + }; fun fromV8Audio(plugin: JSClient, obj: V8ValueObject) : IAudioSource? { + obj.ensureIsBusy(); val type = obj.getString("plugin_type"); return when(type) { TYPE_HLS -> JSHLSManifestAudioSource.fromV8HLS(plugin, obj); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoSourceDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoSourceDescriptor.kt index e68f0ae0..e7c0fe50 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoSourceDescriptor.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoSourceDescriptor.kt @@ -6,6 +6,7 @@ import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor import com.futo.platformplayer.api.media.models.streams.VideoMuxedSourceDescriptor import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow class JSVideoSourceDescriptor : VideoMuxedSourceDescriptor { @@ -31,6 +32,7 @@ class JSVideoSourceDescriptor : VideoMuxedSourceDescriptor { fun fromV8(plugin: JSClient, obj: V8ValueObject) : IVideoSourceDescriptor { + obj.ensureIsBusy(); val type = obj.getString("plugin_type") return when(type) { TYPE_MUXED -> JSVideoSourceDescriptor(plugin, obj); diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/NoInternetException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/NoInternetException.kt index bce39025..4011b0a8 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/exceptions/NoInternetException.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/NoInternetException.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.engine.exceptions import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow open class NoInternetException(config: IV8PluginConfig, error: String, ex: Exception? = null, stack: String? = null, code: String? = null) : ScriptException(config, error, ex, stack, code) { @@ -11,6 +12,7 @@ open class NoInternetException(config: IV8PluginConfig, error: String, ex: Excep companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : NoInternetException { + obj.ensureIsBusy(); return NoInternetException(config, obj.getOrThrow(config, "message", "NoInternetException")); } } diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptAgeException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptAgeException.kt index ef1ca13f..48c3142f 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptAgeException.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptAgeException.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.engine.exceptions import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow open class ScriptAgeException(config: IV8PluginConfig, error: String, ex: Exception? = null, stack: String? = null, code: String? = null) : ScriptException(config, error, ex, stack, code) { @@ -11,6 +12,7 @@ open class ScriptAgeException(config: IV8PluginConfig, error: String, ex: Except companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptException { + obj.ensureIsBusy(); return ScriptException(config, obj.getOrThrow(config, "message", "ScriptAgeException")); } } diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCaptchaRequiredException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCaptchaRequiredException.kt index 8aa7f2c8..6bbf536b 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCaptchaRequiredException.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCaptchaRequiredException.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.engine.exceptions import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrDefault import com.futo.platformplayer.getOrThrow @@ -9,6 +10,7 @@ class ScriptCaptchaRequiredException(config: IV8PluginConfig, val url: String?, companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptException { + obj.ensureIsBusy(); val contextName = "ScriptCaptchaRequiredException"; return ScriptCaptchaRequiredException(config, obj.getOrDefault(config, "url", contextName, null), diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCompilationException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCompilationException.kt index 2db245d3..26b2eebc 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCompilationException.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCompilationException.kt @@ -2,12 +2,14 @@ package com.futo.platformplayer.engine.exceptions import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow class ScriptCompilationException(config: IV8PluginConfig, error: String, ex: Exception? = null, code: String? = null) : PluginException(config, error, ex, code) { companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptCompilationException { + obj.ensureIsBusy(); return ScriptCompilationException(config, obj.getOrThrow(config, "message", "ScriptCompilationException")); } } diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCriticalException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCriticalException.kt index 6581ec25..d8eda509 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCriticalException.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCriticalException.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.engine.exceptions import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow open class ScriptCriticalException(config: IV8PluginConfig, error: String, ex: Exception? = null, stack: String? = null, code: String? = null) : ScriptException(config, error, ex, stack, code) { @@ -11,6 +12,7 @@ open class ScriptCriticalException(config: IV8PluginConfig, error: String, ex: E companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptException { + obj.ensureIsBusy(); return ScriptCriticalException(config, obj.getOrThrow(config, "message", "ScriptCriticalException")); } } diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptException.kt index cf038a23..de777a9f 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptException.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptException.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.engine.exceptions import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow open class ScriptException(config: IV8PluginConfig, error: String, ex: Exception? = null, stack: String? = null, code: String? = null) : ScriptExecutionException(config, error, ex, stack, code) { @@ -11,6 +12,7 @@ open class ScriptException(config: IV8PluginConfig, error: String, ex: Exception companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptException { + obj.ensureIsBusy(); return ScriptException(config, obj.getOrThrow(config, "message", "ScriptException")); } } diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptExecutionException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptExecutionException.kt index 28b9b0e9..8bfd49d6 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptExecutionException.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptExecutionException.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.engine.exceptions import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow open class ScriptExecutionException(config: IV8PluginConfig, error: String, ex: Exception? = null, val stack: String? = null, code: String? = null) : PluginException(config, error, ex, code) { @@ -11,6 +12,7 @@ open class ScriptExecutionException(config: IV8PluginConfig, error: String, ex: companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptExecutionException { + obj.ensureIsBusy(); return ScriptExecutionException(config, obj.getOrThrow(config, "message", "ScriptExecutionException")); } } diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptImplementationException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptImplementationException.kt index dd2aaf7a..943b4fe9 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptImplementationException.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptImplementationException.kt @@ -2,12 +2,14 @@ package com.futo.platformplayer.engine.exceptions import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow class ScriptImplementationException(config: IV8PluginConfig, error: String, ex: Exception? = null, var pluginId: String? = null, code: String? = null) : PluginException(config, error, ex, code) { companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptImplementationException { + obj.ensureIsBusy(); return ScriptImplementationException(config, obj.getOrThrow(config, "message", "ScriptImplementationException")); } } diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptLoginRequiredException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptLoginRequiredException.kt index 423d5786..4acf0c55 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptLoginRequiredException.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptLoginRequiredException.kt @@ -2,12 +2,14 @@ package com.futo.platformplayer.engine.exceptions import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow class ScriptLoginRequiredException(config: IV8PluginConfig, error: String, ex: Exception? = null, stack: String? = null, code: String? = null) : ScriptException(config, error, ex, stack, code) { companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptException { + obj.ensureIsBusy(); return ScriptLoginRequiredException(config, obj.getOrThrow(config, "message", "ScriptLoginRequiredException")); } } diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptReloadRequiredException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptReloadRequiredException.kt index 98c0635d..6c792a32 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptReloadRequiredException.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptReloadRequiredException.kt @@ -4,6 +4,7 @@ import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.engine.IV8PluginConfig import com.futo.platformplayer.engine.V8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrDefault import com.futo.platformplayer.getOrThrow @@ -11,6 +12,7 @@ class ScriptReloadRequiredException(config: IV8PluginConfig, val msg: String?, v companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptException { + obj.ensureIsBusy(); val contextName = "ScriptReloadRequiredException"; return ScriptReloadRequiredException(config, obj.getOrThrow(config, "message", contextName), diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptTimeoutException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptTimeoutException.kt index 6f883854..17d02073 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptTimeoutException.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptTimeoutException.kt @@ -2,11 +2,13 @@ package com.futo.platformplayer.engine.exceptions import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow class ScriptTimeoutException(config: IV8PluginConfig, error: String, ex: Exception? = null) : ScriptException(config, error, ex) { companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptTimeoutException { + obj.ensureIsBusy(); return ScriptTimeoutException(config, obj.getOrThrow(config, "message", "ScriptException")); } } diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptUnavailableException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptUnavailableException.kt index 5d331b8b..feb47c35 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptUnavailableException.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptUnavailableException.kt @@ -2,12 +2,14 @@ package com.futo.platformplayer.engine.exceptions import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow class ScriptUnavailableException(config: IV8PluginConfig, error: String, ex: Exception? = null, stack: String? = null, code: String? = null) : ScriptException(config, error, ex, stack, code) { companion object { fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptException { + obj.ensureIsBusy(); return ScriptUnavailableException(config, obj.getOrThrow(config, "message", "ScriptUnavailableException")); } } diff --git a/app/src/main/java/com/futo/platformplayer/models/Playlist.kt b/app/src/main/java/com/futo/platformplayer/models/Playlist.kt index 9862e675..758929d5 100644 --- a/app/src/main/java/com/futo/platformplayer/models/Playlist.kt +++ b/app/src/main/java/com/futo/platformplayer/models/Playlist.kt @@ -5,6 +5,7 @@ import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.api.media.platforms.js.models.JSVideo +import com.futo.platformplayer.ensureIsBusy import com.futo.platformplayer.getOrThrow import com.futo.platformplayer.serializers.OffsetDateTimeSerializer import kotlinx.serialization.Serializable @@ -43,6 +44,7 @@ class Playlist { fun fromV8(config: SourcePluginConfig, obj: V8ValueObject?): Playlist? { if(obj == null) return null; + obj.ensureIsBusy(); val contextName = "Playlist"; From a464ae9df5a50d7766b47d60c1e329bf2ec8540a Mon Sep 17 00:00:00 2001 From: Koen J Date: Wed, 18 Jun 2025 10:07:02 +0200 Subject: [PATCH 26/39] Added missing loader causing crash. --- app/src/main/res/layout/list_locked_preview.xml | 6 +++++- app/src/main/res/layout/list_locked_thumbnail.xml | 8 +++++++- app/src/main/res/layout/list_video_preview_nested.xml | 8 +++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/layout/list_locked_preview.xml b/app/src/main/res/layout/list_locked_preview.xml index 2413c98c..e814c2da 100644 --- a/app/src/main/res/layout/list_locked_preview.xml +++ b/app/src/main/res/layout/list_locked_preview.xml @@ -116,9 +116,13 @@ android:layout_marginBottom="6dp" android:background="#DD000000" android:visibility="gone" + android:gravity="center" android:orientation="vertical"> - + + android:gravity="center" + android:orientation="vertical"> + + - - - + Date: Wed, 18 Jun 2025 10:29:12 +0200 Subject: [PATCH 27/39] Reverted changes. --- app/src/main/res/layout/list_locked_preview.xml | 4 ---- app/src/main/res/layout/list_locked_thumbnail.xml | 5 +---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/app/src/main/res/layout/list_locked_preview.xml b/app/src/main/res/layout/list_locked_preview.xml index e814c2da..1aefd5b8 100644 --- a/app/src/main/res/layout/list_locked_preview.xml +++ b/app/src/main/res/layout/list_locked_preview.xml @@ -119,10 +119,6 @@ android:gravity="center" android:orientation="vertical"> - - + Date: Wed, 18 Jun 2025 12:40:25 +0200 Subject: [PATCH 28/39] Downgrade v8, revert comments on diff thread --- app/build.gradle | 4 +-- .../com/futo/platformplayer/Extensions_V8.kt | 2 +- .../futo/platformplayer/engine/V8Plugin.kt | 33 ++++++++++++++++++- .../platformplayer/states/StatePlatform.kt | 2 +- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 8c30e58d..278e8b0f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -179,8 +179,8 @@ dependencies { implementation 'com.google.code.gson:gson:2.10.1' //Used for complex/anonymous cases like during development conversions (eg. V8RemoteObject) //JS - //implementation("com.caoccao.javet:javet-android:3.0.2") - implementation 'com.caoccao.javet:javet-v8-android:4.1.4' + implementation("com.caoccao.javet:javet-android:3.0.2") + //implementation 'com.caoccao.javet:javet-v8-android:4.1.4' //Change after extensive testing the freezing edge cases are solved. //Exoplayer implementation 'androidx.media3:media3-exoplayer:1.2.1' diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt b/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt index 7a43f078..aa51393f 100644 --- a/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt +++ b/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt @@ -118,7 +118,7 @@ inline fun V8Value.ensureIsBusy() { } inline fun V8Value.expectV8Variant(config: IV8PluginConfig, contextName: String): T { - if(true) + if(false) ensureIsBusy(); return when(T::class) { String::class -> this.expectOrThrow(config, contextName).value as T; diff --git a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt index 170c2d56..0cb2f196 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt @@ -4,7 +4,6 @@ import android.content.Context import com.caoccao.javet.exceptions.JavetCompilationException import com.caoccao.javet.exceptions.JavetException import com.caoccao.javet.exceptions.JavetExecutionException -import com.caoccao.javet.interfaces.IJavetEntityError import com.caoccao.javet.interop.V8Host import com.caoccao.javet.interop.V8Runtime import com.caoccao.javet.values.V8Value @@ -321,6 +320,37 @@ class V8Plugin { throw ScriptCompilationException(config, "Compilation: [${context}]: ${scriptEx.message}\n(${scriptEx.scriptingError.lineNumber})[${scriptEx.scriptingError.startColumn}-${scriptEx.scriptingError.endColumn}]: ${scriptEx.scriptingError.sourceLine}", null, codeStripped); } catch(executeEx: JavetExecutionException) { + val obj = executeEx.scriptingError?.context + if(obj != null && obj.containsKey("plugin_type") == true) { + val pluginType = obj["plugin_type"].toString(); + + //Captcha + if (pluginType == "CaptchaRequiredException") { + throw ScriptCaptchaRequiredException(config, + obj["url"]?.toString(), + obj["body"]?.toString(), + executeEx, executeEx.scriptingError?.stack, codeStripped); + } + + //Reload Required + if (pluginType == "ReloadRequiredException") { + throw ScriptReloadRequiredException(config, + obj["msg"]?.toString(), + obj["reloadData"]?.toString(), + executeEx, executeEx.scriptingError?.stack, codeStripped); + } + + //Others + throwExceptionFromV8( + config, + pluginType, + (extractJSExceptionMessage(executeEx) ?: ""), + executeEx, + executeEx.scriptingError?.stack, + codeStripped + ); + } + /* //Required for newer V8 versions if(executeEx.scriptingError?.context is IJavetEntityError) { val obj = executeEx.scriptingError?.context as IJavetEntityError if(obj.context.containsKey("plugin_type") == true) { @@ -354,6 +384,7 @@ class V8Plugin { } } + */ throw ScriptExecutionException(config, extractJSExceptionMessage(executeEx) ?: "", null, executeEx.scriptingError?.stack, codeStripped); } catch(ex: Exception) { diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt index 9376644f..1f1e5625 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt @@ -958,7 +958,7 @@ class StatePlatform { //Comments fun getComments(content: IPlatformContentDetails): IPager { val client = getContentClient(content.url); - val pager = null;//content.getComments(client); + val pager = content.getComments(client); return pager ?: getComments(content.url); } From 7922aa6f80d4ef4db9ec68e0a7d37c88e34f4dfc Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Wed, 18 Jun 2025 12:41:21 +0200 Subject: [PATCH 29/39] Log on busy on main --- app/src/main/java/com/futo/platformplayer/Extensions_V8.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt b/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt index aa51393f..240a6cfc 100644 --- a/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt +++ b/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt @@ -98,16 +98,14 @@ inline fun V8ValueArray.expectV8Variants(config: IV8PluginConfig, co inline fun V8Plugin.ensureIsBusy() { this.let { if (!it.isThreadAlreadyBusy()) { - throw IllegalStateException("Tried to access V8Plugin without busy"); - /* + //throw IllegalStateException("Tried to access V8Plugin without busy"); val stacktrace = Thread.currentThread().stackTrace; Logger.w("Extensions_V8", "V8 USE OUTSIDE BUSY: " + stacktrace.drop(3)?.firstOrNull().toString() + ", " + stacktrace.drop(4)?.firstOrNull().toString() + ", " + stacktrace.drop(5)?.firstOrNull()?.toString() + ", " + stacktrace.drop(6)?.firstOrNull()?.toString() - ) - */ + ); } } } From 15d771f7fc01a284aadc7c4652687b6dfdab5ea1 Mon Sep 17 00:00:00 2001 From: Koen J Date: Wed, 18 Jun 2025 13:43:50 +0200 Subject: [PATCH 30/39] Fixed channel loader not being animated. --- .../fragment/mainactivity/main/ChannelFragment.kt | 2 +- app/src/main/res/layout/fragment_channel.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt index 74116069..4cd8455c 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt @@ -172,7 +172,7 @@ class ChannelFragment : MainFragment() { _buttonSubscribe = findViewById(R.id.button_subscribe) _buttonSubscriptionSettings = findViewById(R.id.button_sub_settings) _overlayLoading = findViewById(R.id.channel_loading_overlay) - _overlayLoadingSpinner = findViewById(R.id.channel_loader) + _overlayLoadingSpinner = findViewById(R.id.channel_loader_frag) _overlayContainer = findViewById(R.id.overlay_container) _buttonSubscribe.onSubscribed.subscribe { UISlideOverlays.showSubscriptionOptionsOverlay(it, _overlayContainer) diff --git a/app/src/main/res/layout/fragment_channel.xml b/app/src/main/res/layout/fragment_channel.xml index ea9d4f52..29292034 100644 --- a/app/src/main/res/layout/fragment_channel.xml +++ b/app/src/main/res/layout/fragment_channel.xml @@ -173,7 +173,7 @@ android:background="#77000000" android:gravity="center"> Date: Wed, 18 Jun 2025 14:27:20 +0200 Subject: [PATCH 31/39] Hide duration if unknown --- .../views/adapters/VideoListEditorViewHolder.kt | 8 +++++++- .../views/adapters/feedtypes/PreviewVideoView.kt | 10 ++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt index 42cef197..b059d2c9 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt @@ -95,7 +95,13 @@ class VideoListEditorViewHolder : ViewHolder { .into(_imageThumbnail); _textName.text = v.name; _textAuthor.text = v.author.name; - _textVideoDuration.text = v.duration.toHumanTime(false); + + if(v.duration > 0) { + _textVideoDuration.text = v.duration.toHumanTime(false); + _textVideoDuration.visibility = View.VISIBLE; + } + else + _textVideoDuration.visibility = View.GONE; val historyPosition = StateHistory.instance.getHistoryPosition(v.url) _timeBar.progress = historyPosition.toFloat() / v.duration.toFloat(); diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoView.kt index bcabda4f..898b7e14 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoView.kt @@ -204,8 +204,14 @@ open class PreviewVideoView : LinearLayout { .into(_imageVideo); }; - if(!isPlanned) - _textVideoDuration.text = video.duration.toHumanTime(false); + if(!isPlanned) { + if(video.duration > 0) { + _textVideoDuration.text = video.duration.toHumanTime(false); + _textVideoDuration.visibility = View.VISIBLE; + } + else + _textVideoDuration.visibility = View.GONE; + } else _textVideoDuration.text = context.getString(R.string.planned); From e0e90c5f7462ff136e225af36caf2f0fbc14dd61 Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Wed, 18 Jun 2025 14:33:07 +0200 Subject: [PATCH 32/39] submodules --- app/src/stable/assets/sources/kick | 2 +- app/src/stable/assets/sources/patreon | 2 +- app/src/stable/assets/sources/soundcloud | 2 +- app/src/stable/assets/sources/youtube | 2 +- app/src/unstable/assets/sources/kick | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/stable/assets/sources/kick b/app/src/stable/assets/sources/kick index ffdf4cda..b7173f15 160000 --- a/app/src/stable/assets/sources/kick +++ b/app/src/stable/assets/sources/kick @@ -1 +1 @@ -Subproject commit ffdf4cda380e5e4e9e370412f014e704bd14c09e +Subproject commit b7173f1538a8259ace0c606dfc3441426a659536 diff --git a/app/src/stable/assets/sources/patreon b/app/src/stable/assets/sources/patreon index d98c7f8a..b811f8bd 160000 --- a/app/src/stable/assets/sources/patreon +++ b/app/src/stable/assets/sources/patreon @@ -1 +1 @@ -Subproject commit d98c7f8aee36101d60a0c671d16a0800d5d715d0 +Subproject commit b811f8bdfbbff73cf0d7581c9d7596911cb132b6 diff --git a/app/src/stable/assets/sources/soundcloud b/app/src/stable/assets/sources/soundcloud index 54564312..048acef1 160000 --- a/app/src/stable/assets/sources/soundcloud +++ b/app/src/stable/assets/sources/soundcloud @@ -1 +1 @@ -Subproject commit 54564312683a0ae06d7085405478f96cade325e3 +Subproject commit 048acef152823d2621da177d3b4e1535cf4ca8ac diff --git a/app/src/stable/assets/sources/youtube b/app/src/stable/assets/sources/youtube index 0167dfb4..71285696 160000 --- a/app/src/stable/assets/sources/youtube +++ b/app/src/stable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 0167dfb471e6f90ab08e997ac7151072576c42db +Subproject commit 712856962838dc229bf1b91aa99d22e101957f15 diff --git a/app/src/unstable/assets/sources/kick b/app/src/unstable/assets/sources/kick index ffdf4cda..b7173f15 160000 --- a/app/src/unstable/assets/sources/kick +++ b/app/src/unstable/assets/sources/kick @@ -1 +1 @@ -Subproject commit ffdf4cda380e5e4e9e370412f014e704bd14c09e +Subproject commit b7173f1538a8259ace0c606dfc3441426a659536 From a2986a72bd945f4a5a1c926f4c3d789daa0de99d Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Wed, 18 Jun 2025 14:43:20 +0200 Subject: [PATCH 33/39] Refs --- app/src/stable/assets/sources/youtube | 2 +- app/src/unstable/assets/sources/youtube | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/stable/assets/sources/youtube b/app/src/stable/assets/sources/youtube index 71285696..568d5605 160000 --- a/app/src/stable/assets/sources/youtube +++ b/app/src/stable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 712856962838dc229bf1b91aa99d22e101957f15 +Subproject commit 568d560520d6eff77d710aeac66057c76aedd9c0 diff --git a/app/src/unstable/assets/sources/youtube b/app/src/unstable/assets/sources/youtube index 0167dfb4..568d5605 160000 --- a/app/src/unstable/assets/sources/youtube +++ b/app/src/unstable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 0167dfb471e6f90ab08e997ac7151072576c42db +Subproject commit 568d560520d6eff77d710aeac66057c76aedd9c0 From c6100ede70e7f9287913845d83829430b4f3f8ca Mon Sep 17 00:00:00 2001 From: Koen J Date: Wed, 18 Jun 2025 15:43:12 +0200 Subject: [PATCH 34/39] Added disable for hold playback rate increase. --- .../java/com/futo/platformplayer/Settings.kt | 19 ++++++++++--------- .../views/behavior/GestureControlView.kt | 3 ++- app/src/main/res/values/strings.xml | 1 + 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index b000d586..da414d8f 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -587,18 +587,19 @@ class Settings : FragmentedStorageFileJson() { @FormField(R.string.hold_playback_speed, FieldForm.DROPDOWN, R.string.hold_playback_speed_description, 27) @DropdownFieldOptionsId(R.array.hold_playback_speeds) - var holdPlaybackSpeed: Int = 3; + var holdPlaybackSpeed: Int = 4; fun getHoldPlaybackSpeed(): Double { return when(holdPlaybackSpeed) { - 0 -> 1.25 - 1 -> 1.5 - 2 -> 1.75 - 3 -> 2.0 - 4 -> 2.25 - 5 -> 2.5 - 6 -> 2.75 - 7 -> 3.0 + 0 -> 1.0 + 1 -> 1.25 + 2 -> 1.5 + 3 -> 1.75 + 4 -> 2.0 + 5 -> 2.25 + 6 -> 2.5 + 7 -> 2.75 + 8 -> 3.0 else -> 2.0 } } diff --git a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt index 4bb864da..10a88341 100644 --- a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt @@ -240,7 +240,8 @@ class GestureControlView : LinearLayout { && !_adjustingFullscreenUp && !_adjustingFullscreenDown && !_isPanning - && !_isZooming) { + && !_isZooming + && Settings.instance.playback.getHoldPlaybackSpeed() > 1.0) { _speedHolding = true showHoldSpeedControls() onSpeedHoldStart.emit() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4dd158e0..39cf819a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1112,6 +1112,7 @@ 5.0 + Disabled 1.25 1.5 1.75 From 11319e0ec5d4632180a8e52003aa51991a0b95f6 Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Wed, 18 Jun 2025 16:22:28 +0200 Subject: [PATCH 35/39] Refs --- app/src/stable/assets/sources/youtube | 2 +- app/src/unstable/assets/sources/youtube | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/stable/assets/sources/youtube b/app/src/stable/assets/sources/youtube index 568d5605..97480075 160000 --- a/app/src/stable/assets/sources/youtube +++ b/app/src/stable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 568d560520d6eff77d710aeac66057c76aedd9c0 +Subproject commit 97480075fd8a50fa7eb601b5ed4bc1e531207dd0 diff --git a/app/src/unstable/assets/sources/youtube b/app/src/unstable/assets/sources/youtube index 568d5605..97480075 160000 --- a/app/src/unstable/assets/sources/youtube +++ b/app/src/unstable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 568d560520d6eff77d710aeac66057c76aedd9c0 +Subproject commit 97480075fd8a50fa7eb601b5ed4bc1e531207dd0 From 49ddecdea4d5b275286b2eec0f758fd198bb1a67 Mon Sep 17 00:00:00 2001 From: Koen J Date: Thu, 19 Jun 2025 11:21:46 +0200 Subject: [PATCH 36/39] Potential crashfix #2382. --- .../java/com/futo/platformplayer/services/DownloadService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/futo/platformplayer/services/DownloadService.kt b/app/src/main/java/com/futo/platformplayer/services/DownloadService.kt index b39b4592..5ab75011 100644 --- a/app/src/main/java/com/futo/platformplayer/services/DownloadService.kt +++ b/app/src/main/java/com/futo/platformplayer/services/DownloadService.kt @@ -62,7 +62,7 @@ class DownloadService : Service() { Logger.i(TAG, "onStartCommand"); synchronized(this) { if(_started) - return START_STICKY; + return START_NOT_STICKY; if(!FragmentedStorage.isInitialized) { Logger.i(TAG, "Attempted to start DownloadService without initialized files"); From 3a8167644772cdc34fafc53b57ffa19bfc70e383 Mon Sep 17 00:00:00 2001 From: Koen J Date: Fri, 20 Jun 2025 10:47:10 +0200 Subject: [PATCH 37/39] Fixed crash #2389. --- .../main/java/com/futo/platformplayer/states/StateSync.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/futo/platformplayer/states/StateSync.kt b/app/src/main/java/com/futo/platformplayer/states/StateSync.kt index 90c334e3..25cff055 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateSync.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateSync.kt @@ -78,7 +78,13 @@ class StateSync { onAuthorized = { sess, isNewlyAuthorized, isNewSession -> if (isNewSession) { deviceUpdatedOrAdded.emit(sess.remotePublicKey, sess) - StateApp.instance.scope.launch(Dispatchers.IO) { checkForSync(sess) } + StateApp.instance.scope.launch(Dispatchers.IO) { + try { + checkForSync(sess) + } catch (e: Throwable) { + Logger.e(TAG, "Failed to check for sync.", e) + } + } } } From edb9eda0a933098e2fb171480ebd56cfee24387e Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Fri, 20 Jun 2025 15:35:02 +0200 Subject: [PATCH 38/39] Improved locking --- .../models/sources/JSDashManifestRawAudioSource.kt | 14 ++++++++------ .../js/models/sources/JSDashManifestRawSource.kt | 14 ++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt index 2c6d4b35..f4994e0b 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt @@ -75,12 +75,14 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS } if(result != null){ - val initStart = _obj.getOrDefault(_config, "initStart", "JSDashManifestRawSource", null) ?: 0; - val initEnd = _obj.getOrDefault(_config, "initEnd", "JSDashManifestRawSource", null) ?: 0; - val indexStart = _obj.getOrDefault(_config, "indexStart", "JSDashManifestRawSource", null) ?: 0; - val indexEnd = _obj.getOrDefault(_config, "indexEnd", "JSDashManifestRawSource", null) ?: 0; - if(initEnd > 0 && indexStart > 0 && indexEnd > 0) { - streamMetaData = StreamMetaData(initStart, initEnd, indexStart, indexEnd); + plugin.busy { + val initStart = _obj.getOrDefault(_config, "initStart", "JSDashManifestRawSource", null) ?: 0; + val initEnd = _obj.getOrDefault(_config, "initEnd", "JSDashManifestRawSource", null) ?: 0; + val indexStart = _obj.getOrDefault(_config, "indexStart", "JSDashManifestRawSource", null) ?: 0; + val indexEnd = _obj.getOrDefault(_config, "indexEnd", "JSDashManifestRawSource", null) ?: 0; + if(initEnd > 0 && indexStart > 0 && indexEnd > 0) { + streamMetaData = StreamMetaData(initStart, initEnd, indexStart, indexEnd); + } } } return result; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt index 7f0de0af..184b783d 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt @@ -81,12 +81,14 @@ open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSo }); if(result != null){ - val initStart = _obj.getOrDefault(_config, "initStart", "JSDashManifestRawSource", null) ?: 0; - val initEnd = _obj.getOrDefault(_config, "initEnd", "JSDashManifestRawSource", null) ?: 0; - val indexStart = _obj.getOrDefault(_config, "indexStart", "JSDashManifestRawSource", null) ?: 0; - val indexEnd = _obj.getOrDefault(_config, "indexEnd", "JSDashManifestRawSource", null) ?: 0; - if(initEnd > 0 && indexStart > 0 && indexEnd > 0) { - streamMetaData = StreamMetaData(initStart, initEnd, indexStart, indexEnd); + _plugin.busy { + val initStart = _obj.getOrDefault(_config, "initStart", "JSDashManifestRawSource", null) ?: 0; + val initEnd = _obj.getOrDefault(_config, "initEnd", "JSDashManifestRawSource", null) ?: 0; + val indexStart = _obj.getOrDefault(_config, "indexStart", "JSDashManifestRawSource", null) ?: 0; + val indexEnd = _obj.getOrDefault(_config, "indexEnd", "JSDashManifestRawSource", null) ?: 0; + if(initEnd > 0 && indexStart > 0 && indexEnd > 0) { + streamMetaData = StreamMetaData(initStart, initEnd, indexStart, indexEnd); + } } } return result; From 4d720b1d81c809a04604867f3ef74b6a1d92c0c8 Mon Sep 17 00:00:00 2001 From: Koen J Date: Tue, 24 Jun 2025 11:43:40 +0200 Subject: [PATCH 39/39] Fixed app freezing when exporting Polycentric Identity #2405 --- .../activities/PolycentricBackupActivity.kt | 55 ++++++++++++++----- .../layout/activity_polycentric_backup.xml | 11 ++++ 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricBackupActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricBackupActivity.kt index a0a0fac1..9cf58134 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricBackupActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricBackupActivity.kt @@ -14,10 +14,12 @@ import android.widget.ImageButton import android.widget.ImageView import android.widget.TextView import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.R import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.setNavigationBarColorAndIcons import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StateApp.Companion.withContext import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.views.buttons.BigButton import com.futo.polycentric.core.ContentType @@ -29,6 +31,9 @@ import com.futo.polycentric.core.toBase64Url import com.google.zxing.BarcodeFormat import com.google.zxing.MultiFormatWriter import com.google.zxing.common.BitMatrix +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import userpackage.Protocol import userpackage.Protocol.ExportBundle import userpackage.Protocol.URLInfo @@ -39,6 +44,7 @@ class PolycentricBackupActivity : AppCompatActivity() { private lateinit var _imageQR: ImageView; private lateinit var _exportBundle: String; private lateinit var _textQR: TextView; + private lateinit var _loader: View override fun attachBaseContext(newBase: Context?) { super.attachBaseContext(StateApp.instance.getLocaleContext(newBase)) @@ -49,24 +55,47 @@ class PolycentricBackupActivity : AppCompatActivity() { setContentView(R.layout.activity_polycentric_backup); setNavigationBarColorAndIcons(); - _buttonShare = findViewById(R.id.button_share); - _buttonCopy = findViewById(R.id.button_copy); - _imageQR = findViewById(R.id.image_qr); - _textQR = findViewById(R.id.text_qr); + _buttonShare = findViewById(R.id.button_share) + _buttonCopy = findViewById(R.id.button_copy) + _imageQR = findViewById(R.id.image_qr) + _textQR = findViewById(R.id.text_qr) + _loader = findViewById(R.id.progress_loader) findViewById(R.id.button_back).setOnClickListener { finish(); }; - _exportBundle = createExportBundle(); + _imageQR.visibility = View.INVISIBLE + _textQR.visibility = View.INVISIBLE + _loader.visibility = View.VISIBLE + _buttonShare.visibility = View.INVISIBLE + _buttonCopy.visibility = View.INVISIBLE - try { - val dimension = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200f, resources.displayMetrics).toInt(); - val qrCodeBitmap = generateQRCode(_exportBundle, dimension, dimension); - _imageQR.setImageBitmap(qrCodeBitmap); - } catch (e: Exception) { - Logger.e(TAG, getString(R.string.failed_to_generate_qr_code), e); - _imageQR.visibility = View.INVISIBLE; - _textQR.visibility = View.INVISIBLE; + lifecycleScope.launch { + try { + val pair = withContext(Dispatchers.IO) { + val bundle = createExportBundle() + val dimension = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 200f, resources.displayMetrics + ).toInt() + val qr = generateQRCode(bundle, dimension, dimension) + Pair(bundle, qr) + } + + _exportBundle = pair.first + _imageQR.setImageBitmap(pair.second) + _imageQR.visibility = View.VISIBLE + _textQR.visibility = View.VISIBLE + _buttonShare.visibility = View.VISIBLE + _buttonCopy.visibility = View.VISIBLE + } catch (e: Exception) { + Logger.e(TAG, getString(R.string.failed_to_generate_qr_code), e) + _imageQR.visibility = View.INVISIBLE + _textQR.visibility = View.INVISIBLE + _buttonShare.visibility = View.INVISIBLE + _buttonCopy.visibility = View.INVISIBLE + } finally { + _loader.visibility = View.GONE + } } _buttonShare.onClick.subscribe { diff --git a/app/src/main/res/layout/activity_polycentric_backup.xml b/app/src/main/res/layout/activity_polycentric_backup.xml index e31e8584..d6579dd7 100644 --- a/app/src/main/res/layout/activity_polycentric_backup.xml +++ b/app/src/main/res/layout/activity_polycentric_backup.xml @@ -76,4 +76,15 @@ app:buttonIcon="@drawable/ic_copy" android:layout_marginTop="8dp" /> + + \ No newline at end of file