From 1e57ade2899746f8d84cb7b87c78c010017989b5 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 30 May 2026 13:51:41 -0600 Subject: [PATCH 1/4] ExtractorAPI: support Kotlin Uuid I didn't find any extensions using the variables directly, so doesn't keep back compat there, as it was very hard to because of the way CS3IPlayer uses them etc... I decided not to change `DrmMetadata` to use the Kotlin Uuid and just convert all usage in CS3IPlayer to Java UUIDs for now. We could eventually change `DrmMetadata` to Kotlin Uuid though, and just convert to Java in one single place for Media3 but I figured that was out of scope and this accomplish the main goal of supporting Kotlin Uuid in the main API surface. This, combined with some of my other PRs accomplish the majority of the remaining API surface migrations, so I wanted to get it done before next stable also. I added a global OptIn for `kotlin.uuid.ExperimentalUuidApi` in `:app` only because adding it as `@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)` doesn't work for some reason though it does in library and would for extensions. This also will no longer be necessary at all in Kotlin 2.4, but I just wanted to do this now so that the API surface is updated and we can support the new version for the next stable release. If there is some changes or suggestions needed for this I am happy to make those. --- app/build.gradle.kts | 1 + .../cloudstream3/ui/player/CS3IPlayer.kt | 13 +++-- .../cloudstream3/utils/ExtractorApi.kt | 56 +++++++++++++++---- .../cloudstream3/utils/HlsPlaylistParser.kt | 4 +- 4 files changed, 58 insertions(+), 16 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 05b25237a87..6c784f3ef8d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -317,6 +317,7 @@ tasks.withType { optIn.addAll( "com.lagradost.cloudstream3.InternalAPI", "com.lagradost.cloudstream3.Prerelease", + "kotlin.uuid.ExperimentalUuidApi", ) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index aa44b92359b..20953ae529a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -118,6 +118,7 @@ import java.util.concurrent.Executors import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLContext import javax.net.ssl.SSLSession +import kotlin.uuid.Uuid const val TAG = "CS3ExoPlayer" const val PREFERRED_AUDIO_LANGUAGE_KEY = "preferred_audio_language" @@ -247,6 +248,10 @@ class CS3IPlayer : IPlayer { } } + private fun Uuid.toJavaUUID(): UUID { + return UUID.fromString(this.toString()) + } + fun String.stripTrackId(): String { return this.replace(Regex("""^\d+:"""), "") } @@ -1278,7 +1283,7 @@ class CS3IPlayer : IPlayer { item.drm?.let { drm -> when (drm.uuid) { - CLEARKEY_UUID -> { + CLEARKEY_UUID.toJavaUUID() -> { // Use headers from DrmMetadata for media requests val client = dataSourceFactory ?: throw IllegalArgumentException("Must supply onlineSource") @@ -1299,8 +1304,8 @@ class CS3IPlayer : IPlayer { .createMediaSource(item.mediaItem) } - WIDEVINE_UUID, - PLAYREADY_UUID -> { + WIDEVINE_UUID.toJavaUUID(), + PLAYREADY_UUID.toJavaUUID() -> { // Use headers from DrmMetadata for media requests val client = dataSourceFactory ?: throw IllegalArgumentException("Must supply onlineSource") @@ -1914,7 +1919,7 @@ class CS3IPlayer : IPlayer { drm = DrmMetadata( kid = link.kid, key = link.key, - uuid = link.uuid, + uuid = link.uuid.toJavaUUID(), kty = link.kty, licenseUrl = link.licenseUrl, keyRequestParameters = link.keyRequestParameters, diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 66055d7def5..53e82afaacd 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -1,8 +1,11 @@ +@file:OptIn(ExperimentalUuidApi::class) + package com.lagradost.cloudstream3.utils import com.fasterxml.jackson.annotation.JsonIgnore import com.lagradost.cloudstream3.AudioFile import com.lagradost.cloudstream3.IDownloadableMinimum +import com.lagradost.cloudstream3.Prerelease import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.app @@ -316,6 +319,8 @@ import org.jsoup.Jsoup import java.net.URI import java.util.UUID import kotlin.coroutines.cancellation.CancellationException +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid /** * For use in the ConcatenatingMediaSource. @@ -431,29 +436,29 @@ private fun inferTypeFromUrl(url: String): ExtractorLinkType { val INFER_TYPE: ExtractorLinkType? = null /** - * UUID for the ClearKey DRM scheme. + * [Uuid] for the ClearKey DRM scheme. * * * ClearKey is supported on Android devices running Android 5.0 (API Level 21) and up. */ -val CLEARKEY_UUID = UUID(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL) +val CLEARKEY_UUID = Uuid.fromLongs(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL) /** - * UUID for the Widevine DRM scheme. + * [Uuid] for the Widevine DRM scheme. * * * Widevine is supported on Android devices running Android 4.3 (API Level 18) and up. */ -val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L) +val WIDEVINE_UUID = Uuid.fromLongs(-0x121074568629b532L, -0x5c37d8232ae2de13L) /** - * UUID for the PlayReady DRM scheme. + * [Uuid] for the PlayReady DRM scheme. * * * PlayReady is supported on all AndroidTV devices. Note that most other Android devices do not * provide PlayReady support. */ -val PLAYREADY_UUID = UUID(-0x65fb0f8667bfbd7aL, -0x546d19a41f77a06bL) +val PLAYREADY_UUID = Uuid.fromLongs(-0x65fb0f8667bfbd7aL, -0x546d19a41f77a06bL) suspend fun newExtractorLink( source: String, @@ -476,6 +481,11 @@ suspend fun newExtractorLink( return builder } +// Deprecate after next stable +/* @Deprecated( + message = "Use Kotlin Uuid (kotlin.uuid.Uuid) instead of Java UUID.", + level = DeprecationLevel.WARNING, +) */ suspend fun newDrmExtractorLink( source: String, name: String, @@ -484,7 +494,33 @@ suspend fun newDrmExtractorLink( uuid: UUID, initializer: suspend DrmExtractorLink.() -> Unit = { } ): DrmExtractorLink { + fun UUID.toKotlinUuid(): Uuid { + return Uuid.fromLongs(mostSignificantBits, leastSignificantBits) + } + + @Suppress("DEPRECATION_ERROR") + val builder = + DrmExtractorLink( + source = source, + name = name, + url = url, + uuid = uuid.toKotlinUuid(), + type = type ?: INFER_TYPE + ) + builder.initializer() + return builder +} + +@Prerelease +suspend fun newDrmExtractorLink( + source: String, + name: String, + url: String, + type: ExtractorLinkType? = null, + uuid: Uuid, + initializer: suspend DrmExtractorLink.() -> Unit = { } +): DrmExtractorLink { @Suppress("DEPRECATION_ERROR") val builder = DrmExtractorLink( @@ -510,7 +546,7 @@ suspend fun newDrmExtractorLink( * @property type the type of the media, use [INFER_TYPE] if you want to auto infer the type from the url * @property kid Base64 value of The KID element (Key Id) contains the identifier of the key associated with a license. * @property key Base64 value of Key to be used to decrypt the media file. - * @property uuid Drm UUID [WIDEVINE_UUID], [PLAYREADY_UUID], [CLEARKEY_UUID] (by default) .. etc + * @property uuid Drm [Uuid] [WIDEVINE_UUID], [PLAYREADY_UUID], [CLEARKEY_UUID] (by default) .. etc * @property kty Key type "oct" (octet sequence) by default * @property keyRequestParameters Parameters that will used to request the key. * @see newDrmExtractorLink @@ -528,7 +564,7 @@ open class DrmExtractorLink private constructor( override var type: ExtractorLinkType, open var kid: String? = null, open var key: String? = null, - open var uuid: UUID, + open var uuid: Uuid, open var kty: String? = null, open var keyRequestParameters: HashMap, open var licenseUrl: String? = null, @@ -550,7 +586,7 @@ open class DrmExtractorLink private constructor( extractorData: String? = null, kid: String? = null, key: String? = null, - uuid: UUID = CLEARKEY_UUID, + uuid: Uuid = CLEARKEY_UUID, kty: String? = "oct", keyRequestParameters: HashMap = hashMapOf(), licenseUrl: String? = null, @@ -585,7 +621,7 @@ open class DrmExtractorLink private constructor( extractorData: String? = null, kid: String? = null, key: String? = null, - uuid: UUID = CLEARKEY_UUID, + uuid: Uuid = CLEARKEY_UUID, kty: String? = "oct", keyRequestParameters: HashMap = hashMapOf(), licenseUrl: String? = null, diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt index 898550b2467..f6da18390b4 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt @@ -1179,7 +1179,7 @@ object HlsPlaylistParser { if (KEYFORMAT_WIDEVINE_PSSH_BINARY == keyFormat) { val uriString = parseStringAttr(line, REGEX_URI, variableDefinitions) return SchemeData( - uuid = WIDEVINE_UUID, + uuid = C.WIDEVINE_UUID, mimeType = MimeTypes.VIDEO_MP4, data = Base64.Default.decode(uriString.substring(uriString.indexOf(','))) ) @@ -2078,4 +2078,4 @@ object HlsPlaylistParser { sessionKeyDrmInitData = sessionKeyDrmInitData ) } -} \ No newline at end of file +} From 86df7679b3196124a0fec8c1420c300d98479a63 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 30 May 2026 15:28:24 -0600 Subject: [PATCH 2/4] Use built in conversion methods and full back compat --- .../cloudstream3/ui/player/CS3IPlayer.kt | 20 ++++------ .../cloudstream3/utils/ExtractorApi.kt | 37 ++++++++++++------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index 20953ae529a..d7e10c81441 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -96,7 +96,7 @@ import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.applyStyle import com.lagradost.cloudstream3.utils.AppContextUtils.isUsingMobileData import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus -import com.lagradost.cloudstream3.utils.CLEARKEY_UUID +import com.lagradost.cloudstream3.utils.CLEARKEY_DRM_UUID import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount @@ -104,9 +104,9 @@ import com.lagradost.cloudstream3.utils.DrmExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList import com.lagradost.cloudstream3.utils.ExtractorLinkType -import com.lagradost.cloudstream3.utils.PLAYREADY_UUID +import com.lagradost.cloudstream3.utils.PLAYREADY_DRM_UUID import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTagToLanguageName -import com.lagradost.cloudstream3.utils.WIDEVINE_UUID +import com.lagradost.cloudstream3.utils.WIDEVINE_DRM_UUID import com.lagradost.cloudstream3.utils.videoskip.VideoSkipStamp import kotlinx.coroutines.delay import okhttp3.Interceptor @@ -118,7 +118,7 @@ import java.util.concurrent.Executors import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLContext import javax.net.ssl.SSLSession -import kotlin.uuid.Uuid +import kotlin.uuid.toJavaUuid const val TAG = "CS3ExoPlayer" const val PREFERRED_AUDIO_LANGUAGE_KEY = "preferred_audio_language" @@ -248,10 +248,6 @@ class CS3IPlayer : IPlayer { } } - private fun Uuid.toJavaUUID(): UUID { - return UUID.fromString(this.toString()) - } - fun String.stripTrackId(): String { return this.replace(Regex("""^\d+:"""), "") } @@ -1283,7 +1279,7 @@ class CS3IPlayer : IPlayer { item.drm?.let { drm -> when (drm.uuid) { - CLEARKEY_UUID.toJavaUUID() -> { + CLEARKEY_DRM_UUID.toJavaUuid() -> { // Use headers from DrmMetadata for media requests val client = dataSourceFactory ?: throw IllegalArgumentException("Must supply onlineSource") @@ -1304,8 +1300,8 @@ class CS3IPlayer : IPlayer { .createMediaSource(item.mediaItem) } - WIDEVINE_UUID.toJavaUUID(), - PLAYREADY_UUID.toJavaUUID() -> { + WIDEVINE_DRM_UUID.toJavaUuid(), + PLAYREADY_DRM_UUID.toJavaUuid() -> { // Use headers from DrmMetadata for media requests val client = dataSourceFactory ?: throw IllegalArgumentException("Must supply onlineSource") @@ -1919,7 +1915,7 @@ class CS3IPlayer : IPlayer { drm = DrmMetadata( kid = link.kid, key = link.key, - uuid = link.uuid.toJavaUUID(), + uuid = link.uuid.toJavaUuid(), kty = link.kty, licenseUrl = link.licenseUrl, keyRequestParameters = link.keyRequestParameters, diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 53e82afaacd..37c81feab99 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -317,10 +317,11 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive import org.jsoup.Jsoup import java.net.URI -import java.util.UUID import kotlin.coroutines.cancellation.CancellationException import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid +import kotlin.uuid.toJavaUuid +import kotlin.uuid.toKotlinUuid /** * For use in the ConcatenatingMediaSource. @@ -441,7 +442,8 @@ val INFER_TYPE: ExtractorLinkType? = null * * ClearKey is supported on Android devices running Android 5.0 (API Level 21) and up. */ -val CLEARKEY_UUID = Uuid.fromLongs(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL) +@Prerelease +val CLEARKEY_DRM_UUID = Uuid.fromLongs(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL) /** * [Uuid] for the Widevine DRM scheme. @@ -449,7 +451,8 @@ val CLEARKEY_UUID = Uuid.fromLongs(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL) * * Widevine is supported on Android devices running Android 4.3 (API Level 18) and up. */ -val WIDEVINE_UUID = Uuid.fromLongs(-0x121074568629b532L, -0x5c37d8232ae2de13L) +@Prerelease +val WIDEVINE_DRM_UUID = Uuid.fromLongs(-0x121074568629b532L, -0x5c37d8232ae2de13L) /** * [Uuid] for the PlayReady DRM scheme. @@ -458,7 +461,19 @@ val WIDEVINE_UUID = Uuid.fromLongs(-0x121074568629b532L, -0x5c37d8232ae2de13L) * PlayReady is supported on all AndroidTV devices. Note that most other Android devices do not * provide PlayReady support. */ -val PLAYREADY_UUID = Uuid.fromLongs(-0x65fb0f8667bfbd7aL, -0x546d19a41f77a06bL) +@Prerelease +val PLAYREADY_DRM_UUID = Uuid.fromLongs(-0x65fb0f8667bfbd7aL, -0x546d19a41f77a06bL) + +// Deprecate after next stable + +// @Deprecated("Use CLEARKEY_DRM_UUID", ReplaceWith("CLEARKEY_DRM_UUID"), level = DeprecationLevel.WARNING) +val CLEARKEY_UUID = CLEARKEY_DRM_UUID.toJavaUuid() + +// @Deprecated("Use WIDEVINE_DRM_UUID", ReplaceWith("WIDEVINE_DRM_UUID"), level = DeprecationLevel.WARNING) +val WIDEVINE_UUID = WIDEVINE_DRM_UUID.toJavaUuid() + +// @Deprecated("Use PLAYREADY_DRM_UUID", ReplaceWith("PLAYREADY_DRM_UUID"), level = DeprecationLevel.WARNING) +val PLAYREADY_UUID = PLAYREADY_DRM_UUID.toJavaUuid() suspend fun newExtractorLink( source: String, @@ -491,13 +506,9 @@ suspend fun newDrmExtractorLink( name: String, url: String, type: ExtractorLinkType? = null, - uuid: UUID, + uuid: java.util.UUID, initializer: suspend DrmExtractorLink.() -> Unit = { } ): DrmExtractorLink { - fun UUID.toKotlinUuid(): Uuid { - return Uuid.fromLongs(mostSignificantBits, leastSignificantBits) - } - @Suppress("DEPRECATION_ERROR") val builder = DrmExtractorLink( @@ -519,7 +530,7 @@ suspend fun newDrmExtractorLink( url: String, type: ExtractorLinkType? = null, uuid: Uuid, - initializer: suspend DrmExtractorLink.() -> Unit = { } + initializer: suspend DrmExtractorLink.() -> Unit = {}, ): DrmExtractorLink { @Suppress("DEPRECATION_ERROR") val builder = @@ -546,7 +557,7 @@ suspend fun newDrmExtractorLink( * @property type the type of the media, use [INFER_TYPE] if you want to auto infer the type from the url * @property kid Base64 value of The KID element (Key Id) contains the identifier of the key associated with a license. * @property key Base64 value of Key to be used to decrypt the media file. - * @property uuid Drm [Uuid] [WIDEVINE_UUID], [PLAYREADY_UUID], [CLEARKEY_UUID] (by default) .. etc + * @property uuid Drm [Uuid] [WIDEVINE_DRM_UUID], [PLAYREADY_DRM_UUID], [CLEARKEY_DRM_UUID] (by default) .. etc * @property kty Key type "oct" (octet sequence) by default * @property keyRequestParameters Parameters that will used to request the key. * @see newDrmExtractorLink @@ -586,7 +597,7 @@ open class DrmExtractorLink private constructor( extractorData: String? = null, kid: String? = null, key: String? = null, - uuid: Uuid = CLEARKEY_UUID, + uuid: Uuid = CLEARKEY_DRM_UUID, kty: String? = "oct", keyRequestParameters: HashMap = hashMapOf(), licenseUrl: String? = null, @@ -621,7 +632,7 @@ open class DrmExtractorLink private constructor( extractorData: String? = null, kid: String? = null, key: String? = null, - uuid: Uuid = CLEARKEY_UUID, + uuid: Uuid = CLEARKEY_DRM_UUID, kty: String? = "oct", keyRequestParameters: HashMap = hashMapOf(), licenseUrl: String? = null, From f68817fcdd22de446c235f34623b5a4c0a529cc8 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 30 May 2026 17:04:37 -0600 Subject: [PATCH 3/4] Add getter and setter --- .../com/lagradost/cloudstream3/utils/ExtractorApi.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 37c81feab99..eb1fb50d7d2 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -652,6 +652,14 @@ open class DrmExtractorLink private constructor( kty = kty, licenseUrl = licenseUrl, ) + + @Deprecated(level = DeprecationLevel.HIDDEN) + fun setUuid(uuid: java.util.UUID) { + this.uuid = uuid.toKotlinUuid() + } + + @Deprecated(level = DeprecationLevel.HIDDEN) + fun getUuid(): java.util.UUID = this.uuid.toJavaUuid() } /** Class holds extracted media info to be passed to the player. From 57517568b9cee8009b76f6a4d48efb04ee5f9306 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 30 May 2026 17:07:36 -0600 Subject: [PATCH 4/4] Add message --- .../kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index eb1fb50d7d2..f42128b1013 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -653,12 +653,12 @@ open class DrmExtractorLink private constructor( licenseUrl = licenseUrl, ) - @Deprecated(level = DeprecationLevel.HIDDEN) + @Deprecated(message = "Use Kotlin Uuid", level = DeprecationLevel.HIDDEN) fun setUuid(uuid: java.util.UUID) { this.uuid = uuid.toKotlinUuid() } - @Deprecated(level = DeprecationLevel.HIDDEN) + @Deprecated(message = "Use Kotlin Uuid", level = DeprecationLevel.HIDDEN) fun getUuid(): java.util.UUID = this.uuid.toJavaUuid() }