diff --git a/static/js/podcasts-web.js b/static/js/podcasts-web.js index 03f5add..c505aff 100644 --- a/static/js/podcasts-web.js +++ b/static/js/podcasts-web.js @@ -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); })(); diff --git a/static/js/sw.js b/static/js/sw.js index b45d5fc..b784aa0 100644 --- a/static/js/sw.js +++ b/static/js/sw.js @@ -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',