Haha, seven years after commenting I finally had to implement this :-D I want a tumbleweed badge for this ;-)
- Not to my knowledge
- Yes
- Not to my knowledge, but polling timers between 30-60 seconds should be fine. At the beginning I wanted to reduce network traffic for users, but this is irrelevant if you are streaming radio at the same time :-D
And here my quick and dirty solution, just in case someone needs it. There are some custom classes in the example, but you ll get the point
import androidx.core.text.HtmlCompat
import de.jahpress.android.main.L
import de.jahpress.android.main.MAX_REQUEST_FOR_SHOUTCAST_TRACK_INFO
import de.jahpress.android.service.Data
import de.jahpress.android.service.radio.model.BaseStation
import okhttp3.OkHttpClient
import okhttp3.Request
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread
class ShoutCastTrackInfoManager {
private val timeOut = 5L
private val pollingIntervalMs = 60_000L
private var updateTimer: Timer? = null
private var trackInfoThread: Thread? = null
private var invalidTrackInfoCounter = 0
//will ask track info only one time (select station in my use case)
fun updateTrackInfoFor(station: BaseStation, resultCallback: (info: String?) -> Unit) {
L.d("TrackInfo: Get title info for ${station.getStationName()}")
invalidTrackInfoCounter = 0
stopTrackInfoPolling()
requestTrackInfoFromShoutcast(station, resultCallback)
}
//will start track info polling (if station is playing)
fun startTrackInfoPolling(station: BaseStation) {
L.d("TrackInfo: Get title info for ${station.getStationName()}")
stopTrackInfoPolling()
updateTimer = Timer()
updateTimer?.schedule(object : TimerTask() {
override fun run() {
requestTrackInfoFromShoutcast(station, null)
}
}, 0, pollingIntervalMs)
}
fun stopTrackInfoPolling() {
trackInfoThread?.let {
L.d("TrackInfo: Stopping current title update for stream")
it.interrupt()
}
updateTimer?.cancel()
}
private fun requestTrackInfoFromShoutcast(
station: BaseStation,
resultCallback: ((info: String?) -> Unit)?
) {
if (invalidTrackInfoCounter >= MAX_REQUEST_FOR_SHOUTCAST_TRACK_INFO) {
L.d("TrackInfo: $MAX_REQUEST_FOR_SHOUTCAST_TRACK_INFO invalid stream titles. Sto...")
invalidTrackInfoCounter = 0
stopTrackInfoPolling()
Data.currentTitleInfo = null //reset track info
return
}
trackInfoThread = thread {
try {
var trackInfo: String? = null
get7HtmlFromStream(station)?.let {
L.d("TrackInfo: Request track info at $it")
val request = Request.Builder().url(it).build()
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(timeOut, TimeUnit.SECONDS)
.writeTimeout(timeOut, TimeUnit.SECONDS)
.readTimeout(timeOut, TimeUnit.SECONDS)
.build()
val response = okHttpClient.newCall(request).execute()
if (response.isSuccessful) {
val result = response.body?.string()
trackInfo = extractTrackInfoFrom7Html(result)
if (trackInfo != null) {
Data.currentTitleInfo = trackInfo
}
}
response.close()
}
resultCallback?.invoke(trackInfo)
} catch (e: Exception) {
L.e(e)
resultCallback?.invoke(null)
stopTrackInfoPolling()
}
}
}
/**
* Will create Shoutcast 7.html which is located at stream url.
*
* For example: http://66.55.145.43:7473/stream
* 7.html at http://66.55.145.43:7473/7.html
*/
private fun get7HtmlFromStream(station: BaseStation): String? {
val baseStreamUrl = station.getStreamUrl()
L.w("Base url -> $baseStreamUrl")
if (baseStreamUrl == null) return null
val numberSlash = baseStreamUrl.count { c -> c == '/' }
if (numberSlash <= 2) {
return "$baseStreamUrl/7.html"
}
val startOfPath = station.getStreamUrl().indexOf("/", 8)
val streamUrl = station.getStreamUrl().subSequence(0, startOfPath)
return "$streamUrl/7.html"
}
/**
* Will convert webpage to trackinfo. Therefore
* 1. Remove all html-tags
* 2. Get <body> content of webpage
* 3. Extract and return trackinfo
*
* Trackinfo format is always like
* "632,1,1943,2000,439,128,Various Artists - Dance to Dancehall"
* so method will return everything after sixth "," comma character.
*
* Important:
* - Shoutcast might return invalid html
* - Site will return 404 error strings
* - might be empty
*/
private fun extractTrackInfoFrom7Html(html: String?): String? {
L.i("Extract track info from -> $html")
if (html == null) return null
val content = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT).toString()
val array = content.split(",")
return if (array.size < 7) {
null
} else {
var combinedTrackInfo = ""
for (index in 6 until array.size) {
combinedTrackInfo += "${array[index]} "
}
if (combinedTrackInfo.trim().isEmpty()) {
return null
}
return combinedTrackInfo
}
}
}