2026-03-16 19:19:22 +01:00
|
|
|
/**
|
|
|
|
|
* diora service worker — caches the app shell for offline use.
|
|
|
|
|
*/
|
|
|
|
|
|
2026-10-03 23:08:57 +02:00
|
|
|
const CACHE = 'diora-v73';
|
2026-03-16 19:19:22 +01:00
|
|
|
const SHELL = [
|
|
|
|
|
'/static/css/app.css',
|
|
|
|
|
'/static/js/app.js',
|
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
|
|
|
'/static/js/podcasts-web.js',
|
|
|
|
|
'/static/podcasts/native-podcast.js',
|
|
|
|
|
'/static/podcasts/podcast-ui.css',
|
|
|
|
|
'/static/podcasts/podcasts-web.css',
|
2026-03-16 19:19:22 +01:00
|
|
|
'/static/manifest.json',
|
2026-10-03 23:08:57 +02:00
|
|
|
'/static/ambient/index.html',
|
2026-09-12 15:05:08 +02:00
|
|
|
'/static/fonts/SourceSerif4-normal-latin.woff2',
|
|
|
|
|
'/static/fonts/SourceSerif4-normal-latin-ext.woff2',
|
|
|
|
|
'/static/fonts/SourceSerif4-italic-latin.woff2',
|
|
|
|
|
'/static/fonts/SourceSerif4-italic-latin-ext.woff2',
|
2026-03-16 19:19:22 +01:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Install: pre-cache the app shell
|
|
|
|
|
self.addEventListener('install', function (event) {
|
|
|
|
|
event.waitUntil(
|
|
|
|
|
caches.open(CACHE).then(function (cache) {
|
|
|
|
|
return cache.addAll(SHELL);
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
// Activate immediately without waiting for old tabs to close
|
|
|
|
|
self.skipWaiting();
|
|
|
|
|
});
|
|
|
|
|
|
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
|
|
|
// Activate: remove stale caches from previous versions (also the old
|
|
|
|
|
// 'diora-podcast-v1' of episodes the web app used to download)
|
2026-03-16 19:19:22 +01:00
|
|
|
self.addEventListener('activate', function (event) {
|
|
|
|
|
event.waitUntil(
|
|
|
|
|
caches.keys().then(function (keys) {
|
|
|
|
|
return Promise.all(
|
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
|
|
|
keys.filter(function (key) { return key !== CACHE; })
|
2026-03-16 19:19:22 +01:00
|
|
|
.map(function (key) { return caches.delete(key); })
|
|
|
|
|
);
|
|
|
|
|
}).then(function () {
|
|
|
|
|
return self.clients.claim();
|
2026-05-30 00:19:00 +02:00
|
|
|
}).then(function () {
|
|
|
|
|
// Tell all open tabs to reload so they get the latest cached assets
|
|
|
|
|
return self.clients.matchAll({type: 'window'}).then(function (clients) {
|
|
|
|
|
clients.forEach(function (client) {
|
|
|
|
|
client.postMessage({type: 'SW_ACTIVATED'});
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-03-16 19:19:22 +01:00
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Fetch: serve from cache, fall back to network
|
|
|
|
|
self.addEventListener('fetch', function (event) {
|
|
|
|
|
// Only handle GET requests; let POST/SSE etc. pass through
|
|
|
|
|
if (event.request.method !== 'GET') return;
|
|
|
|
|
|
|
|
|
|
const url = new URL(event.request.url);
|
2026-03-19 13:39:59 +01:00
|
|
|
|
|
|
|
|
// Bypass for API/mutation endpoints
|
2026-03-16 19:19:22 +01:00
|
|
|
if (url.pathname.startsWith('/radio/sse/') ||
|
|
|
|
|
url.pathname.startsWith('/radio/record/') ||
|
|
|
|
|
url.pathname.startsWith('/radio/affiliate/') ||
|
2026-08-31 22:29:13 +02:00
|
|
|
url.pathname.startsWith('/tts/') ||
|
2026-03-19 13:39:59 +01:00
|
|
|
url.pathname.startsWith('/admin/') ||
|
|
|
|
|
url.pathname.startsWith('/podcasts/progress/') ||
|
|
|
|
|
url.pathname.startsWith('/podcasts/queue/') ||
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
url.pathname.startsWith('/podcasts/player/') ||
|
2026-03-19 13:39:59 +01:00
|
|
|
url.pathname === '/podcasts/feeds/add' ||
|
|
|
|
|
url.pathname.startsWith('/podcasts/feeds/add') ||
|
|
|
|
|
url.pathname.includes('/remove') ||
|
|
|
|
|
url.pathname.startsWith('/podcasts/feeds/refresh')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-29 23:58:07 +02:00
|
|
|
// Cache-first for pre-defined shell assets
|
2026-03-19 22:05:50 +01:00
|
|
|
const isShell = SHELL.some(function (s) { return url.pathname === s; });
|
|
|
|
|
if (isShell) {
|
|
|
|
|
event.respondWith(
|
2026-10-03 23:08:57 +02:00
|
|
|
// ignoreSearch: the Ambient frame loads with ?embed=1&bg=…
|
|
|
|
|
caches.match(event.request, {ignoreSearch: true}).then(function (cached) {
|
2026-03-19 22:05:50 +01:00
|
|
|
return cached || fetch(event.request);
|
|
|
|
|
})
|
|
|
|
|
);
|
2026-05-29 23:58:07 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Navigation requests (the HTML page itself): network-first, fall back to
|
|
|
|
|
// last cached version so the app opens offline after being visited once.
|
|
|
|
|
if (event.request.mode === 'navigate') {
|
|
|
|
|
event.respondWith(
|
|
|
|
|
fetch(event.request).then(function (response) {
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
var clone = response.clone();
|
|
|
|
|
caches.open(CACHE).then(function (cache) { cache.put(event.request, clone); });
|
|
|
|
|
}
|
|
|
|
|
return response;
|
|
|
|
|
}).catch(function () {
|
|
|
|
|
return caches.match(event.request);
|
|
|
|
|
})
|
|
|
|
|
);
|
2026-03-19 22:05:50 +01:00
|
|
|
}
|
2026-03-16 19:19:22 +01:00
|
|
|
});
|