mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
c9bd9a5dc9
Though not part of the library, this corrects/normalizes the license headers in the demo app to be consistent with those in the library. Issue #2638 Change-Id: I4546c4c6970d72ff71a37489fd582623bd6e2ca3
356 lines
11 KiB
JavaScript
356 lines
11 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @fileoverview Shaka Player demo, service worker.
|
|
*/
|
|
|
|
|
|
/**
|
|
* The name of the cache for this version of the application.
|
|
* This should be updated when old, unneeded application resources could be
|
|
* cleaned up by a newer version of the application.
|
|
*
|
|
* @const {string}
|
|
*/
|
|
const CACHE_NAME = 'shaka-player-v3.0+';
|
|
|
|
|
|
/**
|
|
* The prefix of all cache versions that belong to this application.
|
|
* This is used to identify old caches to clean up. Must match CACHE_NAME
|
|
* above.
|
|
*
|
|
* @const {string}
|
|
*/
|
|
const CACHE_NAME_PREFIX = 'shaka-player';
|
|
|
|
console.assert(CACHE_NAME.startsWith(CACHE_NAME_PREFIX),
|
|
'Cache name does not match prefix!');
|
|
|
|
|
|
/**
|
|
* The maximum number of seconds to wait for an updated version of something
|
|
* if we have a cached version we could use instead.
|
|
*
|
|
* @const {number}
|
|
*/
|
|
const NETWORK_TIMEOUT = 2;
|
|
|
|
|
|
/**
|
|
* An array of resources that MUST be cached to make the application
|
|
* available offline.
|
|
*
|
|
* @const {!Array.<string>}
|
|
*/
|
|
const CRITICAL_RESOURCES = [
|
|
'.', // This resolves to the page.
|
|
'index.html', // Another way to access the page.
|
|
'app_manifest.json',
|
|
'shaka_logo_trans.png',
|
|
|
|
'load.js',
|
|
'../dist/shaka-player.ui.js',
|
|
'../dist/demo.compiled.js',
|
|
'../dist/controls.css',
|
|
'../dist/demo.css',
|
|
|
|
// These files are required for the demo to include MDL.
|
|
'../node_modules/material-design-lite/dist/material.min.js',
|
|
|
|
// MDL modal dialogs are enabled by including these:
|
|
'../node_modules/dialog-polyfill/dist/dialog-polyfill.js',
|
|
|
|
// Datalist-like fields are enabled by including these:
|
|
'../node_modules/awesomplete/awesomplete.min.js',
|
|
|
|
// Tooltips are enabled by including these:
|
|
'../node_modules/tippy.js/umd/index.min.js',
|
|
'../node_modules/popper.js/dist/umd/popper.min.js',
|
|
|
|
// PWA compatibility for iOS:
|
|
'../node_modules/pwacompat/pwacompat.min.js',
|
|
].map(resolveRelativeUrl);
|
|
|
|
|
|
/**
|
|
* An array of resources that SHOULD be cached, but which are not critical.
|
|
*
|
|
* The application does not need to read these, so these can use the no-cors
|
|
* flag and be cached as "opaque" resources. This is critical for the cast
|
|
* sender SDK below.
|
|
*
|
|
* @const {!Array.<string>}
|
|
*/
|
|
const OPTIONAL_RESOURCES = [
|
|
// Optional graphics. Without these, the site won't be broken.
|
|
'favicon.ico',
|
|
'https://shaka-player-demo.appspot.com/assets/poster.jpg',
|
|
'https://shaka-player-demo.appspot.com/assets/audioOnly.gif',
|
|
|
|
// The mux.js transmuxing library for MPEG-2 TS and CEA support.
|
|
'../node_modules/mux.js/dist/mux.min.js',
|
|
|
|
// The cast sender SDK.
|
|
'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js',
|
|
|
|
// The IMA ads SDK.
|
|
'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
|
|
'https://imasdk.googleapis.com/js/sdkloader/ima3_dai.js',
|
|
].map(resolveRelativeUrl);
|
|
|
|
|
|
/**
|
|
* An array of URI prefixes. Matching resources SHOULD be cached whenever seen
|
|
* and SHOULD be served from cache first without waiting for updated versions
|
|
* from the network.
|
|
*
|
|
* @const {!Array.<string>}
|
|
*/
|
|
const CACHEABLE_URL_PREFIXES = [
|
|
// Translations should be cached. We don't know which ones the user will
|
|
// want, so use this prefix.
|
|
'locales/',
|
|
'../ui/locales/',
|
|
|
|
// The various app logos should be cached, too. We don't know which ones the
|
|
// browser will load, so use this prefix.
|
|
'app_logo_',
|
|
|
|
// Google Web Fonts should be cached when first seen, without being explicitly
|
|
// listed, and should be preferred from cache for speed.
|
|
'https://fonts.gstatic.com/',
|
|
// Same goes for asset icons.
|
|
'https://storage.googleapis.com/shaka-asset-icons/',
|
|
].map(resolveRelativeUrl);
|
|
|
|
|
|
/**
|
|
* This constant is used to catch local resources which may be missing from the
|
|
* set of cacheable URLs and prefixes above.
|
|
*
|
|
* @const {string}
|
|
*/
|
|
const LOCAL_BASE = resolveRelativeUrl('../');
|
|
|
|
|
|
/**
|
|
* This event fires when the service worker is installed.
|
|
*
|
|
* @param {!InstallEvent} event
|
|
*/
|
|
function onInstall(event) {
|
|
// Activate as soon as installation is complete.
|
|
self.skipWaiting();
|
|
|
|
const preCacheApplication = async () => {
|
|
const cache = await caches.open(CACHE_NAME);
|
|
// Fetching these with addAll fails for CORS-restricted content, so we use
|
|
// fetchAndCache with no-cors mode to work around it.
|
|
|
|
// Optional resources: failure on these will NOT fail the Promise chain.
|
|
// We will also not wait for them to be installed.
|
|
for (const url of OPTIONAL_RESOURCES) {
|
|
const request = new Request(url, {mode: 'no-cors'});
|
|
fetchAndCache(cache, request).catch(() => {});
|
|
}
|
|
|
|
// Critical resources: failure on these will fail the Promise chain.
|
|
// The installation will not be complete until these are all cached.
|
|
const criticalFetches = [];
|
|
for (const url of CRITICAL_RESOURCES) {
|
|
const request = new Request(url, {mode: 'no-cors'});
|
|
criticalFetches.push(fetchAndCache(cache, request));
|
|
}
|
|
return Promise.all(criticalFetches);
|
|
};
|
|
|
|
event.waitUntil(preCacheApplication());
|
|
}
|
|
|
|
/**
|
|
* This event fires when the service worker is activated.
|
|
* This can be after installation or upgrade.
|
|
*
|
|
* @param {!ExtendableEvent} event
|
|
*/
|
|
function onActivate(event) {
|
|
// Delete old caches to save space.
|
|
const dropOldCaches = async () => {
|
|
const cacheNames = await caches.keys();
|
|
|
|
// Return true on all the caches we want to clean up.
|
|
// Note that caches are shared across the origin, so only remove
|
|
// caches we are sure we created.
|
|
const cleanTheseUp = cacheNames.filter((cacheName) =>
|
|
cacheName.startsWith(CACHE_NAME_PREFIX) && cacheName != CACHE_NAME);
|
|
|
|
const cleanUpPromises =
|
|
cleanTheseUp.map((cacheName) => caches.delete(cacheName));
|
|
|
|
await Promise.all(cleanUpPromises);
|
|
};
|
|
|
|
event.waitUntil(Promise.all([
|
|
dropOldCaches(),
|
|
|
|
// makes this the active service worker for all open tabs
|
|
clients.claim(),
|
|
]));
|
|
}
|
|
|
|
/**
|
|
* This event fires when any resource is fetched.
|
|
* This is where we can use the cache to respond offline.
|
|
*
|
|
* @param {!FetchEvent} event
|
|
*/
|
|
function onFetch(event) {
|
|
// For some reason, on a page load, we get hash parameters in the URL for this
|
|
// event. The hash should not be used when we do any of the lookups below.
|
|
const url = event.request.url.split('#')[0];
|
|
|
|
// Make sure this is a request we should be handling in the first place.
|
|
// If it's not, it's important to leave it alone and not call respondWith.
|
|
let useCache = false;
|
|
for (const prefix of CACHEABLE_URL_PREFIXES) {
|
|
if (url.startsWith(prefix)) {
|
|
useCache = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Now we need to check our resource lists. The list of prefixes above won't
|
|
// cover everything that was installed initially, and those things still need
|
|
// to be read from cache. So we check if this request URL matches one of
|
|
// those lists.
|
|
if (!useCache) {
|
|
if (CRITICAL_RESOURCES.includes(url) ||
|
|
OPTIONAL_RESOURCES.includes(url)) {
|
|
useCache = true;
|
|
}
|
|
}
|
|
|
|
if (!useCache && url.startsWith(LOCAL_BASE)) {
|
|
// If we have the correct resource lists above, then all local resources
|
|
// should have useCache set to true by now.
|
|
|
|
// Check to see if this request is coming from a compiled build of the demo,
|
|
// and only log an error if this missing request is from a compiled build.
|
|
|
|
// The check is async, and that's fine because we aren't handling this fetch
|
|
// event anyway.
|
|
(async () => {
|
|
// This client represents the tab that made the request.
|
|
const client = await clients.get(event.clientId);
|
|
// This is the URL of that tab's main document.
|
|
const urlHash = client.url.split('#')[1] || '';
|
|
const hashParameters = urlHash.split(';');
|
|
if (hashParameters.includes('build=compiled')) {
|
|
console.error('Local resource missing!', url);
|
|
}
|
|
})();
|
|
}
|
|
|
|
if (useCache) {
|
|
event.respondWith(fetchCacheableResource(event.request));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch a cacheable resource. Decide whether to request from the network,
|
|
* the cache, or both, and return the appropriate version of the resource.
|
|
*
|
|
* @param {!Request} request
|
|
* @return {!Promise.<!Response>}
|
|
*/
|
|
async function fetchCacheableResource(request) {
|
|
const cache = await caches.open(CACHE_NAME);
|
|
const cachedResponse = await cache.match(request);
|
|
|
|
if (!navigator.onLine) {
|
|
// We are offline, and we know it. Just return the cached response, to
|
|
// avoid a bunch of pointless errors in the JS console that will confuse
|
|
// us developers. If there is no cached response, this will just be a
|
|
// failed request.
|
|
return cachedResponse;
|
|
}
|
|
|
|
if (cachedResponse) {
|
|
// We have it in cache. Try to fetch a live version and update the cache,
|
|
// but limit how long we will wait for the updated version.
|
|
try {
|
|
return await timeout(NETWORK_TIMEOUT, fetchAndCache(cache, request));
|
|
} catch (error) {
|
|
// We tried to fetch a live version, but it either failed or took too
|
|
// long. If it took too long, the fetch and cache operation will continue
|
|
// in the background. In both cases, we should go ahead with a cached
|
|
// version.
|
|
return cachedResponse;
|
|
}
|
|
} else {
|
|
// We should have this in cache, but we don't. Fetch and cache a fresh
|
|
// copy and then return it.
|
|
return fetchAndCache(cache, request);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch the resource from the network, then store this new version in the
|
|
* cache.
|
|
*
|
|
* @param {!Cache} cache
|
|
* @param {!Request} request
|
|
* @return {!Promise.<!Response>}
|
|
*/
|
|
async function fetchAndCache(cache, request) {
|
|
const response = await fetch(request);
|
|
cache.put(request, response.clone());
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Returns a Promise which is resolved only if |asyncProcess| is resolved, and
|
|
* only if it is resolved in less than |seconds| seconds.
|
|
*
|
|
* If the returned Promise is resolved, it returns the same value as
|
|
* |asyncProcess|.
|
|
*
|
|
* If |asyncProcess| fails, the returned Promise is rejected.
|
|
* If |asyncProcess| takes too long, the returned Promise is rejected, but
|
|
* |asyncProcess| is still allowed to complete.
|
|
*
|
|
* @param {number} seconds
|
|
* @param {!Promise.<T>} asyncProcess
|
|
* @return {!Promise.<T>}
|
|
* @template T
|
|
*/
|
|
function timeout(seconds, asyncProcess) {
|
|
return Promise.race([
|
|
asyncProcess,
|
|
new Promise(((_, reject) => {
|
|
setTimeout(reject, seconds * 1000);
|
|
})),
|
|
]);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param {string} relativeUrl A URL which may be relative to this service
|
|
* worker.
|
|
* @return {string} The same URL converted to an absolute URL.
|
|
*/
|
|
function resolveRelativeUrl(relativeUrl) {
|
|
// NOTE: This is the URL of the service worker, not the main HTML document.
|
|
const baseUrl = location.href;
|
|
return (new URL(relativeUrl, baseUrl)).href;
|
|
}
|
|
|
|
self.addEventListener('install', /** @type {function(!Event)} */(onInstall));
|
|
self.addEventListener('activate', /** @type {function(!Event)} */(onActivate));
|
|
self.addEventListener('fetch', /** @type {function(!Event)} */(onFetch));
|