From 1075ded170a44f353e8236e903b5a096ab3f82f8 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Wed, 17 Dec 2025 15:32:37 +0100 Subject: [PATCH] Language for video support, original for video support, deduplication fix for languages on videos, submods --- app/src/main/assets/scripts/source.js | 7 +++++ .../streams/sources/DashManifestSource.kt | 3 ++ .../streams/sources/HLSManifestSource.kt | 3 ++ .../streams/sources/HLSVariantUrlSource.kt | 3 ++ .../models/streams/sources/IVideoSource.kt | 2 ++ .../streams/sources/LocalVideoSource.kt | 4 +++ .../models/streams/sources/VideoUrlSource.kt | 3 ++ .../models/sources/JSDashManifestRawSource.kt | 7 +++++ .../js/models/sources/JSDashManifestSource.kt | 6 ++++ .../sources/JSDashManifestWidevineSource.kt | 6 ++++ .../js/models/sources/JSHLSManifestSource.kt | 6 ++++ .../js/models/sources/JSVideoUrlSource.kt | 3 ++ .../models/sources/LocalVideoContentSource.kt | 3 ++ .../models/sources/LocalVideoFileSource.kt | 3 ++ .../mainactivity/main/VideoDetailView.kt | 14 ++++++++-- .../platformplayer/helpers/VideoHelper.kt | 28 +++++++++++++++++-- 16 files changed, 95 insertions(+), 6 deletions(-) diff --git a/app/src/main/assets/scripts/source.js b/app/src/main/assets/scripts/source.js index 821fa656..03d9c810 100644 --- a/app/src/main/assets/scripts/source.js +++ b/app/src/main/assets/scripts/source.js @@ -415,6 +415,8 @@ class VideoUrlSource { this.url = obj.url; if(obj.requestModifier) this.requestModifier = obj.requestModifier; + this.language = obj?.language; + this.original = obj?.original; } } class VideoUrlWidevineSource extends VideoUrlSource { @@ -512,6 +514,8 @@ class HLSSource { this.language = obj.language; if(obj.requestModifier) this.requestModifier = obj.requestModifier; + this.language = obj?.language; + this.original = obj?.original; } } class DashSource { @@ -525,6 +529,8 @@ class DashSource { this.language = obj.language; if(obj.requestModifier) this.requestModifier = obj.requestModifier; + this.language = obj?.language; + this.original = obj?.original; } } class DashWidevineSource extends DashSource { @@ -550,6 +556,7 @@ class DashManifestRawSource { this.language = obj.language ?? Language.UNKNOWN; if(obj.requestModifier) this.requestModifier = obj.requestModifier; + this.original = obj?.original; } } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/DashManifestSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/DashManifestSource.kt index 3d117516..fa23ac28 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/DashManifestSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/DashManifestSource.kt @@ -12,6 +12,9 @@ class DashManifestSource : IVideoSource, IDashManifestSource { override var priority: Boolean = false; + override val language: String? = null; + override val original: Boolean? = false; + constructor(url : String) { this.url = url; } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSManifestSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSManifestSource.kt index 52304473..5f0c94a6 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSManifestSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSManifestSource.kt @@ -12,6 +12,9 @@ class HLSManifestSource : IVideoSource, IHLSManifestSource { override var priority: Boolean = false; + override val language: String? = null; + override val original: Boolean? = false; + constructor(url : String) { this.url = url; } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSVariantUrlSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSVariantUrlSource.kt index 858c53ca..12be8b16 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSVariantUrlSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSVariantUrlSource.kt @@ -14,6 +14,9 @@ class HLSVariantVideoUrlSource( override val priority: Boolean, val url: String ) : IVideoUrlSource { + override val language: String? = null; + override val original: Boolean? = false; + override fun getVideoUrl(): String { return url } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IVideoSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IVideoSource.kt index 867c1ee5..059bafde 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IVideoSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IVideoSource.kt @@ -9,4 +9,6 @@ interface IVideoSource { val bitrate : Int?; val duration: Long; val priority: Boolean; + val language: String?; + val original: Boolean?; } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalVideoSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalVideoSource.kt index 5d15ddb8..ae72e823 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalVideoSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalVideoSource.kt @@ -16,6 +16,10 @@ class LocalVideoSource : IVideoSource, IStreamMetaDataSource { override var priority: Boolean = false; + override val language: String? = null; + override val original: Boolean? = false; + + val filePath : String; val fileSize : Long; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/VideoUrlSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/VideoUrlSource.kt index ebc112ec..21c69a04 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/VideoUrlSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/VideoUrlSource.kt @@ -19,6 +19,9 @@ open class VideoUrlSource( ) : IVideoUrlSource, IStreamMetaDataSource { override var streamMetaData: StreamMetaData? = null; + override val language: String? = null; + override val original: Boolean? = false; + override fun getVideoUrl() : String { return url; } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt index b9c08db9..463a43f9 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt @@ -39,6 +39,10 @@ open class JSDashManifestRawSource( private val ctx = "DashRawSource" private val cfg = plugin.config + override val language: String? = _obj.getOrDefault(cfg, "language", ctx, null); + override val original: Boolean? = _obj.getOrDefault(cfg, "original", ctx, null); + + override val container: String = _obj.getOrDefault(cfg, "container", ctx, null) ?: "application/dash+xml" @@ -185,6 +189,9 @@ class JSDashManifestMergingRawSource( override val priority: Boolean get() = video.priority; + override val language: String? get() = audio.language + override val original: Boolean? get() = audio.original; + override fun generateAsync(scope: CoroutineScope): V8Deferred { val videoDashDef = video.generateAsync(scope); val audioDashDef = audio.generateAsync(scope); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestSource.kt index 3070a2d4..8d26b689 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestSource.kt @@ -21,6 +21,9 @@ class JSDashManifestSource : IVideoUrlSource, IDashManifestSource, JSSource { override var priority: Boolean = false; + override val language: String?; + override val original: Boolean?; + constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_DASH, plugin, obj) { val contextName = "DashSource"; val config = plugin.config; @@ -29,6 +32,9 @@ class JSDashManifestSource : IVideoUrlSource, IDashManifestSource, JSSource { duration = _obj.getOrThrow(config, "duration", contextName); priority = obj.getOrNull(config, "priority", contextName) ?: false; + + language = obj.getOrNull(config, "language", contextName); + original = obj.getOrNull(config, "original", contextName); } override fun getVideoUrl(): String { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestWidevineSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestWidevineSource.kt index 7700bd82..f6ce30f3 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestWidevineSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestWidevineSource.kt @@ -28,6 +28,9 @@ class JSDashManifestWidevineSource : IVideoUrlSource, IDashManifestSource, override val licenseUri: String override val hasLicenseRequestExecutor: Boolean + override val language: String?; + override val original: Boolean?; + @Suppress("ConvertSecondaryConstructorToPrimary") constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_DASH, plugin, obj) { val contextName = "DashWidevineSource" @@ -40,6 +43,9 @@ class JSDashManifestWidevineSource : IVideoUrlSource, IDashManifestSource, licenseUri = _obj.getOrThrow(config, "licenseUri", contextName) hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor") + + language = _obj.getOrNull(config, "language", contextName); + original = _obj.getOrNull(config, "original", contextName); } override fun getLicenseRequestExecutor(): JSRequestExecutor? { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestSource.kt index 606d107c..1e003dcb 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestSource.kt @@ -21,6 +21,9 @@ class JSHLSManifestSource : IHLSManifestSource, JSSource { override var priority: Boolean = false; + override val language: String?; + override val original: Boolean?; + constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_HLS, plugin, obj) { val contextName = "HLSSource"; val config = plugin.config; @@ -30,5 +33,8 @@ class JSHLSManifestSource : IHLSManifestSource, JSSource { duration = _obj.getOrThrow(config, "duration", contextName).toLong(); priority = obj.getOrNull(config, "priority", contextName) ?: false; + + language = _obj.getOrNull(config, "language", contextName); + original = _obj.getOrNull(config, "original", contextName); } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoUrlSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoUrlSource.kt index dbf5242a..da514d78 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoUrlSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoUrlSource.kt @@ -44,6 +44,9 @@ open class JSVideoUrlSource( override var priority: Boolean = _obj.getOrDefault(cfg, "priority", ctx, false) ?: false + override val language: String? = _obj.getOrDefault(cfg, "language", ctx, null); + override val original: Boolean? = _obj.getOrDefault(cfg, "original", ctx, null); + override fun getVideoUrl(): String = url override fun toString(): String = diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/local/models/sources/LocalVideoContentSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/local/models/sources/LocalVideoContentSource.kt index e8b37364..e09f7d5a 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/local/models/sources/LocalVideoContentSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/local/models/sources/LocalVideoContentSource.kt @@ -20,6 +20,9 @@ class LocalVideoContentSource: IVideoSource { override val duration: Long; override val priority: Boolean = false; + override val language: String? = null; + override val original: Boolean? = false; + var contentUrl: String; constructor(contentUrl: String, mime: String, name: String? = null, duration: Long = 0) { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/local/models/sources/LocalVideoFileSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/local/models/sources/LocalVideoFileSource.kt index 4b4ff583..7378748f 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/local/models/sources/LocalVideoFileSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/local/models/sources/LocalVideoFileSource.kt @@ -20,6 +20,9 @@ class LocalVideoFileSource: IVideoSource { override val duration: Long; override val priority: Boolean = false; + override val language: String? = null; + override val original: Boolean? = null; + var file: File; constructor(file: File) { 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 9eae9587..9266df66 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 @@ -2423,9 +2423,17 @@ class VideoDetailView : ConstraintLayout { val doDedup = Settings.instance.playback.simplifySources; - val bestVideoSources = if(doDedup) (videoSources?.map { it.height * it.width } - ?.distinct() - ?.map { x -> VideoHelper.selectBestVideoSource(videoSources.filter { x == it.height * it.width }, -1, FutoVideoPlayerBase.PREFERED_VIDEO_CONTAINERS) } + val allLanguages = videoSources?.map { it.language } ?: listOf(); + val langResCombinations = if(videoSources != null) allLanguages.flatMap { + lang -> videoSources + .filter { v -> v.language == lang } + .map { it.height * it.width } + .distinct() + .map { res -> Pair(res, lang) } + } else listOf(); + + val bestVideoSources = if(doDedup && videoSources != null) (langResCombinations + ?.map { comb -> VideoHelper.selectBestVideoSource(videoSources.filter { comb.first == it.height * it.width && comb.second == it.language }, -1, FutoVideoPlayerBase.PREFERED_VIDEO_CONTAINERS) } ?.plus(videoSources.filter { it is IHLSManifestSource || it is IDashManifestSource })) ?.distinct() ?.filterNotNull() diff --git a/app/src/main/java/com/futo/platformplayer/helpers/VideoHelper.kt b/app/src/main/java/com/futo/platformplayer/helpers/VideoHelper.kt index 9c65221e..1947736d 100644 --- a/app/src/main/java/com/futo/platformplayer/helpers/VideoHelper.kt +++ b/app/src/main/java/com/futo/platformplayer/helpers/VideoHelper.kt @@ -52,8 +52,8 @@ class VideoHelper { fun isDownloadable(source: IVideoSource) = (source is IVideoUrlSource || source is IHLSManifestSource || source is JSDashManifestRawSource) && source !is IWidevineSource fun isDownloadable(source: IAudioSource) = (source is IAudioUrlSource || source is IHLSManifestAudioSource || source is JSDashManifestRawAudioSource) && source !is IWidevineSource - fun selectBestVideoSource(desc: IVideoSourceDescriptor, desiredPixelCount : Int, prefContainers : Array) : IVideoSource? = selectBestVideoSource(desc.videoSources.toList(), desiredPixelCount, prefContainers); - fun selectBestVideoSource(sources: Iterable, desiredPixelCount : Int, prefContainers : Array) : IVideoSource? { + fun selectBestVideoSource(desc: IVideoSourceDescriptor, desiredPixelCount : Int, prefContainers : Array, preferredLanguage: String? = null) : IVideoSource? = selectBestVideoSource(desc.videoSources.toList(), desiredPixelCount, prefContainers, preferredLanguage); + fun selectBestVideoSource(sources: Iterable, desiredPixelCount : Int, prefContainers : Array, preferredLanguage: String? = null) : IVideoSource? { val targetVideo = if(desiredPixelCount > 0) { sources.toList().minByOrNull { x -> abs(x.height * x.width - desiredPixelCount) }; } else { @@ -63,12 +63,34 @@ class VideoHelper { val hasPriority = sources.any { it.priority }; val targetPixelCount = if(targetVideo != null) targetVideo.width * targetVideo.height else desiredPixelCount; - val altSources = if(hasPriority) { + + //Filter priority + var altSources = if(hasPriority) { sources.filter { it.priority }.sortedBy { x -> abs(x.height * x.width - targetPixelCount) }; } else { sources.filter { it.height == (targetVideo?.height ?: 0) }; } + //Filter Original + val hasOriginal = altSources.any { it.original == true }; + if(hasOriginal && Settings.instance.playback.preferOriginalAudio) + altSources = altSources.filter { it.original == true }; + + //Filter Language + val languageToFilter = if(preferredLanguage != null && altSources.any { it.language == preferredLanguage }) { + preferredLanguage + } else { + if(altSources.any { it.language == Language.ENGLISH }) + Language.ENGLISH; + else + Language.UNKNOWN; + } + if(altSources.any { it.language == languageToFilter }) { + altSources.filter { it.language == languageToFilter }.sortedBy { it.bitrate }.toList(); + } else { + altSources.sortedBy { it.bitrate } + } + var bestSource = altSources.firstOrNull(); for (prefContainer in prefContainers) { val betterSource = altSources.firstOrNull { it.container == prefContainer };