add raw dash audio source widevine support

This commit is contained in:
Kai 2024-12-18 14:12:38 -06:00
commit 6ec033274f
No known key found for this signature in database
7 changed files with 120 additions and 46 deletions

View file

@ -372,7 +372,7 @@ class VideoUrlWidevineSource extends VideoUrlSource {
super(obj); super(obj);
this.plugin_type = "VideoUrlWidevineSource"; this.plugin_type = "VideoUrlWidevineSource";
this.licenseUri = obj.licenseUri; this.widevineLicenseUri = obj.licenseUri;
if(obj.getLicenseRequestExecutor) if(obj.getLicenseRequestExecutor)
this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor; this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor;
} }
@ -409,7 +409,7 @@ class AudioUrlWidevineSource extends AudioUrlSource {
super(obj); super(obj);
this.plugin_type = "AudioUrlWidevineSource"; this.plugin_type = "AudioUrlWidevineSource";
this.licenseUri = obj.licenseUri; this.widevineLicenseUri = obj.licenseUri;
if(obj.getLicenseRequestExecutor) if(obj.getLicenseRequestExecutor)
this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor; this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor;
@ -469,6 +469,8 @@ class DashSource {
this.language = obj.language; this.language = obj.language;
if(obj.requestModifier) if(obj.requestModifier)
this.requestModifier = obj.requestModifier; this.requestModifier = obj.requestModifier;
if(obj.getRequestExecutor)
this.getRequestExecutor = obj.getRequestExecutor;
} }
} }
class DashWidevineSource extends DashSource { class DashWidevineSource extends DashSource {
@ -476,7 +478,7 @@ class DashWidevineSource extends DashSource {
super(obj); super(obj);
this.plugin_type = "DashWidevineSource"; this.plugin_type = "DashWidevineSource";
this.licenseUri = obj.licenseUri; this.widevineLicenseUri = obj.licenseUri;
if(obj.getLicenseRequestExecutor) if(obj.getLicenseRequestExecutor)
this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor; this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor;
} }
@ -511,6 +513,9 @@ class DashManifestRawAudioSource {
this.manifest = obj.manifest ?? null; this.manifest = obj.manifest ?? null;
if(obj.requestModifier) if(obj.requestModifier)
this.requestModifier = obj.requestModifier; this.requestModifier = obj.requestModifier;
this.widevineLicenseUri = obj.widevineLicenseUri;
if(obj.getLicenseRequestExecutor)
this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor;
} }
} }

View file

@ -3,7 +3,7 @@ package com.futo.platformplayer.api.media.models.streams.sources
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestExecutor import com.futo.platformplayer.api.media.platforms.js.models.JSRequestExecutor
interface IWidevineSource { interface IWidevineSource {
val licenseUri: String val widevineLicenseUri: String?
val hasLicenseRequestExecutor: Boolean val hasLicenseRequestExecutor: Boolean
fun getLicenseRequestExecutor(): JSRequestExecutor? fun getLicenseRequestExecutor(): JSRequestExecutor?
} }

View file

