Ambient: Sleep-Timer zählt und stoppt Drift (Browser und App), ☾ im Ambient-Tab; sw v74
- app.js: __dioraAmbientCmd, meldet Start/Stopp an die App (DioraNative.ambientState, sie hält Drift bei ausgeschaltetem Bildschirm am Laufen) und an den Browser-Host - podcasts-web.js: der Sleep-Timer zählt auch, solange Ambient spielt, stoppt es beim Ablauf; Nachtautomatik auch beim Ambient-Start Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b7a81f3e26
commit
794dbadbf5
5 changed files with 70 additions and 17 deletions
|
|
@ -1268,17 +1268,26 @@ function _ambientSize() {
|
||||||
}
|
}
|
||||||
window.addEventListener('resize', () => requestAnimationFrame(_ambientSize));
|
window.addEventListener('resize', () => requestAnimationFrame(_ambientSize));
|
||||||
|
|
||||||
|
// A command for Drift ('start' | 'stop'): the app's notification, the sleep timer.
|
||||||
|
window.__dioraAmbientCmd = function (cmd) {
|
||||||
|
const f = _ambientFrame();
|
||||||
|
if (f && f.contentWindow && f.getAttribute('src')) f.contentWindow.postMessage({type: 'ambient', cmd}, '*');
|
||||||
|
};
|
||||||
|
|
||||||
// The radio or a podcast starts: Ambient stops.
|
// The radio or a podcast starts: Ambient stops.
|
||||||
window.__dioraAmbientStop = function () {
|
window.__dioraAmbientStop = function () {
|
||||||
const f = _ambientFrame();
|
if (_ambientPlaying) window.__dioraAmbientCmd('stop');
|
||||||
if (_ambientPlaying && f && f.contentWindow) f.contentWindow.postMessage({type: 'ambient', cmd: 'stop'}, '*');
|
|
||||||
};
|
};
|
||||||
|
window.__dioraAmbientPlaying = () => _ambientPlaying;
|
||||||
|
|
||||||
window.addEventListener('message', e => {
|
window.addEventListener('message', e => {
|
||||||
const f = _ambientFrame();
|
const f = _ambientFrame();
|
||||||
const d = e.data;
|
const d = e.data;
|
||||||
if (!f || e.source !== f.contentWindow || !d || d.type !== 'ambient' || !d.state) return;
|
if (!f || e.source !== f.contentWindow || !d || d.type !== 'ambient' || !d.state) return;
|
||||||
_ambientPlaying = d.state === 'playing';
|
_ambientPlaying = d.state === 'playing';
|
||||||
|
// The app keeps it going with the screen off (AmbientService); the browser's sleep timer counts it.
|
||||||
|
if (window.DioraNative && DioraNative.ambientState) DioraNative.ambientState(_ambientPlaying);
|
||||||
|
if (window.__dioraOnAmbient) window.__dioraOnAmbient(_ambientPlaying);
|
||||||
if (!_ambientPlaying) return;
|
if (!_ambientPlaying) return;
|
||||||
// Ambient starts: the radio and a podcast here stop.
|
// Ambient starts: the radio and a podcast here stop.
|
||||||
if (typeof isPlaying !== 'undefined' && isPlaying) stopPlayback(false);
|
if (typeof isPlaying !== 'undefined' && isPlaying) stopPlayback(false);
|
||||||
|
|
|
||||||
|
|
@ -544,6 +544,7 @@
|
||||||
setFade(1);
|
setFade(1);
|
||||||
pause();
|
pause();
|
||||||
stopRadio(); // the radio sleeps too (it plays in app.js's element)
|
stopRadio(); // the radio sleeps too (it plays in app.js's element)
|
||||||
|
if (window.__dioraAmbientStop) window.__dioraAmbientStop(); // and Ambient
|
||||||
sleepChanged();
|
sleepChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -574,6 +575,14 @@
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ambientPlaying = () => !!(window.__dioraAmbientPlaying && window.__dioraAmbientPlaying());
|
||||||
|
|
||||||
|
// Ambient starts or stops (app.js): at night the timer may switch itself on; the page shows it.
|
||||||
|
window.__dioraOnAmbient = function (on) {
|
||||||
|
if (on) sleep.maybeAutoEnable();
|
||||||
|
setTimeout(report, 100);
|
||||||
|
};
|
||||||
|
|
||||||
// Set, changed or done here: the other devices show it (when this one is the active one).
|
// Set, changed or done here: the other devices show it (when this one is the active one).
|
||||||
function sleepChanged() {
|
function sleepChanged() {
|
||||||
if (typeof sync !== 'undefined' && sync.state && sync.state.device_id === DEVICE_ID) syncSoon();
|
if (typeof sync !== 'undefined' && sync.state && sync.state.device_id === DEVICE_ID) syncSoon();
|
||||||
|
|
@ -924,8 +933,8 @@
|
||||||
if (playing && Date.now() - lastSaved > 10000) saveNow(id, true);
|
if (playing && Date.now() - lastSaved > 10000) saveNow(id, true);
|
||||||
if (playing) skipOutro(id);
|
if (playing) skipOutro(id);
|
||||||
}
|
}
|
||||||
// The sleep timer counts while either plays (radio and podcasts share the player).
|
// The sleep timer counts while any of them plays (radio and podcasts share the player; Ambient).
|
||||||
sleep.tick(playing || radioPlaying());
|
sleep.tick(playing || radioPlaying() || ambientPlaying());
|
||||||
// While playing, the shared player hears from us every 10 s (its place, and that we're alive).
|
// While playing, the shared player hears from us every 10 s (its place, and that we're alive).
|
||||||
if ((playing || radioPlaying()) && Date.now() - sync.lastSent >= REPORT_EVERY_MS && !busy()) syncReport();
|
if ((playing || radioPlaying()) && Date.now() - sync.lastSent >= REPORT_EVERY_MS && !busy()) syncReport();
|
||||||
// Another device's place moves on in the bar too.
|
// Another device's place moves on in the bar too.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
* diora service worker — caches the app shell for offline use.
|
* diora service worker — caches the app shell for offline use.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CACHE = 'diora-v73';
|
const CACHE = 'diora-v74';
|
||||||
const SHELL = [
|
const SHELL = [
|
||||||
'/static/css/app.css',
|
'/static/css/app.css',
|
||||||
'/static/js/app.js',
|
'/static/js/app.js',
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copied from diora-android d323614 2026-10-03: web-native/static/native-podcast.js
|
// Copied from diora-android 67a915e 2026-10-03: web-native/static/native-podcast.js
|
||||||
// by tools/sync_podcast_ui.py. Do not edit here: change it in diora-android
|
// by tools/sync_podcast_ui.py. Do not edit here: change it in diora-android
|
||||||
// and copy again. The browser host is static/js/podcasts-web.js.
|
// and copy again. The browser host is static/js/podcasts-web.js.
|
||||||
|
|
||||||
|
|
@ -2825,6 +2825,22 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ambient (app.js's #tab-ambient): its sleep timer, a ☾ at the frame's top right corner.
|
||||||
|
function ambientSleepButton() {
|
||||||
|
const tab = $id('tab-ambient');
|
||||||
|
if (!tab || !N.podcastSleep || $id('nui-amb-sleep')) return;
|
||||||
|
const b = button('☾', 'Sleep-Timer', () => sleepMenu(true, true), 'nui-pod-sleep');
|
||||||
|
b.id = 'nui-amb-sleep';
|
||||||
|
tab.append(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAmbientSleep() {
|
||||||
|
const b = $id('nui-amb-sleep');
|
||||||
|
if (!b) return;
|
||||||
|
b.textContent = sleepLabel(true) || '☾';
|
||||||
|
b.classList.toggle('on', !!sleepLabel(true));
|
||||||
|
}
|
||||||
|
|
||||||
// The bar names a running sleep timer: "… · ☾ 12 min".
|
// The bar names a running sleep timer: "… · ☾ 12 min".
|
||||||
function sleepSuffix() {
|
function sleepSuffix() {
|
||||||
const l = sleepLabel();
|
const l = sleepLabel();
|
||||||
|
|
@ -2854,6 +2870,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMini() {
|
function renderMini() {
|
||||||
|
renderAmbientSleep();
|
||||||
const r = remote();
|
const r = remote();
|
||||||
const isPod = player.kind === 'podcast';
|
const isPod = player.kind === 'podcast';
|
||||||
document.documentElement.classList.toggle('nui-pod-playing', isPod || !!r);
|
document.documentElement.classList.toggle('nui-pod-playing', isPod || !!r);
|
||||||
|
|
@ -3516,14 +3533,15 @@
|
||||||
|
|
||||||
// ---- sleep timer (runs in the app, also with the screen off; episodes and the radio) ----
|
// ---- sleep timer (runs in the app, also with the screen off; episodes and the radio) ----
|
||||||
|
|
||||||
// The timer that counts: the other device's while that one is the active one, else ours.
|
// The timer that counts: the other device's while that one is the active one, else ours
|
||||||
function sleepState() {
|
// (`local`: ours anyway — Ambient's button, Ambient plays here).
|
||||||
const r = remote();
|
function sleepState(local) {
|
||||||
|
const r = local ? null : remote();
|
||||||
return (r ? r.sleep : player.sleep) || {};
|
return (r ? r.sleep : player.sleep) || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
function sleepLabel() {
|
function sleepLabel(local) {
|
||||||
const t = sleepState();
|
const t = sleepState(local);
|
||||||
if (t.episodes > 0) return t.episodes === 1 ? '☾ Folgenende' : `☾ ${t.episodes} Folgen`;
|
if (t.episodes > 0) return t.episodes === 1 ? '☾ Folgenende' : `☾ ${t.episodes} Folgen`;
|
||||||
if (t.leftMs > 0) return `☾ ${Math.max(1, Math.ceil(t.leftMs / 60000))} min`;
|
if (t.leftMs > 0) return `☾ ${Math.max(1, Math.ceil(t.leftMs / 60000))} min`;
|
||||||
return '';
|
return '';
|
||||||
|
|
@ -3562,18 +3580,18 @@
|
||||||
|
|
||||||
// Minutes count only while playing; shaking the phone starts them over. With another
|
// Minutes count only while playing; shaking the phone starts them over. With another
|
||||||
// device playing, the timer is that device's (set there by the player sync).
|
// device playing, the timer is that device's (set there by the player sync).
|
||||||
function sleepMenu(radio) {
|
function sleepMenu(radio, local) {
|
||||||
const r = remote();
|
const r = local ? null : remote();
|
||||||
const set = r ? (m => remoteCmd('sleep', m)) : N.podcastSleep ? (m => N.podcastSleep(m)) : null;
|
const set = r ? (m => remoteCmd('sleep', m)) : N.podcastSleep ? (m => N.podcastSleep(m)) : null;
|
||||||
if (!set) return;
|
if (!set) return;
|
||||||
const onRadio = r ? r.kind === 'radio' : radio === true;
|
const onRadio = r ? r.kind === 'radio' : radio === true;
|
||||||
const t = sleepState();
|
const t = sleepState(local);
|
||||||
const own = player.sleep || {};
|
const own = player.sleep || {};
|
||||||
const on = !!sleepLabel();
|
const on = !!sleepLabel(local);
|
||||||
const items = [];
|
const items = [];
|
||||||
if (r) items.push({custom: el('div', 'nui-pod-meta nui-pod-devnote', `Der Sleep-Timer gilt für ${remoteName(r)}.`)});
|
if (r) items.push({custom: el('div', 'nui-pod-meta nui-pod-devnote', `Der Sleep-Timer gilt für ${remoteName(r)}.`)});
|
||||||
if (on) {
|
if (on) {
|
||||||
items.push({icon: '✕', label: 'Ausschalten', hint: sleepLabel().replace('☾ ', 'läuft: '), action: () => set(0)});
|
items.push({icon: '✕', label: 'Ausschalten', hint: sleepLabel(local).replace('☾ ', 'läuft: '), action: () => set(0)});
|
||||||
if (t.leftMs > 0) {
|
if (t.leftMs > 0) {
|
||||||
for (const m of [5, 10]) {
|
for (const m of [5, 10]) {
|
||||||
items.push({icon: '+', label: `${m} Minuten länger`, action: () => (!r && N.podcastSleepExtend
|
items.push({icon: '+', label: `${m} Minuten länger`, action: () => (!r && N.podcastSleepExtend
|
||||||
|
|
@ -4322,6 +4340,7 @@
|
||||||
window.refreshAllFeeds = async function () {};
|
window.refreshAllFeeds = async function () {};
|
||||||
build();
|
build();
|
||||||
buildMini();
|
buildMini();
|
||||||
|
ambientSleepButton();
|
||||||
player = parse(N.playerState(), {kind: null});
|
player = parse(N.playerState(), {kind: null});
|
||||||
moveFeedListsToServer();
|
moveFeedListsToServer();
|
||||||
render();
|
render();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* Copied from diora-android abda34a 2026-10-03: the podcast parts of web-native/static/native.css
|
/* Copied from diora-android 67a915e 2026-10-03: the podcast parts of web-native/static/native.css
|
||||||
(bottom sheet, podcasts) by tools/sync_podcast_ui.py. Do not edit here.
|
(bottom sheet, podcasts) by tools/sync_podcast_ui.py. Do not edit here.
|
||||||
The web's own additions are in podcasts-web.css. */
|
The web's own additions are in podcasts-web.css. */
|
||||||
|
|
||||||
|
|
@ -780,3 +780,19 @@ body.eink .nui-pod-facts a { text-decoration-color: #000; }
|
||||||
sheet comes above both (app.css has it at 500/501, under them). */
|
sheet comes above both (app.css has it at 500/501, under them). */
|
||||||
.modal-overlay { z-index: 990; }
|
.modal-overlay { z-index: 990; }
|
||||||
.modal-dialog { z-index: 991; }
|
.modal-dialog { z-index: 991; }
|
||||||
|
|
||||||
|
/* Ambient's sleep timer: a ☾ on the synthesizer's frame, top right (native-podcast.js). */
|
||||||
|
#tab-ambient { position: relative; }
|
||||||
|
#nui-amb-sleep {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 2;
|
||||||
|
min-width: 44px;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-radius: 18px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||||
|
background: rgba(0, 0, 0, 0.55);
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue