322 lines
9.2 KiB
JavaScript
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');
|
|
});
|
|
}
|
|
}
|