Fix Dispatcharr playback: stream URLs must use the channel UUID

The live proxy resolves /proxy/ts/stream/<key> by channel uuid (or stream
hash) — integer ids 404, so every Dispatcharr channel failed to play.
Streams now use the uuid, channels use effective_name/effective_tvg_id and
skip hidden_from_output entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 21:20:50 +02:00
parent 24d18f1066
commit 4dd59ed7aa
2 changed files with 13 additions and 6 deletions

View File

@@ -78,14 +78,15 @@ class DispatcharrRepository(private val auth: DeviceAuth) {
} }
} }
private fun streamUrl(id: Int): String { // The live proxy resolves channels by UUID (integer ids 404).
val base = "${auth.serverUrl}/proxy/ts/stream/$id" private fun streamUrl(uuid: String): String {
val base = "${auth.serverUrl}/proxy/ts/stream/$uuid"
return if (outputProfile.isNotEmpty()) "$base?output_profile=$outputProfile" else base return if (outputProfile.isNotEmpty()) "$base?output_profile=$outputProfile" else base
} }
/** Re-derive stream URLs (e.g. after the output profile changed). */ /** Re-derive stream URLs (e.g. after the output profile changed). */
fun rebuildStreamUrls() { fun rebuildStreamUrls() {
channels.value = channels.value.map { it.copy(url = streamUrl(it.backendId)) } channels.value = channels.value.map { it.copy(url = streamUrl(it.streamKey)) }
} }
private suspend fun fetchGroups(token: String): Map<Int, String> { private suspend fun fetchGroups(token: String): Map<Int, String> {
@@ -107,14 +108,17 @@ class DispatcharrRepository(private val auth: DeviceAuth) {
for (i in 0 until results.length()) { for (i in 0 until results.length()) {
val obj = results.getJSONObject(i) val obj = results.getJSONObject(i)
val id = obj.getInt("id") val id = obj.getInt("id")
val uuid = obj.optString("uuid")
if (uuid.isEmpty() || obj.optBoolean("hidden_from_output")) continue
list.add( list.add(
Channel( Channel(
name = obj.optString("name"), name = obj.optString("effective_name").ifEmpty { obj.optString("name") },
url = streamUrl(id), url = streamUrl(uuid),
group = groups[obj.optInt("channel_group_id")].orEmpty(), group = groups[obj.optInt("channel_group_id")].orEmpty(),
logo = "", logo = "",
tvgId = obj.optString("tvg_id"), tvgId = obj.optString("effective_tvg_id").ifEmpty { obj.optString("tvg_id") },
backendId = id, backendId = id,
streamKey = uuid,
) )
) )
} }

View File

@@ -10,6 +10,7 @@ data class Channel(
val logo: String, val logo: String,
val tvgId: String = "", val tvgId: String = "",
val backendId: Int = 0, val backendId: Int = 0,
val streamKey: String = "",
) { ) {
fun toJson(): JSONObject = JSONObject() fun toJson(): JSONObject = JSONObject()
.put("name", name) .put("name", name)
@@ -18,6 +19,7 @@ data class Channel(
.put("logo", logo) .put("logo", logo)
.put("tvgId", tvgId) .put("tvgId", tvgId)
.put("backendId", backendId) .put("backendId", backendId)
.put("streamKey", streamKey)
companion object { companion object {
fun fromJson(obj: JSONObject): Channel = Channel( fun fromJson(obj: JSONObject): Channel = Channel(
@@ -27,6 +29,7 @@ data class Channel(
logo = obj.optString("logo"), logo = obj.optString("logo"),
tvgId = obj.optString("tvgId"), tvgId = obj.optString("tvgId"),
backendId = obj.optInt("backendId"), backendId = obj.optInt("backendId"),
streamKey = obj.optString("streamKey"),
) )
fun listToJson(channels: List<Channel>): JSONArray { fun listToJson(channels: List<Channel>): JSONArray {