I have an offline app that caches all static resources. Currently, only the first 15 seconds of video assets are cached.
Below shows basic implementations of the install
and fetch
event listeners.
Service Worker:
self.addEventListener('install', event => {
event.waitUntil(
caches.open('v1').then(cache => {
return cache.addAll([
'/',
'/videos/one.mp4',
'/videos/two.mp4'
]);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
if (response) return response;
return fetch(event.request);
});
);
});
And in index.html
<video controls preload>
<source src="/videos/one.mp4" type="video/mp4">
</video>
Using a Service worker you can easily set an app up to use cached assets first, thus providing a default experience even when offline, before then getting more data from the network (commonly known as Offline First).
Implementing user personalization in scripts allows caching of the page's HTML. Then the scripts can modify the page after loading asynchronously. Beyond using JavaScript for personalization, The Gap is caching HTML.
I'd recommend following the approach outlined in this "Service Worker Caching Strategies Based on Request Types" article, and use request. destination inside of your fetch handler to figure out which requests are going to be used for images. self. addEventListener('fetch', (event) => { if (event.
A service worker intercepts network-type HTTP requests and uses a caching strategy to determine what resources should be returned to the browser.
I used the following steps to accomplish offline video on the first page load without first watching the entire video(s).
'/'
for this case. If you inspect the service worker's fetch
event, you'll see that subsequent requests are also cached.fetch
API to request a video as a blob
.Example using fetch to request a video as a blob
const videoRequest = fetch('/path/to/video.mp4').then(response => response.blob());
videoRequest.then(blob => {
...
});
IndexedDB
API to store the blob
. (Use IndexedDB
instead of LocalStorage
to avoid blocking the main thread while storing.)That's it! Now when in offline mode, the service worker will intercept requests and serve both the html
and blob
from cache.
index.html
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
<h1>Service Worker Test</h1>
<p>Try reloading the page without an Internet connection.</p>
<video controls></video>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}).catch(error => {
console.log('ServiceWorker registration failed: ', error);
});
});
} else {
alert('serviceWorker is not in navigator');
}
</script>
<script>
const videos = {
one: document.querySelector('video')
};
const videoRequest = fetch('/path/to/video.mp4').then(response => response.blob());
videoRequest.then(blob => {
const request = indexedDB.open('databaseNameHere', 1);
request.onsuccess = event => {
const db = event.target.result;
const transaction = db.transaction(['videos']);
const objectStore = transaction.objectStore('videos');
const test = objectStore.get('test');
test.onerror = event => {
console.log('error');
};
test.onsuccess = event => {
videos.one.src = window.URL.createObjectURL(test.result.blob);
};
}
request.onupgradeneeded = event => {
const db = event.target.result;
const objectStore = db.createObjectStore('videos', { keyPath: 'name' });
objectStore.transaction.oncomplete = event => {
const videoObjectStore = db.transaction('videos', 'readwrite').objectStore('videos');
videoObjectStore.add({name: 'test', blob: blob});
};
}
});
</script>
</body>
</html>
Service Worker
const latest = {
cache: 'some-cache-name/v1'
};
self.addEventListener('install', event => {
event.waitUntil(
caches.open(latest.cache).then(cache => {
return cache.addAll([
'/'
]);
})
);
});
self.addEventListener('fetch', event => {
// exclude requests that start with chrome-extension://
if (event.request.url.startsWith('chrome-extension://')) return;
event.respondWith(
caches.open(latest.cache).then(cache => {
return cache.match(event.request).then(response => {
var fetchPromise = fetch(event.request).then(networkResponse => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
})
return response || fetchPromise;
})
})
);
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.filter(cacheName => {
if (cacheName === latest.cache) {
return false;
}
return true;
}).map(cacheName => {
return caches.delete(cacheName)
})
);
})
);
});
Resources:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With