-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Add WIP Media Session API updates article #4057
Changes from 1 commit
6f74628
bd09396
dce240d
8fc3720
eaa390d
94b91a5
6105127
ec154d8
de6ab6f
db3fe29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,374 @@ | ||
project_path: /web/_project.yaml | ||
book_path: /web/updates/_book.yaml | ||
description: Customize media metadata (artist, album, title, artwork) and respond to media controls (play, pause, etc.) on the Web. | ||
|
||
{# wf_updated_on: 2017-02-06 #} | ||
{# wf_published_on: 2017-02-06 #} | ||
{# wf_tags: news,mediasession,play,pause #} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also add chrome57 to the list of tags. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
{# wf_featured_image: /web/updates/images/2017/02/featured.png #} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Build Error:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I strongly encourage also including a The featured snipped is used on the index pages as the primary description. It CAN include HTML and is your "hook" to get someone to read your article. The description at the top of the document is used as the meta description and is only used as the primary description if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noted. Thanks! |
||
# Customize Media Metadata & Controls {: .page-title } | ||
|
||
{% include "web/_shared/contributors/beaufortfrancois.html" %} | ||
|
||
With the brand new [Media Session API], the ability to **set the artist, album, | ||
title, and artwork** of the media (audio or video) you're playing in your web | ||
app is now possible in Chrome 57 (beta in January 2017, stable in March 2017). | ||
It also allows you to **respond to media control events** (play, pause, etc.) | ||
which may come from notifications or media keys. | ||
|
||
<figure> | ||
<img src="/web/updates/images/2017/02/tldr.png" | ||
alt="Media Session TL;DR;"/> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a creative commons picture of Astley on wikipedia https://en.wikipedia.org/wiki/File:Rick_Astley_Tivoli_Gardens.jpg There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added it. |
||
</figure> | ||
|
||
## Gimme What I Want | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All titles level 2 and lower should be sentence case. Only the article title should be title case. Please change throughout. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
You already know all about the Media Session API and are simply coming back to | ||
copy and paste with no shame some boilerplate code? So here it is. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a great idea. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! |
||
|
||
if ('mediaSession' in navigator) { | ||
|
||
navigator.mediaSession.metadata = new MediaMetadata({ | ||
title: 'Never Gonna Give You Up', | ||
album: 'Whenever You Need Somebody', | ||
artist: 'Rick Astley', | ||
artwork: [ | ||
{ src: 'https://dummyimage.com/96x96', sizes: '96x96', type: 'image/png' }, | ||
{ src: 'https://dummyimage.com/128x128', sizes: '128x128', type: 'image/png' }, | ||
{ src: 'https://dummyimage.com/192x192', sizes: '192x192', type: 'image/png' }, | ||
{ src: 'https://dummyimage.com/256x256', sizes: '256x256', type: 'image/png' }, | ||
{ src: 'https://dummyimage.com/384x384', sizes: '384x384', type: 'image/png' }, | ||
{ src: 'https://dummyimage.com/512x512', sizes: '512x512', type: 'image/png' }, | ||
] | ||
}); | ||
|
||
navigator.mediaSession.setActionHandler('play', function() {}); | ||
navigator.mediaSession.setActionHandler('pause', function() {}); | ||
navigator.mediaSession.setActionHandler('seekbackward', function() {}); | ||
navigator.mediaSession.setActionHandler('seekforward', function() {}); | ||
navigator.mediaSession.setActionHandler('previoustrack', function() {}); | ||
navigator.mediaSession.setActionHandler('nexttrack', function() {}); | ||
} | ||
|
||
## Get into the code | ||
|
||
### Let's play 🎷 | ||
|
||
Add a simple `<audio>` tag to your web page and assign several media sources so | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: should probably say ' There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! Done. |
||
that the browser can choose which one works best. | ||
|
||
<audio controls> | ||
<source src="audio.mp3" type="audio/mp3"/> | ||
<source src="audio.ogg" type="audio/ogg"/> | ||
</audio> | ||
|
||
Note: I could have used a `<video>` tag as well there to showcase the Media | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As well, or instead? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
Session API. | ||
|
||
As you may know, `autoplay` is disabled on Chrome for Android which | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It work for some types of media doesn't it? Dunno if that's worth clarifying. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added |
||
means we have to use the `play()` method of the audio element there. This | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "there" where? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nowhere ;) |
||
method must be triggered by [a user gesture] such as a touch or a mouse click. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does [ ] do here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It automagically adds URLs when they're defined at the very bottom of the file. |
||
We're talking about listening to | ||
[`pointerup`](/web/updates/2016/10/pointer-events), `click`, and `touchend` | ||
events. In other words, the user must click on a button before your web app can | ||
actually make some noise. | ||
|
||
playButton.addEventListener('pointerup', function(event) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not 'click'? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/function (event)/e =>/ ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tend to use arrow function for non |
||
let audio = document.querySelector('audio'); | ||
|
||
// User interacted with the page. Let's play audio... | ||
audio.play() | ||
.then(_ => { /* Set up Media Session... */ }) | ||
.catch(error => { console.log(error) }); | ||
}); | ||
|
||
If you don't want to play audio right after the first interaction, I recommend | ||
you use the `load()` method of the audio element. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can explain that the reason is that Blink will keep track of whether the user interacted with the element in some ways and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! Explanation added. |
||
|
||
<pre class="prettyprint"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of wrapping in pretty print, the preferred method is to indent four spaces. I think this works, but there may also be some weirdness around it if I remember right. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this particular code sample, I'm using |
||
|
||
let audio = document.querySelector('audio'); | ||
|
||
welcomeButton.addEventListener('pointerup', function(event) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same answer. |
||
// User interacted with the page. Let's load audio... | ||
<strong>audio.load()</strong> | ||
.then(_ => { /* Show play button for instance... */ }) | ||
.catch(error => { console.log(error) }); | ||
}); | ||
|
||
// Later... | ||
playButton.addEventListener('pointerup', function(event) { | ||
<strong>audio.play()</strong> | ||
.then(_ => { /* Set up Media Session... */ }) | ||
.catch(error => { console.log(error) }); | ||
}); | ||
|
||
</pre> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this section necessary? The audio element has That stuff's important, but feels like it could be a different article. In fact, does the code in this article fail if the user interacts with the audio element controls rather than There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The play/load trick is not documented anywhere at all for the moment. This has beaten me when playing with 2 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added a note. |
||
|
||
### Customize the notification | ||
|
||
When your web app is playing audio, you can already see a media notification | ||
sitting in the notification tray. On Android, Chrome does its best there to show | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd ditch the "there" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
appropriate information by using the document's title and the largest favicon | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: it's not a favicon, is it, just an 'icon image'? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right! Thanks |
||
it can find. | ||
|
||
<div class="clearfix"></div> | ||
<div class="attempt-left"> | ||
<figure> | ||
<img src="https://placehold.it/350x350?text=With no Media Session" alt="TODO"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed! |
||
<figcaption>With no Media Session</figcaption> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe MediaSession rather than Media Session (throughout)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still nope ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. np :) |
||
</figure> | ||
</div> | ||
<div class="attempt-right"> | ||
<figure> | ||
<img src="https://placehold.it/350x350?text=With Media Session" alt="TODO"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
<figcaption>With Media Session</figcaption> | ||
</figure> | ||
</div> | ||
|
||
Let's see how to customize this media notification by setting some media | ||
session metadata such as the title, album, artist, and artwork with the Media | ||
Session API. | ||
|
||
if ('mediaSession' in navigator) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's kinda covered in earlier code, but is it worth mentioning when this code should execute. Maybe just a comment like: // When the audio starts playing… There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea! |
||
|
||
navigator.mediaSession.metadata = new MediaMetadata({ | ||
title: 'Never Gonna Give You Up', | ||
album: 'Whenever You Need Somebody', | ||
artist: 'Rick Astley', | ||
artwork: [ | ||
{ src: 'https://dummyimage.com/96x96', sizes: '96x96', type: 'image/png' }, | ||
{ src: 'https://dummyimage.com/128x128', sizes: '128x128', type: 'image/png' }, | ||
{ src: 'https://dummyimage.com/192x192', sizes: '192x192', type: 'image/png' }, | ||
{ src: 'https://dummyimage.com/256x256', sizes: '256x256', type: 'image/png' }, | ||
{ src: 'https://dummyimage.com/384x384', sizes: '384x384', type: 'image/png' }, | ||
{ src: 'https://dummyimage.com/512x512', sizes: '512x512', type: 'image/png' }, | ||
] | ||
}); | ||
} | ||
|
||
If your web app provides a playlist for instance, you may want to allow the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe delete 'for instance'. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deleted ;) |
||
user to navigate through your playlist directly from the media notification | ||
with some "Previous Track" and "Next Track" icons. | ||
|
||
let audio = document.createElement('audio'); | ||
|
||
let playlist = ['audio1.mp3', 'audio2.mp3', 'audio3.mp3']; | ||
let index = 0; | ||
|
||
navigator.mediaSession.setActionHandler('previoustrack', function() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any idea why this is What if the user starts playing something without a playlist? Do I need to remove this handler somehow? If I set the metadata to something else, do the handlers persist? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See w3c/mediasession#148 (comment) for the all story about why not simply using I've added a section to answer your questions. |
||
// User clicked "Previous Track" media notification icon. | ||
index = (index - 1 + playlist.length) % playlist.length; | ||
playAudio(); | ||
}); | ||
|
||
navigator.mediaSession.setActionHandler('nexttrack', function() { | ||
// User clicked "Next Track" media notification icon. | ||
index = (index + 1) % playlist.length; | ||
playAudio(); | ||
}); | ||
|
||
function playAudio() { | ||
audio.src = playlist[index]; | ||
audio.play() | ||
.then(_ => { /* Set up Media Session... */ }) | ||
.catch(error => { console.log(error); }); | ||
} | ||
|
||
playButton.addEventListener('pointerup', function(event) { | ||
playAudio(); | ||
}); | ||
|
||
The Media Session API allows you as well to show "Seek Backward" and "Seek | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Remove "as well" It's kinda suggested here, but is it worth being explicit that these buttons won't be shown unless you add a handler? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
Forward" media notification icons if you want to control the amount of time | ||
skipped. | ||
|
||
let skipTime= 10; /* Time to skip in seconds */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: space before There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, probably only need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. Done. |
||
|
||
navigator.mediaSession.setActionHandler('seekbackward', function() { | ||
// User clicked "Seek Backward" media notification icon. | ||
audio.currentTime = Math.max(audio.currentTime - skipTime, 0); | ||
}); | ||
|
||
navigator.mediaSession.setActionHandler('seekforward', function() { | ||
// User clicked "Seek Forward" media notification icon. | ||
audio.currentTime = Math.min(audio.currentTime + skipTime, audio.duration); | ||
}); | ||
|
||
Note: As the browser may consider the web app is not be playing when media | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 'The browser may consider that the web app is not playing media when files are seeking or loading. You can override ...' There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. |
||
files are seeking or loading, you can override this behaviour by setting | ||
`navigator.mediaSession.playbackState` to `"playing"` or `"paused"`. This | ||
comes handy when you want to make sure your web app UI state is synced with the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: s/state/stay/ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! |
||
media notification controls. | ||
|
||
The cool thing about the Media Session API is that the notification tray is not | ||
the only place where media metadata and controls are visible. The media | ||
notification is synced automagically to any paired wearable device. And it also | ||
shows up on lock screens. | ||
|
||
<div class="clearfix"></div> | ||
<div class="attempt-left"> | ||
<figure> | ||
<img src="https://placehold.it/350x350?text=Lock screen" alt="TODO"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
<figcaption>Lockscreen</figcaption> | ||
</figure> | ||
</div> | ||
<div class="attempt-right"> | ||
<figure> | ||
<img src="https://placehold.it/350x350?text=Wear notification" alt="TODO"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
<figcaption>Wear</figcaption> | ||
</figure> | ||
</div> | ||
<div class="clearfix"></div> | ||
|
||
## Make it play nice offline | ||
|
||
I know what you're thinking now... *[Service Worker] to the rescue!* | ||
|
||
True but first and foremost, you want to make sure **all items in this | ||
checklist are checked**: | ||
|
||
1. All media and artwork files are served with the appropriate `Cache-Control` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Items that aren't required to be in a particular order should be bulleted, not numbered. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
HTTP header. This will allow the browser to cache and reuse previously fetched | ||
resources. See the [Caching checklist]. | ||
2. Make sure all media and artwork files are served with the | ||
`Allow-Control-Allow-Origin: *` HTTP header. This will allow third-party web | ||
apps to fetch and consume HTTP responses from your web server. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to advertise There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a good question. @samdutton WDYT? |
||
|
||
### The Service Worker caching strategy | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The term service worker is lower case unless referring to the 'Service Worker API'. See my rules for media source, above. Please check throughout. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
Regarding media files, I would recommend a simple "[Cache, falling back to | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete the word 'would'. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
network]" strategy as illustrated by Jake Archibald. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe 'as described by Jake Archibald', with a link. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah — thanks. |
||
|
||
For artworks though, I'd be a little bit more specific and choose the approach | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: I think 'artwork' is better — 'artworks' sounds like works of art :). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops ;) |
||
below: | ||
|
||
- `If` artwork is already in cache, serve it from the cache | ||
- `Else` fetch artwork from network | ||
- `If` fetch is successful, add network artwork to the cache and serve it | ||
- `Else` serve fallback artwork from the cache | ||
|
||
That way, media notifications would always have a nice artwork icon even when | ||
browser can't fetch it. Here's how you could implement this for instance: | ||
|
||
const FALLBACK_ARTWORK_URL = 'fallbackArtwork.png'; | ||
|
||
self.addEventListener('install', event => { | ||
self.skipWaiting(); | ||
event.waitUntil(initArtworksCache()); | ||
}); | ||
|
||
function initArtworksCache() { | ||
caches.open('artworks-cache-v1') | ||
.then(cache => cache.add(FALLBACK_ARTWORK_URL)); | ||
} | ||
|
||
self.addEventListener('fetch', event => { | ||
if (/artwork-[0-9]+\.png$/.test(event.request.url)) { | ||
event.respondWith(handleFetchArtwork(event.request)); | ||
} | ||
}); | ||
|
||
function handleFetchArtwork(request) { | ||
// Return cache request if it's in the cache already, otherwise fetch | ||
// network artwork. | ||
return getCacheArtwork(request) | ||
.then(cacheResponse => cacheResponse || getNetworkArtwork(request)); | ||
} | ||
|
||
function getCacheArtwork(request) { | ||
return caches.open('artworks-cache-v1') | ||
.then(cache => cache.match(request)); | ||
} | ||
|
||
function getNetworkArtwork(request) { | ||
// Fetch network artwork. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: indent 2 spaces There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, this is fine as is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like this is a 3-space indent and the other functions have a 2-space indent. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
return fetch(request) | ||
.then(networkResponse => { | ||
if (networkResponse.status !== 200) { | ||
return Promise.reject('Network artwork response is not valid'); | ||
} | ||
// Add artwork to the cache for later use and return network response. | ||
addArtworkToCache(request, networkResponse.clone()) | ||
return networkResponse; | ||
}) | ||
.catch(error => { | ||
// Return cached fallback artwork. | ||
return getCacheArtwork(new Request(FALLBACK_ARTWORK_URL)) | ||
}); | ||
} | ||
|
||
function addArtworkToCache(request, response) { | ||
return caches.open('artworks-cache-v1') | ||
.then(cache => cache.put(request, response)); | ||
} | ||
|
||
Caution: If you want your service worker to be able to intercept artwork | ||
network requests on [the very first page load], you may want to call | ||
`clients.claim()` within your service worker once it's activated. | ||
|
||
### Let user control cache | ||
|
||
As user will consume content from your web app, media files and artworks may | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 'As the user consumes content from your web app, media files and artwork may take a lot of space on their device.' There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! |
||
take a lot of space on user's device. It is **your responsibility to show how | ||
much cache is used and give user the ability to clear it**. Thankfully for us, | ||
doing so is pretty easy with the [Cache API]. | ||
|
||
// Here's how I'd compute how much cache is used by artworks... | ||
caches.open('artworks-cache-v1') | ||
.then(cache => cache.matchAll()) | ||
.then(responses => Promise.all(responses.map(r => r.blob()))) | ||
.then(blobs => blobs.map(blob => blob.size).reduce((acc, cur) => acc + cur, 0)) | ||
.then(cacheSize => { console.log('Artworks cache is ' + cacheSize + ' bytes.'); }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is going to bring all the artworks into memory. Alternatively you could add up the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here's the code below I've used to add up content-length headers. It gives me different results than using caches.open('artworks-cache-v1')
.then(cache => cache.matchAll())
.then(responses => {
let cacheSize = 0;
let queue = Promise.resolve();
responses.forEach(response => {
let responseSize = response.headers.get('content-length');
if (responseSize) {
queue = queue.then(_ => cacheSize += Number(responseSize));
} else {
queue = queue.then(_ => response.blob())
.then(blob => cacheSize += blob.size);
}
});
return queue.then(_ => {
console.log('Artworks cache is ' + cacheSize + ' Bytes.');
});
})
.catch(error => { console.log(error); }); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jakearchibald What do you think of using this code? caches.open('artworks-cache-v1')
.then(cache => cache.matchAll())
.then(responses => {
let cacheSize = 0;
let queue = Promise.resolve();
responses.forEach(response => {
queue = queue.then(_ => response.blob())
.then(blob => { cacheSize += blob.size; blob.close(); });
});
return queue.then(_ => {
console.log('Artworks cache is ' + cacheSize + ' Bytes.');
});
})
.catch(error => { console.log(error); }); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nvm https://github.com/google/WebFundamentals/pull/4057/files/dce240d58480a124e1817e80ecefdaa3612f68b9#r97076285 is "correct". I'll use this! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And here's w3c/ServiceWorker#1059 |
||
.catch(error => { console.log(error); }); | ||
|
||
// And here's how to delete some artworks... | ||
const artworksToDelete = ['artwork1.png', 'artwork2.png', 'artwork3.png']; | ||
|
||
caches.open('artworks-cache-v1') | ||
.then(cache => Promise.all(artworksToDelete.map(artwork => cache.delete(artwork)))) | ||
.catch(error => { console.log(error); }); | ||
|
||
## Implementation nits | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think 'Implementation notes' would be a better title. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
- Chrome Media notifications show only if media files duration is [at least 5 seconds]. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In case of, we don't take full audio focus if shorter than 5 seconds. We take the intermittent one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mounirlamouri I'm not sure to understand what you mean. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it. |
||
- Muted media files won't trigger Chrome media notifications. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's not entirely true. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that case, I'll remove this nit. |
||
- Media notifications use the favicon if no artworks are defined. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rephrase: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI, the full logic is: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @xxyzzzq If image download failed, it doesn't always fallback to favicon right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We fallback to favicon in this case. If not then it should be a bug :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You might also want to check your "artwork" vs "artworks". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh you're right! Sorry for that ;) |
||
- Notification artwork size in Chrome for Android is `512x512`. For | ||
[low-end devices], it is `256x256`. | ||
- Dismiss media notifications with `audio.src = ''`. | ||
- Hook up an `<audio>` element as the input source to the [Web Audio API] makes | ||
it work with the Media Session API. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should you have a short paragraph/section about audio focus? This might sound weird but if we were to explain that Media Session API only applies when Chrome will take audio focus could help some readers understand more deeply what's happening. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mounirlamouri Do you mind sharing an abstract? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
## Support | ||
|
||
At the time of writing, Chrome for Android is the only platform that supports | ||
the Media Session API. More up-to-date information on browser implementation | ||
status can be found on [chromestatus.com]. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exact link to the entry maybe? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is available at the very bottom of the page. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree this should be a link to the exact entry. |
||
|
||
## Samples & Demos | ||
|
||
Check out our official [Media Session Chrome samples]. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Broken link There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The media session sample has not been reviewed yet. |
||
|
||
## Resources | ||
|
||
- Chrome Feature Status: [https://www.chromestatus.com/feature/5639924124483584](https://www.chromestatus.com/feature/5639924124483584) | ||
- Chrome Implementation Bugs: [https://crbug.com/?q=component:Internals>Media>Session](https://crbug.com/?q=component:Internals>Media>Session) | ||
- Media Session Spec: [https://wicg.github.io/mediasession](https://wicg.github.io/mediasession) | ||
- Spec Issues: [https://github.com/WICG/mediasession/issues](https://github.com/WICG/mediasession/issues) | ||
|
||
{% include "comment-widget.html" %} | ||
|
||
[Media Session API]: https://wicg.github.io/mediasession/ | ||
[a user gesture]: https://html.spec.whatwg.org/multipage/interaction.html#activation | ||
[low-end devices]: TODO | ||
[Service Worker]: /web/fundamentals/instant-and-offline/service-worker/lifecycle | ||
[Caching checklist]: /web/fundamentals/performance/optimizing-content-efficiency/http-caching | ||
[Cache, falling back to network]: https://jakearchibald.com/2014/offline-cookbook/#cache-falling-back-to-network | ||
[the first page load]: /web/fundamentals/instant-and-offline/service-worker/lifecycle#clientsclaim | ||
[at least 5 seconds]: https://chromium.googlesource.com/chromium/src/+/5d8eab739eb23c4fd27ba6a18b0e1afc15182321/media/base/media_content_type.cc#10 | ||
[Cache API]: /web/fundamentals/instant-and-offline/web-storage/offline-for-pwa | ||
[Media Session Chrome Samples]: https://googlechrome.github.io/samples/media-session | ||
[Web Audio API]: /web/updates/2012/02/HTML5-audio-and-the-Web-Audio-API-are-BFFs | ||
[chromestatus.com]: https://www.chromestatus.com/feature/5639924124483584?embed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Build Errors for uncommon tags. You can add these tags in
src/data/commonTags.json
. Be sure to review the file first to make sure you're not creating similar tags.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know this file! Thank you ;)
Fixed.