Podcasts: diora desktop kann den Browser-Host mitbenutzen (DIORA_PODCAST_HOST), sw v68
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 6m39s

Setzt eine Seite window.DIORA_PODCAST_HOST, arbeitet podcasts-web.js trotz
vorhandenem DioraNative als ihr Podcast-Host:
- dock: 'existing'  nimmt native-radio.js' #nui-dock statt einer Leiste in der
  Web-Radioleiste; die Podcast-Seite lädt erst nach DOMContentLoaded
- styles: 'ui-only' nur podcast-ui.css, ohne das Browser-Layout (html.dpw)
- radio: false      die Radio-Hooks (__dioraRadioNow …) kommen von native-radio.js;
  der Sender legt die Folge weiterhin beiseite, Radio-Stopp meldet der Sync weiter
- deviceName/deviceKind  wie der gemeinsame Player das Gerät führt
Ohne die Variable ändert sich im Browser nichts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrwnslz 2026-10-02 19:39:54 +02:00
parent 3e67d324c7
commit 461b42a5bf
2 changed files with 73 additions and 52 deletions

View file

@ -23,17 +23,27 @@
* Radio and podcasts never play at the same time: starting an episode stops
* the radio (app.js stopPlayback), starting a station puts the episode away.
*
* In the app (and the desktop app) window.DioraNative exists and this file
* does nothing. Written without ?. and ?? so old WebViews can still parse it.
* In the app window.DioraNative exists and this file does nothing. diora
* desktop has a DioraNative too (books, radio) but no podcast part of its own:
* it sets window.DIORA_PODCAST_HOST and this file is its podcast host, fitted
* into the phone layout it already shows:
* dock: 'existing' use native-radio.js's #nui-dock instead of a bar in the web radio bar
* styles: 'ui-only' only the podcast page's own CSS, not the browser layout
* radio: false native-radio.js provides the radio hooks (__dioraRadioNow …)
* deviceName, deviceKind how the shared player lists this device
* Written without ?. and ?? so old WebViews can still parse it.
*/
(function () {
'use strict';
if (window.DioraNative) return;
const HOST = window.DIORA_PODCAST_HOST || null;
if (window.DioraNative && !HOST) return;
if (typeof IS_AUTHENTICATED === 'undefined' || !IS_AUTHENTICATED) return;
if (!document.getElementById('tab-podcasts')) return;
const OPT = Object.assign({dock: 'bar', styles: 'web', radio: true, deviceName: '', deviceKind: 'web'}, HOST || {});
const UI_SCRIPT = '/static/podcasts/native-podcast.js';
const UI_STYLES = ['/static/podcasts/podcast-ui.css', '/static/podcasts/podcasts-web.css'];
const UI_STYLES = OPT.styles === 'ui-only' ? ['/static/podcasts/podcast-ui.css']
: ['/static/podcasts/podcast-ui.css', '/static/podcasts/podcasts-web.css'];
// ---- storage ------------------------------------------------------------------
@ -890,31 +900,34 @@
const radioPlaying = () => typeof isPlaying !== 'undefined' && !!isPlaying;
window.__dioraRadioNow = function () {
const st = typeof currentStation !== 'undefined' ? currentStation : null;
if (!st) return null;
return {
name: st.name || '',
logo: radioLogo(st),
track: typeof currentTrack !== 'undefined' ? currentTrack : '',
playing: radioPlaying(),
buffering: false,
saved: radioSaved(st.url),
// diora desktop: native-radio.js already shows the radio in the shared bar.
if (OPT.radio) {
window.__dioraRadioNow = function () {
const st = typeof currentStation !== 'undefined' ? currentStation : null;
if (!st) return null;
return {
name: st.name || '',
logo: radioLogo(st),
track: typeof currentTrack !== 'undefined' ? currentTrack : '',
playing: radioPlaying(),
buffering: false,
saved: radioSaved(st.url),
};
};
};
// Stop keeps the station (like a paused episode): the bar stays with ▶.
window.__dioraRadioToggle = () => (radioPlaying() ? stopPlayback(false) : togglePlayStop());
window.__dioraRadioSave = () => saveCurrentStation();
// Stop keeps the station (like a paused episode): the bar stays with ▶.
window.__dioraRadioToggle = () => (radioPlaying() ? stopPlayback(false) : togglePlayStop());
window.__dioraRadioSave = () => saveCurrentStation();
for (const name of ['stopPlayback', 'updateNowPlayingUI', 'saveCurrentStation']) {
const fn = window[name];
if (typeof fn !== 'function') continue;
window[name] = function () {
const r = fn.apply(this, arguments);
if (r && typeof r.then === 'function') r.then(radioChanged, radioChanged);
else radioChanged();
return r;
};
for (const name of ['stopPlayback', 'updateNowPlayingUI', 'saveCurrentStation']) {
const fn = window[name];
if (typeof fn !== 'function') continue;
window[name] = function () {
const r = fn.apply(this, arguments);
if (r && typeof r.then === 'function') r.then(radioChanged, radioChanged);
else radioChanged();
return r;
};
}
}
// The radio stopping is news for the shared player too.
if (typeof window.stopPlayback === 'function') {
@ -931,7 +944,7 @@
// Player sync: the station another device played last becomes the current
// one here, without playing (the bar shows it with ▶).
window.__dioraAdoptStation = function (st) {
if (OPT.radio) window.__dioraAdoptStation = function (st) {
if (!st || !st.url || radioPlaying() || typeof currentStation === 'undefined') return;
currentStation = {url: st.url, name: st.name || '', id: st.id || null};
currentTrack = '';
@ -964,7 +977,7 @@
let id = '';
try { id = localStorage.getItem('diora_device_id') || ''; } catch (e) {}
if (!id) {
id = 'web-' + Array.from(crypto.getRandomValues(new Uint8Array(12)), b => b.toString(16).padStart(2, '0')).join('');
id = OPT.deviceKind + '-' + Array.from(crypto.getRandomValues(new Uint8Array(12)), b => b.toString(16).padStart(2, '0')).join('');
try { localStorage.setItem('diora_device_id', id); } catch (e) {}
}
return id;
@ -981,7 +994,7 @@
const DEVICE_ID = deviceId();
// Short, it stands in the bar ("läuft auf Chrome · Windows").
const DEVICE_NAME = browserName();
const DEVICE_NAME = OPT.deviceName || browserName();
const sync = {
state: null, // the server's state, as last taken in
@ -1362,7 +1375,7 @@
clearTimeout(retryTimer);
if (source) source.close();
const seen = Math.max(sync.state ? sync.state.seq : -1, sync.later ? sync.later.seq : -1);
const q = new URLSearchParams({since: String(seen), device_id: DEVICE_ID, device_name: DEVICE_NAME, device_kind: 'web'});
const q = new URLSearchParams({since: String(seen), device_id: DEVICE_ID, device_name: DEVICE_NAME, device_kind: OPT.deviceKind});
source = new EventSource('/podcasts/player/stream/?' + q);
source.onopen = () => { retryMs = 1000; };
source.onmessage = e => heard(parse(e.data, null));
@ -1603,7 +1616,8 @@
// ---- page set-up -------------------------------------------------------------------------
document.documentElement.classList.add('dpw');
// The browser layout (podcasts-web.css hangs off html.dpw); diora desktop keeps the phone's.
if (OPT.styles !== 'ui-only') document.documentElement.classList.add('dpw');
for (const href of UI_STYLES) {
const link = document.createElement('link');
@ -1615,24 +1629,27 @@
// The dock the page puts its player bar in (native-radio.js builds it in the app):
// here it is part of the radio bar at the bottom, in place of its "now playing",
// so radio and podcasts share one bar.
const dock = document.createElement('div');
dock.id = 'nui-dock';
const bar = document.getElementById('now-playing-bar');
if (bar) {
bar.insertBefore(dock, bar.firstChild);
document.documentElement.classList.add('dpw-in-bar');
} else {
document.body.appendChild(dock);
}
// diora desktop: native-radio.js builds the dock (on DOMContentLoaded) with the tab bar.
if (OPT.dock !== 'existing') {
const dock = document.createElement('div');
dock.id = 'nui-dock';
const bar = document.getElementById('now-playing-bar');
if (bar) {
bar.insertBefore(dock, bar.firstChild);
document.documentElement.classList.add('dpw-in-bar');
} else {
document.body.appendChild(dock);
}
// The page keeps clear of the bar, whose height changes with the screen.
const placeDock = () => {
const h = bar ? bar.getBoundingClientRect().height : 0;
document.documentElement.style.setProperty('--dpw-bar-h', Math.round(h) + 'px');
};
placeDock();
if (bar && window.ResizeObserver) new ResizeObserver(placeDock).observe(bar);
window.addEventListener('resize', placeDock);
// The page keeps clear of the bar, whose height changes with the screen.
const placeDock = () => {
const h = bar ? bar.getBoundingClientRect().height : 0;
document.documentElement.style.setProperty('--dpw-bar-h', Math.round(h) + 'px');
};
placeDock();
if (bar && window.ResizeObserver) new ResizeObserver(placeDock).observe(bar);
window.addEventListener('resize', placeDock);
}
// Escape closes what the podcast page has open: a sheet, the player, notes,
// a podcast, choosing several (its back handler) — never the tab itself.
@ -1696,8 +1713,12 @@
if (typeof _currentTopTab === 'undefined' || _currentTopTab !== tab) showTab(tab);
})();
// The page (copied from the app) comes last, once all of the above is in place.
// The page (copied from the app) comes last, once all of the above is in place —
// with an existing dock, also after the page's own DOMContentLoaded work built it.
const script = document.createElement('script');
script.src = UI_SCRIPT;
document.body.appendChild(script);
if (OPT.dock !== 'existing') document.body.appendChild(script);
else if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => setTimeout(() => document.body.appendChild(script), 0));
} else setTimeout(() => document.body.appendChild(script), 0);
})();

View file

@ -2,7 +2,7 @@
* diora service worker — caches the app shell for offline use.
*/
const CACHE = 'diora-v67';
const CACHE = 'diora-v68';
const SHELL = [
'/static/css/app.css',
'/static/js/app.js',