@ -8,7 +8,7 @@ import com.futo.platformplayer.engine.V8Plugin
import com.futo.platformplayer.getOrThrow import com.futo.platformplayer.getOrThrow
class JSAudioUrlWidevineSource : JSAudioUrlSource, IAudioUrlWidevineSource { class JSAudioUrlWidevineSource : JSAudioUrlSource, IAudioUrlWidevineSource {
override val licenseUri: String override val widevineLicenseUri: String
override val hasLicenseRequestExecutor: Boolean override val hasLicenseRequestExecutor: Boolean
@Suppress("ConvertSecondaryConstructorToPrimary") @Suppress("ConvertSecondaryConstructorToPrimary")
@ -16,7 +16,7 @@ class JSAudioUrlWidevineSource : JSAudioUrlSource, IAudioUrlWidevineSource {
val contextName = "JSAudioUrlWidevineSource" val contextName = "JSAudioUrlWidevineSource"
val config = plugin.config val config = plugin.config
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName) widevineLicenseUri = _obj.getOrThrow(config, "widevineLicenseUri", contextName)
hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor") hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor")
} }
@ -36,6 +36,6 @@ class JSAudioUrlWidevineSource : JSAudioUrlSource, IAudioUrlWidevineSource {
override fun toString(): String { override fun toString(): String {
val url = getAudioUrl() val url = getAudioUrl()
return "(name=$name, container=$container, bitrate=$bitrate, codec=$codec, url=$url, language=$language, duration=$duration, hasLicenseRequestExecutor=${hasLicenseRequestExecutor}, licenseUri=$licenseUri)" return "(name=$name, container=$container, bitrate=$bitrate, codec=$codec, url=$url, language=$language, duration=$duration, hasLicenseRequestExecutor=${hasLicenseRequestExecutor}, widevineLicenseUri=$widevineLicenseUri)"
} }
} }

View file

@ -2,19 +2,17 @@ package com.futo.platformplayer.api.media.platforms.js.models.sources
import com.caoccao.javet.values.reference.V8ValueObject import com.caoccao.javet.values.reference.V8ValueObject
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource import com.futo.platformplayer.api.media.models.streams.sources.IWidevineSource
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
import com.futo.platformplayer.api.media.platforms.js.DevJSClient import com.futo.platformplayer.api.media.platforms.js.DevJSClient
import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.api.media.platforms.js.JSClient
import com.futo.platformplayer.engine.IV8PluginConfig import com.futo.platformplayer.api.media.platforms.js.models.JSRequestExecutor
import com.futo.platformplayer.engine.V8Plugin import com.futo.platformplayer.engine.V8Plugin
import com.futo.platformplayer.getOrDefault import com.futo.platformplayer.getOrDefault
import com.futo.platformplayer.getOrNull
import com.futo.platformplayer.getOrThrow import com.futo.platformplayer.getOrThrow
import com.futo.platformplayer.others.Language import com.futo.platformplayer.others.Language
import com.futo.platformplayer.states.StateDeveloper import com.futo.platformplayer.states.StateDeveloper
class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawSource { class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawSource, IWidevineSource {
override val container : String = "application/dash+xml"; override val container : String = "application/dash+xml";
override val name : String; override val name : String;
override val codec: String; override val codec: String;
@ -29,6 +27,9 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS
override val hasGenerate: Boolean; override val hasGenerate: Boolean;
override val widevineLicenseUri: String?
override val hasLicenseRequestExecutor: Boolean
constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_DASH_RAW, plugin, obj) { constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_DASH_RAW, plugin, obj) {
val contextName = "DashRawSource"; val contextName = "DashRawSource";
val config = plugin.config; val config = plugin.config;
@ -41,6 +42,23 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS
priority = _obj.getOrDefault(config, "priority", contextName, false) ?: false; priority = _obj.getOrDefault(config, "priority", contextName, false) ?: false;
language = _obj.getOrDefault(config, "language", contextName, Language.UNKNOWN) ?: Language.UNKNOWN; language = _obj.getOrDefault(config, "language", contextName, Language.UNKNOWN) ?: Language.UNKNOWN;
hasGenerate = _obj.has("generate"); hasGenerate = _obj.has("generate");
widevineLicenseUri = _obj.getOrThrow(config, "widevineLicenseUri", contextName)
hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor")
}
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
if (!hasLicenseRequestExecutor || _obj.isClosed)
return null
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSDashManifestRawAudioSource", "obj.getLicenseRequestExecutor()") {
_obj.invoke("getLicenseRequestExecutor", arrayOf<Any>())
}
if (result !is V8ValueObject)
return null
return JSRequestExecutor(_plugin, result)
} }
override fun generate(): String? { override fun generate(): String? {
@ -61,4 +79,4 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS
_obj.invokeString("generate"); _obj.invokeString("generate");
} }
} }
} }

