From b1c079aaee16e4782d84c8f2c1c24d8c3a9d819f Mon Sep 17 00:00:00 2001 From: Marcus Hanestad Date: Thu, 4 Sep 2025 10:55:10 +0200 Subject: [PATCH] casting: undo formatting --- .../platformplayer/activities/MainActivity.kt | 3 +- .../platformplayer/casting/StateCasting.kt | 921 +++++------------- .../dialogs/ConnectCastingDialog.kt | 36 +- .../dialogs/ConnectedCastingDialog.kt | 27 +- .../mainactivity/main/VideoDetailView.kt | 5 +- .../futo/platformplayer/states/StateApp.kt | 2 +- 6 files changed, 272 insertions(+), 722 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt index 9993d4c3..5e4e1e42 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt @@ -117,6 +117,7 @@ import java.util.LinkedList import java.util.UUID import java.util.concurrent.ConcurrentLinkedQueue + class MainActivity : AppCompatActivity, IWithResultLauncher { //TODO: Move to dimensions @@ -506,7 +507,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { handleIntent(intent); if (Settings.instance.casting.enabled) { - StateCasting.instance.start(this) + StateCasting.instance.start(this); } StatePlatform.instance.onDevSourceChanged.subscribe { diff --git a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt index e57d0de2..44b5167f 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt @@ -58,28 +58,28 @@ import java.util.UUID import java.util.concurrent.atomic.AtomicInteger abstract class StateCasting { - val _castServer = ManagedHttpServer() + val _scopeIO = CoroutineScope(Dispatchers.IO); + val _scopeMain = CoroutineScope(Dispatchers.Main); + private val _storage: CastingDeviceInfoStorage = FragmentedStorage.get(); + + val _castServer = ManagedHttpServer(); + var _started = false; + + var devices: HashMap = hashMapOf(); + val onDeviceAdded = Event1(); + val onDeviceChanged = Event1(); + val onDeviceRemoved = Event1(); + val onActiveDeviceConnectionStateChanged = Event2(); + val onActiveDevicePlayChanged = Event1(); + val onActiveDeviceTimeChanged = Event1(); + val onActiveDeviceDurationChanged = Event1(); + val onActiveDeviceVolumeChanged = Event1(); + var activeDevice: CastingDevice? = null; private var _videoExecutor: JSRequestExecutor? = null private var _audioExecutor: JSRequestExecutor? = null - val _scopeIO = CoroutineScope(Dispatchers.IO); - var _started = false; - private val _storage: CastingDeviceInfoStorage = FragmentedStorage.get(); - val _client = ManagedHttpClient(); - var devices: HashMap = hashMapOf() - val onDeviceAdded = Event1() - val onDeviceChanged = Event1() - val onDeviceRemoved = Event1() - val onActiveDeviceConnectionStateChanged = Event2() - val onActiveDevicePlayChanged = Event1() - val onActiveDeviceTimeChanged = Event1() - val onActiveDeviceDurationChanged = Event1() - val onActiveDeviceVolumeChanged = Event1() - var activeDevice: CastingDevice? = null - val isCasting: Boolean get() = activeDevice != null - var _resumeCastingDevice: CastingDeviceInfo? = null - val _scopeMain = CoroutineScope(Dispatchers.Main) - private val _castingDialogLock = Any(); - private var _currentDialog: AlertDialog? = null; + private val _client = ManagedHttpClient(); + var _resumeCastingDevice: CastingDeviceInfo? = null; + val isCasting: Boolean get() = activeDevice != null; private val _castId = AtomicInteger(0) abstract fun handleUrl(url: String) @@ -121,6 +121,9 @@ abstract class StateCasting { action(); } + private val _castingDialogLock = Any(); + private var _currentDialog: AlertDialog? = null; + @Synchronized fun connectDevice(device: CastingDevice) { if (activeDevice == device) { @@ -130,11 +133,11 @@ abstract class StateCasting { val ad = activeDevice; if (ad != null) { Logger.i(TAG, "Stopping previous device because a new one is being connected.") - device.onConnectionStateChanged.clear() - device.onPlayChanged.clear() - device.onTimeChanged.clear() - device.onVolumeChanged.clear() - device.onDurationChanged.clear() + device.onConnectionStateChanged.clear(); + device.onPlayChanged.clear(); + device.onTimeChanged.clear(); + device.onVolumeChanged.clear(); + device.onDurationChanged.clear(); ad.disconnect() } @@ -142,14 +145,14 @@ abstract class StateCasting { Logger.i(TAG, "Active device connection state changed: $castConnectionState") if (castConnectionState == CastConnectionState.DISCONNECTED) { - Logger.i(TAG, "Clearing events: $castConnectionState") + Logger.i(TAG, "Clearing events: $castConnectionState"); - device.onConnectionStateChanged.clear() - device.onPlayChanged.clear() - device.onTimeChanged.clear() - device.onVolumeChanged.clear() - device.onDurationChanged.clear() - activeDevice = null + device.onConnectionStateChanged.clear(); + device.onPlayChanged.clear(); + device.onTimeChanged.clear(); + device.onVolumeChanged.clear(); + device.onDurationChanged.clear(); + activeDevice = null; } invokeInMainScopeIfRequired { @@ -161,26 +164,20 @@ abstract class StateCasting { Logger.i(TAG, "Casting connected to [${device.name}]"); UIDialogs.appToast("Connected to device") synchronized(_castingDialogLock) { - if (_currentDialog != null) { - _currentDialog?.hide() - _currentDialog = null + if(_currentDialog != null) { + _currentDialog?.hide(); + _currentDialog = null; } } } - CastConnectionState.CONNECTING -> { Logger.i(TAG, "Casting connecting to [${device.name}]"); UIDialogs.toast(it, "Connecting to device...") synchronized(_castingDialogLock) { if (_currentDialog == null) { - _currentDialog = UIDialogs.showDialog( - context, - R.drawable.ic_loader_animated, - true, + _currentDialog = UIDialogs.showDialog(context, R.drawable.ic_loader_animated, true, "Connecting to [${device.name}]", - "Make sure you are on the same network\n\nVPNs and guest networks can cause issues", - null, - -2, + "Make sure you are on the same network\n\nVPNs and guest networks can cause issues", null, -2, UIDialogs.Action("Disconnect", { try { device.disconnect() @@ -198,40 +195,40 @@ abstract class StateCasting { CastConnectionState.DISCONNECTED -> { UIDialogs.toast(it, "Disconnected from device") synchronized(_castingDialogLock) { - if (_currentDialog != null) { - _currentDialog?.hide() - _currentDialog = null + if(_currentDialog != null) { + _currentDialog?.hide(); + _currentDialog = null; } } } } } - } - onActiveDeviceConnectionStateChanged.emit(device, castConnectionState) - } - } + }; + onActiveDeviceConnectionStateChanged.emit(device, castConnectionState); + }; + }; device.onPlayChanged.subscribe { - invokeInMainScopeIfRequired { onActiveDevicePlayChanged.emit(it) } - } + invokeInMainScopeIfRequired { onActiveDevicePlayChanged.emit(it) }; + }; device.onDurationChanged.subscribe { - invokeInMainScopeIfRequired { onActiveDeviceDurationChanged.emit(it) } - } + invokeInMainScopeIfRequired { onActiveDeviceDurationChanged.emit(it) }; + }; device.onVolumeChanged.subscribe { - invokeInMainScopeIfRequired { onActiveDeviceVolumeChanged.emit(it) } - } + invokeInMainScopeIfRequired { onActiveDeviceVolumeChanged.emit(it) }; + }; device.onTimeChanged.subscribe { - invokeInMainScopeIfRequired { onActiveDeviceTimeChanged.emit(it) } - } + invokeInMainScopeIfRequired { onActiveDeviceTimeChanged.emit(it) }; + }; try { device.connect(); } catch (e: Throwable) { - Logger.w(TAG, "Failed to connect to device.") - device.onConnectionStateChanged.clear() - device.onPlayChanged.clear() - device.onTimeChanged.clear() - device.onVolumeChanged.clear() - device.onDurationChanged.clear() + Logger.w(TAG, "Failed to connect to device."); + device.onConnectionStateChanged.clear(); + device.onPlayChanged.clear(); + device.onTimeChanged.clear(); + device.onVolumeChanged.clear(); + device.onDurationChanged.clear(); return } @@ -245,25 +242,7 @@ abstract class StateCasting { ) } - private fun shouldProxyStreams( - castingDevice: CastingDevice, videoSource: IVideoSource?, audioSource: IAudioSource? - ): Boolean { - val hasRequestModifier = - (videoSource as? JSSource)?.hasRequestModifier == true || (audioSource as? JSSource)?.hasRequestModifier == true - return Settings.instance.casting.alwaysProxyRequests || castingDevice.protocolType != CastProtocolType.FCAST || hasRequestModifier - } - - suspend fun castIfAvailable( - contentResolver: ContentResolver, - video: IPlatformVideoDetails, - videoSource: IVideoSource?, - audioSource: IAudioSource?, - subtitleSource: ISubtitleSource?, - ms: Long, - speed: Double?, - onLoadingEstimate: ((Int) -> Unit)?, - onLoading: ((Boolean) -> Unit)? - ): Boolean { + suspend fun castIfAvailable(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoSource?, audioSource: IAudioSource?, subtitleSource: ISubtitleSource?, ms: Long, speed: Double?, onLoadingEstimate: ((Int) -> Unit)?, onLoading: ((Boolean) -> Unit)?): Boolean { return withContext(Dispatchers.IO) { val ad = activeDevice ?: return@withContext false; if (ad.connectionState != CastConnectionState.CONNECTED) { @@ -287,24 +266,10 @@ abstract class StateCasting { if (videoSource is LocalVideoSource || audioSource is LocalAudioSource || subtitleSource is LocalSubtitleSource) { if (deviceProto == CastProtocolType.AIRPLAY) { Logger.i(TAG, "Casting as local HLS"); - castLocalHls( - video, - videoSource as LocalVideoSource?, - audioSource as LocalAudioSource?, - subtitleSource as LocalSubtitleSource?, - resumePosition, - speed - ); + castLocalHls(video, videoSource as LocalVideoSource?, audioSource as LocalAudioSource?, subtitleSource as LocalSubtitleSource?, resumePosition, speed); } else { Logger.i(TAG, "Casting as local DASH"); - castLocalDash( - video, - videoSource as LocalVideoSource?, - audioSource as LocalAudioSource?, - subtitleSource as LocalSubtitleSource?, - resumePosition, - speed - ); + castLocalDash(video, videoSource as LocalVideoSource?, audioSource as LocalAudioSource?, subtitleSource as LocalSubtitleSource?, resumePosition, speed); } } else { val isRawDash = @@ -312,52 +277,17 @@ abstract class StateCasting { if (isRawDash) { Logger.i(TAG, "Casting as raw DASH"); - castDashRaw( - contentResolver, - video, - videoSource as JSDashManifestRawSource?, - audioSource as JSDashManifestRawAudioSource?, - subtitleSource, - resumePosition, - speed, - castId, - onLoadingEstimate, - onLoading - ); + castDashRaw(contentResolver, video, videoSource as JSDashManifestRawSource?, audioSource as JSDashManifestRawAudioSource?, subtitleSource, resumePosition, speed, castId, onLoadingEstimate, onLoading); } else { if (deviceProto == CastProtocolType.FCAST) { Logger.i(TAG, "Casting as DASH direct"); - castDashDirect( - contentResolver, - video, - videoSource as IVideoUrlSource?, - audioSource as IAudioUrlSource?, - subtitleSource, - resumePosition, - speed - ); + castDashDirect(contentResolver, video, videoSource as IVideoUrlSource?, audioSource as IAudioUrlSource?, subtitleSource, resumePosition, speed); } else if (deviceProto == CastProtocolType.AIRPLAY) { Logger.i(TAG, "Casting as HLS indirect"); - castHlsIndirect( - contentResolver, - video, - videoSource as IVideoUrlSource?, - audioSource as IAudioUrlSource?, - subtitleSource, - resumePosition, - speed - ); + castHlsIndirect(contentResolver, video, videoSource as IVideoUrlSource?, audioSource as IAudioUrlSource?, subtitleSource, resumePosition, speed); } else { Logger.i(TAG, "Casting as DASH indirect"); - castDashIndirect( - contentResolver, - video, - videoSource as IVideoUrlSource?, - audioSource as IAudioUrlSource?, - subtitleSource, - resumePosition, - speed - ); + castDashIndirect(contentResolver, video, videoSource as IVideoUrlSource?, audioSource as IAudioUrlSource?, subtitleSource, resumePosition, speed); } } } @@ -413,9 +343,7 @@ abstract class StateCasting { } else if (audioSource is IHLSManifestAudioSource) { if (proxyStreams || deviceProto == CastProtocolType.CHROMECAST) { Logger.i(TAG, "Casting as proxied audio HLS"); - castProxiedHls( - video, audioSource.url, audioSource.codec, resumePosition, speed - ); + castProxiedHls(video, audioSource.url, audioSource.codec, resumePosition, speed); } else { Logger.i(TAG, "Casting as non-proxied audio HLS"); ad.loadVideo( @@ -436,37 +364,15 @@ abstract class StateCasting { castLocalAudio(video, audioSource, resumePosition, speed); } else if (videoSource is JSDashManifestRawSource) { Logger.i(TAG, "Casting as JSDashManifestRawSource video"); - castDashRaw( - contentResolver, - video, - videoSource as JSDashManifestRawSource?, - null, - null, - resumePosition, - speed, - castId, - onLoadingEstimate, - onLoading - ); + castDashRaw(contentResolver, video, videoSource as JSDashManifestRawSource?, null, null, resumePosition, speed, castId, onLoadingEstimate, onLoading); } else if (audioSource is JSDashManifestRawAudioSource) { Logger.i(TAG, "Casting as JSDashManifestRawSource audio"); - castDashRaw( - contentResolver, - video, - null, - audioSource as JSDashManifestRawAudioSource?, - null, - resumePosition, - speed, - castId, - onLoadingEstimate, - onLoading - ); + castDashRaw(contentResolver, video, null, audioSource as JSDashManifestRawAudioSource?, null, resumePosition, speed, castId, onLoadingEstimate, onLoading); } else { var str = listOf( - if (videoSource != null) "Video: ${videoSource::class.java.simpleName}" else null, - if (audioSource != null) "Audio: ${audioSource::class.java.simpleName}" else null, - if (subtitleSource != null) "Subtitles: ${subtitleSource::class.java.simpleName}" else null + if(videoSource != null) "Video: ${videoSource::class.java.simpleName}" else null, + if(audioSource != null) "Audio: ${audioSource::class.java.simpleName}" else null, + if(subtitleSource != null) "Subtitles: ${subtitleSource::class.java.simpleName}" else null ).filterNotNull().joinToString(", "); throw UnsupportedCastException(str); } @@ -512,12 +418,7 @@ abstract class StateCasting { return true; } - private fun castLocalVideo( - video: IPlatformVideoDetails, - videoSource: LocalVideoSource, - resumePosition: Double, - speed: Double? - ): List { + private fun castLocalVideo(video: IPlatformVideoDetails, videoSource: LocalVideoSource, resumePosition: Double, speed: Double?) : List { val ad = activeDevice ?: return listOf(); val url = getLocalUrl(ad); @@ -526,34 +427,17 @@ abstract class StateCasting { val videoUrl = url + videoPath; _castServer.addHandlerWithAllowAllOptions( - HttpFileHandler( - "GET", - videoPath, - videoSource.container, - videoSource.filePath - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpFileHandler("GET", videoPath, videoSource.container, videoSource.filePath) + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("cast"); Logger.i(TAG, "Casting local video (videoUrl: $videoUrl)."); - ad.loadVideo( - "BUFFERED", - videoSource.container, - videoUrl, - resumePosition, - video.duration.toDouble(), - speed, - metadataFromVideo(video) - ); + ad.loadVideo("BUFFERED", videoSource.container, videoUrl, resumePosition, video.duration.toDouble(), speed, metadataFromVideo(video)); return listOf(videoUrl); } - private fun castLocalAudio( - video: IPlatformVideoDetails, - audioSource: LocalAudioSource, - resumePosition: Double, - speed: Double? - ): List { + private fun castLocalAudio(video: IPlatformVideoDetails, audioSource: LocalAudioSource, resumePosition: Double, speed: Double?): List { val ad = activeDevice ?: return listOf(); val url = getLocalUrl(ad); @@ -562,36 +446,17 @@ abstract class StateCasting { val audioUrl = url + audioPath; _castServer.addHandlerWithAllowAllOptions( - HttpFileHandler( - "GET", - audioPath, - audioSource.container, - audioSource.filePath - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpFileHandler("GET", audioPath, audioSource.container, audioSource.filePath) + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("cast"); Logger.i(TAG, "Casting local audio (audioUrl: $audioUrl)."); - ad.loadVideo( - "BUFFERED", - audioSource.container, - audioUrl, - resumePosition, - video.duration.toDouble(), - speed, - metadataFromVideo(video) - ); + ad.loadVideo("BUFFERED", audioSource.container, audioUrl, resumePosition, video.duration.toDouble(), speed, metadataFromVideo(video)); return listOf(audioUrl); } - private fun castLocalHls( - video: IPlatformVideoDetails, - videoSource: LocalVideoSource?, - audioSource: LocalAudioSource?, - subtitleSource: LocalSubtitleSource?, - resumePosition: Double, - speed: Double? - ): List { + private fun castLocalHls(video: IPlatformVideoDetails, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?, resumePosition: Double, speed: Double?): List { val ad = activeDevice ?: return listOf() val url = getLocalUrl(ad) @@ -612,161 +477,82 @@ abstract class StateCasting { if (videoSource != null) { _castServer.addHandlerWithAllowAllOptions( - HttpFileHandler( - "GET", - videoPath, - videoSource.container, - videoSource.filePath - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpFileHandler("GET", videoPath, videoSource.container, videoSource.filePath) + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castLocalHls") val duration = videoSource.duration val videoVariantPlaylistPath = "/video-playlist-${id}" val videoVariantPlaylistUrl = url + videoVariantPlaylistPath - val videoVariantPlaylistSegments = - listOf(HLS.MediaSegment(duration.toDouble(), videoUrl)) - val videoVariantPlaylist = HLS.VariantPlaylist( - 3, duration.toInt(), 0, 0, null, null, null, videoVariantPlaylistSegments - ) + val videoVariantPlaylistSegments = listOf(HLS.MediaSegment(duration.toDouble(), videoUrl)) + val videoVariantPlaylist = HLS.VariantPlaylist(3, duration.toInt(), 0, 0, null, null, null, videoVariantPlaylistSegments) _castServer.addHandlerWithAllowAllOptions( - HttpConstantHandler( - "GET", - videoVariantPlaylistPath, - videoVariantPlaylist.buildM3U8(), - "application/vnd.apple.mpegurl" - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpConstantHandler("GET", videoVariantPlaylistPath, videoVariantPlaylist.buildM3U8(), + "application/vnd.apple.mpegurl") + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castLocalHls") - variantPlaylistReferences.add( - HLS.VariantPlaylistReference( - videoVariantPlaylistUrl, HLS.StreamInfo( - videoSource.bitrate, - "${videoSource.width}x${videoSource.height}", - videoSource.codec, - null, - null, - if (audioSource != null) "audio" else null, - if (subtitleSource != null) "subtitles" else null, - null, - null - ) - ) - ) + variantPlaylistReferences.add(HLS.VariantPlaylistReference(videoVariantPlaylistUrl, HLS.StreamInfo( + videoSource.bitrate, "${videoSource.width}x${videoSource.height}", videoSource.codec, null, null, if (audioSource != null) "audio" else null, if (subtitleSource != null) "subtitles" else null, null, null))) } if (audioSource != null) { _castServer.addHandlerWithAllowAllOptions( - HttpFileHandler( - "GET", - audioPath, - audioSource.container, - audioSource.filePath - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpFileHandler("GET", audioPath, audioSource.container, audioSource.filePath) + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castLocalHls") - val duration = - audioSource.duration ?: videoSource?.duration ?: throw Exception("Duration unknown") + val duration = audioSource.duration ?: videoSource?.duration ?: throw Exception("Duration unknown") val audioVariantPlaylistPath = "/audio-playlist-${id}" val audioVariantPlaylistUrl = url + audioVariantPlaylistPath - val audioVariantPlaylistSegments = - listOf(HLS.MediaSegment(duration.toDouble(), audioUrl)) - val audioVariantPlaylist = HLS.VariantPlaylist( - 3, duration.toInt(), 0, 0, null, null, null, audioVariantPlaylistSegments - ) + val audioVariantPlaylistSegments = listOf(HLS.MediaSegment(duration.toDouble(), audioUrl)) + val audioVariantPlaylist = HLS.VariantPlaylist(3, duration.toInt(), 0, 0, null, null, null, audioVariantPlaylistSegments) _castServer.addHandlerWithAllowAllOptions( - HttpConstantHandler( - "GET", - audioVariantPlaylistPath, - audioVariantPlaylist.buildM3U8(), - "application/vnd.apple.mpegurl" - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpConstantHandler("GET", audioVariantPlaylistPath, audioVariantPlaylist.buildM3U8(), + "application/vnd.apple.mpegurl") + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castLocalHls") - mediaRenditions.add( - HLS.MediaRendition( - "AUDIO", audioVariantPlaylistUrl, "audio", "df", "default", true, true, true - ) - ) + mediaRenditions.add(HLS.MediaRendition("AUDIO", audioVariantPlaylistUrl, "audio", "df", "default", true, true, true)) } if (subtitleSource != null) { _castServer.addHandlerWithAllowAllOptions( - HttpFileHandler( - "GET", - subtitlePath, - subtitleSource.format ?: "text/vtt", - subtitleSource.filePath - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpFileHandler("GET", subtitlePath, subtitleSource.format ?: "text/vtt", subtitleSource.filePath) + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castLocalHls") - val duration = videoSource?.duration ?: audioSource?.duration - ?: throw Exception("Duration unknown") + val duration = videoSource?.duration ?: audioSource?.duration ?: throw Exception("Duration unknown") val subtitleVariantPlaylistPath = "/subtitle-playlist-${id}" val subtitleVariantPlaylistUrl = url + subtitleVariantPlaylistPath - val subtitleVariantPlaylistSegments = - listOf(HLS.MediaSegment(duration.toDouble(), subtitleUrl)) - val subtitleVariantPlaylist = HLS.VariantPlaylist( - 3, duration.toInt(), 0, 0, null, null, null, subtitleVariantPlaylistSegments - ) + val subtitleVariantPlaylistSegments = listOf(HLS.MediaSegment(duration.toDouble(), subtitleUrl)) + val subtitleVariantPlaylist = HLS.VariantPlaylist(3, duration.toInt(), 0, 0, null, null, null, subtitleVariantPlaylistSegments) _castServer.addHandlerWithAllowAllOptions( - HttpConstantHandler( - "GET", - subtitleVariantPlaylistPath, - subtitleVariantPlaylist.buildM3U8(), - "application/vnd.apple.mpegurl" - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpConstantHandler("GET", subtitleVariantPlaylistPath, subtitleVariantPlaylist.buildM3U8(), + "application/vnd.apple.mpegurl") + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castLocalHls") - mediaRenditions.add( - HLS.MediaRendition( - "SUBTITLES", - subtitleVariantPlaylistUrl, - "subtitles", - "df", - "default", - true, - true, - true - ) - ) + mediaRenditions.add(HLS.MediaRendition("SUBTITLES", subtitleVariantPlaylistUrl, "subtitles", "df", "default", true, true, true)) } - val masterPlaylist = - HLS.MasterPlaylist(variantPlaylistReferences, mediaRenditions, listOf(), true) + val masterPlaylist = HLS.MasterPlaylist(variantPlaylistReferences, mediaRenditions, listOf(), true) _castServer.addHandlerWithAllowAllOptions( - HttpConstantHandler( - "GET", hlsPath, masterPlaylist.buildM3U8(), "application/vnd.apple.mpegurl" - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpConstantHandler("GET", hlsPath, masterPlaylist.buildM3U8(), + "application/vnd.apple.mpegurl") + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castLocalHls") - Logger.i( - TAG, - "added new castLocalHls handlers (hlsPath: $hlsPath, videoPath: $videoPath, audioPath: $audioPath, subtitlePath: $subtitlePath)." - ) - ad.loadVideo( - "BUFFERED", - "application/vnd.apple.mpegurl", - hlsUrl, - resumePosition, - video.duration.toDouble(), - speed, - metadataFromVideo(video) - ) + Logger.i(TAG, "added new castLocalHls handlers (hlsPath: $hlsPath, videoPath: $videoPath, audioPath: $audioPath, subtitlePath: $subtitlePath).") + ad.loadVideo("BUFFERED", "application/vnd.apple.mpegurl", hlsUrl, resumePosition, video.duration.toDouble(), speed, metadataFromVideo(video)) return listOf(hlsUrl, videoUrl, audioUrl, subtitleUrl) } - private fun castLocalDash( - video: IPlatformVideoDetails, - videoSource: LocalVideoSource?, - audioSource: LocalAudioSource?, - subtitleSource: LocalSubtitleSource?, - resumePosition: Double, - speed: Double? - ): List { + private fun castLocalDash(video: IPlatformVideoDetails, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?, resumePosition: Double, speed: Double?) : List { val ad = activeDevice ?: return listOf(); val url = getLocalUrl(ad); @@ -782,73 +568,40 @@ abstract class StateCasting { val audioUrl = url + audioPath; val subtitleUrl = url + subtitlePath; - val dashContent = DashBuilder.generateOnDemandDash( - videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitleUrl - ); + val dashContent = DashBuilder.generateOnDemandDash(videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitleUrl); Logger.v(TAG) { "Dash manifest: $dashContent" }; _castServer.addHandlerWithAllowAllOptions( - HttpConstantHandler( - "GET", dashPath, dashContent, "application/dash+xml" - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpConstantHandler("GET", dashPath, dashContent, + "application/dash+xml") + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("cast"); if (videoSource != null) { _castServer.addHandlerWithAllowAllOptions( - HttpFileHandler( - "GET", - videoPath, - videoSource.container, - videoSource.filePath - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpFileHandler("GET", videoPath, videoSource.container, videoSource.filePath) + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("cast"); } if (audioSource != null) { _castServer.addHandlerWithAllowAllOptions( - HttpFileHandler( - "GET", - audioPath, - audioSource.container, - audioSource.filePath - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpFileHandler("GET", audioPath, audioSource.container, audioSource.filePath) + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("cast"); } if (subtitleSource != null) { _castServer.addHandlerWithAllowAllOptions( - HttpFileHandler( - "GET", - subtitlePath, - subtitleSource.format ?: "text/vtt", - subtitleSource.filePath - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpFileHandler("GET", subtitlePath, subtitleSource.format ?: "text/vtt", subtitleSource.filePath) + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("cast"); } - Logger.i( - TAG, - "added new castLocalDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath, subtitlePath: $subtitlePath)." - ); - ad.loadVideo( - "BUFFERED", - "application/dash+xml", - dashUrl, - resumePosition, - video.duration.toDouble(), - speed, - metadataFromVideo(video) - ); + Logger.i(TAG, "added new castLocalDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath, subtitlePath: $subtitlePath)."); + ad.loadVideo("BUFFERED", "application/dash+xml", dashUrl, resumePosition, video.duration.toDouble(), speed, metadataFromVideo(video)); return listOf(dashUrl, videoUrl, audioUrl, subtitleUrl); } - private suspend fun castDashDirect( - contentResolver: ContentResolver, - video: IPlatformVideoDetails, - videoSource: IVideoUrlSource?, - audioSource: IAudioUrlSource?, - subtitleSource: ISubtitleSource?, - resumePosition: Double, - speed: Double? - ): List { + private suspend fun castDashDirect(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoUrlSource?, audioSource: IAudioUrlSource?, subtitleSource: ISubtitleSource?, resumePosition: Double, speed: Double?) : List { val ad = activeDevice ?: return listOf(); val proxyStreams = shouldProxyStreams(ad, videoSource, audioSource) @@ -859,8 +612,8 @@ abstract class StateCasting { val audioPath = "/audio-${id}" val subtitlePath = "/subtitle-${id}" - val videoUrl = if (proxyStreams) url + videoPath else videoSource?.getVideoUrl(); - val audioUrl = if (proxyStreams) url + audioPath else audioSource?.getAudioUrl(); + val videoUrl = if(proxyStreams) url + videoPath else videoSource?.getVideoUrl(); + val audioUrl = if(proxyStreams) url + audioPath else audioSource?.getAudioUrl(); val subtitlesUri = if (subtitleSource != null) withContext(Dispatchers.IO) { return@withContext subtitleSource.getSubtitlesURI(); @@ -868,7 +621,7 @@ abstract class StateCasting { var subtitlesUrl: String? = null; if (subtitlesUri != null) { - if (subtitlesUri.scheme == "file") { + if(subtitlesUri.scheme == "file") { var content: String? = null; val inputStream = contentResolver.openInputStream(subtitlesUri); inputStream?.use { stream -> @@ -878,9 +631,8 @@ abstract class StateCasting { if (content != null) { _castServer.addHandlerWithAllowAllOptions( - HttpConstantHandler( - "GET", subtitlePath, content!!, subtitleSource?.format ?: "text/vtt" - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpConstantHandler("GET", subtitlePath, content!!, subtitleSource?.format ?: "text/vtt") + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("cast"); } @@ -892,50 +644,26 @@ abstract class StateCasting { if (videoSource != null) { _castServer.addHandlerWithAllowAllOptions( - HttpProxyHandler( - "GET", - videoPath, - videoSource.getVideoUrl(), - true - ).withInjectedHost().withHeader("Access-Control-Allow-Origin", "*"), true + HttpProxyHandler("GET", videoPath, videoSource.getVideoUrl(), true) + .withInjectedHost() + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("cast"); } if (audioSource != null) { _castServer.addHandlerWithAllowAllOptions( - HttpProxyHandler( - "GET", - audioPath, - audioSource.getAudioUrl(), - true - ).withInjectedHost().withHeader("Access-Control-Allow-Origin", "*"), true + HttpProxyHandler("GET", audioPath, audioSource.getAudioUrl(), true) + .withInjectedHost() + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("cast"); } - val content = DashBuilder.generateOnDemandDash( - videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitlesUrl - ); + val content = DashBuilder.generateOnDemandDash(videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitlesUrl); - Logger.i( - TAG, "Direct dash cast to casting device (videoUrl: $videoUrl, audioUrl: $audioUrl)." - ); + Logger.i(TAG, "Direct dash cast to casting device (videoUrl: $videoUrl, audioUrl: $audioUrl)."); Logger.v(TAG) { "Dash manifest: $content" }; - ad.loadContent( - "application/dash+xml", - content, - resumePosition, - video.duration.toDouble(), - speed, - metadataFromVideo(video) - ); + ad.loadContent("application/dash+xml", content, resumePosition, video.duration.toDouble(), speed, metadataFromVideo(video)); - return listOf( - videoUrl ?: "", - audioUrl ?: "", - subtitlesUrl ?: "", - videoSource?.getVideoUrl() ?: "", - audioSource?.getAudioUrl() ?: "", - subtitlesUri.toString() - ); + return listOf(videoUrl ?: "", audioUrl ?: "", subtitlesUrl ?: "", videoSource?.getVideoUrl() ?: "", audioSource?.getAudioUrl() ?: "", subtitlesUri.toString()); } private fun castProxiedHls( @@ -1070,18 +798,16 @@ abstract class StateCasting { ).withTag("castProxiedHlsVariant") } - newMediaRenditions.add( - HLS.MediaRendition( - mediaRendition.type, - newPlaylistUrl, - mediaRendition.groupID, - mediaRendition.language, - mediaRendition.name, - mediaRendition.isDefault, - mediaRendition.isAutoSelect, - mediaRendition.isForced - ) - ) + newMediaRenditions.add(HLS.MediaRendition( + mediaRendition.type, + newPlaylistUrl, + mediaRendition.groupID, + mediaRendition.language, + mediaRendition.name, + mediaRendition.isDefault, + mediaRendition.isAutoSelect, + mediaRendition.isForced + )) } masterContext.respondCode(200, headers, newMasterPlaylist.buildM3U8()); @@ -1106,13 +832,7 @@ abstract class StateCasting { return listOf(hlsUrl); } - private fun proxyVariantPlaylist( - url: String, - playlistId: UUID, - variantPlaylist: HLS.VariantPlaylist, - isLive: Boolean, - proxySegments: Boolean = true - ): HLS.VariantPlaylist { + private fun proxyVariantPlaylist(url: String, playlistId: UUID, variantPlaylist: HLS.VariantPlaylist, isLive: Boolean, proxySegments: Boolean = true): HLS.VariantPlaylist { val newSegments = arrayListOf() if (proxySegments) { @@ -1136,37 +856,29 @@ abstract class StateCasting { ) } - private fun proxySegment( - url: String, playlistId: UUID, segment: HLS.Segment, index: Long - ): HLS.Segment { + private fun proxySegment(url: String, playlistId: UUID, segment: HLS.Segment, index: Long): HLS.Segment { if (segment is HLS.MediaSegment) { val newSegmentPath = "/hls-playlist-${playlistId}-segment-${index}" val newSegmentUrl = url + newSegmentPath; if (_castServer.getHandler("GET", newSegmentPath) == null) { _castServer.addHandlerWithAllowAllOptions( - HttpProxyHandler("GET", newSegmentPath, segment.uri, true).withInjectedHost() + HttpProxyHandler("GET", newSegmentPath, segment.uri, true) + .withInjectedHost() .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castProxiedHlsVariant") } return HLS.MediaSegment( - segment.duration, newSegmentUrl + segment.duration, + newSegmentUrl ) } else { return segment } } - private suspend fun castHlsIndirect( - contentResolver: ContentResolver, - video: IPlatformVideoDetails, - videoSource: IVideoUrlSource?, - audioSource: IAudioUrlSource?, - subtitleSource: ISubtitleSource?, - resumePosition: Double, - speed: Double? - ): List { + private suspend fun castHlsIndirect(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoUrlSource?, audioSource: IAudioUrlSource?, subtitleSource: ISubtitleSource?, resumePosition: Double, speed: Double?) : List { val ad = activeDevice ?: return listOf(); val url = getLocalUrl(ad); val id = UUID.randomUUID(); @@ -1183,38 +895,24 @@ abstract class StateCasting { val audioPath = "/audio-${id}" val audioUrl = url + audioPath - val duration = - audioSource.duration ?: videoSource?.duration ?: throw Exception("Duration unknown") + val duration = audioSource.duration ?: videoSource?.duration ?: throw Exception("Duration unknown") val audioVariantPlaylistPath = "/audio-playlist-${id}" val audioVariantPlaylistUrl = url + audioVariantPlaylistPath - val audioVariantPlaylistSegments = - listOf(HLS.MediaSegment(duration.toDouble(), audioUrl)) - val audioVariantPlaylist = HLS.VariantPlaylist( - 3, duration.toInt(), 0, 0, null, null, null, audioVariantPlaylistSegments - ) + val audioVariantPlaylistSegments = listOf(HLS.MediaSegment(duration.toDouble(), audioUrl)) + val audioVariantPlaylist = HLS.VariantPlaylist(3, duration.toInt(), 0, 0, null, null, null, audioVariantPlaylistSegments) _castServer.addHandlerWithAllowAllOptions( - HttpConstantHandler( - "GET", - audioVariantPlaylistPath, - audioVariantPlaylist.buildM3U8(), - "application/vnd.apple.mpegurl" - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpConstantHandler("GET", audioVariantPlaylistPath, audioVariantPlaylist.buildM3U8(), + "application/vnd.apple.mpegurl") + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castHlsIndirectVariant"); - mediaRenditions.add( - HLS.MediaRendition( - "AUDIO", audioVariantPlaylistUrl, "audio", "df", "default", true, true, true - ) - ) + mediaRenditions.add(HLS.MediaRendition("AUDIO", audioVariantPlaylistUrl, "audio", "df", "default", true, true, true)) _castServer.addHandlerWithAllowAllOptions( - HttpProxyHandler( - "GET", - audioPath, - audioSource.getAudioUrl(), - true - ).withInjectedHost().withHeader("Access-Control-Allow-Origin", "*"), true + HttpProxyHandler("GET", audioPath, audioSource.getAudioUrl(), true) + .withInjectedHost() + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castHlsIndirectVariant"); } @@ -1225,7 +923,7 @@ abstract class StateCasting { var subtitlesUrl: String? = null; if (subtitlesUri != null) { val subtitlePath = "/subtitles-${id}" - if (subtitlesUri.scheme == "file") { + if(subtitlesUri.scheme == "file") { var content: String? = null; val inputStream = contentResolver.openInputStream(subtitlesUri); inputStream?.use { stream -> @@ -1235,9 +933,8 @@ abstract class StateCasting { if (content != null) { _castServer.addHandlerWithAllowAllOptions( - HttpConstantHandler( - "GET", subtitlePath, content!!, subtitleSource?.format ?: "text/vtt" - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpConstantHandler("GET", subtitlePath, content!!, subtitleSource?.format ?: "text/vtt") + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castHlsIndirectVariant"); } @@ -1248,37 +945,19 @@ abstract class StateCasting { } if (subtitlesUrl != null) { - val duration = videoSource?.duration ?: audioSource?.duration - ?: throw Exception("Duration unknown") + val duration = videoSource?.duration ?: audioSource?.duration ?: throw Exception("Duration unknown") val subtitleVariantPlaylistPath = "/subtitle-playlist-${id}" val subtitleVariantPlaylistUrl = url + subtitleVariantPlaylistPath - val subtitleVariantPlaylistSegments = - listOf(HLS.MediaSegment(duration.toDouble(), subtitlesUrl)) - val subtitleVariantPlaylist = HLS.VariantPlaylist( - 3, duration.toInt(), 0, 0, null, null, null, subtitleVariantPlaylistSegments - ) + val subtitleVariantPlaylistSegments = listOf(HLS.MediaSegment(duration.toDouble(), subtitlesUrl)) + val subtitleVariantPlaylist = HLS.VariantPlaylist(3, duration.toInt(), 0, 0, null, null, null, subtitleVariantPlaylistSegments) _castServer.addHandlerWithAllowAllOptions( - HttpConstantHandler( - "GET", - subtitleVariantPlaylistPath, - subtitleVariantPlaylist.buildM3U8(), - "application/vnd.apple.mpegurl" - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpConstantHandler("GET", subtitleVariantPlaylistPath, subtitleVariantPlaylist.buildM3U8(), + "application/vnd.apple.mpegurl") + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castHlsIndirectVariant"); - mediaRenditions.add( - HLS.MediaRendition( - "SUBTITLES", - subtitleVariantPlaylistUrl, - "subtitles", - "df", - "default", - true, - true, - true - ) - ) + mediaRenditions.add(HLS.MediaRendition("SUBTITLES", subtitleVariantPlaylistUrl, "subtitles", "df", "default", true, true, true)) } if (videoSource != null) { @@ -1288,83 +967,51 @@ abstract class StateCasting { val duration = videoSource.duration val videoVariantPlaylistPath = "/video-playlist-${id}" val videoVariantPlaylistUrl = url + videoVariantPlaylistPath - val videoVariantPlaylistSegments = - listOf(HLS.MediaSegment(duration.toDouble(), videoUrl)) - val videoVariantPlaylist = HLS.VariantPlaylist( - 3, duration.toInt(), 0, 0, null, null, null, videoVariantPlaylistSegments - ) + val videoVariantPlaylistSegments = listOf(HLS.MediaSegment(duration.toDouble(), videoUrl)) + val videoVariantPlaylist = HLS.VariantPlaylist(3, duration.toInt(), 0, 0, null, null, null, videoVariantPlaylistSegments) _castServer.addHandlerWithAllowAllOptions( - HttpConstantHandler( - "GET", - videoVariantPlaylistPath, - videoVariantPlaylist.buildM3U8(), - "application/vnd.apple.mpegurl" - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpConstantHandler("GET", videoVariantPlaylistPath, videoVariantPlaylist.buildM3U8(), + "application/vnd.apple.mpegurl") + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castHlsIndirectVariant"); - variantPlaylistReferences.add( - HLS.VariantPlaylistReference( - videoVariantPlaylistUrl, HLS.StreamInfo( - videoSource.bitrate ?: 0, - "${videoSource.width}x${videoSource.height}", - videoSource.codec, - null, - null, - if (audioSource != null) "audio" else null, - if (subtitleSource != null) "subtitles" else null, - null, - null - ) - ) - ) + variantPlaylistReferences.add(HLS.VariantPlaylistReference(videoVariantPlaylistUrl, HLS.StreamInfo( + videoSource.bitrate ?: 0, + "${videoSource.width}x${videoSource.height}", + videoSource.codec, + null, + null, + if (audioSource != null) "audio" else null, + if (subtitleSource != null) "subtitles" else null, + null, null))) _castServer.addHandlerWithAllowAllOptions( - HttpProxyHandler( - "GET", - videoPath, - videoSource.getVideoUrl(), - true - ).withInjectedHost().withHeader("Access-Control-Allow-Origin", "*"), true + HttpProxyHandler("GET", videoPath, videoSource.getVideoUrl(), true) + .withInjectedHost() + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castHlsIndirectVariant"); } - val masterPlaylist = - HLS.MasterPlaylist(variantPlaylistReferences, mediaRenditions, listOf(), true) + val masterPlaylist = HLS.MasterPlaylist(variantPlaylistReferences, mediaRenditions, listOf(), true) _castServer.addHandlerWithAllowAllOptions( - HttpConstantHandler( - "GET", hlsPath, masterPlaylist.buildM3U8(), "application/vnd.apple.mpegurl" - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpConstantHandler("GET", hlsPath, masterPlaylist.buildM3U8(), + "application/vnd.apple.mpegurl") + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castHlsIndirectMaster") Logger.i(TAG, "added new castHls handlers (hlsPath: $hlsPath)."); - ad.loadVideo( - if (video.isLive) "LIVE" else "BUFFERED", - "application/vnd.apple.mpegurl", - hlsUrl, - resumePosition, - video.duration.toDouble(), - speed, - metadataFromVideo(video) - ); + ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", "application/vnd.apple.mpegurl", hlsUrl, resumePosition, video.duration.toDouble(), speed, metadataFromVideo(video)); - return listOf( - hlsUrl, - videoSource?.getVideoUrl() ?: "", - audioSource?.getAudioUrl() ?: "", - subtitlesUri.toString() - ); + return listOf(hlsUrl, videoSource?.getVideoUrl() ?: "", audioSource?.getAudioUrl() ?: "", subtitlesUri.toString()); } - private suspend fun castDashIndirect( - contentResolver: ContentResolver, - video: IPlatformVideoDetails, - videoSource: IVideoUrlSource?, - audioSource: IAudioUrlSource?, - subtitleSource: ISubtitleSource?, - resumePosition: Double, - speed: Double? - ): List { + private fun shouldProxyStreams(castingDevice: CastingDevice, videoSource: IVideoSource?, audioSource: IAudioSource?): Boolean { + val hasRequestModifier = (videoSource as? JSSource)?.hasRequestModifier == true || (audioSource as? JSSource)?.hasRequestModifier == true + return Settings.instance.casting.alwaysProxyRequests || castingDevice.protocolType != CastProtocolType.FCAST || hasRequestModifier + } + + private suspend fun castDashIndirect(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoUrlSource?, audioSource: IAudioUrlSource?, subtitleSource: ISubtitleSource?, resumePosition: Double, speed: Double?) : List { val ad = activeDevice ?: return listOf(); val proxyStreams = shouldProxyStreams(ad, videoSource, audioSource) @@ -1379,8 +1026,8 @@ abstract class StateCasting { val dashUrl = url + dashPath; Logger.i(TAG, "DASH url: $dashUrl"); - val videoUrl = if (proxyStreams) url + videoPath else videoSource?.getVideoUrl(); - val audioUrl = if (proxyStreams) url + audioPath else audioSource?.getAudioUrl(); + val videoUrl = if(proxyStreams) url + videoPath else videoSource?.getVideoUrl(); + val audioUrl = if(proxyStreams) url + audioPath else audioSource?.getAudioUrl(); val subtitlesUri = if (subtitleSource != null) withContext(Dispatchers.IO) { return@withContext subtitleSource.getSubtitlesURI(); @@ -1391,7 +1038,7 @@ abstract class StateCasting { var subtitlesUrl: String? = null; if (subtitlesUri != null) { - if (subtitlesUri.scheme == "file") { + if(subtitlesUri.scheme == "file") { var content: String? = null; val inputStream = contentResolver.openInputStream(subtitlesUri); inputStream?.use { stream -> @@ -1401,9 +1048,8 @@ abstract class StateCasting { if (content != null) { _castServer.addHandlerWithAllowAllOptions( - HttpConstantHandler( - "GET", subtitlePath, content!!, subtitleSource?.format ?: "text/vtt" - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpConstantHandler("GET", subtitlePath, content!!, subtitleSource?.format ?: "text/vtt") + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("cast"); } @@ -1413,61 +1059,34 @@ abstract class StateCasting { } } - val dashContent = DashBuilder.generateOnDemandDash( - videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitlesUrl - ); + val dashContent = DashBuilder.generateOnDemandDash(videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitlesUrl); Logger.v(TAG) { "Dash manifest: $dashContent" }; _castServer.addHandlerWithAllowAllOptions( - HttpConstantHandler( - "GET", dashPath, dashContent, "application/dash+xml" - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpConstantHandler("GET", dashPath, dashContent, + "application/dash+xml") + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("cast"); if (videoSource != null) { _castServer.addHandlerWithAllowAllOptions( - HttpProxyHandler( - "GET", - videoPath, - videoSource.getVideoUrl(), - true - ).withInjectedHost().withHeader("Access-Control-Allow-Origin", "*"), true + HttpProxyHandler("GET", videoPath, videoSource.getVideoUrl(), true) + .withInjectedHost() + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("cast"); } if (audioSource != null) { _castServer.addHandlerWithAllowAllOptions( - HttpProxyHandler( - "GET", - audioPath, - audioSource.getAudioUrl(), - true - ).withInjectedHost().withHeader("Access-Control-Allow-Origin", "*"), true + HttpProxyHandler("GET", audioPath, audioSource.getAudioUrl(), true) + .withInjectedHost() + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("cast"); } - Logger.i( - TAG, - "added new castDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath)." - ); - ad.loadVideo( - if (video.isLive) "LIVE" else "BUFFERED", - "application/dash+xml", - dashUrl, - resumePosition, - video.duration.toDouble(), - speed, - metadataFromVideo(video) - ); + Logger.i(TAG, "added new castDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath)."); + ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", "application/dash+xml", dashUrl, resumePosition, video.duration.toDouble(), speed, metadataFromVideo(video)); - return listOf( - dashUrl, - videoUrl ?: "", - audioUrl ?: "", - subtitlesUrl ?: "", - videoSource?.getVideoUrl() ?: "", - audioSource?.getAudioUrl() ?: "", - subtitlesUri.toString() - ); + return listOf(dashUrl, videoUrl ?: "", audioUrl ?: "", subtitlesUrl ?: "", videoSource?.getVideoUrl() ?: "", audioSource?.getAudioUrl() ?: "", subtitlesUri.toString()); } fun cleanExecutors() { @@ -1482,7 +1101,7 @@ abstract class StateCasting { } } - fun getLocalUrl(ad: CastingDevice): String { + private fun getLocalUrl(ad: CastingDevice): String { var address = ad.localAddress!! if (Settings.instance.casting.allowLinkLocalIpv4) { if (address.isLinkLocalAddress && address is Inet6Address) { @@ -1499,18 +1118,7 @@ abstract class StateCasting { } @OptIn(UnstableApi::class) - suspend fun castDashRaw( - contentResolver: ContentResolver, - video: IPlatformVideoDetails, - videoSource: JSDashManifestRawSource?, - audioSource: JSDashManifestRawAudioSource?, - subtitleSource: ISubtitleSource?, - resumePosition: Double, - speed: Double?, - castId: Int, - onLoadingEstimate: ((Int) -> Unit)? = null, - onLoading: ((Boolean) -> Unit)? = null - ): List { + private suspend fun castDashRaw(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: JSDashManifestRawSource?, audioSource: JSDashManifestRawAudioSource?, subtitleSource: ISubtitleSource?, resumePosition: Double, speed: Double?, castId: Int, onLoadingEstimate: ((Int) -> Unit)? = null, onLoading: ((Boolean) -> Unit)? = null) : List { val ad = activeDevice ?: return listOf(); cleanExecutors() @@ -1536,7 +1144,7 @@ abstract class StateCasting { var subtitlesUrl: String? = null; if (subtitlesUri != null) { - if (subtitlesUri.scheme == "file") { + if(subtitlesUri.scheme == "file") { var content: String? = null; val inputStream = contentResolver.openInputStream(subtitlesUri); inputStream?.use { stream -> @@ -1546,9 +1154,8 @@ abstract class StateCasting { if (content != null) { _castServer.addHandlerWithAllowAllOptions( - HttpConstantHandler( - "GET", subtitlePath, content!!, subtitleSource?.format ?: "text/vtt" - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpConstantHandler("GET", subtitlePath, content!!, subtitleSource?.format ?: "text/vtt") + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("cast"); } @@ -1601,25 +1208,16 @@ abstract class StateCasting { } for (representation in representationRegex.findAll(dashContent)) { - val mediaType = - representation.groups[1]?.value ?: throw Exception("Media type should be found") + val mediaType = representation.groups[1]?.value ?: throw Exception("Media type should be found") dashContent = mediaInitializationRegex.replace(dashContent) { if (it.range.first < representation.range.first || it.range.last > representation.range.last) { return@replace it.value } if (mediaType.startsWith("video/")) { - return@replace "${it.groups[1]!!.value}=\"${videoUrl}?url=${ - URLEncoder.encode( - it.groups[2]!!.value, "UTF-8" - ).replace("%24Number%24", "\$Number\$") - }&mediaType=${URLEncoder.encode(mediaType, "UTF-8")}\"" + return@replace "${it.groups[1]!!.value}=\"${videoUrl}?url=${URLEncoder.encode(it.groups[2]!!.value, "UTF-8").replace("%24Number%24", "\$Number\$")}&mediaType=${URLEncoder.encode(mediaType, "UTF-8")}\"" } else if (mediaType.startsWith("audio/")) { - return@replace "${it.groups[1]!!.value}=\"${audioUrl}?url=${ - URLEncoder.encode( - it.groups[2]!!.value, "UTF-8" - ).replace("%24Number%24", "\$Number\$") - }&mediaType=${URLEncoder.encode(mediaType, "UTF-8")}\"" + return@replace "${it.groups[1]!!.value}=\"${audioUrl}?url=${URLEncoder.encode(it.groups[2]!!.value, "UTF-8").replace("%24Number%24", "\$Number\$")}&mediaType=${URLEncoder.encode(mediaType, "UTF-8")}\"" } else { throw Exception("Expected audio or video") } @@ -1647,26 +1245,20 @@ abstract class StateCasting { Logger.v(TAG) { "Dash manifest: $dashContent" }; _castServer.addHandlerWithAllowAllOptions( - HttpConstantHandler( - "GET", dashPath, dashContent, "application/dash+xml" - ).withHeader("Access-Control-Allow-Origin", "*"), true + HttpConstantHandler("GET", dashPath, dashContent, + "application/dash+xml") + .withHeader("Access-Control-Allow-Origin", "*"), true ).withTag("castDashRaw"); if (videoSource != null) { _castServer.addHandlerWithAllowAllOptions( HttpFunctionHandler("GET", videoPath) { httpContext -> - val originalUrl = - httpContext.query["url"]?.let { URLDecoder.decode(it, "UTF-8") } - ?: return@HttpFunctionHandler - val mediaType = - httpContext.query["mediaType"]?.let { URLDecoder.decode(it, "UTF-8") } - ?: return@HttpFunctionHandler + val originalUrl = httpContext.query["url"]?.let { URLDecoder.decode(it, "UTF-8") } ?: return@HttpFunctionHandler + val mediaType = httpContext.query["mediaType"]?.let { URLDecoder.decode(it, "UTF-8") } ?: return@HttpFunctionHandler val videoExecutor = _videoExecutor; if (videoExecutor != null) { - val data = videoExecutor.executeRequest( - "GET", originalUrl, null, httpContext.headers - ) + val data = videoExecutor.executeRequest("GET", originalUrl, null, httpContext.headers) httpContext.respondBytes(200, HttpHeaders().apply { put("Content-Type", mediaType) }, data); @@ -1679,18 +1271,12 @@ abstract class StateCasting { if (audioSource != null) { _castServer.addHandlerWithAllowAllOptions( HttpFunctionHandler("GET", audioPath) { httpContext -> - val originalUrl = - httpContext.query["url"]?.let { URLDecoder.decode(it, "UTF-8") } - ?: return@HttpFunctionHandler - val mediaType = - httpContext.query["mediaType"]?.let { URLDecoder.decode(it, "UTF-8") } - ?: return@HttpFunctionHandler + val originalUrl = httpContext.query["url"]?.let { URLDecoder.decode(it, "UTF-8") } ?: return@HttpFunctionHandler + val mediaType = httpContext.query["mediaType"]?.let { URLDecoder.decode(it, "UTF-8") } ?: return@HttpFunctionHandler val audioExecutor = _audioExecutor; if (audioExecutor != null) { - val data = audioExecutor.executeRequest( - "GET", originalUrl, null, httpContext.headers - ) + val data = audioExecutor.executeRequest("GET", originalUrl, null, httpContext.headers) httpContext.respondBytes(200, HttpHeaders().apply { put("Content-Type", mediaType) }, data); @@ -1701,19 +1287,8 @@ abstract class StateCasting { ).withTag("castDashRaw"); } - Logger.i( - TAG, - "added new castDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath)." - ); - ad.loadVideo( - if (video.isLive) "LIVE" else "BUFFERED", - "application/dash+xml", - dashUrl, - resumePosition, - video.duration.toDouble(), - speed, - metadataFromVideo(video) - ); + Logger.i(TAG, "added new castDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath)."); + ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", "application/dash+xml", dashUrl, resumePosition, video.duration.toDouble(), speed, metadataFromVideo(video)); return listOf() } 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 c6cebbe8..b1b63080 100644 --- a/app/src/main/java/com/futo/platformplayer/dialogs/ConnectCastingDialog.kt +++ b/app/src/main/java/com/futo/platformplayer/dialogs/ConnectCastingDialog.kt @@ -121,17 +121,13 @@ class ConnectCastingDialog(context: Context?) : AlertDialog(context) { StateCasting.instance.onDeviceChanged.subscribe(this) { d -> val index = _unifiedDevices.indexOfFirst { it.castingDevice.name == d.name } if (index != -1) { - _unifiedDevices[index] = DeviceAdapterEntry( - d, - _unifiedDevices[index].isPinnedDevice, - _unifiedDevices[index].isOnlineDevice - ) + _unifiedDevices[index] = DeviceAdapterEntry(d, _unifiedDevices[index].isPinnedDevice, _unifiedDevices[index].isOnlineDevice) _adapter.notifyItemChanged(index) } } - StateCasting.instance.onDeviceRemoved.subscribe(this) { deviceName -> - _devices.remove(deviceName.name) + StateCasting.instance.onDeviceRemoved.subscribe(this) { d -> + _devices.remove(d.name) updateUnifiedList() } @@ -168,7 +164,6 @@ class ConnectCastingDialog(context: Context?) : AlertDialog(context) { && oldItem.isOnlineDevice == newItem.isOnlineDevice && oldItem.isPinnedDevice == newItem.isPinnedDevice } - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldItem = oldList[oldItemPosition] val newItem = newList[newItemPosition] @@ -191,40 +186,23 @@ class ConnectCastingDialog(context: Context?) : AlertDialog(context) { val unifiedList = mutableListOf() val onlineDevices = StateCasting.instance.devices.values.associateBy { it.name } - val rememberedDevices = - StateCasting.instance.getRememberedCastingDevices().associateBy { it.name } + val rememberedDevices = StateCasting.instance.getRememberedCastingDevices().associateBy { it.name } val intersectionNames = _devices.intersect(_rememberedDevices) for (name in intersectionNames) { - onlineDevices[name]?.let { - unifiedList.add( - DeviceAdapterEntry( - it, true, true - ) + onlineDevices[name]?.let { unifiedList.add(DeviceAdapterEntry(it, true, true) ) } } val onlineOnlyNames = _devices - _rememberedDevices for (name in onlineOnlyNames) { - onlineDevices[name]?.let { - unifiedList.add( - DeviceAdapterEntry( - it, false, true - ) - ) - } + onlineDevices[name]?.let { unifiedList.add(DeviceAdapterEntry(it, false, true )) } } val rememberedOnlyNames = _rememberedDevices - _devices for (name in rememberedOnlyNames) { - rememberedDevices[name]?.let { - unifiedList.add( - DeviceAdapterEntry( - it, true, false - ) - ) - } + rememberedDevices[name]?.let { unifiedList.add(DeviceAdapterEntry(it, true, false)) } } return unifiedList 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 bd76f3ea..1597cc2c 100644 --- a/app/src/main/java/com/futo/platformplayer/dialogs/ConnectedCastingDialog.kt +++ b/app/src/main/java/com/futo/platformplayer/dialogs/ConnectedCastingDialog.kt @@ -138,20 +138,20 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) { StateCasting.instance.onActiveDeviceVolumeChanged.remove(this) StateCasting.instance.onActiveDeviceVolumeChanged.subscribe { - _sliderVolume.value = it.toFloat().coerceAtLeast(0.0f).coerceAtMost(_sliderVolume.valueTo) - } + _sliderVolume.value = it.toFloat().coerceAtLeast(0.0f).coerceAtMost(_sliderVolume.valueTo); + }; - StateCasting.instance.onActiveDeviceTimeChanged.remove(this) + StateCasting.instance.onActiveDeviceTimeChanged.remove(this); StateCasting.instance.onActiveDeviceTimeChanged.subscribe { - _sliderPosition.value = it.toFloat().coerceAtLeast(0.0f).coerceAtMost(_sliderPosition.valueTo) - } + _sliderPosition.value = it.toFloat().coerceAtLeast(0.0f).coerceAtMost(_sliderPosition.valueTo); + }; - StateCasting.instance.onActiveDeviceDurationChanged.remove(this) + StateCasting.instance.onActiveDeviceDurationChanged.remove(this); StateCasting.instance.onActiveDeviceDurationChanged.subscribe { val dur = it.toFloat().coerceAtLeast(1.0f) - _sliderPosition.value = _sliderPosition.value.coerceAtLeast(0.0f).coerceAtMost(dur) + _sliderPosition.value = _sliderPosition.value.coerceAtLeast(0.0f).coerceAtMost(dur); _sliderPosition.valueTo = dur - } + }; val ad = StateCasting.instance.activeDevice if (ad != null) { @@ -160,23 +160,20 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) { 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() - } + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { setLoading(connectionState != CastConnectionState.CONNECTED); }; + updateDevice(); + }; updateDevice(); } override fun dismiss() { super.dismiss(); - StateCasting.instance.onActiveDeviceVolumeChanged.remove(this); StateCasting.instance.onActiveDeviceDurationChanged.remove(this); StateCasting.instance.onActiveDeviceTimeChanged.remove(this); - StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); _device = null; + StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); } private fun updateDevice() { 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 84029c7a..07f83b17 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 @@ -1171,9 +1171,8 @@ class VideoDetailView : ConstraintLayout { _onPauseCalled = true; _taskLoadVideo.cancel(); - if (StateCasting.instance.isCasting) { - return - } + if (StateCasting.instance.isCasting) + return; if(allowBackground) StatePlayer.instance.startOrUpdateMediaSession(context, video); 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 b1b87d4c..14247544 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt @@ -760,7 +760,7 @@ class StateApp { _connectivityManager?.unregisterNetworkCallback(_connectivityEvents); StatePlayer.instance.closeMediaSession(); - StateCasting.instance.stop() + StateCasting.instance.stop(); StateSync.instance.stop(); StatePlayer.dispose(); Companion.dispose();