radiostasis/site/radiostasis.js

322 lines
9.2 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
// initialize audio player
var player = new Player();
player.initialize();
let lastSearch;
// function that gets run on content load
var wirePage = (e) => {
// wire up episode items to play
const episodes = e.detail.elt.getElementsByClassName('episode');
for (var episode of episodes) {
const title = episode.getAttribute('title');
const series = episode.dataset.series;
const cover = episode.dataset.cover;
const file = episode.dataset.file;
episode.addEventListener('click', () => {
player.playEpisode(cover, series, title, file);
});
}
// wire up series filter inputs
var filter = e.detail.elt.getElementsByClassName('filter').item(0);
let debouncer;
if (filter) {
var allSeries = e.detail.elt.getElementsByTagName('section');
// show all series by default when the page loads
filter.addEventListener('input', ev => {
clearTimeout(debouncer);
debouncer = setTimeout(() => {
lastSearch = ev.target.value.toLowerCase()
var terms = lastSearch.split(' ');
for (var series of allSeries) {
var match = true;
for (var term of terms) {
if (term.length > 0 && series.dataset.filter.indexOf(term) < 0) {
match = false;
break;
}
}
if (match) {
series.classList.remove('no-match');
} else {
series.classList.add('no-match');
}
}
}, 499);
});
}
};
var main = document.getElementsByTagName('main').item(0);
document.addEventListener('htmx:historyRestore', (e) => {
// repopulate filter on history restore
var filter = e.detail.elt.getElementsByClassName('filter').item(0);
if (lastSearch && filter) {
filter.value = lastSearch;
lastSearch = null;
}
// then wire up the page
wirePage(e);
});
// set up episode links on content swap
main.addEventListener('htmx:afterSwap', wirePage);
// load page
const pname = location.pathname == '/'
? '/home.html'
: location.pathname + '.html';
const path = '/partial' + pname;
htmx.ajax('GET', path, 'main');
});
class Player {
constructor() {
this.playSvg = 'M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM6.79 5.093A.5.5 0 0 0 6 5.5v5a.5.5 0 0 0 .79.407l3.5-2.5a.5.5 0 0 0 0-.814l-3.5-2.5z';
this.pauseSvg = 'M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM6.25 5C5.56 5 5 5.56 5 6.25v3.5a1.25 1.25 0 1 0 2.5 0v-3.5C7.5 5.56 6.94 5 6.25 5zm3.5 0c-.69 0-1.25.56-1.25 1.25v3.5a1.25 1.25 0 1 0 2.5 0v-3.5C11 5.56 10.44 5 9.75 5z';
const controls = document.getElementById('controls');
const timeVolume = document.getElementById('timeVolume');
this.nowPlaying = document.getElementById('nowPlaying');
this.rewButton = controls.getElementsByTagName('button').item(0);
this.playButton = controls.getElementsByTagName('button').item(1);
this.playButtonPath = this.playButton.getElementsByTagName('path').item(0);
this.ffwButton = controls.getElementsByTagName('button').item(2);
this.cover = this.nowPlaying.getElementsByTagName('img').item(0);
this.seriesName = this.nowPlaying.getElementsByTagName('span').item(0);
this.episodeName = this.nowPlaying.getElementsByTagName('span').item(1);
this.timeDisplay = timeVolume.getElementsByTagName('span').item(0);
this.volumeSlider = timeVolume.getElementsByTagName('input').item(0);
this.howl = null;
this.ticker = null;
}
initialize() {
this.reset();
const player = this;
this.playButton.addEventListener('click', () => {
player.playPause();
});
this.rewButton.addEventListener('click', () => {
player.rewind();
});
this.ffwButton.addEventListener('click', () => {
player.fastForward();
});
this.volumeSlider.addEventListener('change', () => {
this.setVolume();
});
// set up mediasession in browsers that support it
if ('mediaSession' in navigator) {
const player = this;
navigator.mediaSession.setActionHandler('pause',
() => player.playPause());
navigator.mediaSession.setActionHandler('play',
() => player.playPause());
navigator.mediaSession.setActionHandler('stop',
() => player.playPause());
navigator.mediaSession.setActionHandler('seekforward',
() => player.fastForward());
navigator.mediaSession.setActionHandler('seekbackward',
() => player.rewind());
navigator.mediaSession.setActionHandler('nexttrack',
() => player.fastForward());
navigator.mediaSession.setActionHandler('previoustrack',
() => player.rewind());
}
}
startTicker() {
if (!this.ticker) {
const player = this;
this.ticker = setInterval(() => {
player.setTime();
}, 500);
}
}
stopTicker() {
if (this.ticker) {
clearInterval(this.ticker);
this.ticker = null;
}
}
disableControls(disabled) {
this.playButton.disabled = disabled === true;
this.rewButton.disabled = disabled === true;
this.ffwButton.disabled = disabled === true;
}
setPlay() {
this.playButtonPath.setAttribute('d', this.playSvg);
}
setPause() {
this.playButtonPath.setAttribute('d', this.pauseSvg);
}
setSeries(series, cover) {
this.seriesName.innerHTML = series ?? 'No episode playing';
this.seriesName.setAttribute('title', series ?? 'No episode playing');
this.cover.setAttribute('src', cover ?? '/transparent.png');
}
setEpisode(episode) {
this.episodeName.innerHTML = episode ?? '';
this.episodeName.setAttribute('title', episode ?? '');
}
setMetadata(series, episode, cover) {
if ('mediaSession' in navigator) {
this.metadata = new MediaMetadata({
title: episode,
album: series,
artist: 'Radiostasis',
artwork: [{
src: cover,
sizes: '256x256',
type: 'image/jpeg',
}],
});
}
}
sendMetadata() {
if ('mediaSession' in navigator) {
navigator.mediaSession.metadata = this.metadata;
}
}
clearMetadata() {
if ('mediaSession' in navigator) {
navigator.mediaSession.metadata = null;
}
}
_getDisplayTime(time) {
const minutes = Math.floor(time / 60);
const seconds = Math.round(time - (minutes * 60));
const secStr = seconds < 10 ? '0' + seconds : seconds;
return minutes + ':' + secStr;
}
setTime() {
if (this.howl && this.howl.state() === 'loaded') {
const total = this.howl.duration();
const current = this.howl.seek();
this.timeDisplay.innerHTML =
this._getDisplayTime(current) + ' / ' + this._getDisplayTime(total);
} else {
this.timeDisplay.innerHTML = '--:-- / --:--';
}
}
getVolume() {
return parseFloat(this.volumeSlider.value) / 100;
}
setVolume() {
if (this.howl) {
this.howl.volume(this.getVolume());
}
}
reset() {
this.howl = null;
this.setPlay();
this.setSeries();
this.setEpisode();
this.clearMetadata();
this.setTime();
this.nowPlaying.classList.remove('error');
this.disableControls(true);
this.stopTicker();
this.metadata = null;
}
error(message) {
this.reset();
this.setSeries('Error playing episode');
this.setEpisode(message);
this.nowPlaying.classList.add('error');
}
playPause() {
if (this.howl && this.howl.playing()) {
this.howl.pause();
} else if (this.howl && !this.howl.playing()) {
this.howl.play();
}
}
rewind() {
if (this.howl && this.howl.state() === 'loaded') {
let newPos = this.howl.seek() - 10;
if (newPos < 0) newPos = 0;
this.howl.seek(newPos);
}
}
fastForward() {
if (this.howl && this.howl.state() === 'loaded') {
let newPos = this.howl.seek() + 30;
if (newPos > this.howl.duration()) newPos = this.howl.duration();
this.howl.seek(newPos);
}
}
playEpisode(cover, series, episode, file) {
if (this.howl) {
this.howl.stop();
}
this.reset();
this.setPause();
this.disableControls(true);
this.setSeries('Loading episode', '/loading.gif');
this.setEpisode(episode);
fetch('/api/r/' + file).then(async (res) => {
if (!res.ok) {
this.error('API returned ' + res.status);
return;
}
const link = await res.json();
const player = this;
this.howl = new Howl({
src: link.url + '?Authorization=' + link.token,
html5: true,
autoplay: true,
volume: this.getVolume(),
onload: () => {
player.setTime();
player.setMetadata(series, episode, cover);
},
onplay: () => {
player.sendMetadata();
player.setSeries(series, cover);
player.setPause();
player.disableControls(false);
player.startTicker();
},
onpause: () => {
player.setPlay();
player.stopTicker();
},
onend: () => {
player.reset();
},
onloaderror: () => {
player.reset();
player.error('Playback error');
},
});
}).catch(err => {
console.log(err);
this.error('API request error');
});
}
}