Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service Worker Video Caching Sample #336

Merged
merged 32 commits into from May 26, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6a91f84
Added Web Audio method chaining sample
samdutton Nov 30, 2015
10c4e91
Added webrtc-vp8-vp9
samdutton Feb 1, 2016
56571a8
Updated webaudio-method-chaining files from upstream
samdutton Feb 1, 2016
eecbc0c
Tweaks to pass linting
samdutton Feb 2, 2016
eb7bcec
Removed npm-debug.log
samdutton Feb 2, 2016
a6f3581
Fixed eslint errors shown by Travis but not locally
samdutton Feb 2, 2016
3ccb2ff
Added prefetch-video example
samdutton May 10, 2016
3b73311
Merge branch 'gh-pages' of github.com:samdutton/samples into gh-pages
samdutton May 10, 2016
d9ce725
Added status
samdutton May 10, 2016
e1cf5a8
Removed redundant file list
samdutton May 10, 2016
19edfce
Tweaked wording
samdutton May 10, 2016
0b9f1d8
Moved to new template
samdutton May 11, 2016
b40258d
Added video element
samdutton May 11, 2016
12ca525
Removed service worker polyfill
samdutton May 11, 2016
40a0cb4
Added better heading
samdutton May 11, 2016
f44b630
Escape colon
samdutton May 11, 2016
0e208f0
Try agin
samdutton May 11, 2016
3fa68b2
Merge branch 'gh-pages' of github.com:GoogleChrome/samples into gh-pages
samdutton May 11, 2016
2dc6e6a
Removed redundant webrtc-vp8-vp9 demo
samdutton May 11, 2016
fa88523
Fixed README.md
samdutton May 11, 2016
cf9c4ef
Updated prefetch-video to handle Range requests
samdutton May 24, 2016
5e1a707
Checking headers code
samdutton May 24, 2016
4d67559
Checking headers code
samdutton May 24, 2016
009afd5
Added check for range requests
samdutton May 25, 2016
75f94b5
Added files to precache
samdutton May 26, 2016
565c1cb
Changed video
samdutton May 26, 2016
6d73b3e
Changed video
samdutton May 26, 2016
8d3bfb1
Removed files added when building
samdutton May 26, 2016
a43294b
Removing more redundant files
samdutton May 26, 2016
24e2da7
Adding back ignore files
samdutton May 26, 2016
c6fe93f
Fixed caching of video
samdutton May 26, 2016
e741e87
Added currentSrc
samdutton May 26, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Empty file modified .gitignore 100755 → 100644
Empty file.
5 changes: 5 additions & 0 deletions service-worker/prefetch-video/README.md
@@ -0,0 +1,5 @@
Service Worker Video Cache Sample
===
See https://googlechrome.github.io/samples/service-worker/prefetch-video/index.html for a live demo.

Learn more at https://www.chromestatus.com/feature/6561526227927040
27 changes: 27 additions & 0 deletions service-worker/prefetch-video/index.html
@@ -0,0 +1,27 @@
---
feature_name: "Service Worker Sample: Cache Video"
chrome_version: 52
feature_id: 6561526227927040
local_js_files: ['index.js', 'service-worker.js']
---

<video controls poster="static/poster.jpg"
style="margin: 0 0 1em 0; max-width: 100%; width: 480px;">
<source src="static/gbike.webm" type="video/webm" />
<source src="static/gbike.mp4" type="video/mp4" />
<p>Your browser does not support the video element.</p>
</video>

<div id="currentSrc"></div>

<h3>Background</h3>

<p>This sample demonstrates basic service worker registration, in conjunction with pre-fetching a video and its poster image.</p>

<p>Visit <code>chrome://inspect/#service-workers</code> and click on the "inspect" link below the registered service worker to view logging statements for the various actions the <code><a href="service-worker.js">service-worker.js</a></code> script is performing. Since most of the action takes place during the service worker's install handler, and that is only executed the very first time you visit this page (unless the service worker script changes), it can be helpful to inspect the logging in a <a href="https://support.google.com/chrome/answer/95464">Chrome Incognito window</a>.</p>

