Add presenter view, stats, lyrics worker, watch party with voice chat, timestamp sharing, soundbites, notes, transcript search, PiP, EQ, sleep fade, gestures and external players; fix the crossfade end-of-song race

This commit is contained in:
Jonathan Sykes
2026-09-20 00:07:12 +08:00
parent ca07e1fdf1
commit 9c1ec4ac51
17 changed files with 2912 additions and 111 deletions

View File

@@ -39,7 +39,10 @@
<button class="nav-item" data-view="history">🕘 History</button>
<button class="nav-item" data-view="saved">💾 Saved</button>
<button class="nav-item" data-view="downloads">⬇ Downloads <span id="navDlCount" class="nav-badge hidden">0</span></button>
<button class="nav-item" data-view="notes">📝 Notes</button>
<button class="nav-item" data-view="stats">📊 Stats</button>
<button class="nav-item" data-view="settings">⚙ Settings</button>
<button class="nav-item" id="navPartyBtn" type="button" title="Watch together with friends">🎉 Watch party</button>
<button class="nav-item" id="navRemoteBtn" type="button" title="Control this screen from a phone, or use this phone as a remote">📱 Remote</button>
</nav>
@@ -135,6 +138,8 @@
</div>
</div>
<div id="bufferSpinner" class="spinner hidden"></div>
<!-- Touch gesture feedback (Gestures in app.js) -->
<div id="gestureHud" class="gesture-hud hidden"></div>
</div>
<div id="controls" class="controls hidden">
@@ -193,6 +198,8 @@
<button id="loopBtn" class="ctrl" title="Loop current video (L)">🔂</button>
<button id="repeatBtn" class="ctrl" title="Repeat list when finished (R)">🔁</button>
<button id="transitionBtn" class="ctrl" title="Between songs in a playlist: Normal — click to change">↦</button>
<button id="eqBtn" class="ctrl" title="Equalizer (E)">🎚</button>
<button id="pipBtn" class="ctrl hidden" title="Picture-in-picture (I)">⧉</button>
<button id="fsBtn" class="ctrl" title="Fullscreen">⛶</button>
</div>
</div>
@@ -212,6 +219,8 @@
<button id="saveBtn" class="np-btn" title="Save this video for offline playback">⬇ Save</button>
<button id="editBtn" class="np-btn" title="Cut parts out and save a custom edited copy">✂ Edit &amp; download</button>
<button id="addPlaylistBtn" class="np-btn" title="Add to a playlist">+ Playlist</button>
<button id="shareMomentBtn" class="np-btn" title="Share a link or GIF of this exact moment">🔗 Share moment</button>
<button id="externalBtn" class="np-btn" title="Play this in VLC, MX Player or another app">📺 Open in…</button>
<button id="brokenBtn" class="np-btn" title="Video won't play or looks broken? Re-download the server's copy" hidden>⚠ Broken</button>
</div>
</div>
@@ -221,6 +230,7 @@
<div class="notes-tabs" role="tablist">
<button class="notes-tab active" data-tab="lyrics" role="tab" type="button">Lyrics</button>
<button class="notes-tab" data-tab="markers" role="tab" type="button">Chapters &amp; bookmarks <span id="markersCount" class="notes-count"></span></button>
<button class="notes-tab" data-tab="search" role="tab" type="button">🔎 Search</button>
<span id="notesStatus" class="notes-status"></span>
</div>
@@ -248,11 +258,24 @@
<div id="lyricsList" class="lyrics-list"></div>
</div>
<div id="searchPane" class="notes-pane hidden">
<div class="ts-bar">
<input id="tsQuery" class="ts-query" type="search" placeholder="Search what's said or sung…" autocomplete="off" />
<select id="tsSource" class="ts-source" aria-label="Search in">
<option value="lyrics">Lyrics</option>
<option value="captions">YouTube transcript</option>
</select>
</div>
<div id="tsInfo" class="ts-info"></div>
<div id="tsResults" class="ts-results"></div>
</div>
<div id="markersPane" class="notes-pane hidden">
<div id="markerChips" class="marker-chips"></div>
<div class="markers-toolbar">
<button id="addChapterBtn" class="np-btn" type="button">+ Chapter</button>
<button id="addBookmarkBtn" class="np-btn" type="button" title="Pin a private bookmark here [P]">📌 Bookmark</button>
<button id="addNoteBtn" class="np-btn" type="button" title="Write a note tied to this moment [N]">📝 Note</button>
<span class="notes-spacer"></span>
<button id="chaptersHistoryBtn" class="np-btn" type="button" title="Earlier versions of the shared chapters">🕘</button>
<button id="exportMarkersBtn" class="np-btn" type="button">⤓ Export</button>
@@ -262,7 +285,7 @@
<div id="chapterList" class="marker-list"></div>
</div>
<div class="markers-section">
<div class="markers-heading">My bookmarks <span class="markers-scope">only you</span></div>
<div class="markers-heading">My notes &amp; bookmarks <span class="markers-scope">only you</span></div>
<div id="bookmarkList" class="marker-list"></div>
</div>
</div>
@@ -357,6 +380,11 @@
</div>
</section>
<!-- Sleep timer "screen off": black curtain; tap to wake -->
<div id="sleepCurtain" class="sleep-curtain hidden" role="button" aria-label="Wake">
<span id="sleepCurtainMsg">Sleep timer ended · tap to wake</span>
</div>
<div id="toastContainer" class="toast-container"></div>
<!-- Mini now-playing bar -->
@@ -376,11 +404,36 @@
</div>
<!-- YouTube-style bottom navigation bar (portrait PWA mode only) -->
<!-- Watch party panel (Party in app.js) -->
<aside id="partyPanel" class="party-panel hidden" aria-label="Watch party">
<div class="pt-head"><span id="ptTitle">🎉 Watch party</span><span id="ptStatus" class="pt-status"></span><button id="ptMin" class="rv-icon" type="button" aria-label="Minimise">▁</button></div>
<div class="pt-body"></div>
</aside>
<!-- Desktop: shown while phones are driving this screen (remote.js) -->
<button id="remoteChip" class="remote-chip hidden" type="button" title="Phone remote settings">
<span class="remote-chip-dot"></span><span id="remoteChipText">Remote connected</span>
</button>
<!-- Presenter / projector screen: lyrics of whatever the paired screen plays -->
<section id="presenterView" class="presenter-view hidden" aria-label="Presenter view">
<div id="pvList" class="sl-list pv-list"></div>
<div id="pvTitle" class="pv-title hidden"></div>
<div id="pvBar" class="pv-bar">
<span id="pvStatus" class="pv-status">Connecting…</span>
<span class="pv-hint">Double-click or F = fullscreen · B = blank screen</span>
<button id="pvFull" class="pv-btn" type="button">⛶ Fullscreen</button>
</div>
<div id="pvPair" class="pv-pair hidden">
<div class="pv-pair-title">Presenter screen</div>
<p>Enter the 6-digit code shown on the playing computer (📱 Remote → Control this screen → Presenter).</p>
<input id="pvCode" inputmode="numeric" maxlength="7" placeholder="000 000" />
<button id="pvPairBtn" class="rv-primary" type="button">Connect</button>
<div id="pvMsg" class="rv-pair-msg"></div>
</div>
<div id="pvBlank" class="pv-blank hidden"></div>
</section>
<!-- Phone: remote control for a desktop instance -->
<section id="remoteView" class="remote-view hidden" aria-label="Phone remote">
<div class="rv-top">
@@ -481,6 +534,9 @@
<div class="shortcut-row"><kbd>A</kbd><span>Set A-B loop start point</span></div>
<div class="shortcut-row"><kbd>B</kbd><span>Set A-B loop end point</span></div>
<div class="shortcut-row"><kbd>P</kbd><span>Pin a bookmark at the current time</span></div>
<div class="shortcut-row"><kbd>N</kbd><span>Write a note at this moment</span></div>
<div class="shortcut-row"><kbd>I</kbd><span>Picture-in-picture</span></div>
<div class="shortcut-row"><kbd>E</kbd><span>Equalizer</span></div>
<div class="shortcut-row"><kbd>?</kbd><span>Show this help</span></div>
<div class="shortcut-row"><kbd>Esc</kbd><span>Close overlay / cancel selection</span></div>
</div>
@@ -491,6 +547,7 @@
<script src="opfs.js"></script>
<script src="video-edit.js"></script>
<script src="lyrics-core.js"></script>
<script src="stats-core.js"></script>
<script src="async-guard.js"></script>
<script src="sw-update.js"></script>
<script src="app.js"></script>