1const CACHE_NAME = 'video-cache-v2'; 2 3self.addEventListener('install', (event) => { 4 self.skipWaiting(); 5}); 6 7self.addEventListener('activate', (event) => { 8 event.waitUntil( 9 caches.keys().then((cacheNames) => { 10 return Promise.all( 11 cacheNames.map((cacheName) => { 12 if (cacheName !== CACHE_NAME) { 13 return caches.delete(cacheName); 14 } 15 }) 16 ); 17 }).then(() => self.clients.claim()) 18 ); 19}); 20 21self.addEventListener('fetch', (event) => { 22 const url = new URL(event.request.url); 23 24 // Skip proxy API routes - they handle their own caching and URL rewriting 25 if (url.pathname.startsWith('/api/proxy')) { 26 return; // Let the request pass through without Service Worker intervention 27 } 28 29 // Intercept HLS manifest files (.m3u8) 30 if (url.pathname.endsWith('.m3u8')) { 31 event.respondWith( 32 caches.open(CACHE_NAME).then((cache) => { 33 return cache.match(event.request, { ignoreSearch: true }).then((cachedResponse) => { 34 // Always fetch fresh manifest but return cached while fetching 35 const fetchPromise = fetch(event.request).then((networkResponse) => { 36 // Check if network response is valid 37 if (!networkResponse || networkResponse.status !== 200) { 38 // Return the response as-is so client can see the error status 39 if (networkResponse) return networkResponse; 40 // If no response at all, throw to trigger catch block 41 throw new Error('Network response was not ok'); 42 } 43 cache.put(event.request, networkResponse.clone()); 44 return networkResponse; 45 }).catch((err) => { 46 console.error('[SW] Fetch failed for manifest:', err); 47 // If network fails, return cached response if available 48 if (cachedResponse) { 49 return cachedResponse; 50 } 51 // If no cache, return a proper error Response instead of throwing 52 // This prevents "Load failed" and lets the client handle it 53 return new Response('Network error', { 54 status: 503, 55 statusText: 'Service Worker: Network Unavailable' 56 }); 57 }); 58 59 // Return cache immediately if available, otherwise wait for network 60 return cachedResponse || fetchPromise; 61 }); 62 }) 63 ); 64 } 65 66 // Intercept video segment files (.ts) 67 if (url.pathname.endsWith('.ts')) { 68 event.respondWith( 69 caches.open(CACHE_NAME).then((cache) => { 70 return cache.match(event.request, { ignoreSearch: true }).then((cachedResponse) => { 71 // Cache hit - return immediately for instant playback 72 if (cachedResponse) { 73 return cachedResponse; 74 } 75 76 // Cache miss - fetch from network 77 return fetch(event.request).then((response) => { 78 // Only cache valid responses 79 if (response && response.status === 200) { 80 cache.put(event.request, response.clone()); 81 return response; 82 } 83 // If response is not valid (e.g. 403, 404), return it as is 84 // so the browser/player can handle the error status 85 return response; 86 }).catch((error) => { 87 console.error('[SW] Failed to fetch segment:', error); 88 // Return a proper error Response instead of throwing 89 // This prevents "Load failed" and lets the client handle it 90 return new Response('Network error', { 91 status: 503, 92 statusText: 'Service Worker: Network Unavailable' 93 }); 94 }); 95 }); 96 }) 97 ); 98 } 99});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.