<p>To try out caching, disconnect from your network then reload this page.</p>


{% include js_snippet.html filename='index.js' title='index.js' %}
{% include js_snippet.html filename='service-worker.js' displayonly=true title='service-worker.js' %}
49 changes: 49 additions & 0 deletions service-worker/prefetch-video/index.js
@@ -0,0 +1,49 @@
// Helper function which returns a promise which resolves once the service worker registration
// is past the "installing" state.
function waitUntilInstalled(registration) {
return new Promise(function(resolve, reject) {
if (registration.installing) {
// If the current registration represents the "installing" service worker, then wait
// until the installation step (during which the resources are pre-fetched) completes
// to display the file list.
registration.installing.addEventListener('statechange', function(e) {
if (e.target.state === 'installed') {
resolve();
} else if (e.target.state === 'redundant') {
reject();
}
});
} else {
// Otherwise, if this isn't the "installing" service worker, then installation must have been
// completed during a previous visit to this page, and the resources are already pre-fetched.
// So we can show the list of files right away.
resolve();
}
});
}

if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./service-worker.js', {
scope: './'
})
.then(waitUntilInstalled)
// .then(showFilesList)
.catch(function(error) {
// Something went wrong during registration. The service-worker.js file
// might be unavailable or contain a syntax error.
document.querySelector('#status').textContent = error;
});
} else {
// The current browser doesn't support service workers.
var aElement = document.createElement('a');
aElement.href =
'http://www.chromium.org/blink/serviceworker/service-worker-faq';
aElement.textContent =
'Service workers are not supported in the current browser.';
document.querySelector('#status').appendChild(aElement);
}

