Der Server findet die Pausen jetzt nach den Regeln der App (PauseCutter): 10-ms-Fenster, Rauschboden als gleitendes Perzentil, ein Fünftel jeder Pause bleibt (60–250 ms), halb davor, halb danach. Ältere Ergebnisse werden neu berechnet (v: 2). Der Player läuft durch diese Teile höchstens 4× schnell statt 8×: Chromium schaltet darüber stumm, und weil currentTime in 10–20-ms-Schritten läuft, landete der Wechsel zurück bei 8× bis zu 200 ms in der Sprache. Pro Pause wird das Tempo gewählt, das am meisten spart und trotzdem rechtzeitig endet. Das Tempo des Podcasts setzt die Rate während einer Pause nicht mehr zurück. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
137 lines
4.6 KiB
JavaScript
137 lines
4.6 KiB
JavaScript
/**
|
|
* diora service worker — caches the app shell for offline use.
|
|
*/
|
|
|
|
const CACHE = 'diora-v110';
|
|
const SHELL = [
|
|
'/static/css/app.css',
|
|
'/static/js/app/01-core.js',
|
|
'/static/js/app/10-radio.js',
|
|
'/static/js/app/14-focus.js',
|
|
'/static/js/app/16-stations.js',
|
|
'/static/js/app/20-tabs.js',
|
|
'/static/js/app/24-ambient.js',
|
|
'/static/js/app/26-sidebar.js',
|
|
'/static/js/app/28-serviceworker.js',
|
|
'/static/js/app/29-m3u.js',
|
|
'/static/js/app/30-contrast.js',
|
|
'/static/js/app/32-crypto.js',
|
|
'/static/js/app/34-wallpaper.js',
|
|
'/static/js/app/40-epub.js',
|
|
'/static/js/app/44-books.js',
|
|
'/static/js/app/46-readpos.js',
|
|
'/static/js/app/48-book-cache.js',
|
|
'/static/js/app/50-reader-state.js',
|
|
'/static/js/app/52-book-list.js',
|
|
'/static/js/app/56-cloud-import.js',
|
|
'/static/js/app/58-pdf.js',
|
|
'/static/js/app/60-tts.js',
|
|
'/static/js/app/62-reader.js',
|
|
'/static/js/app/64-reader-settings.js',
|
|
'/static/js/app/66-pdf-paginated.js',
|
|
'/static/js/app/68-bookmarks.js',
|
|
'/static/js/app/70-search.js',
|
|
'/static/js/app/72-highlights.js',
|
|
'/static/js/app/74-margin.js',
|
|
'/static/js/app/76-export.js',
|
|
'/static/js/app/78-footnotes.js',
|
|
'/static/js/app/80-radio-sidebar.js',
|
|
'/static/js/app/99-init.js',
|
|
'/static/js/podcasts-web.js',
|
|
'/static/js/home.js',
|
|
'/static/js/feeds.js',
|
|
'/static/podcasts/native-podcast.js',
|
|
'/static/podcasts/podcast-ui.css',
|
|
'/static/podcasts/podcasts-web.css',
|
|
'/static/manifest.json',
|
|
'/static/ambient/index.html',
|
|
'/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',
|
|
];
|
|
|
|
// 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();
|
|
});
|
|
|
|
// Activate: remove stale caches from previous versions (also the old
|
|
// 'diora-podcast-v1' of episodes the web app used to download)
|
|
self.addEventListener('activate', function (event) {
|
|
event.waitUntil(
|
|
caches.keys().then(function (keys) {
|
|
return Promise.all(
|
|
keys.filter(function (key) { return key !== CACHE; })
|
|
.map(function (key) { return caches.delete(key); })
|
|
);
|
|
}).then(function () {
|
|
return self.clients.claim();
|
|
}).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'});
|
|
});
|
|
});
|
|
})
|
|
);
|
|
});
|
|
|
|
// 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);
|
|
|
|
// Bypass for API/mutation endpoints
|
|
if (url.pathname.startsWith('/radio/sse/') ||
|
|
url.pathname.startsWith('/radio/record/') ||
|
|
url.pathname.startsWith('/radio/affiliate/') ||
|
|
url.pathname.startsWith('/tts/') ||
|
|
url.pathname.startsWith('/admin/') ||
|
|
url.pathname.startsWith('/podcasts/progress/') ||
|
|
url.pathname.startsWith('/podcasts/queue/') ||
|
|
url.pathname.startsWith('/podcasts/player/') ||
|
|
url.pathname === '/podcasts/feeds/add' ||
|
|
url.pathname.startsWith('/podcasts/feeds/add') ||
|
|
url.pathname.includes('/remove') ||
|
|
url.pathname.startsWith('/podcasts/feeds/refresh')) {
|
|
return;
|
|
}
|
|
|
|
// Cache-first for pre-defined shell assets
|
|
const isShell = SHELL.some(function (s) { return url.pathname === s; });
|
|
if (isShell) {
|
|
event.respondWith(
|
|
// ignoreSearch: the Ambient frame loads with ?embed=1&bg=…
|
|
caches.match(event.request, {ignoreSearch: true}).then(function (cached) {
|
|
return cached || fetch(event.request);
|
|
})
|
|
);
|
|
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);
|
|
})
|
|
);
|
|
}
|
|
});
|