Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay

This commit is contained in:
Kelvin 2024-07-16 20:18:54 +02:00
commit 3f9477c246
5 changed files with 67 additions and 12 deletions

View file

@ -525,6 +525,10 @@ class Settings : FragmentedStorageFileJson() {
@Serializable(with = FlexibleBooleanSerializer::class)
var keepScreenOn: Boolean = true;
@FormField(R.string.always_proxy_requests, FieldForm.TOGGLE, R.string.always_proxy_requests_description, 1)
@Serializable(with = FlexibleBooleanSerializer::class)
var alwaysProxyRequests: Boolean = false;
/*TODO: Should we have a different casting quality?
@FormField("Preferred Casting Quality", FieldForm.DROPDOWN, "", 3)
@DropdownFieldOptionsId(R.array.preferred_quality_array)

View file

@ -7,6 +7,7 @@ import android.os.Looper
import android.util.Base64
import android.util.Log
import com.futo.platformplayer.BuildConfig
import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.http.server.ManagedHttpServer
@ -452,14 +453,22 @@ class StateCasting {
}
}
} else {
val proxyStreams = Settings.instance.casting.alwaysProxyRequests;
val url = "http://${ad.localAddress.toString().trim('/')}:${_castServer.port}";
val id = UUID.randomUUID();
if (videoSource is IVideoUrlSource) {
val videoPath = "/video-${id}"
val videoUrl = if(proxyStreams) url + videoPath else videoSource.getVideoUrl();
Logger.i(TAG, "Casting as singular video");
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", videoSource.container, videoSource.getVideoUrl(), resumePosition, video.duration.toDouble(), speed);
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", videoSource.container, videoUrl, resumePosition, video.duration.toDouble(), speed);
} else if (audioSource is IAudioUrlSource) {
val audioPath = "/audio-${id}"
val audioUrl = if(proxyStreams) url + audioPath else audioSource.getAudioUrl();
Logger.i(TAG, "Casting as singular audio");
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", audioSource.container, audioSource.getAudioUrl(), resumePosition, video.duration.toDouble(), speed);
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", audioSource.container, audioUrl, resumePosition, video.duration.toDouble(), speed);
} else if(videoSource is IHLSManifestSource) {
if (ad is ChromecastCastingDevice) {
if (proxyStreams || ad is ChromecastCastingDevice) {
Logger.i(TAG, "Casting as proxied HLS");
castProxiedHls(video, videoSource.url, videoSource.codec, resumePosition, speed);
} else {
@ -467,7 +476,7 @@ class StateCasting {
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", videoSource.container, videoSource.url, resumePosition, video.duration.toDouble(), speed);
}
} else if(audioSource is IHLSManifestAudioSource) {
if (ad is ChromecastCastingDevice) {
if (proxyStreams || ad is ChromecastCastingDevice) {
Logger.i(TAG, "Casting as proxied audio HLS");
castProxiedHls(video, audioSource.url, audioSource.codec, resumePosition, speed);
} else {
@ -667,8 +676,11 @@ class StateCasting {
val audioUrl = url + audioPath;
val subtitleUrl = url + subtitlePath;
val dashContent = DashBuilder.generateOnDemandDash(videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitleUrl);
Logger.v(TAG) { "Dash manifest: $dashContent" };
_castServer.addHandlerWithAllowAllOptions(
HttpConstantHandler("GET", dashPath, DashBuilder.generateOnDemandDash(videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitleUrl),
HttpConstantHandler("GET", dashPath, dashContent,
"application/dash+xml")
.withHeader("Access-Control-Allow-Origin", "*"), true
).withTag("cast");
@ -699,13 +711,17 @@ class StateCasting {
private suspend fun castDashDirect(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoUrlSource?, audioSource: IAudioUrlSource?, subtitleSource: ISubtitleSource?, resumePosition: Double, speed: Double?) : List<String> {
val ad = activeDevice ?: return listOf();
val proxyStreams = Settings.instance.casting.alwaysProxyRequests || ad !is FCastCastingDevice;
val url = "http://${ad.localAddress.toString().trim('/')}:${_castServer.port}";
val id = UUID.randomUUID();
val subtitlePath = "/subtitle-${id}";
val videoUrl = videoSource?.getVideoUrl();
val audioUrl = audioSource?.getAudioUrl();
val videoPath = "/video-${id}"
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 subtitlesUri = if (subtitleSource != null) withContext(Dispatchers.IO) {
return@withContext subtitleSource.getSubtitlesURI();
@ -734,13 +750,28 @@ class StateCasting {
}
}
if (videoSource != null) {
_castServer.addHandlerWithAllowAllOptions(
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
).withTag("cast");
}
val content = DashBuilder.generateOnDemandDash(videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitlesUrl);
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);
return listOf(videoSource?.getVideoUrl() ?: "", audioSource?.getAudioUrl() ?: "");
}
return listOf(videoUrl ?: "", audioUrl ?: "", subtitlesUrl ?: "", videoSource?.getVideoUrl() ?: "", audioSource?.getAudioUrl() ?: "", subtitlesUri.toString()); }
private fun castProxiedHls(video: IPlatformVideoDetails, sourceUrl: String, codec: String?, resumePosition: Double, speed: Double?): List<String> {
_castServer.removeAllHandlers("castProxiedHlsMaster")
@ -1044,7 +1075,7 @@ class StateCasting {
private suspend fun castDashIndirect(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoUrlSource?, audioSource: IAudioUrlSource?, subtitleSource: ISubtitleSource?, resumePosition: Double, speed: Double?) : List<String> {
val ad = activeDevice ?: return listOf();
val proxyStreams = ad !is FCastCastingDevice;
val proxyStreams = Settings.instance.casting.alwaysProxyRequests || ad !is FCastCastingDevice;
val url = "http://${ad.localAddress.toString().trim('/')}:${_castServer.port}";
val id = UUID.randomUUID();
@ -1090,8 +1121,11 @@ class StateCasting {
}
}
val dashContent = DashBuilder.generateOnDemandDash(videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitlesUrl);
Logger.v(TAG) { "Dash manifest: $dashContent" };
_castServer.addHandlerWithAllowAllOptions(
HttpConstantHandler("GET", dashPath, DashBuilder.generateOnDemandDash(videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitlesUrl),
HttpConstantHandler("GET", dashPath, dashContent,
"application/dash+xml")
.withHeader("Access-Control-Allow-Origin", "*"), true
).withTag("cast");

View file

@ -161,6 +161,7 @@ import java.time.OffsetDateTime
import kotlin.math.abs
import kotlin.math.roundToLong
@androidx.media3.common.util.UnstableApi
class VideoDetailView : ConstraintLayout {
private val TAG = "VideoDetailView"

View file

@ -15,7 +15,9 @@ import android.media.AudioManager
import android.media.AudioManager.OnAudioFocusChangeListener
import android.media.MediaMetadata
import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.os.SystemClock
import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.MediaSessionCompat
@ -57,6 +59,15 @@ class MediaPlaybackService : Service() {
private var _audioFocusLossTime_ms: Long? = null
private var _playbackState = PlaybackStateCompat.STATE_NONE;
private val _updateIntervalMs: Long = 5 * 60 * 1000
private val _handler: Handler = Handler(Looper.getMainLooper())
private val _updateRunnable: Runnable = object : Runnable {
override fun run() {
updateMediaSession(null)
_handler.postDelayed(this, _updateIntervalMs)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Logger.v(TAG, "onStartCommand");
@ -74,6 +85,8 @@ class MediaPlaybackService : Service() {
_callOnStarted?.invoke(this);
_instance = this;
_handler.postDelayed(_updateRunnable, _updateIntervalMs)
}
catch(ex: Throwable) {
Logger.e(TAG, "Failed to start MediaPlaybackService due to: " + ex.message, ex);
@ -143,6 +156,7 @@ class MediaPlaybackService : Service() {
override fun onDestroy() {
Logger.v(TAG, "onDestroy");
_instance = null;
_handler.removeCallbacks(_updateRunnable)
MediaControlReceiver.onPauseReceived.emit();
super.onDestroy();
}

View file

@ -66,6 +66,8 @@
<string name="enabled">Enabled</string>
<string name="keep_screen_on">Keep screen on</string>
<string name="keep_screen_on_while_casting">Keep screen on while casting</string>
<string name="always_proxy_requests">Always proxy requests</string>
<string name="always_proxy_requests_description">Always proxy requests when casting data through the device.</string>
<string name="discover">Discover</string>
<string name="find_new_video_sources_to_add">Find new video sources to add</string>
<string name="these_sources_have_been_disabled">These sources have been disabled</string>