casting: undo formatting to VideoDetailView

This commit is contained in:
Marcus Hanestad 2025-09-04 13:41:11 +02:00
commit feeba10429

View file

@ -576,7 +576,9 @@ class VideoDetailView : ConstraintLayout {
if(chapter?.type == ChapterType.SKIPPABLE) { if(chapter?.type == ChapterType.SKIPPABLE) {
_layoutSkip.visibility = VISIBLE; _layoutSkip.visibility = VISIBLE;
} else if(chapter?.type == ChapterType.SKIP || chapter?.type == ChapterType.SKIPONCE) { } else if(chapter?.type == ChapterType.SKIP || chapter?.type == ChapterType.SKIPONCE) {
if (!StateCasting.instance.videoSeekTo(chapter.timeEnd)) { if (StateCasting.instance.activeDevice != null) {
StateCasting.instance.videoSeekTo(chapter.timeEnd)
} else {
_player.seekTo((chapter.timeEnd * 1000).toLong()); _player.seekTo((chapter.timeEnd * 1000).toLong());
} }
@ -680,7 +682,9 @@ class VideoDetailView : ConstraintLayout {
} }
else -> {} else -> {}
} }
} };
updatePillButtonVisibilities();
StateCasting.instance.onActiveDevicePlayChanged.subscribe(this) { StateCasting.instance.onActiveDevicePlayChanged.subscribe(this) {
val activeDevice = StateCasting.instance.activeDevice; val activeDevice = StateCasting.instance.activeDevice;
@ -704,8 +708,6 @@ class VideoDetailView : ConstraintLayout {
} }
}; };
updatePillButtonVisibilities();
_cast.onTimeJobTimeChanged_s.subscribe { _cast.onTimeJobTimeChanged_s.subscribe {
if (_isCasting) { if (_isCasting) {
setLastPositionMilliseconds((it * 1000.0).toLong(), true); setLastPositionMilliseconds((it * 1000.0).toLong(), true);
@ -883,7 +885,7 @@ class VideoDetailView : ConstraintLayout {
if (ad != null) { if (ad != null) {
val currentChapter = _cast.getCurrentChapter((ad.time * 1000).toLong()); val currentChapter = _cast.getCurrentChapter((ad.time * 1000).toLong());
if(currentChapter?.type == ChapterType.SKIPPABLE) { if(currentChapter?.type == ChapterType.SKIPPABLE) {
ad.seekTo(currentChapter.timeEnd); StateCasting.instance.videoSeekTo(currentChapter.timeEnd);
} }
} else { } else {
val currentChapter = _player.getCurrentChapter(_player.position); val currentChapter = _player.getCurrentChapter(_player.position);
@ -985,11 +987,11 @@ class VideoDetailView : ConstraintLayout {
} }
}, },
_chapters?.let { _chapters?.let {
if(it != null && it.size > 0) if(it != null && it.size > 0)
RoundButton(context, R.drawable.ic_list, "Chapters", TAG_CHAPTERS) { RoundButton(context, R.drawable.ic_list, "Chapters", TAG_CHAPTERS) {
showChaptersUI(); showChaptersUI();
} }
else null else null
}, },
if(video?.isLive ?: false) if(video?.isLive ?: false)
RoundButton(context, R.drawable.ic_chat, context.getString(R.string.live_chat), TAG_LIVECHAT) { RoundButton(context, R.drawable.ic_chat, context.getString(R.string.live_chat), TAG_LIVECHAT) {
@ -1002,6 +1004,17 @@ class VideoDetailView : ConstraintLayout {
} }
} }
_slideUpOverlay?.hide(); _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, } 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 (!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 (!isAudioOnlyUserAction) {
@ -1023,14 +1036,14 @@ class VideoDetailView : ConstraintLayout {
}; };
} }
else null, else null,
RoundButton(context, R.drawable.ic_share, context.getString(R.string.share), TAG_SHARE) { RoundButton(context, R.drawable.ic_share, context.getString(R.string.share), TAG_SHARE) {
video?.let { video?.let {
Logger.i(TAG, "Share preventPictureInPicture = true"); Logger.i(TAG, "Share preventPictureInPicture = true");
preventPictureInPicture = true; preventPictureInPicture = true;
shareVideo(); shareVideo();
}; };
_slideUpOverlay?.hide(); _slideUpOverlay?.hide();
}, },
if(!isLimitedVersion) if(!isLimitedVersion)
RoundButton(context, R.drawable.ic_screen_share, context.getString(R.string.overlay), TAG_OVERLAY) { RoundButton(context, R.drawable.ic_screen_share, context.getString(R.string.overlay), TAG_OVERLAY) {
this.startPictureInPicture(); this.startPictureInPicture();
@ -1126,19 +1139,23 @@ class VideoDetailView : ConstraintLayout {
//Lifecycle //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() { fun onResume() {
Logger.v(TAG, "onResume"); Logger.v(TAG, "onResume");
_onPauseCalled = false; _onPauseCalled = false;
val wasLoginCall = isLoginStop;
isLoginStop = false;
Logger.i(TAG, "_video: ${video?.name ?: "no video"}"); Logger.i(TAG, "_video: ${video?.name ?: "no video"}");
Logger.i(TAG, "_didStop: $_didStop"); Logger.i(TAG, "_didStop: $_didStop");
//Recover cancelled loads //Recover cancelled loads
if(video == null) { if(video == null) {
val t = (lastPositionMilliseconds / 1000.0f).roundToLong(); val t = (lastPositionMilliseconds / 1000.0f).roundToLong();
if(_searchVideo != null) if(_searchVideo != null && !wasLoginCall)
setVideoOverview(_searchVideo!!, true, t); setVideoOverview(_searchVideo!!, true, t);
else if(_url != null) else if(_url != null && !wasLoginCall)
setVideo(_url!!, t, _playWhenReady); setVideo(_url!!, t, _playWhenReady);
} }
else if(_didStop) { else if(_didStop) {
@ -1155,6 +1172,9 @@ class VideoDetailView : ConstraintLayout {
isAudioOnlyUserAction = false; isAudioOnlyUserAction = false;
_buttonPins.getButtonByTag(TAG_BACKGROUND)?.text?.text = resources.getString(R.string.background); _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) if(!_player.isFitMode && !_player.isFullScreen && !fragment.isInPictureInPicture)
_player.fitHeight(); _player.fitHeight();
@ -1167,7 +1187,7 @@ class VideoDetailView : ConstraintLayout {
_onPauseCalled = true; _onPauseCalled = true;
_taskLoadVideo.cancel(); _taskLoadVideo.cancel();
if (StateCasting.instance.isCasting) if(StateCasting.instance.isCasting)
return; return;
if(isAudioOnlyUserAction) if(isAudioOnlyUserAction)
@ -1200,6 +1220,7 @@ class VideoDetailView : ConstraintLayout {
_taskLoadVideo.cancel(); _taskLoadVideo.cancel();
handleStop(); handleStop();
_didStop = true; _didStop = true;
onShouldEnterPictureInPictureChanged.emit()
Logger.i(TAG, "_didStop set to true"); Logger.i(TAG, "_didStop set to true");
StatePlayer.instance.rotationLock = false; StatePlayer.instance.rotationLock = false;
@ -1952,7 +1973,8 @@ class VideoDetailView : ConstraintLayout {
return; return;
} }
if (!StateCasting.instance.isCasting) { val isCasting = StateCasting.instance.isCasting
if (!isCasting) {
setCastEnabled(false); setCastEnabled(false);
val isLimitedVersion = StatePlatform.instance.getContentClientOrNull(video.url)?.let { val isLimitedVersion = StatePlatform.instance.getContentClientOrNull(video.url)?.let {
@ -2023,8 +2045,8 @@ class VideoDetailView : ConstraintLayout {
private suspend fun castIfAvailable(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoSource?, audioSource: IAudioSource?, subtitleSource: ISubtitleSource?, resumePositionMs: Long, speed: Double?) { private suspend fun castIfAvailable(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoSource?, audioSource: IAudioSource?, subtitleSource: ISubtitleSource?, resumePositionMs: Long, speed: Double?) {
try { try {
val plugin = if (videoSource is JSSource) videoSource.getUnderlyingPlugin() val plugin = if (videoSource is JSSource) videoSource.getUnderlyingPlugin()
else if (audioSource is JSSource) audioSource.getUnderlyingPlugin() else if (audioSource is JSSource) audioSource.getUnderlyingPlugin()
else null else null
val startId = plugin?.getUnderlyingPlugin()?.runtimeId val startId = plugin?.getUnderlyingPlugin()?.runtimeId
try { try {
@ -2227,9 +2249,7 @@ class VideoDetailView : ConstraintLayout {
} }
} }
val currentPlaybackRate = (if (_isCasting) { val currentPlaybackRate = (if (_isCasting) StateCasting.instance.activeDevice?.speed else _player.getPlaybackRate()) ?: 1.0
StateCasting.instance.activeDevice?.speed
} else _player.getPlaybackRate()) ?: 1.0
_overlay_quality_selector?.groupItems?.firstOrNull { it is SlideUpMenuButtonList && it.id == "playback_rate" }?.let { _overlay_quality_selector?.groupItems?.firstOrNull { it is SlideUpMenuButtonList && it.id == "playback_rate" }?.let {
(it as SlideUpMenuButtonList).setSelected(currentPlaybackRate.toString()) (it as SlideUpMenuButtonList).setSelected(currentPlaybackRate.toString())
}; };
@ -2347,15 +2367,11 @@ class VideoDetailView : ConstraintLayout {
?.distinct() ?.distinct()
?.toList() ?: listOf() else audioSources?.toList() ?: listOf(); ?.toList() ?: listOf() else audioSources?.toList() ?: listOf();
val canSetSpeed = !_isCasting || StateCasting.instance.activeDevice?.canSetSpeed() ?: false val canSetSpeed = !_isCasting || StateCasting.instance.activeDevice?.canSetSpeed() == true
val currentPlaybackRate = if (_isCasting) { val currentPlaybackRate = if (_isCasting) StateCasting.instance.activeDevice?.speed else _player.getPlaybackRate()
StateCasting.instance.activeDevice?.speed
} else {
_player.getPlaybackRate()
}
val qualityPlaybackSpeedTitle = if (canSetSpeed) SlideUpMenuTitle(this.context).apply { setTitle(context.getString(R.string.playback_rate) + " (${String.format("%.2f", currentPlaybackRate)})"); } else null; val qualityPlaybackSpeedTitle = if (canSetSpeed) SlideUpMenuTitle(this.context).apply { setTitle(context.getString(R.string.playback_rate) + " (${String.format("%.2f", currentPlaybackRate)})"); } else null;
_overlay_quality_selector = SlideUpMenuOverlay(this.context, _overlay_quality_container, context.getString( _overlay_quality_selector = SlideUpMenuOverlay(this.context, _overlay_quality_container, context.getString(
R.string.quality), null, true, R.string.quality), null, true,
qualityPlaybackSpeedTitle, qualityPlaybackSpeedTitle,
if (canSetSpeed) SlideUpMenuButtonList(this.context, null, "playback_rate").apply { if (canSetSpeed) SlideUpMenuButtonList(this.context, null, "playback_rate").apply {
val playbackSpeeds = Settings.instance.playback.getPlaybackSpeeds(); val playbackSpeeds = Settings.instance.playback.getPlaybackSpeeds();
@ -2366,9 +2382,7 @@ class VideoDetailView : ConstraintLayout {
setButtons(playbackLabels, String.format(Locale.US, format, currentPlaybackRate)); setButtons(playbackLabels, String.format(Locale.US, format, currentPlaybackRate));
onClick.subscribe { v -> onClick.subscribe { v ->
val currentPlaybackSpeed = if (_isCasting) { val currentPlaybackSpeed = if (_isCasting) StateCasting.instance.activeDevice?.speed else _player.getPlaybackRate();
StateCasting.instance.activeDevice?.speed
} else _player.getPlaybackRate();
var playbackSpeedString = v; var playbackSpeedString = v;
val stepSpeed = Settings.instance.playback.getPlaybackSpeedStep(); val stepSpeed = Settings.instance.playback.getPlaybackSpeedStep();
if(v == "+") if(v == "+")
@ -2376,9 +2390,14 @@ class VideoDetailView : ConstraintLayout {
else if(v == "-") else if(v == "-")
playbackSpeedString = String.format(Locale.US, "%.2f", Math.max(0.1, (currentPlaybackSpeed?.toDouble() ?: 1.0) - stepSpeed)).toString(); playbackSpeedString = String.format(Locale.US, "%.2f", Math.max(0.1, (currentPlaybackSpeed?.toDouble() ?: 1.0) - stepSpeed)).toString();
val newPlaybackSpeed = playbackSpeedString.toDouble(); val newPlaybackSpeed = playbackSpeedString.toDouble();
if (_isCasting && StateCasting.instance.activeDevice?.canSetSpeed() ?: false) { if (_isCasting) {
val ad = StateCasting.instance.activeDevice ?: return@subscribe
if (!ad.canSetSpeed()) {
return@subscribe
}
qualityPlaybackSpeedTitle?.setTitle(context.getString(R.string.playback_rate) + " (${String.format(Locale.US, "%.2f", newPlaybackSpeed)})"); qualityPlaybackSpeedTitle?.setTitle(context.getString(R.string.playback_rate) + " (${String.format(Locale.US, "%.2f", newPlaybackSpeed)})");
StateCasting.instance.changeSpeed(newPlaybackSpeed) ad.changeSpeed(newPlaybackSpeed)
setSelected(playbackSpeedString); setSelected(playbackSpeedString);
} else { } else {
qualityPlaybackSpeedTitle?.setTitle(context.getString(R.string.playback_rate) + " (${String.format(Locale.US, "%.2f", newPlaybackSpeed)})"); qualityPlaybackSpeedTitle?.setTitle(context.getString(R.string.playback_rate) + " (${String.format(Locale.US, "%.2f", newPlaybackSpeed)})");
@ -2495,8 +2514,9 @@ class VideoDetailView : ConstraintLayout {
private fun handlePlay() { private fun handlePlay() {
Logger.i(TAG, "handlePlay") Logger.i(TAG, "handlePlay")
if (!StateCasting.instance.resumeVideo()) { if (!StateCasting.instance.resumeVideo()) {
_player.play() _player.play();
} }
onShouldEnterPictureInPictureChanged.emit()
//TODO: This was needed because handleLowerVolume was done. //TODO: This was needed because handleLowerVolume was done.
//_player.setVolume(1.0f); //_player.setVolume(1.0f);
@ -2511,26 +2531,28 @@ class VideoDetailView : ConstraintLayout {
private fun handlePause() { private fun handlePause() {
Logger.i(TAG, "handlePause") Logger.i(TAG, "handlePause")
if (!StateCasting.instance.pauseVideo()) { if (!StateCasting.instance.pauseVideo()) {
_player.pause() _player.pause();
} }
onShouldEnterPictureInPictureChanged.emit()
} }
private fun handleSeek(ms: Long) { private fun handleSeek(ms: Long) {
Logger.i(TAG, "handleSeek(ms=$ms)") Logger.i(TAG, "handleSeek(ms=$ms)")
if (!StateCasting.instance.videoSeekTo(ms.toDouble() / 1000.0)) { if (!StateCasting.instance.videoSeekTo(ms.toDouble() / 1000.0)) {
_player.seekTo(ms) _player.seekTo(ms);
} }
} }
private fun handleStop() { private fun handleStop() {
Logger.i(TAG, "handleStop") Logger.i(TAG, "handleStop")
if (!StateCasting.instance.stopVideo()) { if (!StateCasting.instance.stopVideo()) {
_player.stop() _player.stop();
} }
} }
private fun handlePlayChanged(playing: Boolean) { private fun handlePlayChanged(playing: Boolean) {
Logger.i(TAG, "handlePlayChanged(playing=$playing)") Logger.i(TAG, "handlePlayChanged(playing=$playing)")
if (StateCasting.instance.isCasting) { val ad = StateCasting.instance.activeDevice;
if (ad != null) {
_cast.setIsPlaying(playing); _cast.setIsPlaying(playing);
} else { } else {
StatePlayer.instance.updateMediaSession( null); StatePlayer.instance.updateMediaSession( null);
@ -2572,21 +2594,11 @@ class VideoDetailView : ConstraintLayout {
fragment.lifecycleScope.launch(Dispatchers.Main) { fragment.lifecycleScope.launch(Dispatchers.Main) {
try { try {
if (StateCasting.instance.activeDevice != null) { val d = StateCasting.instance.activeDevice;
val expectedCurrentTime = StateCasting.instance.activeDevice?.expectedCurrentTime ?: 0.0 if (d != null && d.connectionState == CastConnectionState.CONNECTED)
val speed = StateCasting.instance.activeDevice?.speed ?: 1.0 castIfAvailable(context.contentResolver, video, videoSource, _lastAudioSource, _lastSubtitleSource, (d.expectedCurrentTime * 1000.0).toLong(), d.speed);
castIfAvailable( else if(!_player.swapSources(videoSource, _lastAudioSource, true, true, true))
context.contentResolver,
video,
videoSource,
_lastAudioSource,
_lastSubtitleSource,
(expectedCurrentTime * 1000.0).toLong(),
speed
)
} else if(!_player.swapSources(videoSource, _lastAudioSource, true, true, true)) {
_player.hideControls(false); //TODO: Disable player? _player.hideControls(false); //TODO: Disable player?
}
} catch (e: Throwable) { } catch (e: Throwable) {
Logger.e(TAG, "handleSelectVideoTrack failed", e) Logger.e(TAG, "handleSelectVideoTrack failed", e)
} }
@ -2603,21 +2615,11 @@ class VideoDetailView : ConstraintLayout {
fragment.lifecycleScope.launch(Dispatchers.Main) { fragment.lifecycleScope.launch(Dispatchers.Main) {
try { try {
if (StateCasting.instance.activeDevice != null) { val d = StateCasting.instance.activeDevice;
val expectedCurrentTime = StateCasting.instance.activeDevice?.expectedCurrentTime ?: 0.0 if (d != null && d.connectionState == CastConnectionState.CONNECTED)
val speed = StateCasting.instance.activeDevice?.speed ?: 1.0 castIfAvailable(context.contentResolver, video, _lastVideoSource, audioSource, _lastSubtitleSource, (d.expectedCurrentTime * 1000.0).toLong(), d.speed)
castIfAvailable( else if (!_player.swapSources(_lastVideoSource, audioSource, true, true, true))
context.contentResolver,
video,
_lastVideoSource,
audioSource,
_lastSubtitleSource,
(expectedCurrentTime * 1000.0).toLong(),
speed
)
} else if (!_player.swapSources(_lastVideoSource, audioSource, true, true, true)) {
_player.hideControls(false); //TODO: Disable player? _player.hideControls(false); //TODO: Disable player?
}
} catch (e: Throwable) { } catch (e: Throwable) {
Logger.e(TAG, "handleSelectAudioTrack failed", e) Logger.e(TAG, "handleSelectAudioTrack failed", e)
} }
@ -2635,19 +2637,10 @@ class VideoDetailView : ConstraintLayout {
fragment.lifecycleScope.launch(Dispatchers.Main) { fragment.lifecycleScope.launch(Dispatchers.Main) {
try { try {
if (StateCasting.instance.activeDevice != null) { val d = StateCasting.instance.activeDevice;
val expectedCurrentTime = StateCasting.instance.activeDevice?.expectedCurrentTime ?: 0.0 if (d != null && d.connectionState == CastConnectionState.CONNECTED)
val speed = StateCasting.instance.activeDevice?.speed ?: 1.0 castIfAvailable(context.contentResolver, video, _lastVideoSource, _lastAudioSource, toSet, (d.expectedCurrentTime * 1000.0).toLong(), d.speed);
castIfAvailable( else {
context.contentResolver,
video,
_lastVideoSource,
_lastAudioSource,
toSet,
(expectedCurrentTime * 1000.0).toLong(),
speed
)
} else {
_player.swapSubtitles(toSet); _player.swapSubtitles(toSet);
} }
} catch (e: Throwable) { } catch (e: Throwable) {
@ -3287,8 +3280,13 @@ class VideoDetailView : ConstraintLayout {
val id = e.config.let { if(it is SourcePluginConfig) it.id else null }; val id = e.config.let { if(it is SourcePluginConfig) it.id else null };
val didLogin = if(id == null) val didLogin = if(id == null)
false false
else StatePlugins.instance.loginPlugin(context, id) { else {
fetchVideo(); isLoginStop = true;
StatePlugins.instance.loginPlugin(context, id) {
fragment.lifecycleScope.launch(Dispatchers.Main) {
fetchVideo();
}
}
} }
if(!didLogin) if(!didLogin)
UIDialogs.showDialogOk(context, R.drawable.ic_error_pred, "Failed to login"); UIDialogs.showDialogOk(context, R.drawable.ic_error_pred, "Failed to login");
@ -3313,7 +3311,7 @@ class VideoDetailView : ConstraintLayout {
UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_video_scriptimplementationexception), it, ::fetchVideo, null, fragment); UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_video_scriptimplementationexception), it, ::fetchVideo, null, fragment);
} else { } else {
StateAnnouncement.instance.registerAnnouncement(video?.id?.value + "_Q_INVALIDVIDEO", context.getString(R.string.invalid_video), context.getString( StateAnnouncement.instance.registerAnnouncement(video?.id?.value + "_Q_INVALIDVIDEO", context.getString(R.string.invalid_video), context.getString(
R.string.there_was_an_invalid_video_in_your_queue_videoname_by_authorname_playback_was_skipped).replace("{videoName}", video?.name ?: "").replace("{authorName}", video?.author?.name ?: ""), AnnouncementType.SESSION) R.string.there_was_an_invalid_video_in_your_queue_videoname_by_authorname_playback_was_skipped).replace("{videoName}", video?.name ?: "").replace("{authorName}", video?.author?.name ?: ""), AnnouncementType.SESSION)
} }
} }
.exception<ScriptAgeException> { .exception<ScriptAgeException> {
@ -3466,6 +3464,7 @@ class VideoDetailView : ConstraintLayout {
const val TAG_SHARE = "share"; const val TAG_SHARE = "share";
const val TAG_OVERLAY = "overlay"; const val TAG_OVERLAY = "overlay";
const val TAG_LIVECHAT = "livechat"; const val TAG_LIVECHAT = "livechat";
const val TAG_VODCHAT = "vodchat";
const val TAG_CHAPTERS = "chapters"; const val TAG_CHAPTERS = "chapters";
const val TAG_OPEN = "open"; const val TAG_OPEN = "open";
const val TAG_SEND_TO_DEVICE = "send_to_device"; const val TAG_SEND_TO_DEVICE = "send_to_device";