Unify transport controls with themed SVG symbols and accessible playback states

This commit is contained in:
Jonathan Sykes
2026-10-09 15:54:22 +08:00
parent 0a5b113ebe
commit 9a37715da4
19 changed files with 180 additions and 73 deletions

View File

@@ -1598,8 +1598,8 @@ function escapeHtml(s) {
.replace(/"/g, '"').replace(/'/g, ''');
}
const ICON_PLAY = '<svg viewBox="0 0 24 24" aria-hidden="true" class="fill"><path d="M9 5.5v13l10-6.5z"/></svg>';
const ICON_PAUSE = '<svg viewBox="0 0 24 24" aria-hidden="true" class="fill"><path d="M7 5h3.5v14H7zM13.5 5H17v14h-3.5z"/></svg>';
const ICON_PLAY = TransportIcons.svg('play');
const ICON_PAUSE = TransportIcons.svg('pause');
// Speaker icon follows the volume (muted / low / high); the level before a mute
// is remembered so un-muting returns to it.
let lastVolume = 1;
@@ -3088,14 +3088,10 @@ function wirePlayerEvents() {
function updatePlayBtn() {
Remote.changed();
els.playBtn.innerHTML = Player.master.paused ? ICON_PLAY : ICON_PAUSE;
const sp = $('stgPlay');
if (sp) sp.innerHTML = Player.master.paused ? ICON_PLAY : ICON_PAUSE;
const kind = Player.master.paused ? 'play' : 'pause';
for (const id of ['playBtn','stgPlay','miniPlayBtn','servicePlayBtn']) TransportIcons.set($(id), kind);
const stage = document.querySelector('.player-stage');
if (stage) stage.classList.toggle('is-paused', !!Player.master.paused);
$('miniPlayBtn').textContent = Player.master.paused ? '▶' : '⏸';
const spBtn = $('servicePlayBtn');
if (spBtn) spBtn.textContent = Player.master.paused ? '▶' : '⏸';
}
// "⚠ Broken": ask the server to drop its cached copy and fetch a fresh one.
// Playback switches to live YouTube streams meanwhile (resuming where it
@@ -3245,7 +3241,7 @@ function openMoreControls() {
['eqBtn', '🎚', 'Equalizer', false],
['pipBtn', '⧉', 'Picture-in-picture', false],
];
const selects = [['speedSelect', '⏩', 'Speed'], ['qualitySelect', '🎞', 'Quality']];
const selects = [['speedSelect', TransportIcons.svg('forward'), 'Speed'], ['qualitySelect', '🎞', 'Quality']];
const list = document.createElement('div');
list.className = 'more-list';
const draw = () => {
@@ -3256,7 +3252,7 @@ function openMoreControls() {
const row = document.createElement('label');
row.className = 'more-row';
row.innerHTML = '<span class="more-ico"></span><span class="more-txt"><b></b></span>';
row.querySelector('.more-ico').textContent = icon;
row.querySelector('.more-ico').innerHTML = icon;
row.querySelector('b').textContent = title;
const sel = document.createElement('select');
sel.className = 'more-sel';
@@ -3274,7 +3270,7 @@ function openMoreControls() {
row.type = 'button';
row.className = 'more-row' + (on ? ' on' : '');
row.innerHTML = '<span class="more-ico"></span><span class="more-txt"><b></b><small></small></span><span class="more-go">' + (on ? '●' : '›') + '</span>';
row.querySelector('.more-ico').textContent = icon;
row.querySelector('.more-ico').innerHTML = icon;
row.querySelector('b').textContent = title;
row.querySelector('small').textContent = String(src.title || '').replace(/\s*\[[A-Za-z]\]\s*$/, '');
row.onclick = () => {
@@ -3301,13 +3297,13 @@ function openAddChoice() {
r.type = 'button';
r.className = 'more-row';
r.innerHTML = '<span class="more-ico"></span><span class="more-txt"><b></b><small></small></span><span class="more-go">›</span>';
r.querySelector('.more-ico').textContent = icon;
r.querySelector('.more-ico').innerHTML = icon;
r.querySelector('b').textContent = title;
r.querySelector('small').textContent = sub;
r.onclick = () => { closeModal(); setTimeout(fn, 0); };
list.appendChild(r);
};
row('▶', 'Add to queue', 'Play it next in this session', () => addToQueue(video));
row(TransportIcons.svg('play'), 'Add to queue', 'Play it next in this session', () => addToQueue(video));
row('☰', 'Add to playlist', 'Save it to one of your playlists', () => openCardMenu(video));
showModal('Add “' + (video.title || 'video').slice(0, 40) + '”', list, [{ label: 'Cancel', onClick: closeModal }]);
}
@@ -3332,7 +3328,7 @@ function openMoreActions() {
row.className = 'more-row' + (danger ? ' danger' : '');
row.disabled = !!src.disabled;
row.innerHTML = '<span class="more-ico"></span><span class="more-txt"><b></b><small></small></span><span class="more-go">›</span>';
row.querySelector('.more-ico').textContent = icon;
row.querySelector('.more-ico').innerHTML = icon;
row.querySelector('b').textContent = title;
row.querySelector('small').textContent = sub;
row.onclick = () => { closeModal(); setTimeout(() => src.click(), 0); };
@@ -3633,7 +3629,7 @@ const Notes = (() => {
<div class="mk-title">${m.kind === 'note' ? '📝 ' : ''}${escapeHtml(m.title)}</div>
${m.note ? `<div class="mk-note${m.kind === 'note' ? ' long' : ''}">${escapeHtml(m.note)}</div>` : ''}
</div>
<button class="mk-btn" type="button" data-act="jump" title="Play from here">▶</button>
<button class="mk-btn" type="button" data-act="jump" title="Play from here">${TransportIcons.svg('play')}</button>
${m.auto ? '' : `<button class="mk-btn" type="button" data-act="edit" title="Edit">✎</button>
<button class="mk-btn" type="button" data-act="del" title="Delete">🗑</button>`}
</div>`;
@@ -5725,7 +5721,7 @@ const Gestures = (() => {
if (x < 1 / 3) seekBy(-10, 'left');
else if (x > 2 / 3) seekBy(10, 'right');
else if (st.zoom > 1.001) { reset(); showHud('<div class="gh-label">Zoom reset</div>'); }
else if (current) { Player.toggle(); updatePlayBtn(); showHud(`<div class="gh-seek">${Player.master.paused ? '⏸' : '▶'}</div>`); }
else if (current) { Player.toggle(); updatePlayBtn(); showHud(`<div class="gh-seek">${TransportIcons.svg(Player.master.paused ? 'pause' : 'play')}</div>`); }
return;
}
st.lastTap = now;
@@ -6285,7 +6281,7 @@ function updateServiceMode() {
const spBtn = $('servicePlayBtn');
if (spBtn && Player.master) {
spBtn.textContent = Player.master.paused ? '▶' : '⏸';
TransportIcons.set(spBtn, Player.master.paused ? 'play' : 'pause');
}
updateWakeStateUI();
@@ -6984,7 +6980,7 @@ function openCardMenu(video) {
// Quick: add to queue
const queueBtn = document.createElement('button');
queueBtn.textContent = '▶ Add to queue';
queueBtn.innerHTML = TransportIcons.svg('play') + ' Add to queue';
queueBtn.onclick = () => { addToQueue(video); closeModal(); };
body.appendChild(queueBtn);