feat: add ICY metadata parser with artist/title extraction
Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
package xyz.cottongin.radio247.audio
|
||||
|
||||
data class IcyMetadata(
|
||||
val raw: String,
|
||||
val title: String?,
|
||||
val artist: String?
|
||||
)
|
||||
91
app/src/main/java/xyz/cottongin/radio247/audio/IcyParser.kt
Normal file
91
app/src/main/java/xyz/cottongin/radio247/audio/IcyParser.kt
Normal file
@@ -0,0 +1,91 @@
|
||||
package xyz.cottongin.radio247.audio
|
||||
|
||||
import java.io.InputStream
|
||||
|
||||
class IcyParser(
|
||||
private val input: InputStream,
|
||||
private val metaint: Int?,
|
||||
private val onAudioData: (ByteArray, Int, Int) -> Unit,
|
||||
private val onMetadata: (IcyMetadata) -> Unit
|
||||
) {
|
||||
private val buffer = ByteArray(8192)
|
||||
|
||||
fun readAll() {
|
||||
if (metaint == null) {
|
||||
passthrough()
|
||||
} else {
|
||||
parseWithMetadata(metaint)
|
||||
}
|
||||
}
|
||||
|
||||
private fun passthrough() {
|
||||
while (true) {
|
||||
val n = input.read(buffer)
|
||||
if (n <= 0) break
|
||||
onAudioData(buffer, 0, n)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseWithMetadata(metaint: Int) {
|
||||
val audioBuf = ByteArray(metaint)
|
||||
while (true) {
|
||||
val audioRead = readFully(audioBuf, 0, metaint)
|
||||
if (audioRead <= 0) break
|
||||
if (audioRead < metaint) {
|
||||
onAudioData(audioBuf, 0, audioRead)
|
||||
break
|
||||
}
|
||||
onAudioData(audioBuf, 0, metaint)
|
||||
|
||||
val lengthByte = input.read()
|
||||
if (lengthByte < 0) break
|
||||
|
||||
val metadataSize = (lengthByte and 0xFF) * 16
|
||||
if (metadataSize == 0) continue
|
||||
|
||||
val metaBytes = ByteArray(metadataSize)
|
||||
val metaRead = readFully(metaBytes, 0, metadataSize)
|
||||
if (metaRead < metadataSize) break
|
||||
|
||||
val raw = String(metaBytes, Charsets.UTF_8).trimEnd { it == '\u0000' }
|
||||
val parsed = parseMetaString(raw)
|
||||
onMetadata(parsed)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readFully(buf: ByteArray, off: Int, len: Int): Int {
|
||||
var total = 0
|
||||
while (total < len) {
|
||||
val n = input.read(buf, off + total, len - total)
|
||||
if (n <= 0) return total
|
||||
total += n
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
private fun parseMetaString(raw: String): IcyMetadata {
|
||||
val streamTitle = extractStreamTitle(raw)
|
||||
if (streamTitle != null) {
|
||||
val separatorIndex = streamTitle.indexOf(" - ")
|
||||
if (separatorIndex >= 0) {
|
||||
return IcyMetadata(
|
||||
raw = raw,
|
||||
artist = streamTitle.substring(0, separatorIndex).trim(),
|
||||
title = streamTitle.substring(separatorIndex + 3).trim()
|
||||
)
|
||||
}
|
||||
return IcyMetadata(raw = raw, title = streamTitle.trim(), artist = null)
|
||||
}
|
||||
return IcyMetadata(raw = raw, title = null, artist = null)
|
||||
}
|
||||
|
||||
private fun extractStreamTitle(meta: String): String? {
|
||||
val key = "StreamTitle='"
|
||||
val start = meta.indexOf(key)
|
||||
if (start < 0) return null
|
||||
val valueStart = start + key.length
|
||||
val end = meta.indexOf("';", valueStart)
|
||||
if (end < 0) return null
|
||||
return meta.substring(valueStart, end)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user