View file

@ -23,7 +23,7 @@ class JSDashManifestWidevineSource : IVideoUrlSource, IDashManifestSource,
override var priority: Boolean = false override var priority: Boolean = false
override val licenseUri: String override val widevineLicenseUri: String
override val hasLicenseRequestExecutor: Boolean override val hasLicenseRequestExecutor: Boolean
@Suppress("ConvertSecondaryConstructorToPrimary") @Suppress("ConvertSecondaryConstructorToPrimary")
@ -36,7 +36,7 @@ class JSDashManifestWidevineSource : IVideoUrlSource, IDashManifestSource,
priority = obj.getOrNull(config, "priority", contextName) ?: false priority = obj.getOrNull(config, "priority", contextName) ?: false
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName) widevineLicenseUri = _obj.getOrThrow(config, "widevineLicenseUri", contextName)
hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor") hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor")
} }

View file

@ -8,7 +8,7 @@ import com.futo.platformplayer.engine.V8Plugin
import com.futo.platformplayer.getOrThrow import com.futo.platformplayer.getOrThrow
class JSVideoUrlWidevineSource : JSVideoUrlSource, IVideoUrlWidevineSource { class JSVideoUrlWidevineSource : JSVideoUrlSource, IVideoUrlWidevineSource {
override val licenseUri: String override val widevineLicenseUri: String
override val hasLicenseRequestExecutor: Boolean override val hasLicenseRequestExecutor: Boolean
@Suppress("ConvertSecondaryConstructorToPrimary") @Suppress("ConvertSecondaryConstructorToPrimary")
@ -16,7 +16,7 @@ class JSVideoUrlWidevineSource : JSVideoUrlSource, IVideoUrlWidevineSource {
val contextName = "JSAudioUrlWidevineSource" val contextName = "JSAudioUrlWidevineSource"
val config = plugin.config val config = plugin.config
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName) widevineLicenseUri = _obj.getOrThrow(config, "widevineLicenseUri", contextName)
hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor") hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor")
} }
@ -36,6 +36,6 @@ class JSVideoUrlWidevineSource : JSVideoUrlSource, IVideoUrlWidevineSource {
override fun toString(): String { override fun toString(): String {
val url = getVideoUrl() val url = getVideoUrl()
return "(width=$width, height=$height, container=$container, codec=$codec, name=$name, bitrate=$bitrate, duration=$duration, url=$url, hasLicenseRequestExecutor=$hasLicenseRequestExecutor, licenseUri=$licenseUri)" return "(width=$width, height=$height, container=$container, codec=$codec, name=$name, bitrate=$bitrate, duration=$duration, url=$url, hasLicenseRequestExecutor=$hasLicenseRequestExecutor, widevineLicenseUri=$widevineLicenseUri)"
} }
} }

View file

