mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-05-04 08:34:28 +02:00
added dash widevine support and made audio url widevine more flexible
This commit is contained in:
parent
3ed2c1ba5d
commit
1e46949dd6
@ -399,8 +399,16 @@ class AudioUrlWidevineSource extends AudioUrlSource {
|
|||||||
super(obj);
|
super(obj);
|
||||||
this.plugin_type = "AudioUrlWidevineSource";
|
this.plugin_type = "AudioUrlWidevineSource";
|
||||||
|
|
||||||
this.bearerToken = obj.bearerToken;
|
if(obj.bearerToken) {
|
||||||
|
this.licenseHeaders = {
|
||||||
|
Authorization: `Bearer ${obj.bearerToken}`
|
||||||
|
}
|
||||||
|
}
|
||||||
this.licenseUri = obj.licenseUri;
|
this.licenseUri = obj.licenseUri;
|
||||||
|
if(obj.licenseHeaders) {
|
||||||
|
this.licenseHeaders = obj.licenseHeaders;
|
||||||
|
}
|
||||||
|
this.decodeLicenseResponse = obj.decodeLicenseResponse
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class AudioUrlRangeSource extends AudioUrlSource {
|
class AudioUrlRangeSource extends AudioUrlSource {
|
||||||
@ -443,6 +451,18 @@ class DashSource {
|
|||||||
this.requestModifier = obj.requestModifier;
|
this.requestModifier = obj.requestModifier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class DashWidevineSource extends DashSource {
|
||||||
|
constructor(obj) {
|
||||||
|
super(obj);
|
||||||
|
this.plugin_type = "DashWidevineSource";
|
||||||
|
|
||||||
|
this.licenseUri = obj.licenseUri;
|
||||||
|
if(obj.licenseHeaders) {
|
||||||
|
this.licenseHeaders = obj.licenseHeaders;
|
||||||
|
}
|
||||||
|
this.decodeLicenseResponse = obj.decodeLicenseResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
class DashManifestRawSource {
|
class DashManifestRawSource {
|
||||||
constructor(obj) {
|
constructor(obj) {
|
||||||
obj = obj ?? {};
|
obj = obj ?? {};
|
||||||
|
@ -1,122 +0,0 @@
|
|||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.ActivityInfo
|
|
||||||
import android.hardware.Sensor
|
|
||||||
import android.hardware.SensorEvent
|
|
||||||
import android.hardware.SensorEventListener
|
|
||||||
import android.hardware.SensorManager
|
|
||||||
import com.futo.platformplayer.constructs.Event1
|
|
||||||
import com.futo.platformplayer.logging.Logger
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
|
|
||||||
class AdvancedOrientationListener(private val activity: Activity, private val lifecycleScope: CoroutineScope) {
|
|
||||||
private val sensorManager: SensorManager = activity.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
|
||||||
private val accelerometer: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
|
|
||||||
private val magnetometer: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
|
|
||||||
|
|
||||||
private var lastOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
|
||||||
private var lastStableOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
|
||||||
private var lastOrientationChangeTime = 0L
|
|
||||||
private val debounceTime = 200L
|
|
||||||
private val stabilityThresholdTime = 800L
|
|
||||||
private var deviceAspectRatio: Float = 1.0f
|
|
||||||
|
|
||||||
private val gravity = FloatArray(3)
|
|
||||||
private val geomagnetic = FloatArray(3)
|
|
||||||
private val rotationMatrix = FloatArray(9)
|
|
||||||
private val orientationAngles = FloatArray(3)
|
|
||||||
|
|
||||||
val onOrientationChanged = Event1<Int>()
|
|
||||||
|
|
||||||
private val sensorListener = object : SensorEventListener {
|
|
||||||
override fun onSensorChanged(event: SensorEvent) {
|
|
||||||
when (event.sensor.type) {
|
|
||||||
Sensor.TYPE_ACCELEROMETER -> {
|
|
||||||
System.arraycopy(event.values, 0, gravity, 0, gravity.size)
|
|
||||||
}
|
|
||||||
Sensor.TYPE_MAGNETIC_FIELD -> {
|
|
||||||
System.arraycopy(event.values, 0, geomagnetic, 0, geomagnetic.size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gravity.isNotEmpty() && geomagnetic.isNotEmpty()) {
|
|
||||||
val success = SensorManager.getRotationMatrix(rotationMatrix, null, gravity, geomagnetic)
|
|
||||||
if (success) {
|
|
||||||
SensorManager.getOrientation(rotationMatrix, orientationAngles)
|
|
||||||
|
|
||||||
val azimuth = Math.toDegrees(orientationAngles[0].toDouble()).toFloat()
|
|
||||||
val pitch = Math.toDegrees(orientationAngles[1].toDouble()).toFloat()
|
|
||||||
val roll = Math.toDegrees(orientationAngles[2].toDouble()).toFloat()
|
|
||||||
|
|
||||||
val newOrientation = when {
|
|
||||||
roll in -155f .. -15f && isWithinThreshold(pitch, 0f, 30.0) -> {
|
|
||||||
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
|
||||||
}
|
|
||||||
roll in 15f .. 155f && isWithinThreshold(pitch, 0f, 30.0) -> {
|
|
||||||
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
|
||||||
}
|
|
||||||
isWithinThreshold(pitch, -90f, 30.0 * deviceAspectRatio) && roll in -15f .. 15f -> {
|
|
||||||
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
|
||||||
}
|
|
||||||
isWithinThreshold(pitch, 90f, 30.0 * deviceAspectRatio) && roll in -15f .. 15f -> {
|
|
||||||
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
|
|
||||||
}
|
|
||||||
else -> lastOrientation
|
|
||||||
}
|
|
||||||
|
|
||||||
//Logger.i("AdvancedOrientationListener", "newOrientation = ${newOrientation}, roll = ${roll}, pitch = ${pitch}, azimuth = ${azimuth}")
|
|
||||||
|
|
||||||
if (newOrientation != lastStableOrientation) {
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
if (currentTime - lastOrientationChangeTime > debounceTime) {
|
|
||||||
lastOrientationChangeTime = currentTime
|
|
||||||
lastStableOrientation = newOrientation
|
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.Main) {
|
|
||||||
try {
|
|
||||||
delay(stabilityThresholdTime)
|
|
||||||
if (newOrientation == lastStableOrientation) {
|
|
||||||
lastOrientation = newOrientation
|
|
||||||
onOrientationChanged.emit(newOrientation)
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
Logger.i(TAG, "Failed to trigger onOrientationChanged", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isWithinThreshold(value: Float, target: Float, threshold: Double): Boolean {
|
|
||||||
return Math.abs(value - target) <= threshold
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
sensorManager.registerListener(sensorListener, accelerometer, SensorManager.SENSOR_DELAY_GAME)
|
|
||||||
sensorManager.registerListener(sensorListener, magnetometer, SensorManager.SENSOR_DELAY_GAME)
|
|
||||||
|
|
||||||
val metrics = activity.resources.displayMetrics
|
|
||||||
deviceAspectRatio = (metrics.heightPixels.toFloat() / metrics.widthPixels.toFloat())
|
|
||||||
if (deviceAspectRatio == 0.0f)
|
|
||||||
deviceAspectRatio = 1.0f
|
|
||||||
|
|
||||||
lastOrientation = activity.resources.configuration.orientation
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stopListening() {
|
|
||||||
sensorManager.unregisterListener(sensorListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val TAG = "AdvancedOrientationListener"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,3 @@
|
|||||||
package com.futo.platformplayer.api.media.models.streams.sources
|
package com.futo.platformplayer.api.media.models.streams.sources
|
||||||
|
|
||||||
interface IAudioUrlWidevineSource : IAudioUrlSource {
|
interface IAudioUrlWidevineSource : IAudioUrlSource, IWidevineSource
|
||||||
val bearerToken: String
|
|
||||||
val licenseUri: String
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.futo.platformplayer.api.media.models.streams.sources
|
||||||
|
|
||||||
|
interface IDashManifestWidevineSource : IWidevineSource {
|
||||||
|
val url: String
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.futo.platformplayer.api.media.models.streams.sources
|
||||||
|
|
||||||
|
interface IWidevineSource {
|
||||||
|
val licenseUri: String
|
||||||
|
val licenseHeaders: Map<String, String>?
|
||||||
|
/**
|
||||||
|
* Set this to true if the license response is Base64 encoded
|
||||||
|
*/
|
||||||
|
val decodeLicenseResponse: Boolean
|
||||||
|
}
|
@ -3,22 +3,26 @@ 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.IAudioUrlWidevineSource
|
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.JSClient
|
||||||
|
import com.futo.platformplayer.getOrDefault
|
||||||
import com.futo.platformplayer.getOrThrow
|
import com.futo.platformplayer.getOrThrow
|
||||||
|
|
||||||
class JSAudioUrlWidevineSource : JSAudioUrlSource, IAudioUrlWidevineSource {
|
class JSAudioUrlWidevineSource : JSAudioUrlSource, IAudioUrlWidevineSource {
|
||||||
override val bearerToken: String
|
override val licenseHeaders: Map<String, String>?
|
||||||
override val licenseUri: String
|
override val licenseUri: String
|
||||||
|
override val decodeLicenseResponse: Boolean
|
||||||
|
|
||||||
@Suppress("ConvertSecondaryConstructorToPrimary")
|
@Suppress("ConvertSecondaryConstructorToPrimary")
|
||||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(plugin, obj) {
|
constructor(plugin: JSClient, obj: V8ValueObject) : super(plugin, obj) {
|
||||||
val contextName = "JSAudioUrlWidevineSource"
|
val contextName = "JSAudioUrlWidevineSource"
|
||||||
val config = plugin.config
|
val config = plugin.config
|
||||||
bearerToken = _obj.getOrThrow(config, "bearerToken", contextName)
|
licenseHeaders =
|
||||||
|
obj.getOrDefault<Map<String, String>>(config, "licenseHeaders", contextName, null)
|
||||||
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName)
|
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName)
|
||||||
|
decodeLicenseResponse = _obj.getOrThrow(config, "decodeLicenseResponse", contextName)
|
||||||
}
|
}
|
||||||
|
|
||||||
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, bearerToken=$bearerToken, licenseUri=$licenseUri)"
|
return "(name=$name, container=$container, bitrate=$bitrate, codec=$codec, url=$url, language=$language, duration=$duration, licenseHeaders=${licenseHeaders.toString()}, licenseUri=$licenseUri)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
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.getOrDefault
|
||||||
|
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 licenseHeaders: Map<String, String>?
|
||||||
|
override val licenseUri: String
|
||||||
|
override val decodeLicenseResponse: 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
|
||||||
|
|
||||||
|
licenseHeaders =
|
||||||
|
obj.getOrDefault<Map<String, String>>(config, "licenseHeaders", contextName, null)
|
||||||
|
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName)
|
||||||
|
decodeLicenseResponse = _obj.getOrThrow(config, "decodeLicenseResponse", contextName)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getVideoUrl(): String {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
@ -98,6 +98,7 @@ abstract class JSSource {
|
|||||||
const val TYPE_AUDIO_WITH_METADATA = "AudioUrlRangeSource";
|
const val TYPE_AUDIO_WITH_METADATA = "AudioUrlRangeSource";
|
||||||
const val TYPE_VIDEO_WITH_METADATA = "VideoUrlRangeSource";
|
const val TYPE_VIDEO_WITH_METADATA = "VideoUrlRangeSource";
|
||||||
const val TYPE_DASH = "DashSource";
|
const val TYPE_DASH = "DashSource";
|
||||||
|
const val TYPE_DASH_WIDEVINE = "DashWidevineSource";
|
||||||
const val TYPE_DASH_RAW = "DashRawSource";
|
const val TYPE_DASH_RAW = "DashRawSource";
|
||||||
const val TYPE_DASH_RAW_AUDIO = "DashRawAudioSource";
|
const val TYPE_DASH_RAW_AUDIO = "DashRawAudioSource";
|
||||||
const val TYPE_HLS = "HLSSource";
|
const val TYPE_HLS = "HLSSource";
|
||||||
@ -110,6 +111,7 @@ abstract class JSSource {
|
|||||||
TYPE_VIDEOURL -> JSVideoUrlSource(plugin, obj);
|
TYPE_VIDEOURL -> JSVideoUrlSource(plugin, obj);
|
||||||
TYPE_VIDEO_WITH_METADATA -> JSVideoUrlRangeSource(plugin, obj);
|
TYPE_VIDEO_WITH_METADATA -> JSVideoUrlRangeSource(plugin, obj);
|
||||||
TYPE_HLS -> fromV8HLS(plugin, obj);
|
TYPE_HLS -> fromV8HLS(plugin, obj);
|
||||||
|
TYPE_DASH_WIDEVINE -> JSDashManifestWidevineSource(plugin, obj)
|
||||||
TYPE_DASH -> fromV8Dash(plugin, obj);
|
TYPE_DASH -> fromV8Dash(plugin, obj);
|
||||||
TYPE_DASH_RAW -> fromV8DashRaw(plugin, obj);
|
TYPE_DASH_RAW -> fromV8DashRaw(plugin, obj);
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -3,14 +3,11 @@ package com.futo.platformplayer.views.video
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.Xml
|
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.fragment.app.findFragment
|
|
||||||
import androidx.lifecycle.coroutineScope
|
import androidx.lifecycle.coroutineScope
|
||||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.C.Encoding
|
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.PlaybackException
|
import androidx.media3.common.PlaybackException
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
@ -22,9 +19,9 @@ import androidx.media3.datasource.DefaultHttpDataSource
|
|||||||
import androidx.media3.datasource.HttpDataSource
|
import androidx.media3.datasource.HttpDataSource
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.dash.DashMediaSource
|
import androidx.media3.exoplayer.dash.DashMediaSource
|
||||||
import androidx.media3.exoplayer.dash.manifest.DashManifest
|
|
||||||
import androidx.media3.exoplayer.dash.manifest.DashManifestParser
|
import androidx.media3.exoplayer.dash.manifest.DashManifestParser
|
||||||
import androidx.media3.exoplayer.drm.DefaultDrmSessionManagerProvider
|
import androidx.media3.exoplayer.drm.DefaultDrmSessionManager
|
||||||
|
import androidx.media3.exoplayer.drm.HttpMediaDrmCallback
|
||||||
import androidx.media3.exoplayer.hls.HlsMediaSource
|
import androidx.media3.exoplayer.hls.HlsMediaSource
|
||||||
import androidx.media3.exoplayer.source.MediaSource
|
import androidx.media3.exoplayer.source.MediaSource
|
||||||
import androidx.media3.exoplayer.source.MergingMediaSource
|
import androidx.media3.exoplayer.source.MergingMediaSource
|
||||||
@ -34,18 +31,17 @@ import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
|||||||
import com.futo.platformplayer.Settings
|
import com.futo.platformplayer.Settings
|
||||||
import com.futo.platformplayer.api.media.models.chapters.IChapter
|
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.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.IAudioSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource
|
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.IAudioUrlWidevineSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
|
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.IHLSManifestAudioSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
|
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.IVideoSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
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.LocalAudioSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource
|
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.subtitles.ISubtitleSource
|
||||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
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.JSAudioUrlRangeSource
|
||||||
@ -55,15 +51,13 @@ import com.futo.platformplayer.api.media.platforms.js.models.sources.JSDashManif
|
|||||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSHLSManifestAudioSource
|
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.JSSource
|
||||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSVideoUrlRangeSource
|
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.constructs.Event1
|
||||||
import com.futo.platformplayer.engine.dev.V8RemoteObject
|
|
||||||
import com.futo.platformplayer.helpers.VideoHelper
|
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.Base64MediaDrmCallback
|
||||||
import com.futo.platformplayer.views.video.datasources.JSHttpDataSource
|
import com.futo.platformplayer.views.video.datasources.JSHttpDataSource
|
||||||
import com.google.gson.Gson
|
|
||||||
import getHttpDataSourceFactory
|
import getHttpDataSourceFactory
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -417,6 +411,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||||||
val didSet = when(videoSource) {
|
val didSet = when(videoSource) {
|
||||||
is LocalVideoSource -> { swapVideoSourceLocal(videoSource); true; }
|
is LocalVideoSource -> { swapVideoSourceLocal(videoSource); true; }
|
||||||
is JSVideoUrlRangeSource -> { swapVideoSourceUrlRange(videoSource); true; }
|
is JSVideoUrlRangeSource -> { swapVideoSourceUrlRange(videoSource); true; }
|
||||||
|
is IDashManifestWidevineSource -> { swapVideoSourceDashWidevine(videoSource); true }
|
||||||
is IDashManifestSource -> { swapVideoSourceDash(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 IHLSManifestSource -> { swapVideoSourceHLS(videoSource); true; }
|
||||||
@ -494,6 +489,29 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||||||
.createMediaSource(MediaItem.fromUri(videoSource.url))
|
.createMediaSource(MediaItem.fromUri(videoSource.url))
|
||||||
}
|
}
|
||||||
@OptIn(UnstableApi::class)
|
@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)
|
||||||
|
|
||||||
|
videoSource.licenseHeaders?.forEach { (key, value) ->
|
||||||
|
baseCallback.setKeyRequestProperty(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
val callback = if (videoSource.decodeLicenseResponse) {
|
||||||
|
Base64MediaDrmCallback(baseCallback)
|
||||||
|
} else {
|
||||||
|
baseCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastVideoMediaSource = DashMediaSource.Factory(dataSource).setDrmSessionManagerProvider {
|
||||||
|
DefaultDrmSessionManager.Builder().setMultiSession(true).build(callback)
|
||||||
|
}.createMediaSource(MediaItem.fromUri(videoSource.url))
|
||||||
|
}
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun swapVideoSourceDashRaw(videoSource: JSDashManifestRawSource, play: Boolean, resume: Boolean): Boolean {
|
private fun swapVideoSourceDashRaw(videoSource: JSDashManifestRawSource, play: Boolean, resume: Boolean): Boolean {
|
||||||
Logger.i(TAG, "Loading VideoSource [Dash]");
|
Logger.i(TAG, "Loading VideoSource [Dash]");
|
||||||
|
|
||||||
@ -639,6 +657,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||||||
return true;
|
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]")
|
||||||
@ -647,20 +666,26 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||||||
else
|
else
|
||||||
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT)
|
DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT)
|
||||||
|
|
||||||
val httpRequestHeaders = mapOf("Authorization" to "Bearer " + audioSource.bearerToken)
|
val baseCallback = HttpMediaDrmCallback(audioSource.licenseUri, dataSource)
|
||||||
val provider = DefaultDrmSessionManagerProvider()
|
|
||||||
provider.setDrmHttpDataSourceFactory(dataSource)
|
audioSource.licenseHeaders?.forEach { (key, value) ->
|
||||||
|
baseCallback.setKeyRequestProperty(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
val callback = if(audioSource.decodeLicenseResponse){
|
||||||
|
Base64MediaDrmCallback(baseCallback)
|
||||||
|
}else{
|
||||||
|
baseCallback
|
||||||
|
}
|
||||||
|
|
||||||
_lastAudioMediaSource = ProgressiveMediaSource.Factory(dataSource)
|
_lastAudioMediaSource = ProgressiveMediaSource.Factory(dataSource)
|
||||||
.setDrmSessionManagerProvider(provider)
|
.setDrmSessionManagerProvider {
|
||||||
.createMediaSource(
|
DefaultDrmSessionManager.Builder()
|
||||||
MediaItem.Builder()
|
|
||||||
.setUri(audioSource.getAudioUrl()).setDrmConfiguration(
|
|
||||||
MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
|
|
||||||
.setLicenseUri(audioSource.licenseUri)
|
|
||||||
.setMultiSession(true)
|
.setMultiSession(true)
|
||||||
.setLicenseRequestHeaders(httpRequestHeaders)
|
.build(callback)
|
||||||
.build()
|
}
|
||||||
).build()
|
.createMediaSource(
|
||||||
|
MediaItem.fromUri(audioSource.getAudioUrl())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.futo.platformplayer.views.video.datasources
|
||||||
|
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.exoplayer.drm.ExoMediaDrm
|
||||||
|
import androidx.media3.exoplayer.drm.MediaDrmCallback
|
||||||
|
import java.util.UUID
|
||||||
|
import kotlin.io.encoding.Base64
|
||||||
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
class Base64MediaDrmCallback(
|
||||||
|
private val delegate: MediaDrmCallback,
|
||||||
|
) : MediaDrmCallback by delegate {
|
||||||
|
|
||||||
|
@ExperimentalEncodingApi
|
||||||
|
override fun executeKeyRequest(uuid: UUID, request: ExoMediaDrm.KeyRequest): ByteArray {
|
||||||
|
val originalResponse = delegate.executeKeyRequest(uuid, request)
|
||||||
|
val decodedData: ByteArray = Base64.decode(originalResponse)
|
||||||
|
|
||||||
|
return decodedData
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user