document.querySelector('video').onloadedmetadata = function() {
var fileName = this.currentSrc.replace(/^.*[\\\/]/, '');
document.querySelector('#currentSrc').textContent = 'Video src: ' + fileName;
};
178 changes: 178 additions & 0 deletions service-worker/prefetch-video/service-worker.js
@@ -0,0 +1,178 @@
/*
Copyright 2014 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// While overkill for this specific sample in which there is only one cache,
// this is one best practice that can be followed in general to keep track of
// multiple caches used by a given service worker, and keep them all versioned.
// It maps a shorthand identifier for a cache to a specific, versioned cache name.

// Note that since global state is discarded in between service worker restarts, these
// variables will be reinitialized each time the service worker handles an event, and you
// should not attempt to change their values inside an event handler. (Treat them as constants.)

// If at any point you want to force pages that use this service worker to start using a fresh
// cache, then increment the CACHE_VERSION value. It will kick off the service worker update
// flow and the old cache(s) will be purged as part of the activate event handler when the
// updated service worker is activated.
var CACHE_VERSION = 1;
var CURRENT_CACHES = {
prefetch: 'prefetch-cache-v' + CACHE_VERSION
};

self.addEventListener('install', function(event) {
var now = Date.now();

var urlsToPrefetch = [
'/service-worker/prefetch-video/',
'/service-worker/prefetch-video/index.js',
'../../styles/main.css',
'static/video.mp4',
'static/video.webm',
'static/poster.jpg'];

// All of these logging statements should be visible via the "Inspect" interface
// for the relevant SW accessed via chrome://serviceworker-internals
console.log('Handling install event. Resources to prefetch:', urlsToPrefetch);

event.waitUntil(
caches.open(CURRENT_CACHES.prefetch).then(function(cache) {
var cachePromises = urlsToPrefetch.map(function(urlToPrefetch) {
// This constructs a new URL object using the service worker's script location as the base
// for relative URLs.
var url = new URL(urlToPrefetch, location.href);
// Append a cache-bust=TIMESTAMP URL parameter to each URL's query string.
// This is particularly important when precaching resources that are later used in the
// fetch handler as responses directly, without consulting the network (i.e. cache-first).
// If we were to get back a response from the HTTP browser cache for this precaching request
// then that stale response would be used indefinitely, or at least until the next time
// the service worker script changes triggering the install flow.
url.search += (url.search ? '&' : '?') + 'cache-bust=' + now;

// It's very important to use {mode: 'no-cors'} if there is any chance that
// the resources being fetched are served off of a server that doesn't support
// CORS (http://en.wikipedia.org/wiki/Cross-origin_resource_sharing).
// In this example, www.chromium.org doesn't support CORS, and the fetch()
// would fail if the default mode of 'cors' was used for the fetch() request.
// The drawback of hardcoding {mode: 'no-cors'} is that the response from all
// cross-origin hosts will always be opaque
// (https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#cross-origin-resources)
// and it is not possible to determine whether an opaque response represents a success or failure
// (https://github.com/whatwg/fetch/issues/14).
var request = new Request(url, {mode: 'no-cors'});
return fetch(request).then(function(response) {
if (response.status >= 400) {
throw new Error('request for ' + urlToPrefetch +
' failed with status ' + response.statusText);
}

// Use the original URL without the cache-busting parameter as the key for cache.put().
return cache.put(urlToPrefetch, response);
}).catch(function(error) {
console.error('Not caching ' + urlToPrefetch + ' due to ' + error);
});
});

return Promise.all(cachePromises).then(function() {
console.log('Pre-fetching complete.');
});
}).catch(function(error) {
console.error('Pre-fetching failed:', error);
})
);
});

self.addEventListener('activate', function(event) {
// Delete all caches that aren't named in CURRENT_CACHES.
// While there is only one cache in this example, the same logic will handle the case where
// there are multiple versioned caches.
var expectedCacheNames = Object.keys(CURRENT_CACHES).map(function(key) {
return CURRENT_CACHES[key];
});

event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (expectedCacheNames.indexOf(cacheName) === -1) {
// If this cache name isn't present in the array of "expected" cache names, then delete it.
console.log('Deleting out of date cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});

self.addEventListener('fetch', function(event) {
console.log('Handling fetch event for', event.request.url);

if (event.request.headers.get('range')) {
var pos =
Number(/^bytes\=(\d+)\-$/g.exec(event.request.headers.get('range'))[1]);
console.log('Range request for', event.request.url,
', starting position:', pos);
event.respondWith(
caches.open(CURRENT_CACHES.prefetch)
.then(function(cache) {
return cache.match(event.request.url);
}).then(function(res) {
if (!res) {
return fetch(event.request)
.then(res => {
return res.arrayBuffer();
});
}
return res.arrayBuffer();
}).then(function(ab) {
return new Response(
ab.slice(pos),
{
status: 206,
statusText: 'Partial Content',
headers: [
// ['Content-Type', 'video/webm'],
['Content-Range', 'bytes ' + pos + '-' +
(ab.byteLength - 1) + '/' + ab.byteLength]]
});
}));
} else {
console.log('Non-range request for', event.request.url);
event.respondWith(
// caches.match() will look for a cache entry in all of the caches available to the service worker.
// It's an alternative to first opening a specific named cache and then matching on that.
caches.match(event.request).then(function(response) {
if (response) {
console.log('Found response in cache:', response);
return response;
}
console.log('No response found in cache. About to fetch from network...');
// event.request will always have the proper mode set ('cors, 'no-cors', etc.) so we don't
// have to hardcode 'no-cors' like we do when fetch()ing in the install handler.
return fetch(event.request).then(function(response) {
console.log('Response from network is:', response);

return response;
}).catch(function(error) {
// This catch() will handle exceptions thrown from the fetch() operation.
// Note that a HTTP error response (e.g. 404) will NOT trigger an exception.
// It will return a normal response object that has the appropriate error code set.
console.error('Fetching failed:', error);

throw error;
});
})
);
}
});

Binary file added service-worker/prefetch-video/static/gbike.mp4
Binary file not shown.
Binary file not shown.
Binary file added service-worker/prefetch-video/static/poster.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.