From 0fd83cbd74a2dcd1054125405df16d3b1c2c85e4 Mon Sep 17 00:00:00 2001 From: Marcus Hanestad Date: Thu, 14 Aug 2025 11:24:13 +0200 Subject: [PATCH] casting: catch exceptions for playback control functions # Conflicts: # app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt # app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt --- .../dialogs/ConnectCastingDialog.kt | 17 ++++-- .../dialogs/ConnectedCastingDialog.kt | 31 ++++++---- .../experimental_casting/CastingDevice.kt | 8 ++- .../experimental_casting/StateCasting.kt | 18 +++++- .../mainactivity/main/VideoDetailFragment.kt | 7 ++- .../mainactivity/main/VideoDetailView.kt | 60 ++++++------------- .../futo/platformplayer/states/StateApp.kt | 7 ++- .../views/adapters/DeviceViewHolder.kt | 14 +++-- .../views/casting/CastButton.kt | 8 ++- .../platformplayer/views/casting/CastView.kt | 13 +++- 10 files changed, 104 insertions(+), 79 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/ConnectCastingDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/ConnectCastingDialog.kt index 204b7df6..54dc7bc4 100644 --- a/app/src/main/java/com/futo/platformplayer/dialogs/ConnectCastingDialog.kt +++ b/app/src/main/java/com/futo/platformplayer/dialogs/ConnectCastingDialog.kt @@ -128,7 +128,7 @@ class ConnectCastingDialog(context: Context?) : AlertDialog(context) { synchronized(ExpStateCasting.instance.devices) { _devices.addAll(ExpStateCasting.instance.devices.values.map { it.device.name() }) } - _rememberedDevices.addAll(StateCasting.instance.getRememberedCastingDeviceNames()) + _rememberedDevices.addAll(ExpStateCasting.instance.getRememberedCastingDeviceNames()) } else { synchronized(StateCasting.instance.devices) { _devices.addAll(StateCasting.instance.devices.values.mapNotNull { it.name }) @@ -203,10 +203,17 @@ class ConnectCastingDialog(context: Context?) : AlertDialog(context) { override fun dismiss() { super.dismiss() (_imageLoader.drawable as Animatable?)?.stop() - StateCasting.instance.onDeviceAdded.remove(this) - StateCasting.instance.onDeviceChanged.remove(this) - StateCasting.instance.onDeviceRemoved.remove(this) - StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this) + if (Settings.instance.casting.experimentalCasting) { + ExpStateCasting.instance.onDeviceAdded.remove(this) + ExpStateCasting.instance.onDeviceChanged.remove(this) + ExpStateCasting.instance.onDeviceRemoved.remove(this) + ExpStateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this) + } else { + StateCasting.instance.onDeviceAdded.remove(this) + StateCasting.instance.onDeviceChanged.remove(this) + StateCasting.instance.onDeviceRemoved.remove(this) + StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this) + } } private fun updateUnifiedList() { diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/ConnectedCastingDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/ConnectedCastingDialog.kt index 042885ed..1dc9084b 100644 --- a/app/src/main/java/com/futo/platformplayer/dialogs/ConnectedCastingDialog.kt +++ b/app/src/main/java/com/futo/platformplayer/dialogs/ConnectedCastingDialog.kt @@ -24,6 +24,7 @@ import com.futo.platformplayer.experimental_casting.ExpStateCasting import com.futo.platformplayer.fragment.mainactivity.main.VideoDetailFragment import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.views.adapters.GenericCastingDevice import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider.OnChangeListener import kotlinx.coroutines.Dispatchers @@ -49,7 +50,7 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) { private lateinit var _buttonStop: ImageButton; private lateinit var _buttonNext: ImageButton; - private var _device: CastingDevice? = null; + private var _device: GenericCastingDevice? = null; override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState); @@ -178,7 +179,7 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) { } ExpStateCasting.instance.onActiveDeviceTimeChanged.remove(this) - StateCasting.instance.onActiveDeviceTimeChanged.subscribe { + ExpStateCasting.instance.onActiveDeviceTimeChanged.subscribe { _sliderPosition.value = it.toFloat().coerceAtLeast(0.0f).coerceAtMost(_sliderPosition.valueTo) } @@ -189,16 +190,18 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) { _sliderPosition.valueTo = dur } - _device = StateCasting.instance.activeDevice - val d = _device - val isConnected = d != null && d.connectionState == CastConnectionState.CONNECTED + val ad = ExpStateCasting.instance.activeDevice + if (ad != null) { + _device = GenericCastingDevice.Experimental(ad) + } + val isConnected = ad != null && ad.connectionState == com.futo.platformplayer.experimental_casting.CastConnectionState.CONNECTED setLoading(!isConnected) ExpStateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { _, connectionState -> StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { setLoading(connectionState != com.futo.platformplayer.experimental_casting.CastConnectionState.CONNECTED) } - updateDevice(); - }; + updateDevice() + } } else { StateCasting.instance.onActiveDeviceVolumeChanged.remove(this); StateCasting.instance.onActiveDeviceVolumeChanged.subscribe { @@ -217,14 +220,16 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) { _sliderPosition.valueTo = dur }; - _device = StateCasting.instance.activeDevice; - val d = _device; - val isConnected = d != null && d.connectionState == CastConnectionState.CONNECTED; + val ad = StateCasting.instance.activeDevice + if (ad != null) { + _device = GenericCastingDevice.Normal(ad) + } + val isConnected = ad != null && ad.connectionState == CastConnectionState.CONNECTED; setLoading(!isConnected); StateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { _, connectionState -> StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { setLoading(connectionState != CastConnectionState.CONNECTED); }; - updateDevice(); - }; + updateDevice() + } } updateDevice(); @@ -235,12 +240,12 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) { StateCasting.instance.onActiveDeviceVolumeChanged.remove(this); StateCasting.instance.onActiveDeviceDurationChanged.remove(this); StateCasting.instance.onActiveDeviceTimeChanged.remove(this); - _device = null; StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); ExpStateCasting.instance.onActiveDeviceVolumeChanged.remove(this); ExpStateCasting.instance.onActiveDeviceDurationChanged.remove(this); ExpStateCasting.instance.onActiveDeviceTimeChanged.remove(this); ExpStateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); + _device = null; } private fun updateDevice() { diff --git a/app/src/main/java/com/futo/platformplayer/experimental_casting/CastingDevice.kt b/app/src/main/java/com/futo/platformplayer/experimental_casting/CastingDevice.kt index e24a9ec8..8be10138 100644 --- a/app/src/main/java/com/futo/platformplayer/experimental_casting/CastingDevice.kt +++ b/app/src/main/java/com/futo/platformplayer/experimental_casting/CastingDevice.kt @@ -110,7 +110,7 @@ class CastingDeviceHandle { ) { try { device.loadVideo(contentType, contentId, resumePosition, speed) - } catch (e: Exception) { + } catch (e: Throwable) { Logger.e("CastingDevice", "Failed to load video: $e") } } @@ -122,7 +122,11 @@ class CastingDeviceHandle { duration: Double, speed: Double? ) { - device.loadContent(contentType, content, resumePosition, duration, speed) + try { + device.loadContent(contentType, content, resumePosition, duration, speed) + } catch (e: Throwable) { + Logger.e("CastingDevice", "Failed to load content: $e") + } } } diff --git a/app/src/main/java/com/futo/platformplayer/experimental_casting/StateCasting.kt b/app/src/main/java/com/futo/platformplayer/experimental_casting/StateCasting.kt index 90270089..c34c6204 100644 --- a/app/src/main/java/com/futo/platformplayer/experimental_casting/StateCasting.kt +++ b/app/src/main/java/com/futo/platformplayer/experimental_casting/StateCasting.kt @@ -128,7 +128,11 @@ class ExpStateCasting { _resumeCastingDevice = ad.device.getDeviceInfo() Log.i(TAG, "_resumeCastingDevice set to '${ad.device.name()}'") Logger.i(TAG, "Stopping active device because of onStop.") - ad.device.disconnect() + try { + ad.device.disconnect() + } catch (e: Throwable) { + Logger.e(TAG, "Failed to disconnect from device: $e") + } } fun onResume() { @@ -248,7 +252,11 @@ class ExpStateCasting { device.eventHandler.onTimeChanged.clear(); device.eventHandler.onVolumeChanged.clear(); device.eventHandler.onDurationChanged.clear(); - ad.device.disconnect(); + try { + ad.device.disconnect(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to disconnect from device: $e") + } } device.eventHandler.onConnectionStateChanged.subscribe { castConnectionState -> @@ -441,6 +449,10 @@ class ExpStateCasting { return Settings.instance.casting.alwaysProxyRequests || deviceHandle.device.castingProtocol() != ProtocolType.F_CAST || hasRequestModifier } + fun cancel() { + _castId.incrementAndGet() + } + suspend fun castIfAvailable( contentResolver: ContentResolver, video: IPlatformVideoDetails, @@ -541,7 +553,7 @@ class ExpStateCasting { resumePosition, video.duration.toDouble(), speed - ); + ) } else if (audioSource is IAudioUrlSource) { val audioPath = "/audio-${id}" val audioUrl = if (proxyStreams) url + audioPath else audioSource.getAudioUrl(); 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 af228bf7..9293be3b 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 @@ -30,6 +30,7 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.casting.StateCasting import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.experimental_casting.ExpStateCasting import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.PlatformVideoWithTime import com.futo.platformplayer.models.UrlVideoWithTime @@ -437,7 +438,7 @@ class VideoDetailFragment() : MainFragment() { fun onUserLeaveHint() { val viewDetail = _viewDetail; - Logger.i(TAG, "onUserLeaveHint preventPictureInPicture=${viewDetail?.preventPictureInPicture} isCasting=${StateCasting.instance.isCasting} isBackgroundPictureInPicture=${Settings.instance.playback.isBackgroundPictureInPicture()} allowBackground=${viewDetail?.isAudioOnlyUserAction}"); + Logger.i(TAG, "onUserLeaveHint preventPictureInPicture=${viewDetail?.preventPictureInPicture} isCasting=${StateCasting.instance.isCasting} isBackgroundPictureInPicture=${Settings.instance.playback.isBackgroundPictureInPicture()} allowBackground=${viewDetail?.allowBackground}"); if (viewDetail === null) { return @@ -446,7 +447,7 @@ class VideoDetailFragment() : MainFragment() { if (viewDetail.shouldEnterPictureInPicture) { _leavingPiP = false } - if(Build.VERSION.SDK_INT < Build.VERSION_CODES.S && viewDetail.preventPictureInPicture == false && !StateCasting.instance.isCasting && Settings.instance.playback.isBackgroundPictureInPicture() && !viewDetail.isAudioOnlyUserAction) { + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.S && viewDetail.preventPictureInPicture == false && !StateCasting.instance.isCasting && Settings.instance.playback.isBackgroundPictureInPicture() && !viewDetail.allowBackground) { val params = _viewDetail?.getPictureInPictureParams(); if(params != null) { Logger.i(TAG, "enterPictureInPictureMode") @@ -526,7 +527,7 @@ class VideoDetailFragment() : MainFragment() { private fun stopIfRequired() { var shouldStop = true; - if (_viewDetail?.isAudioOnlyUserAction == true) { + if (_viewDetail?.allowBackground == true) { shouldStop = false; } else if (Settings.instance.playback.isBackgroundPictureInPicture() && !_leavingPiP) { shouldStop = false; 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 594a7c46..6a2c26eb 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 @@ -325,7 +325,7 @@ class VideoDetailView : ConstraintLayout { val onEnterPictureInPicture = Event0(); val onVideoChanged = Event2() - var isAudioOnlyUserAction: Boolean = false + var allowBackground: Boolean = false private set(value) { if (field != value) { field = value @@ -337,7 +337,7 @@ class VideoDetailView : ConstraintLayout { get() = !preventPictureInPicture && !StateCasting.instance.isCasting && Settings.instance.playback.isBackgroundPictureInPicture() && - !isAudioOnlyUserAction && + !allowBackground && isPlaying val onShouldEnterPictureInPictureChanged = Event0(); @@ -808,7 +808,7 @@ class VideoDetailView : ConstraintLayout { MediaControlReceiver.onBackgroundReceived.subscribe(this) { Logger.i(TAG, "MediaControlReceiver.onBackgroundReceived") _player.switchToAudioMode(video); - isAudioOnlyUserAction = true; + allowBackground = true; StateApp.instance.contextOrNull?.let { try { if (it is MainActivity) { @@ -1052,26 +1052,15 @@ class VideoDetailView : ConstraintLayout { } } _slideUpOverlay?.hide(); - } else if(video is JSVideoDetails && (video as JSVideoDetails).hasVODEvents()) - RoundButton(context, R.drawable.ic_chat, context.getString(R.string.vod_chat), TAG_VODCHAT) { - video?.let { - try { - loadVODChat(it); - } - catch(ex: Throwable) { - Logger.e(TAG, "Failed to reopen vod chat", ex); - } - } - _slideUpOverlay?.hide(); } else null, - if (!isLimitedVersion) RoundButton(context, R.drawable.ic_screen_share, if (isAudioOnlyUserAction) context.getString(R.string.background_revert) else context.getString(R.string.background), TAG_BACKGROUND) { - if (!isAudioOnlyUserAction) { + if (!isLimitedVersion) RoundButton(context, R.drawable.ic_screen_share, if (allowBackground) context.getString(R.string.background_revert) else context.getString(R.string.background), TAG_BACKGROUND) { + if (!allowBackground) { _player.switchToAudioMode(video); - isAudioOnlyUserAction = true; + allowBackground = true; it.text.text = resources.getString(R.string.background_revert); } else { _player.switchToVideoMode(); - isAudioOnlyUserAction = false; + allowBackground = false; it.text.text = resources.getString(R.string.background); } _slideUpOverlay?.hide(); @@ -1187,23 +1176,19 @@ class VideoDetailView : ConstraintLayout { //Lifecycle - var isLoginStop = false; //TODO: This is a bit jank, but easiest solution for now without reworking flow. (Alternatively, fix MainActivity getting stopped/disposing video) fun onResume() { Logger.v(TAG, "onResume"); _onPauseCalled = false; - val wasLoginCall = isLoginStop; - isLoginStop = false; - Logger.i(TAG, "_video: ${video?.name ?: "no video"}"); Logger.i(TAG, "_didStop: $_didStop"); //Recover cancelled loads if(video == null) { val t = (lastPositionMilliseconds / 1000.0f).roundToLong(); - if(_searchVideo != null && !wasLoginCall) + if(_searchVideo != null) setVideoOverview(_searchVideo!!, true, t); - else if(_url != null && !wasLoginCall) + else if(_url != null) setVideo(_url!!, t, _playWhenReady); } else if(_didStop) { @@ -1215,14 +1200,11 @@ class VideoDetailView : ConstraintLayout { if(_player.isAudioMode) { //Requested behavior to leave it in audio mode. leaving it commented if it causes issues, revert? - if(!isAudioOnlyUserAction) { + if(!allowBackground) { _player.switchToVideoMode(); - isAudioOnlyUserAction = false; + allowBackground = false; _buttonPins.getButtonByTag(TAG_BACKGROUND)?.text?.text = resources.getString(R.string.background); } - else { - _buttonPins.getButtonByTag(TAG_BACKGROUND)?.text?.text = resources.getString(R.string.video); - } } if(!_player.isFitMode && !_player.isFullScreen && !fragment.isInPictureInPicture) _player.fitHeight(); @@ -1243,7 +1225,7 @@ class VideoDetailView : ConstraintLayout { return; } - if(isAudioOnlyUserAction) + if(allowBackground) StatePlayer.instance.startOrUpdateMediaSession(context, video); else { when (Settings.instance.playback.backgroundPlay) { @@ -1251,6 +1233,7 @@ class VideoDetailView : ConstraintLayout { 1 -> { if(!(video?.isLive ?: false)) { _player.switchToAudioMode(video); + allowBackground = true; } StatePlayer.instance.startOrUpdateMediaSession(context, video); } @@ -1273,7 +1256,6 @@ class VideoDetailView : ConstraintLayout { _taskLoadVideo.cancel(); handleStop(); _didStop = true; - onShouldEnterPictureInPictureChanged.emit() Logger.i(TAG, "_didStop set to true"); StatePlayer.instance.rotationLock = false; @@ -2048,10 +2030,10 @@ class VideoDetailView : ConstraintLayout { if (isLimitedVersion && _player.isAudioMode) { _player.switchToVideoMode() - isAudioOnlyUserAction = false; + allowBackground = false; } else { val thumbnail = video.thumbnails.getHQThumbnail(); - if ((videoSource == null) && !thumbnail.isNullOrBlank()) // || _player.isAudioMode + if ((videoSource == null || _player.isAudioMode) && !thumbnail.isNullOrBlank()) Glide.with(context).asBitmap().load(thumbnail) .into(object: CustomTarget() { override fun onResourceReady(resource: Bitmap, transition: Transition?) { @@ -2626,7 +2608,6 @@ class VideoDetailView : ConstraintLayout { _player.play(); } } - onShouldEnterPictureInPictureChanged.emit() //TODO: This was needed because handleLowerVolume was done. //_player.setVolume(1.0f); @@ -2649,7 +2630,6 @@ class VideoDetailView : ConstraintLayout { _player.pause() } } - onShouldEnterPictureInPictureChanged.emit() } private fun handleSeek(ms: Long) { Logger.i(TAG, "handleSeek(ms=$ms)") @@ -3483,13 +3463,8 @@ class VideoDetailView : ConstraintLayout { val id = e.config.let { if(it is SourcePluginConfig) it.id else null }; val didLogin = if(id == null) false - else { - isLoginStop = true; - StatePlugins.instance.loginPlugin(context, id) { - fragment.lifecycleScope.launch(Dispatchers.Main) { - fetchVideo(); - } - } + else StatePlugins.instance.loginPlugin(context, id) { + fetchVideo(); } if(!didLogin) UIDialogs.showDialogOk(context, R.drawable.ic_error_pred, "Failed to login"); @@ -3667,7 +3642,6 @@ class VideoDetailView : ConstraintLayout { const val TAG_SHARE = "share"; const val TAG_OVERLAY = "overlay"; const val TAG_LIVECHAT = "livechat"; - const val TAG_VODCHAT = "vodchat"; const val TAG_CHAPTERS = "chapters"; const val TAG_OPEN = "open"; const val TAG_SEND_TO_DEVICE = "send_to_device"; diff --git a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt index 7883b4fd..ed9f8922 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt @@ -37,6 +37,7 @@ import com.futo.platformplayer.casting.StateCasting import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException +import com.futo.platformplayer.experimental_casting.ExpStateCasting import com.futo.platformplayer.fragment.mainactivity.main.HomeFragment import com.futo.platformplayer.fragment.mainactivity.main.SourceDetailFragment import com.futo.platformplayer.logging.AndroidLogConsumer @@ -759,7 +760,11 @@ class StateApp { _connectivityManager?.unregisterNetworkCallback(_connectivityEvents); StatePlayer.instance.closeMediaSession(); - StateCasting.instance.stop(); + if (Settings.instance.casting.experimentalCasting) { + ExpStateCasting.instance.stop() + } else { + StateCasting.instance.stop() + } StateSync.instance.stop(); StatePlayer.dispose(); Companion.dispose(); diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/DeviceViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/DeviceViewHolder.kt index b7bd5400..e23a1622 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/DeviceViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/DeviceViewHolder.kt @@ -73,7 +73,11 @@ class DeviceViewHolder : ViewHolder { is GenericCastingDevice.Experimental -> { if (dev.handle.device.isReady()) { // NOTE: we assume experimental casting is used - ExpStateCasting.instance.activeDevice?.device?.stopCasting() + try { + ExpStateCasting.instance.activeDevice?.device?.stopCasting() + } catch (e: Throwable) { + //Ignored + } ExpStateCasting.instance.connectDevice(dev.handle) onConnect.emit(dev) } else { @@ -125,7 +129,7 @@ class DeviceViewHolder : ViewHolder { _textNotReady.visibility = View.GONE; val dev = StateCasting.instance.activeDevice; - if (dev == d) { + if (dev == d.device) { if (dev.connectionState == CastConnectionState.CONNECTED) { _imageLoader.visibility = View.GONE; _textNotReady.visibility = View.GONE; @@ -180,9 +184,9 @@ class DeviceViewHolder : ViewHolder { } else { _textNotReady.visibility = View.GONE; - val dev = StateCasting.instance.activeDevice; - if (dev == d) { - if (dev.connectionState == CastConnectionState.CONNECTED) { + val dev = ExpStateCasting.instance.activeDevice; + if (dev == d.handle) { + if (dev.connectionState == com.futo.platformplayer.experimental_casting.CastConnectionState.CONNECTED) { _imageLoader.visibility = View.GONE; _textNotReady.visibility = View.GONE; _imagePin.visibility = View.VISIBLE; diff --git a/app/src/main/java/com/futo/platformplayer/views/casting/CastButton.kt b/app/src/main/java/com/futo/platformplayer/views/casting/CastButton.kt index 6f081a8c..7184eea8 100644 --- a/app/src/main/java/com/futo/platformplayer/views/casting/CastButton.kt +++ b/app/src/main/java/com/futo/platformplayer/views/casting/CastButton.kt @@ -12,6 +12,7 @@ import androidx.core.content.ContextCompat import com.futo.platformplayer.R import com.futo.platformplayer.Settings import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.casting.CastConnectionState import com.futo.platformplayer.casting.StateCasting import com.futo.platformplayer.constructs.Event1 @@ -81,7 +82,10 @@ class CastButton : androidx.appcompat.widget.AppCompatImageButton { fun cleanup() { setOnClickListener(null); - StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); - ExpStateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); + if (Settings.instance.casting.experimentalCasting) { + ExpStateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); + } else { + StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); + } } } \ No newline at end of file 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 fdcc3c6c..c64ccc0e 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 @@ -29,6 +29,7 @@ import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.experimental_casting.ExpStateCasting import com.futo.platformplayer.formatDuration +import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StateHistory import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.views.TargetTapLoaderView @@ -103,9 +104,17 @@ class CastView : ConstraintLayout { _speedHoldWasPlaying = d.isPlaying _speedHoldPrevRate = d.speed if (d.device.supportsFeature(DeviceFeature.SET_SPEED)) { - d.device.changeSpeed(Settings.instance.playback.getHoldPlaybackSpeed()) + try { + d.device.changeSpeed(Settings.instance.playback.getHoldPlaybackSpeed()) + } catch (e: Throwable) { + // Ignored + } + } + try { + d.device.resumePlayback() + } catch (e: Throwable) { + // Ignored } - d.device.resumePlayback() } else { val d = StateCasting.instance.activeDevice ?: return@subscribe; _speedHoldWasPlaying = d.isPlaying