mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-17 13:32:38 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f9477c246 | |||
| 05ed1e188e | |||
| f3d06e49f8 | |||
| f9a4b68967 |
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -127,7 +127,7 @@ class VideoHelper {
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
fun convertItagSourceToChunkedDashSource(videoSource: JSVideoUrlRangeSource) : MediaSource {
|
||||
fun convertItagSourceToChunkedDashSource(videoSource: JSVideoUrlRangeSource) : Pair<MediaSource, String> {
|
||||
val urlToUse = videoSource.getVideoUrl();
|
||||
val manifestConfig = ProgressiveDashManifestCreator.fromVideoProgressiveStreamingUrl(urlToUse,
|
||||
videoSource.duration * 1000,
|
||||
@@ -145,10 +145,10 @@ class VideoHelper {
|
||||
);
|
||||
|
||||
val manifest = DashManifestParser().parse(Uri.parse(""), manifestConfig.byteInputStream());
|
||||
return DashMediaSource.Factory(ResolvingDataSource.Factory(videoSource.getHttpDataSourceFactory(), ResolvingDataSource.Resolver { dataSpec ->
|
||||
return Pair(DashMediaSource.Factory(ResolvingDataSource.Factory(videoSource.getHttpDataSourceFactory(), ResolvingDataSource.Resolver { dataSpec ->
|
||||
Logger.v("PLAYBACK", "Video REQ Range [" + dataSpec.position + "-" + (dataSpec.position + dataSpec.length) + "](" + dataSpec.length + ")", null);
|
||||
return@Resolver dataSpec;
|
||||
})).createMediaSource(manifest, MediaItem.Builder().setUri(Uri.parse(videoSource.getVideoUrl())).build())
|
||||
})).createMediaSource(manifest, MediaItem.Builder().setUri(Uri.parse(videoSource.getVideoUrl())).build()), manifestConfig);
|
||||
}
|
||||
|
||||
fun getMediaMetadata(media: IPlatformVideoDetails): MediaMetadata {
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.media3.common.text.CueGroup
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.datasource.DefaultDataSource
|
||||
import androidx.media3.datasource.DefaultHttpDataSource
|
||||
import androidx.media3.datasource.HttpDataSource
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.dash.DashMediaSource
|
||||
import androidx.media3.exoplayer.drm.DefaultDrmSessionManagerProvider
|
||||
@@ -26,6 +27,7 @@ import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||
import com.futo.platformplayer.Settings
|
||||
import com.futo.platformplayer.api.media.models.chapters.IChapter
|
||||
import com.futo.platformplayer.api.media.models.streams.VideoMuxedSourceDescriptor
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.AudioUrlSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlWidevineSource
|
||||
@@ -36,17 +38,21 @@ import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.LocalAudioSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.VideoUrlSource
|
||||
import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSAudioUrlRangeSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSHLSManifestAudioSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSVideoUrlRangeSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSVideoUrlSource
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
import com.futo.platformplayer.engine.dev.V8RemoteObject
|
||||
import com.futo.platformplayer.helpers.VideoHelper
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
import com.futo.platformplayer.video.PlayerManager
|
||||
import com.google.gson.Gson
|
||||
import getHttpDataSourceFactory
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -68,6 +74,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||
private set;
|
||||
|
||||
private var _lastVideoMediaSource: MediaSource? = null;
|
||||
private var _lastGeneratedDash: String? = null;
|
||||
private var _lastAudioMediaSource: MediaSource? = null;
|
||||
private var _lastSubtitleMediaSource: MediaSource? = null;
|
||||
private var _shouldPlaybackRestartOnConnectivity: Boolean = false;
|
||||
@@ -375,6 +382,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||
|
||||
|
||||
private fun swapSourceInternal(videoSource: IVideoSource?) {
|
||||
_lastGeneratedDash = null;
|
||||
when(videoSource) {
|
||||
is LocalVideoSource -> swapVideoSourceLocal(videoSource);
|
||||
is JSVideoUrlRangeSource -> swapVideoSourceUrlRange(videoSource);
|
||||
@@ -415,7 +423,9 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||
if(videoSource.hasItag) {
|
||||
//Temporary workaround for Youtube
|
||||
try {
|
||||
_lastVideoMediaSource = VideoHelper.convertItagSourceToChunkedDashSource(videoSource);
|
||||
val results = VideoHelper.convertItagSourceToChunkedDashSource(videoSource);
|
||||
_lastGeneratedDash = results.second;
|
||||
_lastVideoMediaSource = results.first;
|
||||
return;
|
||||
}
|
||||
//If it fails to create the dash workaround, fallback to standard progressive
|
||||
@@ -635,6 +645,16 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||
|
||||
when (error.errorCode) {
|
||||
PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS -> {
|
||||
if(error.cause is HttpDataSource.InvalidResponseCodeException) {
|
||||
val cause = error.cause as HttpDataSource.InvalidResponseCodeException
|
||||
|
||||
Logger.v(TAG, null) {
|
||||
"ERROR BAD HTTP ${cause.responseCode},\n" +
|
||||
"Video Source: ${V8RemoteObject.gsonStandard.toJson(lastVideoSource)}\n" +
|
||||
"Audio Source: ${V8RemoteObject.gsonStandard.toJson(lastAudioSource)}\n" +
|
||||
"Dash: ${_lastGeneratedDash}"
|
||||
};
|
||||
}
|
||||
onDatasourceError.emit(error);
|
||||
}
|
||||
//PlaybackException.ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED,
|
||||
|
||||
+3
@@ -25,6 +25,7 @@ import androidx.media3.datasource.HttpDataSource;
|
||||
import androidx.media3.datasource.HttpUtil;
|
||||
import androidx.media3.datasource.TransferListener;
|
||||
|
||||
import com.futo.platformplayer.logging.Logger;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ForwardingMap;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -582,6 +583,8 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||
requestHeaders = result.getHeaders();
|
||||
}
|
||||
|
||||
Logger.Companion.v("JSHttpDataSource", "DataSource REQ: " + requestUrl, null);
|
||||
|
||||
HttpURLConnection connection = openConnection(new URL(requestUrl));
|
||||
connection.setConnectTimeout(connectTimeoutMillis);
|
||||
connection.setReadTimeout(readTimeoutMillis);
|
||||
|
||||
@@ -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>
|
||||
|
||||
Submodule app/src/stable/assets/sources/bilibili updated: b518be4dd5...2c279cb123
Submodule app/src/stable/assets/sources/youtube updated: d2a903d5dd...546d862342
Submodule app/src/unstable/assets/sources/bilibili updated: b518be4dd5...2c279cb123
Submodule app/src/unstable/assets/sources/youtube updated: d2a903d5dd...546d862342
@@ -8,7 +8,7 @@ The goal of the authentication system is to provide plugins the ability to make
|
||||
>
|
||||
>You should always only login (and install for that matter) plugins you trust.
|
||||
|
||||
How to actually use the authenticated client is described in the Http package documentation (See [Package: Http](./packages/packageHttp.md)).
|
||||
How to actually use the authenticated client is described in the Http package documentation (See [Package: Http](_blank)).
|
||||
This documentation will exclusively focus on configuring authentication and how it behaves.
|
||||
|
||||
## How it works
|
||||
@@ -58,5 +58,5 @@ Headers are exclusively applied to the domains they are retrieved from. A plugin
|
||||
By default, when authentication requests are made, the authenticated client will behave similar to that of a normal browser. Meaning that if the server you are communicating with sets new cookies, the client will use those cookies instead. These new cookies are NOT saved to disk, meaning that whenever that plugin reloads the cookies will revert to those assigned at login.
|
||||
|
||||
This behavior can be modified by using custom http clients as described in the http package documentation.
|
||||
(See [Package: Http](./packages/packageHttp.md))
|
||||
(See [Package: Http](_blank))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user