미디어 알림 맞춤설정 및 재생목록 처리

François Beaufort
François Beaufort

새로운 Media Session API를 사용하면 이제 웹 앱에서 재생 중인 미디어의 메타데이터를 제공하여 미디어 알림을 맞춤설정할 수 있습니다. 또한 알림 또는 미디어 키에서 발생할 수 있는 탐색 또는 추적 변경과 같은 미디어 관련 이벤트를 처리할 수 있습니다. 관심이 있으시면 공식 미디어 세션 샘플을 사용해 보세요.

Media Session API는 Chrome 57 (2017년 2월 베타 버전, 2017년 3월 안정화)에서 지원됩니다.

미디어 세션 요약
사진 : 마이클 알로-닐슨 / CC BY 2.0

원하는 것을 보여 주세요

Media Session API에 대해 이미 알고 있으며 상용구 코드 없이 복사하여 붙여넣기만 하면 됩니다. 여기 있습니다.

if ('mediaSession' in navigator) {

    navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    artist: 'Rick Astley',
    album: 'Whenever You Need Somebody',
    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() {});
}

코드 살펴보기

게임하자 🎷

간단한 <audio> 요소를 웹페이지에 추가하고 여러 미디어 소스를 할당하여 브라우저에서 가장 적합한 소스를 선택할 수 있도록 합니다.

<audio controls>
    <source src="audio.mp3" type="audio/mp3"/>
    <source src="audio.ogg" type="audio/ogg"/>
</audio>

아시다시피 Android용 Chrome에서는 오디오 요소에 autoplay가 사용 중지되어 있으므로 오디오 요소의 play() 메서드를 사용해야 합니다. 이 메서드는 터치 또는 마우스 클릭과 같은 사용자 동작으로 트리거되어야 합니다. 즉, pointerup, click, touchend 이벤트를 수신 대기합니다. 즉, 웹 앱에서 실제로 노이즈가 발생할 수 있으려면 사용자가 버튼을 클릭해야 합니다.

playButton.addEventListener('pointerup', function(event) {
    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) });
});

첫 번째 상호작용 직후에 오디오를 재생하지 않으려면 오디오 요소의 load() 메서드를 사용하는 것이 좋습니다. 이는 브라우저에서 사용자가 요소와 상호작용했는지 추적하는 한 가지 방법입니다. 콘텐츠가 이미 로드되어 있으므로 원활한 재생에 도움이 될 수도 있습니다.

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

welcomeButton.addEventListener('pointerup', function(event) {
  // 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) });
});

알림 맞춤설정

웹 앱에서 오디오를 재생하고 있다면 알림 트레이에 미디어 알림이 이미 표시되어 있습니다. Android에서 Chrome은 문서의 제목과 찾을 수 있는 가장 큰 아이콘 이미지를 사용하여 적절한 정보를 표시하기 위해 최선을 다하고 있습니다.

미디어 세션 제외
미디어 세션 없음
미디어 세션 포함
미디어 세션 포함

메타데이터 설정

Media Session API로 제목, 아티스트, 앨범 이름, 아트워크와 같은 일부 미디어 세션 메타데이터를 설정하여 이 미디어 알림을 맞춤설정하는 방법을 알아보겠습니다.

// When audio starts playing...
if ('mediaSession' in navigator) {

    navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    artist: 'Rick Astley',
    album: 'Whenever You Need Somebody',
    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.metadata가 사용됩니다. 따라서 미디어 알림에 항상 관련 정보를 표시하도록 업데이트해야 합니다.

이전 트랙 / 다음 트랙

웹 앱에서 재생목록을 제공한다면 사용자가 '이전 트랙' 및 '다음 트랙' 아이콘을 사용하여 미디어 알림에서 직접 재생목록을 탐색하도록 할 수 있습니다.

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

let playlist = ['audio1.mp3', 'audio2.mp3', 'audio3.mp3'];
let index = 0;

navigator.mediaSession.setActionHandler('previoustrack', function() {
    // 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();
});

미디어 작업 핸들러는 계속 유지됩니다. 이벤트를 처리하면 브라우저가 기본 동작 실행을 중지하고 이를 웹 앱에서 미디어 작업을 지원한다는 신호로 사용한다는 점을 제외하면 이벤트 리스너 패턴과 매우 유사합니다. 따라서 적절한 작업 핸들러를 설정하지 않으면 미디어 작업 컨트롤이 표시되지 않습니다.

미디어 작업 핸들러 설정 해제는 null에 할당하는 것만큼 쉽습니다.

뒤로 탐색 / 앞으로 탐색

Media Session API를 사용하면 건너뛰는 시간을 제어하려는 경우 '뒤로 탐색' 및 '앞으로 탐색' 미디어 알림 아이콘을 표시할 수 있습니다.

let skipTime = 10; // Time to skip in seconds

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

재생 / 일시중지

'재생/일시중지' 아이콘은 미디어 알림에 항상 표시되며 관련 이벤트는 브라우저에서 자동으로 처리됩니다. 어떤 이유로든 기본 동작이 작동하지 않더라도 미디어 이벤트를 '재생' 및 '일시중지'할 수 있습니다.

navigator.mediaSession.setActionHandler('play', function() {
    // User clicked "Play" media notification icon.
    // Do something more than just playing current audio...
});

navigator.mediaSession.setActionHandler('pause', function() {
    // User clicked "Pause" media notification icon.
    // Do something more than just pausing current audio...
});

모든 위치에서 알림

Media Session API의 멋진 점은 알림 표시줄이 미디어 메타데이터와 컨트롤이 표시되는 유일한 위치가 아니라는 점입니다. 미디어 알림은 페어링된 웨어러블 기기에 자동으로 동기화됩니다. 잠금 화면에도 표시됩니다.

잠금 화면
잠금 화면 - 사진 작성자: 마이클 알로-닐슨/ CC BY 2.0
Wear 알림
Wear 알림

오프라인으로 즐기기

무슨 생각을 하고 있는지 알겠어요. 서비스 워커가 구했습니다!

true이지만 무엇보다도 이 체크리스트의 모든 항목을 확인해야 합니다.

  • 모든 미디어 및 아트워크 파일은 적절한 Cache-Control HTTP 헤더와 함께 제공됩니다. 이렇게 하면 브라우저가 이전에 가져온 리소스를 캐시하고 재사용할 수 있습니다. 캐싱 체크리스트를 참조하세요.
  • 모든 미디어 및 아트워크 파일이 Allow-Control-Allow-Origin: * HTTP 헤더로 제공되었는지 확인합니다. 이렇게 하면 타사 웹 앱이 웹 서버의 HTTP 응답을 가져와 사용할 수 있습니다.

서비스 워커 캐싱 전략

미디어 파일과 관련하여 제이크 아치볼드의 설명과 같이 간단한 '캐시, 네트워크로 복귀' 전략을 사용하는 것이 좋습니다.

하지만 아트워크의 경우 조금 더 구체적으로 아래와 같은 접근 방식을 선택합니다.

  • 아트워크 If개가 이미 캐시에 있습니다. 캐시에서 제공하세요.
  • Else는 네트워크에서 아트워크를 가져옵니다.
    • If 가져오기에 성공했습니다. 네트워크 아트워크를 캐시에 추가하고 제공하세요.
    • Else는 캐시의 대체 아트워크를 제공합니다.

이렇게 하면 브라우저에서 알림을 가져올 수 없는 경우에도 미디어 알림에 항상 멋진 아트워크 아이콘이 표시됩니다. 구현하는 방법은 다음과 같습니다.

const FALLBACK_ARTWORK_URL = 'fallbackArtwork.png';

addEventListener('install', event => {
    self.skipWaiting();
    event.waitUntil(initArtworkCache());
});

function initArtworkCache() {
    caches.open('artwork-cache-v1')
    .then(cache => cache.add(FALLBACK_ARTWORK_URL));
}

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('artwork-cache-v1')
    .then(cache => cache.match(request));
}

function getNetworkArtwork(request) {
    // Fetch network artwork.
    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('artwork-cache-v1')
    .then(cache => cache.put(request, response));
}

사용자가 캐시를 제어하도록 허용

사용자가 웹 앱의 콘텐츠를 소비함에 따라 미디어 및 아트워크 파일은 기기에서 많은 공간을 차지할 수 있습니다. 캐시 사용량 확인 및 사용자가 캐시를 지울 수 있도록 하는 것은 개발자의 책임입니다. 다행히 Cache API를 사용하면 아주 쉽게 할 수 있습니다.

// Here's how I'd compute how much cache is used by artwork files...
caches.open('artwork-cache-v1')
.then(cache => cache.matchAll())
.then(responses => {
    let cacheSize = 0;
    let blobQueue = Promise.resolve();

    responses.forEach(response => {
    let responseSize = response.headers.get('content-length');
    if (responseSize) {
        // Use content-length HTTP header when possible.
        cacheSize += Number(responseSize);
    } else {
        // Otherwise, use the uncompressed blob size.
        blobQueue = blobQueue.then(_ => response.blob())
            .then(blob => { cacheSize += blob.size; blob.close(); });
    }
    });

    return blobQueue.then(_ => {
    console.log('Artwork cache is about ' + cacheSize + ' Bytes.');
    });
})
.catch(error => { console.log(error); });

// And here's how to delete some artwork files...
const artworkFilesToDelete = ['artwork1.png', 'artwork2.png', 'artwork3.png'];

caches.open('artwork-cache-v1')
.then(cache => Promise.all(artworkFilesToDelete.map(artwork => cache.delete(artwork))))
.catch(error => { console.log(error); });

구현 참고사항

  • Android용 Chrome은 미디어 파일 길이가 5초 이상일 때만 미디어 알림을 표시하기 위해 '전체' 오디오 포커스를 요청합니다.
  • 알림 아트워크는 blob URL과 데이터 URL을 지원합니다.
  • 정의된 아트워크가 없고 원하는 크기의 아이콘 이미지가 있으면 미디어 알림에서 이 아트워크를 사용합니다.
  • Android용 Chrome의 알림 아트워크 크기는 512x512입니다. 저사양 기기의 경우에는 256x256입니다.
  • audio.src = ''로 미디어 알림을 닫습니다.
  • Web Audio API는 과거의 이유로 Android 오디오 포커스를 요청하지 않으므로 Media Session API를 사용할 수 있는 유일한 방법은 <audio> 요소를 Web Audio API의 입력 소스로 연결하는 것입니다. 제안된 Web AudioFocus API를 통해 가까운 미래에 이러한 상황이 개선되기를 바랍니다.
  • 미디어 세션 호출은 미디어 리소스와 동일한 프레임에서 발생하는 경우에만 미디어 알림에 영향을 미칩니다. 아래 스니펫을 참조하세요.
<iframe id="iframe">
  <audio>...</audio>
</iframe>
<script>
  iframe.contentWindow.navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    ...
  });
</script>

지원

이 문서 작성 시점을 기준으로 Android용 Chrome은 Media Session API를 지원하는 유일한 플랫폼입니다. 브라우저 구현 상태에 관한 자세한 최신 정보는 Chrome 플랫폼 상태에서 확인할 수 있습니다.

샘플 및 데모

Blender FoundationJan Morgenstern의 작품이 포함된 공식 Chrome 미디어 세션 샘플을 확인하세요.

자료

미디어 세션 사양: wicg.github.io/mediasession

사양 문제: github.com/WICG/mediasession/issues

Chrome 버그: crbug.com