├── Anand-Carlsen2014.mp4 ├── README.md ├── index.html ├── mp4box-sw.js ├── mp4box.all.js ├── registration.html └── video-with-html.mp4 /Anand-Carlsen2014.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpac/mp4box-sw/c2e16d6da0cf1f826c4c6e7729d3547a041d8f34/Anand-Carlsen2014.mp4 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mp4box-sw 2 | Service Worker for MP4 files 3 | 4 | This project shows the use of [MP4Box.js](https://github.com/gpac/mp4box.js) as a Service Worker. 5 | 6 | This Service Worker intercepts requests to resources in its scope, checks if the response is an MP4 file. If so, it parses it and determines if there is HTML content in the MP4 file (stored as a primary item in a 'meta' box whose handler is 'html'). If so, the Service Worker forwards first the HTML content to the browser. All further requests made by the browser (because referenced from the HTML page) will be checked by the Service Worker to see if the resource is in the MP4 file. The MP4 file acts as a package for resources associated to the MP4 file. 7 | 8 | # Creating Content 9 | To create MP4 files with web content in them, 10 | - download first the command-line tool [MP4Box](gpac.io/downloads/gpac-nightly-builds/), 11 | - then find your favorite MP4 file, and run the following commands: 12 | 13 | ``` 14 | MP4Box -set-meta html file.mp4 15 | ``` 16 | This will add a `meta` top-level box to your MP4 file, enabling the packaging of additional resouces. The code `html` is used by the service worker to know if the content in the `meta` box is meant for the browser or not. 17 | 18 | ``` 19 | MP4Box -add-item file.html:mime=text/html:id=1 file.mp4 20 | MP4Box -add-item file.css:mime=text/css:id=2 file.mp4 21 | MP4Box -add-item file.js:mime=text/javascript:id=3 file.mp4 22 | MP4Box -add-item image/file.svg:mime=image/svg+xml:id=4 file.mp4 23 | ``` 24 | Each of these commands will add a resource to the MP4 file. Any resource can be added (HTML, JS, CSS, SVG, XML, JSON, ...) and the provided mime type is used by the service worker to serve the file to the browser. The given path the to resource when packaging is preserved in the MP4 file so that you don't have to change path links in your content. 25 | 26 | ``` 27 | MP4Box -set-primary 1 file.mp4 28 | ``` 29 | This last command tells the Service Worker which resource is to be served first to the browser, called the primary item. The parameter is the id provided previously with the `:id=` part. 30 | 31 | You can call all the commands above in a single call: 32 | ``` 33 | MP4Box -set-meta html 34 | -add-item file.html:mime=text/html:id=1 35 | -add-item file.css:mime=text/css:id=2 36 | -add-item file.js:mime=text/javascript:id=3 37 | -add-item image/file.svg:mime=image/svg+xml:id=4 file.mp4 38 | ``` 39 | There are several other commands that you can use (e.g. removing resources), you can check them with: 40 | ``` 41 | MP4Box -h meta 42 | ``` 43 | 44 | You can check that the resulting packaging is correct using: 45 | ``` 46 | MP4Box -info file.mp4 47 | ``` 48 | or using [MP4Box Online File Analyzer](download.tsi.telecom-paristech.fr/gpac/mp4box.js/filereader.html) 49 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MP4 Service Worker 7 | 8 | 15 | 16 | 17 | 18 | 19 |

MP4 Service Worker

20 | 21 |
22 | Fork me on GitHub 23 | 24 |
25 | Star 26 | Fork 27 | Watch 28 | 29 |
30 |
31 | 32 |

33 | This page demonstrates the use of MP4Box.js as a Service Worker. It has been tested with Google Chrome 45 and Firefox Nightly 44.0a1, make sure you load this page using HTTPS! 34 |

35 |

36 | By loading this page in Chrome, you have registered a Service Worker for the scope './'. You can check the registration using chrome://serviceworker-internals/ or about:serviceworkers. 37 |

38 |
39 |

service worker registration:

40 |
41 | 42 |

43 | This Service Worker intercepts requests to resources in its scope, checks if the response is an MP4 file. If so, it parses it and determines if there is HTML content in the MP4 file (stored as a primary item in a 'meta' box whose handler is 'html'). If so, the Service Worker forwards first the HTML content to the browser. All further requests made by the browser (because referenced from the HTML page) will be checked by the Service Worker to see if the resource is in the MP4 file. The MP4 file acts as a package for resources associated to the MP4 file. 44 |

45 | 46 |

Example videos:

47 | 51 | 52 | 65 | 66 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /mp4box-sw.js: -------------------------------------------------------------------------------- 1 | // Note: in serviceworker environnement the global object is called self (not window) 2 | 3 | importScripts('mp4box.all.js'); 4 | 5 | // Set the callback for the install step 6 | self.addEventListener('install', function(event) { 7 | console.log("[mp4box-sw] Installed"); 8 | }); 9 | 10 | self.addEventListener('activate', function(event) { 11 | console.log("[mp4box-sw] activated"); 12 | }); 13 | 14 | function createResponse(type, data) { 15 | console.log("[mp4box-sw] Creating response of type "+type); 16 | var init = { status: 200 }; 17 | var response = new Response(data,init); 18 | // the response already has a content-type = text/plain, we need to remove it and replace it with svg 19 | response.headers.delete('Content-Type'); 20 | response.headers.append('Content-Type', type); 21 | var r = response.clone(); 22 | console.log("[mp4box-sw] Response: ", response); 23 | if (response.headers.forEach) { 24 | response.headers.forEach(function(value, key) { 25 | console.log("[mp4box-sw] "+key +": "+value); 26 | }); 27 | } 28 | return r; 29 | } 30 | 31 | /* Cache of MP4Box instances by URL */ 32 | var urlToMP4Box = []; 33 | 34 | // Set the callback for when the pages make URL requests 35 | self.addEventListener('fetch', function(event) { 36 | // Need to clone the request because we are consuming it when reading headers 37 | var req = event.request.clone(); 38 | console.log("[mp4box-sw] Fetch request received for URL:", req.url); 39 | console.log("[mp4box-sw] Request object:", req); 40 | if (req.headers.forEach) { 41 | req.headers.forEach(function(value, key) { 42 | console.log("[mp4box-sw] "+key +": "+value); 43 | }); 44 | } 45 | 46 | 47 | /* Closure-bound variable holding the MP4Box object for use in Promise onFullfilled callbacks */ 48 | var mp4box; 49 | var builtResponse = null; 50 | 51 | function fetchPromiseSuccessCallback(response) { 52 | console.log("[mp4box-sw] Received response for URL:", response.url); 53 | console.log("[mp4box-sw] Response object:", response); 54 | if (response.headers.forEach) { 55 | response.headers.forEach(function(value, key) { 56 | console.log("[mp4box-sw] "+ key +": "+value); 57 | }); 58 | } 59 | 60 | /* if the response is an MP4 file, get an ArrayBuffer of it and inspect it 61 | otherwise simply return the response 62 | */ 63 | var contentType = response.headers.get("Content-Type"); 64 | if (contentType === "video/mp4") { 65 | console.log("[mp4box-sw] The resource is an MP4 file"); 66 | console.log("[mp4box-sw] Creating MP4Box instance for "+ req.url); 67 | mp4box = new MP4Box(true, false); 68 | urlToMP4Box[req.url] = mp4box; 69 | mp4box.originalResponse = response.clone(); 70 | return response.arrayBuffer(); 71 | } else { 72 | return response; 73 | } 74 | } 75 | 76 | function arrayBufferPromiseSuccessCallback(arrayBufferOrResponse) { 77 | if (arrayBufferOrResponse instanceof ArrayBuffer) { 78 | console.log("[mp4box-sw] Processing ArrayBuffer"); 79 | var arrayBuffer = arrayBufferOrResponse; 80 | var ok = false; 81 | arrayBuffer.fileStart = 0; 82 | mp4box.appendBuffer(arrayBuffer); 83 | var metaHandler = mp4box.inputIsoFile.getMetaHandler(); 84 | if (metaHandler === 'html') { 85 | var item = mp4box.inputIsoFile.getPrimaryItem(); 86 | console.log("[mp4box-sw] Found primary item in MP4 of type "+item.content_type); 87 | arrayBufferResponse = createResponse(item.content_type, item.data.buffer); 88 | ok = true; 89 | } 90 | if (!ok) { 91 | console.warn("[mp4box-sw] Could not find useful resource, sending the original response"); 92 | arrayBufferResponse = mp4box.originalResponse.clone(); 93 | } 94 | return arrayBufferResponse; 95 | } else { 96 | console.log("[mp4box-sw] Forwarding response"); 97 | return arrayBufferOrResponse; 98 | } 99 | } 100 | 101 | var item_id; 102 | var item_name; 103 | mp4box = urlToMP4Box[req.referrer]; 104 | var checkInMP4 = false; 105 | if (mp4box) { 106 | console.log("[mp4box-sw] There is an MP4Box instance for the referrer of this resource"); 107 | if (req.url == req.referrer) { 108 | console.log("[mp4box-sw] The MP4Box instance was created for this resource, sending the original response"); 109 | return mp4box.originalResponse.clone(); 110 | } else { 111 | /* The requested URL is different from the MP4 referer */ 112 | console.log("[mp4box-sw] Check if this MP4Box has an item for this resource"); 113 | var i; 114 | for (i = 0; i < req.url.length; i++) { 115 | if (req.url.charAt(i) !== req.referrer.charAt(i)) { 116 | break; 117 | } 118 | } 119 | item_name = req.url.substring(i); 120 | item_id = mp4box.inputIsoFile.hasItem(item_name); 121 | if (item_id == -1) { 122 | console.warn("[mp4box-sw] Could not find item "+item_name+" in MP4"); 123 | /* continue to issue the fetch for the resource */ 124 | } else { 125 | builtResponse = createResponseFromItemId(mp4box, item_id, item_name); 126 | } 127 | } 128 | } 129 | 130 | if (builtResponse) { 131 | event.respondWith(builtResponse); 132 | } else { 133 | console.log("[mp4box-sw] Fetching resource"); 134 | event.respondWith(fetch(event.request).then(fetchPromiseSuccessCallback).then(arrayBufferPromiseSuccessCallback)); 135 | } 136 | }); 137 | 138 | function createResponseFromItemId(mp4box, item_id, item_name) { 139 | var item = mp4box.inputIsoFile.getItem(item_id); 140 | console.log("[mp4box-sw] Found item "+item_id+" in MP4 of type "+item.content_type+" for item of name: "+item_name); 141 | return createResponse(item.content_type, item.data.buffer); 142 | } 143 | -------------------------------------------------------------------------------- /registration.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /video-with-html.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpac/mp4box-sw/c2e16d6da0cf1f826c4c6e7729d3547a041d8f34/video-with-html.mp4 --------------------------------------------------------------------------------