Add a service mode shortcut to the now-playing bar and a video panel toggle
This commit is contained in:
@@ -251,6 +251,7 @@ const DEFAULT_SETTINGS = {
|
||||
layout: 'classic', // 'classic'|'sanctuary-stage'|'liturgical'|'living-hymnal'|'broadcaster'|'contemplative'|'bento-hub'
|
||||
autoBackupEnabled: false,
|
||||
autoBackupIntervalDays: 7,
|
||||
serviceVideo: false, // show the video panel inside service mode (lyric videos)
|
||||
};
|
||||
let data = { playlists: [], history: [], queue: [], customVideos: [], pinnedPlaylists: [], settings: { ...DEFAULT_SETTINGS }, resumePositions: {}, rememberPos: {}, playCount: {}, abMarkers: {}, lastAutoBackup: 0, profile: null };
|
||||
let view = { type: 'search' }; // 'search'|'history'|'playlist'|'settings'|'queue'|'saved'|'downloads'|'channel'
|
||||
@@ -2345,6 +2346,62 @@ function updateServiceMode() {
|
||||
updateWakeStateUI();
|
||||
}
|
||||
|
||||
// ---------- Service mode video panel ----------
|
||||
// Lyric videos carry the words in the picture, so service mode can show the
|
||||
// video instead of hiding it. The <video> element is NEVER reparented — moving
|
||||
// a playing video breaks playback on iOS (and drops the stream on some
|
||||
// Androids). Instead .player-stage is pinned over a placeholder slot inside the
|
||||
// overlay with inline fixed coordinates, which are cleared when it closes.
|
||||
function serviceVideoOn() {
|
||||
return !!data.settings.serviceVideo && !data.settings.audioOnly;
|
||||
}
|
||||
|
||||
function positionServiceVideo() {
|
||||
const stage = document.querySelector('.player-stage');
|
||||
if (!stage) return;
|
||||
if (!(isServiceModeOpen() && serviceVideoOn())) {
|
||||
stage.style.position = '';
|
||||
stage.style.top = '';
|
||||
stage.style.left = '';
|
||||
stage.style.width = '';
|
||||
stage.style.height = '';
|
||||
return;
|
||||
}
|
||||
const slot = $('serviceVideoSlot');
|
||||
if (!slot) return;
|
||||
const r = slot.getBoundingClientRect();
|
||||
if (!r.width || !r.height) return; // slot not laid out yet
|
||||
stage.style.position = 'fixed';
|
||||
stage.style.top = `${r.top}px`;
|
||||
stage.style.left = `${r.left}px`;
|
||||
stage.style.width = `${r.width}px`;
|
||||
stage.style.height = `${r.height}px`;
|
||||
}
|
||||
|
||||
function updateServiceVideoUI() {
|
||||
const on = isServiceModeOpen() && serviceVideoOn();
|
||||
document.body.classList.toggle('service-video', on);
|
||||
const btn = $('serviceVideoBtn');
|
||||
if (btn) {
|
||||
btn.textContent = serviceVideoOn() ? '📺 Video on' : '📺 Video off';
|
||||
btn.setAttribute('aria-pressed', serviceVideoOn() ? 'true' : 'false');
|
||||
btn.classList.toggle('on', serviceVideoOn());
|
||||
}
|
||||
// The slot only has a box once the class above has been applied, so measure
|
||||
// on the next frame rather than in this one.
|
||||
requestAnimationFrame(positionServiceVideo);
|
||||
}
|
||||
|
||||
function toggleServiceVideo() {
|
||||
if (data.settings.audioOnly) {
|
||||
toast('Audio-only mode is on — turn it off in Settings to show video');
|
||||
return;
|
||||
}
|
||||
data.settings.serviceVideo = !data.settings.serviceVideo;
|
||||
persist();
|
||||
updateServiceVideoUI();
|
||||
}
|
||||
|
||||
function enterServiceMode(pl) {
|
||||
if (pl && pl.videos && pl.videos.length) {
|
||||
const source = 'playlist:' + pl.id;
|
||||
@@ -2365,6 +2422,7 @@ function enterServiceMode(pl) {
|
||||
// Hides the mini-bar / bottom-nav / sidebar for as long as the overlay is up
|
||||
// (see .service-open in styles.css).
|
||||
document.body.classList.add('service-open');
|
||||
updateServiceVideoUI();
|
||||
updateServiceMode();
|
||||
acquireWakeLock();
|
||||
}
|
||||
@@ -2373,6 +2431,9 @@ function exitServiceMode() {
|
||||
const el = $('serviceMode');
|
||||
if (el) el.classList.add('hidden');
|
||||
document.body.classList.remove('service-open');
|
||||
// Runs after the overlay is hidden, so this clears the pinned video styles
|
||||
// and returns .player-stage to the player pane.
|
||||
updateServiceVideoUI();
|
||||
releaseWakeLock();
|
||||
}
|
||||
|
||||
@@ -4522,6 +4583,11 @@ document.querySelectorAll('.chip').forEach((c) => {
|
||||
$('serviceNextBtn').addEventListener('click', playNext);
|
||||
$('servicePrevBtn').addEventListener('click', playPrev);
|
||||
$('serviceExitBtn').addEventListener('click', exitServiceMode);
|
||||
$('serviceVideoBtn').addEventListener('click', toggleServiceVideo);
|
||||
// The pinned panel is positioned in viewport coordinates, so it has to be
|
||||
// re-measured whenever the viewport changes shape.
|
||||
window.addEventListener('resize', positionServiceVideo);
|
||||
window.addEventListener('orientationchange', () => setTimeout(positionServiceVideo, 150));
|
||||
|
||||
// Mini now-playing bar
|
||||
$('miniPlayBtn').addEventListener('click', (e) => { e.stopPropagation(); Player.toggle(); });
|
||||
@@ -4534,6 +4600,12 @@ document.querySelectorAll('.chip').forEach((c) => {
|
||||
if (isPortraitPWA()) scrollPlayerIntoViewPortrait();
|
||||
else els.playerPane.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
});
|
||||
// Opens the set-list view for whatever is already playing. No playlist arg,
|
||||
// so enterServiceMode leaves the live queue and position untouched.
|
||||
$('miniServiceBtn').addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
enterServiceMode();
|
||||
});
|
||||
$('miniCloseBtn').addEventListener('click', (e) => { e.stopPropagation(); hideMiniBar(); });
|
||||
|
||||
// Drag-to-reorder: prevent default on cards container
|
||||
|
||||
Reference in New Issue
Block a user