diff --git a/server/player/index.html b/server/player/index.html
index db367b0..7d6b2b6 100644
--- a/server/player/index.html
+++ b/server/player/index.html
@@ -952,10 +952,21 @@
// Register service worker for offline content caching
if ('serviceWorker' in navigator) {
- navigator.serviceWorker.register('/player/sw.js').then(
- () => console.log('Service Worker registered'),
- (err) => console.warn('SW registration failed:', err)
- );
+ navigator.serviceWorker.register('/player/sw.js').then(reg => {
+ console.log('Service Worker registered');
+ // When a new SW activates, reload so the fresh code takes effect immediately
+ reg.addEventListener('updatefound', () => {
+ const newWorker = reg.installing;
+ if (newWorker) {
+ newWorker.addEventListener('statechange', () => {
+ if (newWorker.state === 'activated' && navigator.serviceWorker.controller) {
+ console.log('New Service Worker activated — reloading for fresh code');
+ location.reload();
+ }
+ });
+ }
+ });
+ }, (err) => console.warn('SW registration failed:', err));
}
// ==================== Keyboard shortcuts ====================
diff --git a/server/player/sw.js b/server/player/sw.js
index 9db8fbc..4413a0a 100644
--- a/server/player/sw.js
+++ b/server/player/sw.js
@@ -1,61 +1,29 @@
-const CACHE_NAME = 'rd-player-v4';
-const CONTENT_CACHE = 'rd-content-v1';
+const CACHE_NAME = 'rd-player-v5';
// Install: skip waiting to activate immediately
self.addEventListener('install', (event) => {
self.skipWaiting();
});
-// Activate: clean old caches, claim clients
+// Activate: clean old caches (including old content cache), claim clients
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(keys => Promise.all(
- keys.filter(k => k !== CACHE_NAME && k !== CONTENT_CACHE).map(k => caches.delete(k))
+ keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))
)).then(() => self.clients.claim())
);
});
-// Fetch handler
+// Fetch handler — ONLY cache player page and static assets.
+// Content files (/uploads/content/) are NOT intercepted — the server sets
+// Cache-Control: public, max-age=2592000, immutable which lets the browser
+// cache them natively without SW complications (range requests, opaque
+// responses, video seeking, etc.)
self.addEventListener('fetch', (event) => {
- const url = new URL(event.request.url);
-
- // Only handle GET requests — let POST/PUT/DELETE pass through
+ // Only handle GET requests
if (event.request.method !== 'GET') return;
- // Content files (videos, images): cache-first for offline playback
- if (url.pathname.startsWith('/uploads/content/')) {
- // Skip range requests (video seeking) — serve from network, don't cache partial responses
- if (event.request.headers.get('range')) {
- return; // Let the browser handle range requests directly
- }
-
- event.respondWith(
- caches.open(CONTENT_CACHE).then(cache =>
- cache.match(event.request, { ignoreSearch: true }).then(cached => {
- if (cached) return cached;
- return fetch(event.request).then(response => {
- // Only cache successful, complete (non-opaque) responses
- if (response.ok && response.status === 200 && response.type !== 'opaque') {
- cache.put(event.request, response.clone());
- }
- return response;
- }).catch(() => {
- return new Response('Content unavailable offline', {
- status: 503,
- statusText: 'Service Unavailable',
- headers: { 'Content-Type': 'text/plain' }
- });
- });
- })
- ).catch(() => {
- // Cache API itself failed — fall through to network
- return fetch(event.request).catch(() =>
- new Response('Offline', { status: 503, headers: { 'Content-Type': 'text/plain' } })
- );
- })
- );
- return;
- }
+ const url = new URL(event.request.url);
// Player page and static assets: network-first, fall back to cache
if (url.pathname.startsWith('/player') || url.pathname === '/socket.io/socket.io.js') {
@@ -79,6 +47,6 @@ self.addEventListener('fetch', (event) => {
return;
}
- // Everything else: network only, don't intercept failures
- // (Returning without calling event.respondWith lets the browser handle it natively)
+ // Everything else (content files, API calls, etc.): don't intercept.
+ // Returning without event.respondWith lets the browser handle it natively.
});