Skip to content

Instantly share code, notes, and snippets.

@MeFoDy
Last active September 10, 2021 21:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save MeFoDy/df09490111df48da5fe5cae16394dafb to your computer and use it in GitHub Desktop.
Save MeFoDy/df09490111df48da5fe5cae16394dafb to your computer and use it in GitHub Desktop.
const audios = document.querySelectorAll('audio');
const tracks = [...audios].map((audio, i) => {
const article = audio.parentNode;
const title = article.querySelector('h2 a');
const img = article.querySelector('img');
return {
pos: i,
audio: audio,
metadata: {
title: title.textContent,
artist: 'Some Podcast',
album: 'Some Podcast s01',
artwork: [
{
src: img.src,
sizes: `${img.naturalWidth}x${img.naturalHeight}`,
type: 'image/png',
}
]
}
};
});
let currentAudioTrack;
const defaultSkipTime = 7;
function setMetadata(metadata) {
navigator.mediaSession.metadata = new MediaMetadata(metadata);
}
function updatePositionState(audio) {
if ('setPositionState' in navigator.mediaSession) {
navigator.mediaSession.setPositionState({
duration: audio.duration,
playbackRate: audio.playbackRate,
position: audio.currentTime,
});
}
const playbackState = audio.paused ? 'paused' : 'playing';
navigator.mediaSession.playbackState = playbackState;
}
async function playTrack(track) {
if (currentAudioTrack !== track) {
currentAudioTrack = track;
[...tracks].forEach(t => {
if (t.pos !== track.pos) {
t.audio.pause();
}
});
}
await track.audio.play();
track.audio.volume = 0.25;
setMetadata(track.metadata);
updatePositionState(track.audio);
}
async function nextTrack() {
const tracksLength = tracks.length;
const pos = (currentAudioTrack.pos + 1) % tracksLength;
const track = tracks.find(t => t.pos === pos);
await playTrack(track);
}
async function prevTrack() {
const tracksLength = tracks.length;
const pos = (currentAudioTrack.pos - 1 + tracksLength) % tracksLength;
const track = tracks.find(t => t.pos === pos);
await playTrack(track);
}
function resetPositionState() {
if ('setPositionState' in navigator.mediaSession) {
navigator.mediaSession.setPositionState(null);
}
}
if ('mediaSession' in navigator) {
const actionHandlers = [
['play', async () => {
await currentAudioTrack.audio.play();
updatePositionState(currentAudioTrack.audio);
}],
['pause', () => {
currentAudioTrack.audio.pause();
updatePositionState(currentAudioTrack.audio);
}],
['previoustrack', async () => {
await prevTrack();
}],
['nexttrack', async () => {
await nextTrack();
}],
['seekbackward', (details) => {
const audio = currentAudioTrack.audio;
const skipTime = details.seekOffset || defaultSkipTime;
audio.currentTime = Math.max(audio.currentTime - skipTime, 0);
updatePositionState(currentAudioTrack.audio);
}],
['seekforward', (details) => {
const audio = currentAudioTrack.audio;
const skipTime = details.seekOffset || defaultSkipTime;
audio.currentTime = Math.min(audio.currentTime + skipTime, audio.duration);
updatePositionState(currentAudioTrack.audio);
}],
['seekto', (details) => {
const audio = currentAudioTrack.audio;
if (details.fastSeek && 'fastSeek' in audio) {
audio.fastSeek(details.seekTime);
return;
}
audio.currentTime = details.seekTime;
updatePositionState(currentAudioTrack.audio);
}],
// ios15 specific
['coordinatorchange', (coordinator) => {
if (coordinator) {
coordinator.join();
}
}]
];
for (const [action, handler] of actionHandlers) {
try {
navigator.mediaSession.setActionHandler(action, handler);
} catch (error) {
console.log(action + ' — not working, sorry :(');
}
}
[...tracks].forEach(track => {
track.audio.addEventListener('play', async () => {
await playTrack(track);
});
for (let event of ['pause', 'playing', 'durationchange', 'ratechange', 'timechange']) {
track.audio.addEventListener(event, () => {
updatePositionState(track.audio);
});
}
track.audio.addEventListener('ended', async () => {
await nextTrack();
});
});
resetPositionState();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment