Skip to content
This repository has been archived by the owner on Aug 10, 2022. It is now read-only.

Add WIP Media Session API updates article #4057

Merged
merged 10 commits into from
Jan 31, 2017
374 changes: 374 additions & 0 deletions src/content/en/updates/2017/02/media-session.md
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 #}
Copy link
Member

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.

  • src/content/en/updates/2017/02/media-session.md Uncommon tag (mediasession) found.
  • src/content/en/updates/2017/02/media-session.md Uncommon tag (pause) found.

Copy link
Member Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add chrome57 to the list of tags.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

{# wf_featured_image: /web/updates/images/2017/02/featured.png #}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build Error:

  • src/content/en/updates/2017/02/media-session.md WF Tag wf_featured_image found, but couldn't find image - /web/updates/images/2017/02/featured.png

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I strongly encourage also including a {# wf_featured_snippet: blah blah blah #}.

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 wf_featured_snippet isn't provided.

Copy link
Member Author

Choose a reason for hiding this comment

The 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;"/>
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added it.

</figure>

## Gimme What I Want
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great idea.

Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: should probably say '<audio> element' and '<video> element'.

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As well, or instead?

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added for audio elements. Thanks!

means we have to use the `play()` method of the audio element there. This
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"there" where?

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does [ ] do here?

Copy link
Member Author

Choose a reason for hiding this comment

The 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) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not 'click'?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pointerup is what we promote nowadays.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/function (event)/e =>/ ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to use arrow function for non addEventListener stuff as it creates confusion when it's not needed.

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.

Choose a reason for hiding this comment

The 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 load() is one of them. It will also help the playback be more smooth because the content will be loaded.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Explanation added.


<pre class="prettyprint">
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this particular code sample, I'm using <strong>audio.load()</strong> which is not parsed correctly when using four spaces indentation. If there's a way to get it without prettyprint, I'm all for it ;)


let audio = document.querySelector('audio');

welcomeButton.addEventListener('pointerup', function(event) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Member Author

Choose a reason for hiding this comment

The 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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this section necessary? The audio element has controls, could we just rely on that? Then we can skip all the user interaction and "pointerup" stuff.

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 playButton?

Copy link
Member Author

Choose a reason for hiding this comment

The 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 <audio> elements for instance.
I think developers playing with the Media Session API will be happy to learn these kinds of quirks.

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd ditch the "there"

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: it's not a favicon, is it, just an 'icon image'?

Copy link
Member Author

Choose a reason for hiding this comment

The 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">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alt="TODO"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!

<figcaption>With no Media Session</figcaption>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe MediaSession rather than Media Session (throughout)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still nope ;)

Copy link
Contributor

Choose a reason for hiding this comment

The 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">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alt="TODO"

Copy link
Member Author

Choose a reason for hiding this comment

The 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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…

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe delete 'for instance'.

Copy link
Member Author

Choose a reason for hiding this comment

The 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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any idea why this is setActionHandler rather than addEventListener?

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?

Copy link
Member Author

Choose a reason for hiding this comment

The 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 addEventListener.

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
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The 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 */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: space before =

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, probably only need // rather than /* … */

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 ...'

Copy link
Member Author

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: s/state/stay/

Copy link
Member Author

Choose a reason for hiding this comment

The 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">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alt="TODO"

Copy link
Member Author

Choose a reason for hiding this comment

The 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">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alt="TODO"

Copy link
Member Author

Choose a reason for hiding this comment

The 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`
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to advertise * or should we tell web developers that listing their website's origin is preferable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question. @samdutton WDYT?
I'm favor of promoting * to help 3rd party web apps.


### The Service Worker caching strategy
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete the word 'would'.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

network]" strategy as illustrated by Jake Archibald.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe 'as described by Jake Archibald', with a link.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Cache, falling back to network] is already a link.

Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think 'artwork' is better — 'artworks' sounds like works of art :).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops ;)
Done.

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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: indent 2 spaces

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is fine as is.

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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.'

Copy link
Member Author

Choose a reason for hiding this comment

The 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.'); })
Copy link
Contributor

Choose a reason for hiding this comment

The 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 content-length headers, and make estimates where this header is missing.

Copy link
Member Author

@beaufortfrancois beaufortfrancois Jan 20, 2017

Choose a reason for hiding this comment

The 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 blob.size()...

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); });

Copy link
Member Author

Choose a reason for hiding this comment

The 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); });

Copy link
Member Author

@beaufortfrancois beaufortfrancois Jan 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think 'Implementation notes' would be a better title.

Copy link
Member Author

Choose a reason for hiding this comment

The 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].

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mounirlamouri I'm not sure to understand what you mean.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.

- Muted media files won't trigger Chrome media notifications.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not entirely true. <video autoplay muted> will not. Anything else muted will. Even the former case will if it gets unmuted then re-muted.

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rephrase:
- If no artwork is define and there is a favicon at a desirable size, media notifications will use it.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, the full logic is:
If no artwork is defined, no suitable image is available, or image download failed, media notification will fallback to a favicon at a desirable size if available.

Copy link
Member Author

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link

Choose a reason for hiding this comment

The 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 :)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might also want to check your "artwork" vs "artworks".

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

@beaufortfrancois beaufortfrancois Jan 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mounirlamouri Do you mind sharing an abstract?

Copy link
Member Author

Choose a reason for hiding this comment

The 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].

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact link to the entry maybe?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is available at the very bottom of the page.

Copy link
Contributor

Choose a reason for hiding this comment

The 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].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Broken link

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Binary file added src/content/en/updates/images/2017/02/tldr.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.