mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-04-19 19:14:51 +00:00
Merge branch 'raw-dash-audio-widevine' into 'master'
add raw dash audio source widevine support See merge request videostreaming/grayjay!68
This commit is contained in:
commit
bcd6255b45
14 changed files with 168 additions and 289 deletions
|
@ -372,16 +372,20 @@ class VideoUrlSource {
|
|||
this.url = obj.url;
|
||||
if(obj.requestModifier)
|
||||
this.requestModifier = obj.requestModifier;
|
||||
|
||||
// deprecated api conversion
|
||||
if(obj.licenseUri)
|
||||
this.drmLicenseUri = obj.licenseUri;
|
||||
|
||||
if(obj.drmLicenseUri)
|
||||
this.drmLicenseUri = obj.drmLicenseUri;
|
||||
if(obj.getLicenseRequestExecutor)
|
||||
this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor;
|
||||
}
|
||||
}
|
||||
class VideoUrlWidevineSource extends VideoUrlSource {
|
||||
constructor(obj) {
|
||||
super(obj);
|
||||
this.plugin_type = "VideoUrlWidevineSource";
|
||||
|
||||
this.licenseUri = obj.licenseUri;
|
||||
if(obj.getLicenseRequestExecutor)
|
||||
this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor;
|
||||
}
|
||||
}
|
||||
class VideoUrlRangeSource extends VideoUrlSource {
|
||||
|
@ -409,16 +413,6 @@ class AudioUrlSource {
|
|||
this.language = obj.language ?? Language.UNKNOWN;
|
||||
if(obj.requestModifier)
|
||||
this.requestModifier = obj.requestModifier;
|
||||
}
|
||||
}
|
||||
class AudioUrlWidevineSource extends AudioUrlSource {
|
||||
constructor(obj) {
|
||||
super(obj);
|
||||
this.plugin_type = "AudioUrlWidevineSource";
|
||||
|
||||
this.licenseUri = obj.licenseUri;
|
||||
if(obj.getLicenseRequestExecutor)
|
||||
this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor;
|
||||
|
||||
// deprecated api conversion
|
||||
if(obj.bearerToken) {
|
||||
|
@ -436,6 +430,19 @@ class AudioUrlWidevineSource extends AudioUrlSource {
|
|||
}
|
||||
}
|
||||
}
|
||||
// deprecated api conversion
|
||||
if(obj.licenseUri)
|
||||
this.drmLicenseUri = obj.licenseUri;
|
||||
|
||||
if(obj.drmLicenseUri)
|
||||
this.drmLicenseUri = obj.drmLicenseUri;
|
||||
if(obj.getLicenseRequestExecutor)
|
||||
this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor;
|
||||
}
|
||||
}
|
||||
class AudioUrlWidevineSource extends AudioUrlSource {
|
||||
constructor(obj) {
|
||||
super(obj);
|
||||
}
|
||||
}
|
||||
class AudioUrlRangeSource extends AudioUrlSource {
|
||||
|
@ -476,16 +483,22 @@ class DashSource {
|
|||
this.language = obj.language;
|
||||
if(obj.requestModifier)
|
||||
this.requestModifier = obj.requestModifier;
|
||||
if(obj.getRequestExecutor)
|
||||
this.getRequestExecutor = obj.getRequestExecutor;
|
||||
|
||||
// deprecated api conversion
|
||||
if(obj.licenseUri)
|
||||
this.drmLicenseUri = obj.licenseUri;
|
||||
|
||||
if(obj.drmLicenseUri)
|
||||
this.drmLicenseUri = obj.drmLicenseUri;
|
||||
if(obj.getLicenseRequestExecutor)
|
||||
this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor;
|
||||
}
|
||||
}
|
||||
class DashWidevineSource extends DashSource {
|
||||
constructor(obj) {
|
||||
super(obj);
|
||||
this.plugin_type = "DashWidevineSource";
|
||||
|
||||
this.licenseUri = obj.licenseUri;
|
||||
if(obj.getLicenseRequestExecutor)
|
||||
this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor;
|
||||
}
|
||||
}
|
||||
class DashManifestRawSource {
|
||||
|
@ -518,6 +531,11 @@ class DashManifestRawAudioSource {
|
|||
this.manifest = obj.manifest ?? null;
|
||||
if(obj.requestModifier)
|
||||
this.requestModifier = obj.requestModifier;
|
||||
|
||||
if(obj.drmLicenseUri)
|
||||
this.drmLicenseUri = obj.drmLicenseUri;
|
||||
if(obj.getLicenseRequestExecutor)
|
||||
this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
package com.futo.platformplayer.api.media.models.streams.sources
|
||||
|
||||
interface IAudioUrlWidevineSource : IAudioUrlSource, IWidevineSource
|
|
@ -1,5 +0,0 @@
|
|||
package com.futo.platformplayer.api.media.models.streams.sources
|
||||
|
||||
interface IDashManifestWidevineSource : IWidevineSource {
|
||||
val url: String
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package com.futo.platformplayer.api.media.models.streams.sources
|
||||
|
||||
interface IVideoUrlWidevineSource : IVideoUrlSource, IWidevineSource
|
|
@ -1,9 +0,0 @@
|
|||
package com.futo.platformplayer.api.media.models.streams.sources
|
||||
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestExecutor
|
||||
|
||||
interface IWidevineSource {
|
||||
val licenseUri: String
|
||||
val hasLicenseRequestExecutor: Boolean
|
||||
fun getLicenseRequestExecutor(): JSRequestExecutor?
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlWidevineSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestExecutor
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
class JSAudioUrlWidevineSource : JSAudioUrlSource, IAudioUrlWidevineSource {
|
||||
override val licenseUri: String
|
||||
override val hasLicenseRequestExecutor: Boolean
|
||||
|
||||
@Suppress("ConvertSecondaryConstructorToPrimary")
|
||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(plugin, obj) {
|
||||
val contextName = "JSAudioUrlWidevineSource"
|
||||
val config = plugin.config
|
||||
|
||||
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName)
|
||||
hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor")
|
||||
}
|
||||
|
||||
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
||||
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
||||
return null
|
||||
|
||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSAudioUrlWidevineSource", "obj.getLicenseRequestExecutor()") {
|
||||
_obj.invoke("getLicenseRequestExecutor", arrayOf<Any>())
|
||||
}
|
||||
|
||||
if (result !is V8ValueObject)
|
||||
return null
|
||||
|
||||
return JSRequestExecutor(_plugin, result)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val url = getAudioUrl()
|
||||
return "(name=$name, container=$container, bitrate=$bitrate, codec=$codec, url=$url, language=$language, duration=$duration, hasLicenseRequestExecutor=${hasLicenseRequestExecutor}, licenseUri=$licenseUri)"
|
||||
}
|
||||
}
|
|
@ -2,16 +2,11 @@ package com.futo.platformplayer.api.media.platforms.js.models.sources
|
|||
|
||||
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.IDashManifestSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.other.IStreamMetaDataSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.other.StreamMetaData
|
||||
import com.futo.platformplayer.api.media.platforms.js.DevJSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.getOrNull
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.others.Language
|
||||
import com.futo.platformplayer.states.StateDeveloper
|
||||
|
@ -81,4 +76,4 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS
|
|||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestWidevineSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestExecutor
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrNull
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
class JSDashManifestWidevineSource : IVideoUrlSource, IDashManifestSource,
|
||||
IDashManifestWidevineSource, JSSource {
|
||||
override val width: Int = 0
|
||||
override val height: Int = 0
|
||||
override val container: String = "application/dash+xml"
|
||||
override val codec: String = "Dash"
|
||||
override val name: String
|
||||
override val bitrate: Int? = null
|
||||
override val url: String
|
||||
override val duration: Long
|
||||
|
||||
override var priority: Boolean = false
|
||||
|
||||
override val licenseUri: String
|
||||
override val hasLicenseRequestExecutor: Boolean
|
||||
|
||||
@Suppress("ConvertSecondaryConstructorToPrimary")
|
||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_DASH, plugin, obj) {
|
||||
val contextName = "DashWidevineSource"
|
||||
val config = plugin.config
|
||||
name = _obj.getOrThrow(config, "name", contextName)
|
||||
url = _obj.getOrThrow(config, "url", contextName)
|
||||
duration = _obj.getOrThrow(config, "duration", contextName)
|
||||
|
||||
priority = obj.getOrNull(config, "priority", contextName) ?: false
|
||||
|
||||
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName)
|
||||
hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor")
|
||||
}
|
||||
|
||||
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
||||
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
||||
return null
|
||||
|
||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSDashManifestWidevineSource", "obj.getLicenseRequestExecutor()") {
|
||||
_obj.invoke("getLicenseRequestExecutor", arrayOf<Any>())
|
||||
}
|
||||
|
||||
if (result !is V8ValueObject)
|
||||
return null
|
||||
|
||||
return JSRequestExecutor(_plugin, result)
|
||||
}
|
||||
|
||||
override fun getVideoUrl(): String {
|
||||
return url
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||
|
||||
import androidx.media3.datasource.DefaultHttpDataSource
|
||||
import androidx.media3.datasource.HttpDataSource
|
||||
import com.caoccao.javet.values.V8Value
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.modifier.AdhocRequestModifier
|
||||
|
@ -15,9 +13,9 @@ import com.futo.platformplayer.api.media.platforms.js.models.JSRequestModifier
|
|||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.orNull
|
||||
import com.futo.platformplayer.views.video.datasources.JSHttpDataSource
|
||||
|
||||
abstract class JSSource {
|
||||
protected val _plugin: JSClient;
|
||||
|
@ -30,6 +28,9 @@ abstract class JSSource {
|
|||
val hasRequestExecutor: Boolean;
|
||||
private val _requestExecutor: JSRequest?;
|
||||
|
||||
val drmLicenseUri: String?
|
||||
val hasLicenseRequestExecutor: Boolean
|
||||
|
||||
val requiresCustomDatasource: Boolean get() {
|
||||
return hasRequestModifier || hasRequestExecutor;
|
||||
}
|
||||
|
@ -51,6 +52,9 @@ abstract class JSSource {
|
|||
JSRequest(plugin, it, null, null, true);
|
||||
}
|
||||
hasRequestExecutor = _requestExecutor != null || obj.has("getRequestExecutor");
|
||||
|
||||
drmLicenseUri = _obj.getOrDefault(_config, "drmLicenseUri", "JSSource.drmLicenseUri", null)
|
||||
hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor")
|
||||
}
|
||||
|
||||
fun getRequestModifier(): IRequestModifier? {
|
||||
|
@ -84,6 +88,19 @@ abstract class JSSource {
|
|||
|
||||
return JSRequestExecutor(_plugin, result)
|
||||
}
|
||||
fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
||||
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
||||
return null
|
||||
|
||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSSource", "obj.getLicenseRequestExecutor()") {
|
||||
_obj.invoke("getLicenseRequestExecutor", arrayOf<Any>())
|
||||
}
|
||||
|
||||
if (result !is V8ValueObject)
|
||||
return null
|
||||
|
||||
return JSRequestExecutor(_plugin, result)
|
||||
}
|
||||
|
||||
fun getUnderlyingPlugin(): JSClient? {
|
||||
return _plugin;
|
||||
|
@ -98,22 +115,17 @@ abstract class JSSource {
|
|||
const val TYPE_AUDIO_WITH_METADATA = "AudioUrlRangeSource";
|
||||
const val TYPE_VIDEO_WITH_METADATA = "VideoUrlRangeSource";
|
||||
const val TYPE_DASH = "DashSource";
|
||||
const val TYPE_DASH_WIDEVINE = "DashWidevineSource";
|
||||
const val TYPE_DASH_RAW = "DashRawSource";
|
||||
const val TYPE_DASH_RAW_AUDIO = "DashRawAudioSource";
|
||||
const val TYPE_HLS = "HLSSource";
|
||||
const val TYPE_AUDIOURL_WIDEVINE = "AudioUrlWidevineSource"
|
||||
const val TYPE_VIDEOURL_WIDEVINE = "VideoUrlWidevineSource"
|
||||
|
||||
fun fromV8VideoNullable(plugin: JSClient, obj: V8Value?) : IVideoSource? = obj.orNull { fromV8Video(plugin, it as V8ValueObject) };
|
||||
fun fromV8Video(plugin: JSClient, obj: V8ValueObject) : IVideoSource? {
|
||||
val type = obj.getString("plugin_type");
|
||||
return when(type) {
|
||||
TYPE_VIDEOURL -> JSVideoUrlSource(plugin, obj);
|
||||
TYPE_VIDEOURL_WIDEVINE -> JSVideoUrlWidevineSource(plugin, obj);
|
||||
TYPE_VIDEO_WITH_METADATA -> JSVideoUrlRangeSource(plugin, obj);
|
||||
TYPE_HLS -> fromV8HLS(plugin, obj);
|
||||
TYPE_DASH_WIDEVINE -> JSDashManifestWidevineSource(plugin, obj)
|
||||
TYPE_DASH -> fromV8Dash(plugin, obj);
|
||||
TYPE_DASH_RAW -> fromV8DashRaw(plugin, obj);
|
||||
else -> {
|
||||
|
@ -135,7 +147,6 @@ abstract class JSSource {
|
|||
TYPE_HLS -> JSHLSManifestAudioSource.fromV8HLS(plugin, obj);
|
||||
TYPE_AUDIOURL -> JSAudioUrlSource(plugin, obj);
|
||||
TYPE_DASH_RAW_AUDIO -> fromV8DashRawAudio(plugin, obj);
|
||||
TYPE_AUDIOURL_WIDEVINE -> JSAudioUrlWidevineSource(plugin, obj);
|
||||
TYPE_AUDIO_WITH_METADATA -> JSAudioUrlRangeSource(plugin, obj);
|
||||
else -> {
|
||||
Logger.w("JSSource", "Unknown audio type ${type}");
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlWidevineSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestExecutor
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
class JSVideoUrlWidevineSource : JSVideoUrlSource, IVideoUrlWidevineSource {
|
||||
override val licenseUri: String
|
||||
override val hasLicenseRequestExecutor: Boolean
|
||||
|
||||
@Suppress("ConvertSecondaryConstructorToPrimary")
|
||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(plugin, obj) {
|
||||
val contextName = "JSAudioUrlWidevineSource"
|
||||
val config = plugin.config
|
||||
|
||||
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName)
|
||||
hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor")
|
||||
}
|
||||
|
||||
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
||||
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
||||
return null
|
||||
|
||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSAudioUrlWidevineSource", "obj.getLicenseRequestExecutor()") {
|
||||
_obj.invoke("getLicenseRequestExecutor", arrayOf<Any>())
|
||||
}
|
||||
|
||||
if (result !is V8ValueObject)
|
||||
return null
|
||||
|
||||
return JSRequestExecutor(_plugin, result)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val url = getVideoUrl()
|
||||
return "(width=$width, height=$height, container=$container, codec=$codec, name=$name, bitrate=$bitrate, duration=$duration, url=$url, hasLicenseRequestExecutor=$hasLicenseRequestExecutor, licenseUri=$licenseUri)"
|
||||
}
|
||||
}
|
|
@ -31,11 +31,13 @@ import android.widget.ImageView
|
|||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.Format
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.datasource.HttpDataSource
|
||||
import androidx.media3.exoplayer.analytics.AnalyticsListener
|
||||
import androidx.media3.ui.PlayerControlView
|
||||
import androidx.media3.ui.TimeBar
|
||||
import com.bumptech.glide.Glide
|
||||
|
@ -442,6 +444,21 @@ class VideoDetailView : ConstraintLayout {
|
|||
|
||||
_player.attachPlayer();
|
||||
|
||||
_player.exoPlayer?.player?.addAnalyticsListener(object : AnalyticsListener {
|
||||
override fun onDrmSessionManagerError(
|
||||
eventTime: AnalyticsListener.EventTime, error: Exception
|
||||
) {
|
||||
super.onDrmSessionManagerError(eventTime, error)
|
||||
UIDialogs.showDialog(context, R.drawable.ic_lock, context.getString(R.string.drm_not_supported), context.getString(R.string.open_and_play_in_mobile_browser), null, 1, UIDialogs.Action(context.getString(R.string.open_in_browser), {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(video?.url))
|
||||
val options =
|
||||
android.app.ActivityOptions.makeCustomAnimation(context, android.R.anim.fade_in, android.R.anim.fade_out)
|
||||
startActivity(context, intent, options.toBundle())
|
||||
}, UIDialogs.ActionStyle.NONE), UIDialogs.Action(context.getString(R.string.close), {}, UIDialogs.ActionStyle.PRIMARY)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
_container_content_liveChat.onRaidNow.subscribe {
|
||||
StatePlayer.instance.clearQueue();
|
||||
fragment.navigate<VideoDetailFragment>(it.targetUrl);
|
||||
|
|
|
@ -18,11 +18,11 @@ import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestAudi
|
|||
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
|
||||
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.IWidevineSource
|
||||
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.JSDashManifestRawAudioSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSDashManifestRawSource
|
||||
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.logging.Logger
|
||||
import com.futo.platformplayer.others.Language
|
||||
|
@ -46,10 +46,8 @@ class VideoHelper {
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
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 isDownloadable(source: IVideoSource) = (source is IVideoUrlSource || source is IHLSManifestSource || source is JSDashManifestRawSource) && !(source is JSSource && source.drmLicenseUri != null)
|
||||
fun isDownloadable(source: IAudioSource) = (source is IAudioUrlSource || source is IHLSManifestAudioSource || source is JSDashManifestRawAudioSource) && !(source is JSSource && source.drmLicenseUri != null)
|
||||
fun selectBestVideoSource(desc: IVideoSourceDescriptor, desiredPixelCount : Int, prefContainers : Array<String>) : IVideoSource? = selectBestVideoSource(desc.videoSources.toList(), desiredPixelCount, prefContainers);
|
||||
fun selectBestVideoSource(sources: Iterable<IVideoSource>, desiredPixelCount : Int, prefContainers : Array<String>) : IVideoSource? {
|
||||
val targetVideo = if(desiredPixelCount > 0) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.futo.platformplayer.views.video
|
||||
|
||||
import android.content.Context
|
||||
import android.media.MediaDrm
|
||||
import android.net.Uri
|
||||
import android.util.AttributeSet
|
||||
import android.widget.RelativeLayout
|
||||
|
@ -22,6 +23,7 @@ import androidx.media3.exoplayer.dash.DashMediaSource
|
|||
import androidx.media3.exoplayer.dash.manifest.DashManifestParser
|
||||
import androidx.media3.exoplayer.drm.DefaultDrmSessionManager
|
||||
import androidx.media3.exoplayer.drm.HttpMediaDrmCallback
|
||||
import androidx.media3.exoplayer.drm.MediaDrmCallback
|
||||
import androidx.media3.exoplayer.hls.HlsMediaSource
|
||||
import androidx.media3.exoplayer.source.MediaSource
|
||||
import androidx.media3.exoplayer.source.MergingMediaSource
|
||||
|
@ -33,14 +35,11 @@ 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.IAudioSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlWidevineSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestWidevineSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestAudioSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
|
||||
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.IVideoUrlWidevineSource
|
||||
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.subtitles.ISubtitleSource
|
||||
|
@ -57,8 +56,8 @@ 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.futo.platformplayer.views.video.datasources.PluginMediaDrmCallback
|
||||
import com.futo.platformplayer.views.video.datasources.JSHttpDataSource
|
||||
import com.futo.platformplayer.views.video.datasources.PluginMediaDrmCallback
|
||||
import getHttpDataSourceFactory
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -421,11 +420,9 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||
val didSet = when(videoSource) {
|
||||
is LocalVideoSource -> { swapVideoSourceLocal(videoSource); true; }
|
||||
is JSVideoUrlRangeSource -> { swapVideoSourceUrlRange(videoSource); true; }
|
||||
is IDashManifestWidevineSource -> { swapVideoSourceDashWidevine(videoSource); true }
|
||||
is IDashManifestSource -> { swapVideoSourceDash(videoSource); true;}
|
||||
is JSDashManifestRawSource -> swapVideoSourceDashRaw(videoSource, play, resume);
|
||||
is JSDashManifestRawSource -> swapVideoSourceDashRaw(videoSource, play, resume)
|
||||
is IHLSManifestSource -> { swapVideoSourceHLS(videoSource); true; }
|
||||
is IVideoUrlWidevineSource -> { swapVideoSourceUrlWidevine(videoSource); true; }
|
||||
is IVideoUrlSource -> { swapVideoSourceUrl(videoSource); true; }
|
||||
null -> { _lastVideoMediaSource = null; true;}
|
||||
else -> throw IllegalArgumentException("Unsupported video source [${videoSource.javaClass.simpleName}]");
|
||||
|
@ -438,8 +435,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||
is LocalAudioSource -> {swapAudioSourceLocal(audioSource); true; }
|
||||
is JSAudioUrlRangeSource -> { swapAudioSourceUrlRange(audioSource); true; }
|
||||
is JSHLSManifestAudioSource -> { swapAudioSourceHLS(audioSource); true; }
|
||||
is JSDashManifestRawAudioSource -> swapAudioSourceDashRaw(audioSource, play, resume);
|
||||
is IAudioUrlWidevineSource -> { swapAudioSourceUrlWidevine(audioSource); true; }
|
||||
is JSDashManifestRawAudioSource -> swapAudioSourceDashRaw(audioSource, play, resume)
|
||||
is IAudioUrlSource -> { swapAudioSourceUrl(audioSource); true; }
|
||||
null -> { _lastAudioMediaSource = null; true; }
|
||||
else -> throw IllegalArgumentException("Unsupported video source [${audioSource.javaClass.simpleName}]");
|
||||
|
@ -479,70 +475,50 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||
}
|
||||
else throw IllegalArgumentException("source without itag data...");
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun swapVideoSourceUrl(videoSource: IVideoUrlSource) {
|
||||
Logger.i(TAG, "Loading VideoSource [Url]");
|
||||
val dataSource = if(videoSource is JSSource && videoSource.requiresCustomDatasource)
|
||||
val dataSource = if (videoSource is JSSource && videoSource.requiresCustomDatasource)
|
||||
videoSource.getHttpDataSourceFactory()
|
||||
else
|
||||
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT);
|
||||
_lastVideoMediaSource = ProgressiveMediaSource.Factory(dataSource)
|
||||
.createMediaSource(MediaItem.fromUri(videoSource.getVideoUrl()));
|
||||
}
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun swapVideoSourceUrlWidevine(videoSource: IVideoUrlWidevineSource) {
|
||||
Logger.i(TAG, "Loading VideoSource [UrlWidevine]");
|
||||
val dataSource = if(videoSource is JSSource && videoSource.requiresCustomDatasource)
|
||||
videoSource.getHttpDataSourceFactory()
|
||||
else
|
||||
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT)
|
||||
|
||||
val baseCallback = HttpMediaDrmCallback(videoSource.licenseUri, dataSource)
|
||||
val drmCallback = if (videoSource is JSSource) getDrmCallback(videoSource, dataSource) else null
|
||||
|
||||
val callback = if (videoSource.hasLicenseRequestExecutor) {
|
||||
PluginMediaDrmCallback(baseCallback, videoSource.getLicenseRequestExecutor()!!, videoSource.licenseUri)
|
||||
} else {
|
||||
baseCallback
|
||||
}
|
||||
|
||||
_lastVideoMediaSource = ProgressiveMediaSource.Factory(dataSource)
|
||||
.setDrmSessionManagerProvider {
|
||||
val factory = ProgressiveMediaSource.Factory(dataSource)
|
||||
if (drmCallback != null) {
|
||||
factory.setDrmSessionManagerProvider {
|
||||
DefaultDrmSessionManager.Builder()
|
||||
.setMultiSession(true)
|
||||
.build(callback)
|
||||
.build(drmCallback)
|
||||
}
|
||||
.createMediaSource(
|
||||
MediaItem.fromUri(videoSource.getVideoUrl())
|
||||
)
|
||||
}
|
||||
|
||||
_lastVideoMediaSource = factory
|
||||
.createMediaSource(MediaItem.fromUri(videoSource.getVideoUrl()));
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun swapVideoSourceDash(videoSource: IDashManifestSource) {
|
||||
Logger.i(TAG, "Loading VideoSource [Dash]");
|
||||
val dataSource = if(videoSource is JSSource && (videoSource.requiresCustomDatasource))
|
||||
val dataSource = if (videoSource is JSSource && (videoSource.requiresCustomDatasource))
|
||||
videoSource.getHttpDataSourceFactory()
|
||||
else
|
||||
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT);
|
||||
_lastVideoMediaSource = DashMediaSource.Factory(dataSource)
|
||||
.createMediaSource(MediaItem.fromUri(videoSource.url))
|
||||
}
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun swapVideoSourceDashWidevine(videoSource: IDashManifestWidevineSource) {
|
||||
Logger.i(TAG, "Loading VideoSource [DashWidevine]")
|
||||
val dataSource =
|
||||
if (videoSource is JSSource && (videoSource.requiresCustomDatasource)) videoSource.getHttpDataSourceFactory()
|
||||
else DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT)
|
||||
|
||||
val baseCallback = HttpMediaDrmCallback(videoSource.licenseUri, dataSource)
|
||||
val drmCallback =
|
||||
if (videoSource is JSSource) getDrmCallback(videoSource, dataSource) else null
|
||||
|
||||
val callback = if (videoSource.hasLicenseRequestExecutor) {
|
||||
PluginMediaDrmCallback(baseCallback, videoSource.getLicenseRequestExecutor()!!, videoSource.licenseUri)
|
||||
} else {
|
||||
baseCallback
|
||||
val factory = DashMediaSource.Factory(dataSource)
|
||||
if (drmCallback != null) {
|
||||
factory.setDrmSessionManagerProvider {
|
||||
DefaultDrmSessionManager.Builder().setMultiSession(true).build(drmCallback)
|
||||
}
|
||||
}
|
||||
|
||||
_lastVideoMediaSource = DashMediaSource.Factory(dataSource).setDrmSessionManagerProvider {
|
||||
DefaultDrmSessionManager.Builder().setMultiSession(true).build(callback)
|
||||
}.createMediaSource(MediaItem.fromUri(videoSource.url))
|
||||
_lastVideoMediaSource =
|
||||
factory.createMediaSource(MediaItem.fromUri(videoSource.url))
|
||||
}
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun swapVideoSourceDashRaw(videoSource: JSDashManifestRawSource, play: Boolean, resume: Boolean): Boolean {
|
||||
|
@ -637,15 +613,28 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||
}
|
||||
else throw IllegalArgumentException("source without itag data...")
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun swapAudioSourceUrl(audioSource: IAudioUrlSource) {
|
||||
Logger.i(TAG, "Loading AudioSource [Url]");
|
||||
val dataSource = if(audioSource is JSSource && audioSource.requiresCustomDatasource)
|
||||
val dataSource = if (audioSource is JSSource && audioSource.requiresCustomDatasource)
|
||||
audioSource.getHttpDataSourceFactory()
|
||||
else
|
||||
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT);
|
||||
_lastAudioMediaSource = ProgressiveMediaSource.Factory(dataSource)
|
||||
.createMediaSource(MediaItem.fromUri(audioSource.getAudioUrl()));
|
||||
|
||||
val drmCallback =
|
||||
if (audioSource is JSSource) getDrmCallback(audioSource, dataSource) else null
|
||||
|
||||
val factory = ProgressiveMediaSource.Factory(dataSource)
|
||||
if (drmCallback != null) {
|
||||
factory.setDrmSessionManagerProvider {
|
||||
DefaultDrmSessionManager.Builder()
|
||||
.setMultiSession(true)
|
||||
.build(drmCallback)
|
||||
}
|
||||
}
|
||||
_lastAudioMediaSource =
|
||||
factory.createMediaSource(MediaItem.fromUri(audioSource.getAudioUrl()));
|
||||
}
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun swapAudioSourceHLS(audioSource: IHLSManifestAudioSource) {
|
||||
|
@ -661,26 +650,46 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||
@OptIn(UnstableApi::class)
|
||||
private fun swapAudioSourceDashRaw(audioSource: JSDashManifestRawAudioSource, play: Boolean, resume: Boolean): Boolean {
|
||||
Logger.i(TAG, "Loading AudioSource [DashRaw]");
|
||||
val dataSource = if(audioSource is JSSource && (audioSource.requiresCustomDatasource))
|
||||
val dataSource = if (audioSource.requiresCustomDatasource)
|
||||
audioSource.getHttpDataSourceFactory()
|
||||
else
|
||||
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT);
|
||||
if(audioSource.hasGenerate) {
|
||||
|
||||
val drmCallback = getDrmCallback(audioSource, dataSource)
|
||||
|
||||
if (audioSource.hasGenerate) {
|
||||
findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.launch(Dispatchers.IO) {
|
||||
val generated = audioSource.generate();
|
||||
if(generated != null) {
|
||||
if (generated != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
_lastVideoMediaSource = DashMediaSource.Factory(dataSource)
|
||||
.createMediaSource(DashManifestParser().parse(Uri.parse(audioSource.url),
|
||||
ByteArrayInputStream(generated?.toByteArray() ?: ByteArray(0))));
|
||||
val factory = DashMediaSource.Factory(dataSource)
|
||||
if (drmCallback != null) {
|
||||
factory.setDrmSessionManagerProvider {
|
||||
DefaultDrmSessionManager.Builder().setMultiSession(true)
|
||||
.build(drmCallback)
|
||||
}
|
||||
}
|
||||
_lastVideoMediaSource = factory
|
||||
.createMediaSource(
|
||||
DashManifestParser().parse(
|
||||
Uri.parse(audioSource.url),
|
||||
ByteArrayInputStream(generated.toByteArray() ?: ByteArray(0))
|
||||
)
|
||||
);
|
||||
loadSelectedSources(play, resume);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
_lastVideoMediaSource = DashMediaSource.Factory(dataSource)
|
||||
} else {
|
||||
val factory = DashMediaSource.Factory(dataSource)
|
||||
if (drmCallback != null) {
|
||||
factory.setDrmSessionManagerProvider {
|
||||
DefaultDrmSessionManager.Builder().setMultiSession(true)
|
||||
.build(drmCallback)
|
||||
}
|
||||
}
|
||||
_lastVideoMediaSource = factory
|
||||
.createMediaSource(
|
||||
DashManifestParser().parse(
|
||||
Uri.parse(audioSource.url),
|
||||
|
@ -692,32 +701,22 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun swapAudioSourceUrlWidevine(audioSource: IAudioUrlWidevineSource) {
|
||||
Logger.i(TAG, "Loading AudioSource [UrlWidevine]")
|
||||
val dataSource = if (audioSource is JSSource && audioSource.requiresCustomDatasource)
|
||||
audioSource.getHttpDataSourceFactory()
|
||||
else
|
||||
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT)
|
||||
|
||||
val baseCallback = HttpMediaDrmCallback(audioSource.licenseUri, dataSource)
|
||||
|
||||
val callback = if (audioSource.hasLicenseRequestExecutor) {
|
||||
PluginMediaDrmCallback(baseCallback, audioSource.getLicenseRequestExecutor()!!, audioSource.licenseUri)
|
||||
} else {
|
||||
baseCallback
|
||||
}
|
||||
|
||||
_lastAudioMediaSource = ProgressiveMediaSource.Factory(dataSource)
|
||||
.setDrmSessionManagerProvider {
|
||||
DefaultDrmSessionManager.Builder()
|
||||
.setMultiSession(true)
|
||||
.build(callback)
|
||||
private fun getDrmCallback(source: JSSource, dataSource: HttpDataSource.Factory): MediaDrmCallback? {
|
||||
return if (source.drmLicenseUri != null) {
|
||||
if (!MediaDrm.isCryptoSchemeSupported(C.WIDEVINE_UUID)) {
|
||||
throw IllegalArgumentException("Device does not support Widevine")
|
||||
}
|
||||
.createMediaSource(
|
||||
MediaItem.fromUri(audioSource.getAudioUrl())
|
||||
)
|
||||
}
|
||||
|
||||
val delegateCallback =
|
||||
HttpMediaDrmCallback(source.drmLicenseUri, dataSource)
|
||||
|
||||
if (source.hasLicenseRequestExecutor) {
|
||||
PluginMediaDrmCallback(delegateCallback, source.getLicenseRequestExecutor()!!, source.drmLicenseUri)
|
||||
} else {
|
||||
delegateCallback
|
||||
}
|
||||
} else null
|
||||
}
|
||||
|
||||
//Prefered source selection
|
||||
fun getPreferredVideoSource(video: IPlatformVideoDetails, targetPixels: Int = -1): IVideoSource? {
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
<string name="preferred_quality_description">Default quality for watching a video</string>
|
||||
<string name="update">Update</string>
|
||||
<string name="close">Close</string>
|
||||
<string name="open_in_browser">Open In Browser</string>
|
||||
<string name="never">Never</string>
|
||||
<string name="import_options">Select any of the following available import options.</string>
|
||||
<string name="there_is_an_update_available_do_you_wish_to_update">There is an update available, do you wish to update?</string>
|
||||
|
@ -245,6 +246,8 @@
|
|||
<string name="locked_content_description">This content is locked</string>
|
||||
<string name="unknown">Unknown</string>
|
||||
<string name="tap_to_open_in_browser">Tap to open in browser</string>
|
||||
<string name="drm_not_supported">This device does not support the DRM required to play this content</string>
|
||||
<string name="open_and_play_in_mobile_browser">Please try playing this content in a mobile browser on this device. If it plays open a bug report and share your device model and OS version.</string>
|
||||
<string name="missing_plugin">Missing Plugin</string>
|
||||
<string name="viewers_are_raiding">Viewers are raiding</string>
|
||||
<string name="go_now">Go now</string>
|
||||
|
|
Loading…
Add table
Reference in a new issue