Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ object M3u8Helper2 {
return !url.startsWith("https://") && !url.startsWith("http://")
}

/** Resolves a playlist entry against the playlist url, as done for variants in [HlsPlaylistParser] */
private fun resolveUrl(playlistUrl: String, url: String): String {
return if (isNotCompleteUrl(url)) {
HlsPlaylistParser.UrlUtil.resolveToUrl(playlistUrl, url).toString()
} else {
url
}
}

@Throws
suspend fun m3u8Generation(
m3u8: M3u8Helper.M3u8Stream,
Expand Down Expand Up @@ -320,11 +329,7 @@ object M3u8Helper2 {

if (!match.isNullOrEmpty()) {
encryptionState = true
var encryptionUrl = match[2]

if (isNotCompleteUrl(encryptionUrl)) {
encryptionUrl = "${getParentLink(playlistStream.streamUrl)}/$encryptionUrl"
}
val encryptionUrl = resolveUrl(playlistStream.streamUrl, match[2])

encryptionIv = match[3].encodeToByteArray()
val encryptionKeyResponse =
Expand All @@ -339,12 +344,7 @@ object M3u8Helper2 {
val relativeUrl = getParentLink(playlistStream.streamUrl)
val allTsList = TS_EXTENSION_REGEX.findAll(playlistResponse + "\n").map { ts ->
val time = ts.groupValues[1]
val value = ts.groupValues[3]
val url = if (isNotCompleteUrl(value)) {
"$relativeUrl/${value}"
} else {
value
}
val url = resolveUrl(playlistStream.streamUrl, ts.groupValues[3].trim())
TsLink(url = url, time = time.toDoubleOrNull())
}.toList()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.lagradost.cloudstream3.utils

import com.sun.net.httpserver.HttpServer
import kotlinx.coroutines.runBlocking
import java.net.InetSocketAddress
import java.util.Collections
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class M3u8HelperTest {
private lateinit var server: HttpServer
private lateinit var host: String
private val requestedPaths: MutableList<String> = Collections.synchronizedList(mutableListOf())
private val segment = ByteArray(256) { (it or 0x80).toByte() }

@BeforeTest
fun setUp() {
server = HttpServer.create(InetSocketAddress("127.0.0.1", 0), 0)
host = "127.0.0.1:${server.address.port}"
server.createContext("/") { exchange ->
val path = exchange.requestURI.path
requestedPaths += path
val body = when (path) {
"/a/b/index.m3u8" -> playlist(key = null).encodeToByteArray()
"/a/b/encrypted.m3u8" -> playlist(key = "/keys/k.bin").encodeToByteArray()
"/a/b/seg0.ts", "/root/seg1.ts", "/proto/seg2.ts" -> segment
"/keys/k.bin" -> ByteArray(16)
else -> null
}
val bytes = body ?: "404 not found".encodeToByteArray()
exchange.sendResponseHeaders(if (body == null) 404 else 200, bytes.size.toLong())
exchange.responseBody.use { it.write(bytes) }
}
server.start()
}

@AfterTest
fun tearDown() {
server.stop(0)
}

private fun playlist(key: String?): String = buildString {
appendLine("#EXTM3U")
appendLine("#EXT-X-TARGETDURATION:10")
if (key != null) appendLine("#EXT-X-KEY:METHOD=AES-128,URI=\"$key\"")
appendLine("#EXTINF:10.0,")
appendLine("seg0.ts")
appendLine("#EXTINF:10.0,")
appendLine("/root/seg1.ts")
appendLine("#EXTINF:10.0,")
appendLine("//$host/proto/seg2.ts")
appendLine("#EXT-X-ENDLIST")
}

@Test
fun hslLazyResolvesSegmentUrlsAgainstPlaylistUrl() = runBlocking {
val data = M3u8Helper2.hslLazy(
M3u8Helper.M3u8Stream("http://$host/a/b/index.m3u8?token=abc"),
requireAudio = false,
)

assertEquals(
listOf(
"http://$host/a/b/seg0.ts",
"http://$host/root/seg1.ts",
"http://$host/proto/seg2.ts",
),
data.allTsLinks.map { it.url },
)
for (index in 0 until data.size) {
assertContentEquals(segment, data.resolveLink(index))
}
}

@Test
fun hslLazyResolvesKeyUrlAgainstPlaylistUrl() = runBlocking {
M3u8Helper2.hslLazy(
M3u8Helper.M3u8Stream("http://$host/a/b/encrypted.m3u8"),
requireAudio = false,
)

assertTrue("/keys/k.bin" in requestedPaths, "requested: $requestedPaths")
}
}