@ -1,6 +1,7 @@
package com.futo.platformplayer.views.video package com.futo.platformplayer.views.video
import android.content.Context import android.content.Context
import android.media.MediaDrm
import android.net.Uri import android.net.Uri
import android.util.AttributeSet import android.util.AttributeSet
import android.widget.RelativeLayout import android.widget.RelativeLayout
@ -57,8 +58,8 @@ import com.futo.platformplayer.helpers.VideoHelper
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.video.PlayerManager import com.futo.platformplayer.video.PlayerManager
import com.futo.platformplayer.views.video.datasources.PluginMediaDrmCallback
import com.futo.platformplayer.views.video.datasources.JSHttpDataSource import com.futo.platformplayer.views.video.datasources.JSHttpDataSource
import com.futo.platformplayer.views.video.datasources.PluginMediaDrmCallback
import getHttpDataSourceFactory import getHttpDataSourceFactory
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -483,15 +484,20 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
private fun swapVideoSourceUrlWidevine(videoSource: IVideoUrlWidevineSource) { private fun swapVideoSourceUrlWidevine(videoSource: IVideoUrlWidevineSource) {
Logger.i(TAG, "Loading VideoSource [UrlWidevine]"); Logger.i(TAG, "Loading VideoSource [UrlWidevine]");
if (!MediaDrm.isCryptoSchemeSupported(C.WIDEVINE_UUID)) {
throw IllegalArgumentException("Device does not support Widevine")
}
val dataSource = if(videoSource is JSSource && videoSource.requiresCustomDatasource) val dataSource = if(videoSource is JSSource && videoSource.requiresCustomDatasource)
videoSource.getHttpDataSourceFactory() videoSource.getHttpDataSourceFactory()
else else
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT) DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT)
val baseCallback = HttpMediaDrmCallback(videoSource.licenseUri, dataSource) val baseCallback = HttpMediaDrmCallback(videoSource.widevineLicenseUri, dataSource)
val callback = if (videoSource.hasLicenseRequestExecutor) { val callback = if (videoSource.hasLicenseRequestExecutor) {
PluginMediaDrmCallback(baseCallback, videoSource.getLicenseRequestExecutor()!!, videoSource.licenseUri) PluginMediaDrmCallback(baseCallback, videoSource.getLicenseRequestExecutor()!!, videoSource.widevineLicenseUri!!)
} else { } else {
baseCallback baseCallback
} }
@ -519,14 +525,19 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
private fun swapVideoSourceDashWidevine(videoSource: IDashManifestWidevineSource) { private fun swapVideoSourceDashWidevine(videoSource: IDashManifestWidevineSource) {
Logger.i(TAG, "Loading VideoSource [DashWidevine]") Logger.i(TAG, "Loading VideoSource [DashWidevine]")
if (!MediaDrm.isCryptoSchemeSupported(C.WIDEVINE_UUID)) {
throw IllegalArgumentException("Device does not support Widevine")
}
val dataSource = val dataSource =
if (videoSource is JSSource && (videoSource.requiresCustomDatasource)) videoSource.getHttpDataSourceFactory() if (videoSource is JSSource && (videoSource.requiresCustomDatasource)) videoSource.getHttpDataSourceFactory()
else DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT) else DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT)
val baseCallback = HttpMediaDrmCallback(videoSource.licenseUri, dataSource) val baseCallback = HttpMediaDrmCallback(videoSource.widevineLicenseUri, dataSource)
val callback = if (videoSource.hasLicenseRequestExecutor) { val callback = if (videoSource.hasLicenseRequestExecutor) {
PluginMediaDrmCallback(baseCallback, videoSource.getLicenseRequestExecutor()!!, videoSource.licenseUri) PluginMediaDrmCallback(baseCallback, videoSource.getLicenseRequestExecutor()!!, videoSource.widevineLicenseUri!!)
} else { } else {
baseCallback baseCallback
} }
@ -651,49 +662,89 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
private fun swapAudioSourceDashRaw(audioSource: JSDashManifestRawAudioSource, play: Boolean, resume: Boolean): Boolean { private fun swapAudioSourceDashRaw(audioSource: JSDashManifestRawAudioSource, play: Boolean, resume: Boolean): Boolean {
Logger.i(TAG, "Loading AudioSource [DashRaw]"); Logger.i(TAG, "Loading AudioSource [DashRaw]")
val dataSource = if(audioSource is JSSource && (audioSource.requiresCustomDatasource)) val dataSource = if (audioSource.requiresCustomDatasource)
audioSource.getHttpDataSourceFactory() audioSource.getHttpDataSourceFactory()
else else
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT); DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT)
if(audioSource.hasGenerate) {
val baseCallback = HttpMediaDrmCallback(audioSource.widevineLicenseUri, dataSource)
val callback =
if (audioSource.hasLicenseRequestExecutor && audioSource.widevineLicenseUri != null) {
PluginMediaDrmCallback(baseCallback, audioSource.getLicenseRequestExecutor()!!, audioSource.widevineLicenseUri)
} else {
baseCallback
}
_lastVideoMediaSource = DashMediaSource.Factory(dataSource).setDrmSessionManagerProvider {
DefaultDrmSessionManager.Builder().setMultiSession(true).build(callback)
}.createMediaSource(MediaItem.fromUri(audioSource.url))
if (audioSource.hasGenerate) {
findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) { findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) {
val generated = audioSource.generate(); val generated = audioSource.generate()
if(generated != null) { if (generated != null) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
_lastVideoMediaSource = DashMediaSource.Factory(dataSource) val factory = DashMediaSource.Factory(dataSource)
.createMediaSource(DashManifestParser().parse(Uri.parse(audioSource.url), if (audioSource.widevineLicenseUri != null) {
ByteArrayInputStream(generated?.toByteArray() ?: ByteArray(0)))); if (!MediaDrm.isCryptoSchemeSupported(C.WIDEVINE_UUID)) {
loadSelectedSources(play, resume); throw IllegalArgumentException("Device does not support Widevine")
}
factory.setDrmSessionManagerProvider {
DefaultDrmSessionManager.Builder().setMultiSession(true)
.build(callback)
}
}
_lastVideoMediaSource = factory.createMediaSource(
DashManifestParser().parse(
Uri.parse(audioSource.url),
ByteArrayInputStream(generated.toByteArray() ?: ByteArray(0))
)
)
loadSelectedSources(play, resume)
} }
} }
} }
return false; return false
} } else {
else { val factory = DashMediaSource.Factory(dataSource)
_lastVideoMediaSource = DashMediaSource.Factory(dataSource) if (audioSource.widevineLicenseUri != null) {
.createMediaSource( if (!MediaDrm.isCryptoSchemeSupported(C.WIDEVINE_UUID)) {
DashManifestParser().parse( throw IllegalArgumentException("Device does not support Widevine")
Uri.parse(audioSource.url), }
ByteArrayInputStream(audioSource.manifest?.toByteArray() ?: ByteArray(0)) factory.setDrmSessionManagerProvider {
) DefaultDrmSessionManager.Builder().setMultiSession(true)
); .build(callback)
return true; }
}
_lastVideoMediaSource = factory.createMediaSource(
DashManifestParser().parse(
Uri.parse(audioSource.url),
ByteArrayInputStream(audioSource.manifest?.toByteArray() ?: ByteArray(0))
)
)
return true
} }
} }
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
private fun swapAudioSourceUrlWidevine(audioSource: IAudioUrlWidevineSource) { private fun swapAudioSourceUrlWidevine(audioSource: IAudioUrlWidevineSource) {
Logger.i(TAG, "Loading AudioSource [UrlWidevine]") Logger.i(TAG, "Loading AudioSource [UrlWidevine]")
if (!MediaDrm.isCryptoSchemeSupported(C.WIDEVINE_UUID)) {
throw IllegalArgumentException("Device does not support Widevine")
}
val dataSource = if (audioSource is JSSource && audioSource.requiresCustomDatasource) val dataSource = if (audioSource is JSSource && audioSource.requiresCustomDatasource)
audioSource.getHttpDataSourceFactory() audioSource.getHttpDataSourceFactory()
else else
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT) DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT)
val baseCallback = HttpMediaDrmCallback(audioSource.licenseUri, dataSource) val baseCallback = HttpMediaDrmCallback(audioSource.widevineLicenseUri, dataSource)
val callback = if (audioSource.hasLicenseRequestExecutor) { val callback = if (audioSource.hasLicenseRequestExecutor) {
PluginMediaDrmCallback(baseCallback, audioSource.getLicenseRequestExecutor()!!, audioSource.licenseUri) PluginMediaDrmCallback(baseCallback, audioSource.getLicenseRequestExecutor()!!, audioSource.widevineLicenseUri!!)
} else { } else {
baseCallback baseCallback
} }