From b423f40fdba7c61e831d0bd0ede8716fca8e3902 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 23 May 2026 12:59:30 -0600 Subject: [PATCH 1/6] HlsPlaylistParser: replace most usage of Java APIs with kotlin equivalents This does add kotlinx-io as a dependency, it is more light weight then okio, and when we migrate to ktor for example it will become an implicit dependency anyway so it should be fine to just add. Allows for more reliable and readable usage then doing some of this manually. The largest change this does is probably to change from Java UUIDs to Kotlin native Uuid, however nothing calls the overloads this changes except in this class itself. The larger breaking change will be to eventually do that in ExtractorApi as well. --- gradle/libs.versions.toml | 2 + library/build.gradle.kts | 1 + .../cloudstream3/utils/HlsPlaylistParser.kt | 64 +++++++++++-------- 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a97145c3f81..01a35ed3ecf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,6 +28,7 @@ juniversalchardet = "2.5.0" kotlinGradlePlugin = "2.3.20" kotlinxCollectionsImmutable = "0.4.0" kotlinxCoroutinesCore = "1.10.2" +kotlinxIOCore = "0.9.0" lifecycleKtx = "2.10.0" material = "1.14.0-beta01" media3 = "1.9.3" @@ -83,6 +84,7 @@ junit-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "junitKtx" } juniversalchardet = { module = "com.github.albfernandez:juniversalchardet", version.ref = "juniversalchardet" } kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } +kotlinx-io-core = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.ref = "kotlinxIOCore" } lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleKtx" } lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleKtx" } material = { module = "com.google.android.material:material", version.ref = "material" } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 073e49e6483..82175a20ba6 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -57,6 +57,7 @@ kotlin { implementation(libs.nicehttp) // HTTP Lib implementation(libs.jackson.module.kotlin) // JSON Parser implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.io.core) implementation(libs.fuzzywuzzy) // Match Extractors implementation(libs.jsoup) // HTML Parser implementation(libs.rhino) // Run JavaScript 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 bea75aa581c..90368da04f0 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalUuidApi::class) + /* * Copyright (C) 2016 The Android Open Source Project * @@ -19,12 +21,14 @@ */ package com.lagradost.cloudstream3.utils -import java.io.IOException import java.net.URI -import java.nio.ByteBuffer -import java.util.UUID +import kotlinx.io.Buffer +import kotlinx.io.IOException +import kotlinx.io.readByteArray import kotlin.io.encoding.Base64 import kotlin.io.encoding.ExperimentalEncodingApi +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid @Suppress("unused") object HlsPlaylistParser { @@ -187,13 +191,13 @@ object HlsPlaylistParser { data class SchemeData( /** - * The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is universal (i.e. + * The [Uuid] of the DRM scheme, or [C.UUID_NIL] if the data is universal (i.e. * applies to all schemes). */ - val uuid: UUID, + val uuid: Uuid, /** The URL of the server to which license requests should be made. May be null if unknown. */ val licenseServerUrl: String? = null, - /** The mimeType of {@link #data}. */ + /** The mimeType of [data]. */ val mimeType: String, /** The initialization data. May be null for scheme support checks only. */ val data: ByteArray @@ -535,29 +539,29 @@ object HlsPlaylistParser { object C { /** - * 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) /** "cenc" scheme type name as defined in ISO/IEC 23001-7:2016. */ @@ -1067,7 +1071,7 @@ object HlsPlaylistParser { object PsshAtomUtil { fun buildPsshAtom( - systemId: UUID, keyIds: Array?, data: ByteArray? + systemId: Uuid, keyIds: Array?, data: ByteArray? ): ByteArray { val dataLength = data?.size ?: 0 var psshBoxLength: Int = @@ -1075,26 +1079,30 @@ object HlsPlaylistParser { if (keyIds != null) { psshBoxLength += 4 /* KID_count */ + (keyIds.size * 16) /* KIDs */ } - val psshBox: ByteBuffer = ByteBuffer.allocate(psshBoxLength) - psshBox.putInt(psshBoxLength) - psshBox.putInt(Mp4Box.TYPE_pssh) - psshBox.putInt(if (keyIds != null) 0x01000000 else 0 /* version=(buildV1Atom ? 1 : 0), flags=0 */) - psshBox.putLong(systemId.mostSignificantBits) - psshBox.putLong(systemId.leastSignificantBits) + val buffer = Buffer() + buffer.writeInt(psshBoxLength) + buffer.writeInt(Mp4Box.TYPE_pssh) + buffer.writeInt(if (keyIds != null) 0x01000000 else 0 /* version=(buildV1Atom ? 1 : 0), flags=0 */) + systemId.toLongs { mostSignificantBits, leastSignificantBits -> + buffer.writeLong(mostSignificantBits) + buffer.writeLong(leastSignificantBits) + } if (keyIds != null) { - psshBox.putInt(keyIds.size) + buffer.writeInt(keyIds.size) for (keyId in keyIds) { - psshBox.putLong(keyId.mostSignificantBits) - psshBox.putLong(keyId.leastSignificantBits) + keyId.toLongs { mostSignificantBits, leastSignificantBits -> + buffer.writeLong(mostSignificantBits) + buffer.writeLong(leastSignificantBits) + } } } if (data != null && data.size != 0) { - psshBox.putInt(data.size) - psshBox.put(data) + buffer.writeInt(data.size) + buffer.write(data) } else { - psshBox.putInt(0) + buffer.writeInt(0) } - return psshBox.array() + return buffer.readByteArray() } } @@ -1179,7 +1187,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 +2086,4 @@ object HlsPlaylistParser { sessionKeyDrmInitData = sessionKeyDrmInitData ) } -} \ No newline at end of file +} From b3b3ad4d8234046e87be304ef6ef0b8a5c497401 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 30 Jun 2026 18:36:18 -0600 Subject: [PATCH 2/6] 0.9.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 70edad7ce5a..e5f955c2ec8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,7 +30,7 @@ kotlinxAtomicfu = "0.33.0" kotlinxCollectionsImmutable = "0.4.0" kotlinxCoroutinesCore = "1.11.0" kotlinxDatetime = "0.8.0" -kotlinxIOCore = "0.9.0" +kotlinxIOCore = "0.9.1" kotlinxSerializationJson = "1.11.0" ktor = "3.5.0" lifecycleKtx = "2.10.0" From f9ee605069bb9cfea87d6c78ce25f365689f81c6 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:54:43 -0600 Subject: [PATCH 3/6] Use deleteRange --- .../com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 760f6090959..dda31543ef7 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt @@ -458,14 +458,14 @@ object HlsPlaylistParser { // "." or "..", remove the appropriate segments of the path. if (i == segmentStart + 1 && url[segmentStart] == '.') { // Given "abc/def/./ghi", remove "./" to get "abc/def/ghi". - url.delete(segmentStart, nextSegmentStart) + url.deleteRange(segmentStart, nextSegmentStart) limit -= nextSegmentStart - segmentStart i = segmentStart } else if (i == segmentStart + 2 && url[segmentStart] == '.' && url[segmentStart + 1] == '.') { // Given "abc/def/../ghi", remove "def/../" to get "abc/ghi". val prevSegmentStart = url.lastIndexOf("/", segmentStart - 2) + 1 val removeFrom = if (prevSegmentStart > offset) prevSegmentStart else offset - url.delete(removeFrom, nextSegmentStart) + url.deleteRange(removeFrom, nextSegmentStart) limit -= nextSegmentStart - removeFrom segmentStart = prevSegmentStart i = prevSegmentStart From 575022008947c56660623cdc70070c52394d619a Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 12 Jul 2026 18:31:17 +0000 Subject: [PATCH 4/6] Update ABI --- library/api/jvm/library.api | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/library/api/jvm/library.api b/library/api/jvm/library.api index 025ebc39199..cdc2a27c5c3 100644 --- a/library/api/jvm/library.api +++ b/library/api/jvm/library.api @@ -6612,9 +6612,9 @@ public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$C { public static final field TRACK_TYPE_TEXT I public static final field TRACK_TYPE_UNKNOWN I public static final field TRACK_TYPE_VIDEO I - public final fun getCLEARKEY_UUID ()Ljava/util/UUID; - public final fun getPLAYREADY_UUID ()Ljava/util/UUID; - public final fun getWIDEVINE_UUID ()Ljava/util/UUID; + public final fun getCLEARKEY_UUID ()Lkotlin/uuid/Uuid; + public final fun getPLAYREADY_UUID ()Lkotlin/uuid/Uuid; + public final fun getWIDEVINE_UUID ()Lkotlin/uuid/Uuid; } public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$DrmInitData { @@ -6882,7 +6882,7 @@ public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$ParserExce public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$PsshAtomUtil { public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$PsshAtomUtil; - public final fun buildPsshAtom (Ljava/util/UUID;[Ljava/util/UUID;[B)[B + public final fun buildPsshAtom (Lkotlin/uuid/Uuid;[Lkotlin/uuid/Uuid;[B)[B } public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$Rendition { @@ -6903,19 +6903,19 @@ public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$Rendition } public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData { - public fun (Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[B)V - public synthetic fun (Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[BILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/UUID; + public fun (Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[B)V + public synthetic fun (Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[BILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlin/uuid/Uuid; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()[B - public final fun copy (Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[B)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[BILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; + public final fun copy (Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[B)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[BILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; public fun equals (Ljava/lang/Object;)Z public final fun getData ()[B public final fun getLicenseServerUrl ()Ljava/lang/String; public final fun getMimeType ()Ljava/lang/String; - public final fun getUuid ()Ljava/util/UUID; + public final fun getUuid ()Lkotlin/uuid/Uuid; public fun hashCode ()I public fun toString ()Ljava/lang/String; } From c8079d057be2821105dd4618b59735df61ba95e4 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 14 Jul 2026 09:09:06 -0700 Subject: [PATCH 5/6] No longer experimental --- .../com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt | 3 --- 1 file changed, 3 deletions(-) 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 dda31543ef7..0e5d04a87f9 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt @@ -1,5 +1,3 @@ -@file:OptIn(ExperimentalUuidApi::class) - /* * Copyright (C) 2016 The Android Open Source Project * @@ -26,7 +24,6 @@ import io.ktor.http.Url import kotlinx.io.Buffer import kotlinx.io.IOException import kotlinx.io.readByteArray -import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid @Suppress("unused") From 27a5552b2b820c598801740e29722ff0c1f1f658 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 14 Jul 2026 16:16:39 -0700 Subject: [PATCH 6/6] Make object private --- library/api/jvm/library.api | 38 ------------------- .../cloudstream3/utils/HlsPlaylistParser.kt | 2 +- 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/library/api/jvm/library.api b/library/api/jvm/library.api index 318872f77ce..d2e1577b67c 100644 --- a/library/api/jvm/library.api +++ b/library/api/jvm/library.api @@ -7625,44 +7625,6 @@ public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser { public final fun parse (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$HlsMultivariantPlaylist; } -public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$C { - public static final field CENC_TYPE_cbc1 Ljava/lang/String; - public static final field CENC_TYPE_cbcs Ljava/lang/String; - public static final field CENC_TYPE_cenc Ljava/lang/String; - public static final field CENC_TYPE_cens Ljava/lang/String; - public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$C; - public static final field ROLE_FLAG_ALTERNATE I - public static final field ROLE_FLAG_CAPTION I - public static final field ROLE_FLAG_COMMENTARY I - public static final field ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND I - public static final field ROLE_FLAG_DESCRIBES_VIDEO I - public static final field ROLE_FLAG_DUB I - public static final field ROLE_FLAG_EASY_TO_READ I - public static final field ROLE_FLAG_EMERGENCY I - public static final field ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY I - public static final field ROLE_FLAG_MAIN I - public static final field ROLE_FLAG_SIGN I - public static final field ROLE_FLAG_SUBTITLE I - public static final field ROLE_FLAG_SUPPLEMENTARY I - public static final field ROLE_FLAG_TRANSCRIBES_DIALOG I - public static final field ROLE_FLAG_TRICK_PLAY I - public static final field SELECTION_FLAG_AUTOSELECT I - public static final field SELECTION_FLAG_DEFAULT I - public static final field SELECTION_FLAG_FORCED I - public static final field TRACK_TYPE_AUDIO I - public static final field TRACK_TYPE_CAMERA_MOTION I - public static final field TRACK_TYPE_DEFAULT I - public static final field TRACK_TYPE_IMAGE I - public static final field TRACK_TYPE_METADATA I - public static final field TRACK_TYPE_NONE I - public static final field TRACK_TYPE_TEXT I - public static final field TRACK_TYPE_UNKNOWN I - public static final field TRACK_TYPE_VIDEO I - public final fun getCLEARKEY_UUID ()Lkotlin/uuid/Uuid; - public final fun getPLAYREADY_UUID ()Lkotlin/uuid/Uuid; - public final fun getWIDEVINE_UUID ()Lkotlin/uuid/Uuid; -} - public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$DrmInitData { public fun (Ljava/lang/String;[Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;)V public final fun component1 ()Ljava/lang/String; 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 0e5d04a87f9..3edd0f410c2 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt @@ -533,7 +533,7 @@ object HlsPlaylistParser { } } - object C { + private object C { /** * [Uuid] for the ClearKey DRM scheme. *