feat: media session source and media card
This commit is contained in:
@@ -105,6 +105,46 @@ body {
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
/* 媒体卡:正在播放标签 + 标题/艺术家 + 进度条 */
|
||||
/* Media card: playing caption + title/artist + progress bar */
|
||||
.media-card .cap {
|
||||
font-size: 11px;
|
||||
color: var(--text-dim);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.media-card .tt {
|
||||
margin-top: 6px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.media-card .ar {
|
||||
margin-top: 2px;
|
||||
font-size: 13px;
|
||||
color: var(--text-dim);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.media-card .prog {
|
||||
margin-top: 12px;
|
||||
height: 4px;
|
||||
border-radius: 999px;
|
||||
background: var(--glass-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.media-card .bar {
|
||||
height: 100%;
|
||||
border-radius: 999px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
/* 安卓 APP 列表页:搜索框 + 网格 */
|
||||
/* Android app list page: search box + grid */
|
||||
.search { padding: 16px; }
|
||||
|
||||
@@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user