feat: media session source and media card

This commit is contained in:
2026-08-16 16:49:41 +08:00
parent 695df96c24
commit b157dac7ae
6 changed files with 154 additions and 0 deletions
@@ -0,0 +1,15 @@
package top.yeij.hearth.media
// 媒体会话快照:标题/艺术家/专辑 + 播放状态/进度 + 来源包名
// Media session snapshot: title/artist/album + playback state/progress + source package
data class MediaInfo(
val title: String,
val artist: String,
val album: String,
val playing: Boolean,
val position: Long,
val duration: Long,
val packageName: String,
) {
fun toJson(): String = com.google.gson.Gson().toJson(this)
}
@@ -0,0 +1,41 @@
package top.yeij.hearth.media
import android.content.Context
import android.media.MediaMetadata
import android.media.session.MediaController
import android.media.session.MediaSessionManager
import android.media.session.PlaybackState
import android.util.Log
// 监听系统活跃媒体会话,取第一个 controller 的 metadata + playbackState 构造 MediaInfo 回调
// Watch active media sessions; build MediaInfo from the first controller's metadata + playbackState
class MediaSessionSource(context: Context) {
private val msm = context.getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager
private var listener: ((MediaInfo?) -> Unit)? = null
fun start(cb: (MediaInfo?) -> Unit) {
listener = cb
msm.addOnActiveSessionsChangedListener({ controllers ->
val c = controllers?.firstOrNull()
if (c == null) {
Log.d("HearthMedia", "no active media session")
cb(null)
return@addOnActiveSessionsChangedListener
}
val meta = c.metadata
val state = c.playbackState
cb(
MediaInfo(
meta?.getString(MediaMetadata.METADATA_KEY_TITLE) ?: "",
meta?.getString(MediaMetadata.METADATA_KEY_ARTIST) ?: "",
meta?.getString(MediaMetadata.METADATA_KEY_ALBUM) ?: "",
state?.state == PlaybackState.STATE_PLAYING,
state?.position ?: 0L,
meta?.getLong(MediaMetadata.METADATA_KEY_DURATION) ?: 0L,
c.packageName,
)
)
Log.d("HearthMedia", "media: ${c.packageName}")
}, null)
}
}
@@ -4,6 +4,7 @@ import android.util.Log
import top.yeij.hearth.app.AppRepository
import top.yeij.hearth.card.Card
import top.yeij.hearth.card.CardRepository
import top.yeij.hearth.media.MediaSessionSource
import top.yeij.hearth.webapp.WebApp
import top.yeij.hearth.webapp.WebAppRepository
@@ -55,4 +56,18 @@ class JsBridge(
Log.d("HearthBridge", "fetchCards: ${cards.size} cards")
return gson.toJson(cards)
}
// 注册媒体会话监听,回调推送给 H5(info 为 null 时推 "null"
// Register media session listener; push callbacks to H5 (push "null" when info is null)
fun setMediaListener(source: MediaSessionSource, webView: android.webkit.WebView) {
source.start { info ->
webView.post {
val json = info?.toJson() ?: "null"
webView.evaluateJavascript(
"window.HearthEvents && window.HearthEvents.mediaSessionChanged($json);",
null
)
}
}
}
}