【问题标题】:SHOUTcast - Polling 7.htmlSHOUTcast - 轮询 7.html
【发布时间】:2022-03-04 02:43:16
【问题描述】:

我不喜欢通过投票来获取信息,并且怀疑有更好的方法来实现我想要的。

我正在使用 Android 的 MediaPlayer 播放互联网广播流。通过在服务器地址处请求 7.html 文件,我可以找出正在播放的曲调以及由哪个艺术家演奏。

我的问题是:

  1. 有没有办法在新歌开始时收到通知 玩?
  2. 我必须轮询 7.html 以了解正在播放的内容吗?
  3. 如果我必须轮询,有什么方法可以确定 当前歌曲的持续时间,因此我只能在新歌曲时进行轮询 开始了吗?

我想如果我有自己的低级流处理功能,我可以知道歌曲何时发生变化,因为我会收到元数据,但我不知道如何使用 Android MediaPlayer 类.

【问题讨论】:

  • 是的,7.html 方法不可靠,并且越来越不可用。正如您所建议的,您必须编写一个流解复用器来将音频和元数据组件分开。我不是 Android 开发人员,所以我无法建议这样做的方法,但我在 Stack Overflow 上看到了处理元数据的类。我确实写了关于如何在 PHP 中执行此操作的说明。或许可以翻译:stackoverflow.com/questions/4911062/…
  • @Brad:谢谢布拉德。我有一些 C# 代码可以做到这一点。我现在每 10 秒轮询一次,直到我觉得有必要在 Android 中编写自己的流播放器。
  • 您是否尝试过获取此处所述的Metatags (uniqueculture.net/2010/11/stream-metadata-plain-java)?我知道 SHOUTcast 刚刚更新了他们的 API,但我只使用 iOS 版本。我们使用标签StreamTitle,通常98% 是ARTIST - TITLE。如果艺术家包含“-”,您只需使用正则表达式...
  • @longilong:感谢您的回复。自从我这样做以来,似乎已经很久了。我使用烦人的轮询(在 Android 上)让它工作正常,但无论我在 iOS 上做什么都很好。我认为 iOS 中的播放器抽象必须过滤掉流中包含的元数据,而 Android 没有。不敢相信当我问到这个问题时才 11 月。似乎上辈子了:)

标签: android android-mediaplayer audio-streaming shoutcast


【解决方案1】:

哈哈,在发表评论七年后,我终于不得不实施这个 :-D 我想要一个风滚草徽章;-)

  1. 据我所知
  2. 是的
  3. 据我所知,30-60 秒之间的轮询计时器应该没问题。一开始我想减少用户的网络流量,但是如果您同时播放广播,这无关紧要:-D

这是我快速而肮脏的解决方案,以防万一有人需要。示例中有一些自定义类,但您会明白的

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
        }
    }
} 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-11
    • 2013-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-08
    • 2019-05-28
    相关资源
    最近更新 更多