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
+42
View File
@@ -74,3 +74,45 @@ function startTimeCardTicker() {
card.querySelector('.sub').textContent = fmtDate(now);
}, 10000);
}
// 原生媒体会话事件:有媒体时在中间栏渲染媒体卡,无媒体时降级重新布局
// Native media session event: render media card in middle column when present,
// otherwise fall back to re-layout (calendar/weather fill the media card slot)
window.HearthEvents = window.HearthEvents || {};
window.HearthEvents.mediaSessionChanged = function (info) {
const mediaCol = document.getElementById('col-1');
if (!mediaCol) return;
if (info) {
mediaCol.innerHTML = '';
mediaCol.appendChild(renderMediaCard(info));
} else {
// 无媒体:降级到日历/天气(由 cards.layout 重新计算)
// No media: fall back to calendar/weather (recomputed by cards.layout)
window.updateHomeCards();
}
};
// 媒体卡:正在播放标签 + 标题/艺术家 + 进度条
// Media card: playing caption + title/artist + progress bar
function renderMediaCard(info) {
const el = document.createElement('div');
el.className = 'card media-card';
const pct = info.duration ? (info.position / info.duration) * 100 : 0;
// title/artist 来自任意应用元数据,转义后插入,防 XSS
// title/artist come from arbitrary app metadata; escape before insert (XSS)
el.innerHTML = `<div class="cap">正在播放</div>
<div class="tt">${escapeHtml(info.title)}</div><div class="ar">${escapeHtml(info.artist)}</div>
<div class="prog"><div class="bar" style="width:${pct}%"></div></div>`;
return el;
}
// 转义 HTML 特殊字符,避免媒体元数据注入标签
// Escape HTML special chars to prevent metadata injection
function escapeHtml(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}