radiostasis/site/radiostasis.js

400 lines
18 KiB
JavaScript

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var Player = /** @class */ (function () {
function Player(playlist) {
var _this = this;
this.playlist = playlist;
var controls = document.getElementById('controls');
var 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.skipButton = controls.getElementsByTagName('button').item(3);
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.progress = document.getElementById('progress');
this.ticker = null;
this.howl = null;
this.episode = null;
// initialize to stopped state
this.stopPlaybackAndResetUi();
// wire up static ui elements
this.playButton.addEventListener('click', function () { return _this.playPause(); });
this.rewButton.addEventListener('click', function () { return _this.rewind(); });
this.ffwButton.addEventListener('click', function () { return _this.fastForward(); });
this.volumeSlider.addEventListener('change', function () { return _this.setVolume(); });
// wire up mediaSession events
if ('mediaSession' in navigator) {
navigator.mediaSession.setActionHandler('pause', function () { return _this.playPause(); });
navigator.mediaSession.setActionHandler('play', function () { return _this.playPause(); });
navigator.mediaSession.setActionHandler('stop', function () {
return _this.stopPlaybackAndResetUi();
});
navigator.mediaSession.setActionHandler('seekforward', function () {
return _this.fastForward();
});
navigator.mediaSession.setActionHandler('seekbackward', function () {
return _this.rewind();
});
// don't support previous track yet, queue removes them once finished
// wire this up to rewind instead
navigator.mediaSession.setActionHandler('previoustrack', function () {
return _this.rewind();
});
}
// set up playlist changed handler
this.playlist.setPlaylistChangedHandler(function () {
_this.skipButton.disabled = !_this.playlist.hasNextEpisode();
});
}
Player.prototype.setErrorUI = function (message) {
this.stopPlaybackAndResetUi();
this.seriesName.innerHTML = 'Error playing episode';
this.episodeName.innerHTML = message;
this.nowPlaying.classList.add('error');
};
Player.prototype.playEpisode = function (episode) {
var _this = this;
this.stopPlaybackAndResetUi();
this.setPauseButtonUI();
this.episode = episode;
this.updateNowPlayingUI(true);
fetch("/api/r/".concat(episode.file)).then(function (res) { return __awaiter(_this, void 0, void 0, function () {
var link;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!res.ok) {
this.setErrorUI("API returned ".concat(res.status));
return [2 /*return*/];
}
return [4 /*yield*/, res.json()];
case 1:
link = _a.sent();
this.howl = new Howl({
src: "".concat(link.url, "?Authorization=").concat(link.token),
html5: true,
autoplay: true,
volume: this.getVolume(),
onload: function () {
_this.updateTimeUI();
_this.sendMediaSessionMetadata();
},
onplay: function () {
_this.updateNowPlayingUI();
_this.sendMediaSessionMetadata();
_this.setPauseButtonUI();
_this.startTicker();
},
onpause: function () {
_this.setPlayButtonUI();
_this.stopTicker();
},
onend: function () { return _this.stopPlaybackAndResetUi(); },
onloaderror: function () { return _this.setErrorUI('Playback error'); },
});
return [2 /*return*/];
}
});
}); });
};
Player.prototype.playPause = function () {
if (this.howl && this.howl.playing())
this.howl.pause();
else if (this.howl && !this.howl.playing())
this.howl.play();
};
Player.prototype.rewind = function () {
if (this.howl && this.howl.state() === 'loaded') {
var newPos = this.howl.seek() - 10;
if (newPos < 0)
newPos = 0;
this.howl.seek(newPos);
this.updateTimeUI();
}
};
Player.prototype.fastForward = function () {
if (this.howl && this.howl.state() === 'loaded') {
var newPos = this.howl.seek() + 30;
if (newPos > this.howl.duration())
newPos = this.howl.duration();
this.howl.seek(newPos);
this.updateTimeUI();
}
};
Player.prototype.setPlayButtonUI = function () {
this.playButtonPath.setAttribute('d', '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');
};
Player.prototype.setPauseButtonUI = function () {
this.playButtonPath.setAttribute('d', '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');
};
Player.prototype.getVolume = function () {
return parseFloat(this.volumeSlider.value) / 100;
};
Player.prototype.setVolume = function () {
var _a;
(_a = this.howl) === null || _a === void 0 ? void 0 : _a.volume(this.getVolume());
};
Player.prototype.updateNowPlayingUI = function (loading) {
if (loading === void 0) { loading = false; }
this.nowPlaying.classList.remove('error');
if (this.episode) {
this.seriesName.innerHTML = loading
? 'Loading episode'
: this.episode.series.title;
this.episodeName.innerHTML = this.episode.title;
this.cover.src = loading
? '/loading.gif'
: this.episode.series.cover;
this.nowPlaying.title = "".concat(this.episode.series.title, "\n").concat(this.episode.title);
}
else {
this.seriesName.innerHTML = 'No episode playing';
this.episodeName.innerHTML = '';
this.cover.src = '/transparent.png';
this.nowPlaying.title = 'No episode playing';
}
if (loading || !this.episode) {
this.playButton.disabled = true;
this.rewButton.disabled = true;
this.ffwButton.disabled = true;
}
else {
this.playButton.disabled = false;
this.rewButton.disabled = false;
this.ffwButton.disabled = false;
}
this.skipButton.disabled = !this.playlist.hasNextEpisode();
};
Player.prototype.sendMediaSessionMetadata = function () {
if ('mediaSession' in navigator) {
if (this.episode) {
navigator.mediaSession.metadata = new MediaMetadata({
title: this.episode.title,
album: 'Radiostasis',
artist: this.episode.series.title,
artwork: [{
src: this.episode.series.cover,
sizes: '256x256',
type: 'image/jpeg',
}],
});
}
else {
navigator.mediaSession.metadata = null;
}
}
};
Player.prototype.timeToDisplayString = function (time) {
var mins = Math.floor(time / 60);
var secs = Math.round(time - (mins * 60));
var secStr = secs < 10 ? "0".concat(secs) : secs.toString();
return "".concat(mins, ":").concat(secStr);
};
Player.prototype.updateTimeUI = function () {
if (this.howl && this.howl.state() === 'loaded') {
var total = this.howl.duration();
var current = this.howl.seek();
var pct = "".concat(Math.round(current / total * 1000) / 10, "%");
var timeStamp = "".concat(this.timeToDisplayString(current), " / ").concat(this.timeToDisplayString(total));
// set the new values if they've changed since the last tick
if (this.timeDisplay.innerHTML !== timeStamp)
this.timeDisplay.innerHTML = timeStamp;
if (this.progress.style.width !== pct)
this.progress.style.width = pct;
}
else {
this.timeDisplay.innerHTML = '--:-- / --:--';
this.progress.style.width = '0%';
}
};
Player.prototype.startTicker = function () {
var _this = this;
if (!this.ticker) {
this.ticker = setInterval(function () { return _this.updateTimeUI(); }, 500);
}
};
Player.prototype.stopTicker = function () {
if (this.ticker) {
clearInterval(this.ticker);
this.ticker = null;
}
};
Player.prototype.stopPlaybackAndResetUi = function () {
var _a;
(_a = this.howl) === null || _a === void 0 ? void 0 : _a.unload();
this.howl = null;
this.episode = null;
this.setPlayButtonUI();
this.updateNowPlayingUI();
this.sendMediaSessionMetadata();
this.stopTicker();
this.updateTimeUI();
};
return Player;
}());
var Playlist = /** @class */ (function () {
function Playlist() {
var _this = this;
this.queueExpandedHeight = 'calc(100% - 6ex)';
this.queueContainer = document.getElementById('queue-container');
this.queueInitialHeight = getComputedStyle(this.queueContainer).height;
this.queueTab = this.queueContainer.getElementsByTagName('h2').item(0);
this.overlay = document.getElementById('overlay');
this.queueTab.addEventListener('click', function () { return _this.toggleQueueUI(); });
this.overlay.addEventListener('click', function () { return _this.toggleQueueUI(); });
}
Playlist.prototype.toggleQueueUI = function () {
if (this.queueContainer.style.height !== this.queueExpandedHeight) {
this.queueContainer.style.height = this.queueExpandedHeight;
this.overlay.style.backgroundColor = 'rgba(255, 255, 255, 0.5)';
this.overlay.style.backdropFilter = 'blur(5px)';
this.overlay.style.pointerEvents = 'auto';
}
else {
this.queueContainer.style.height = this.queueInitialHeight;
this.overlay.style.backgroundColor = 'rgba(255, 255, 255, 0)';
this.overlay.style.backdropFilter = 'none';
this.overlay.style.pointerEvents = 'none';
}
};
Playlist.prototype.setPlaylistChangedHandler = function (handler) {
this.changedHandler = handler;
};
Playlist.prototype.hasNextEpisode = function () {
return false;
};
Playlist.prototype.queueEpisode = function (episode) {
};
return Playlist;
}());
var Radiostasis = /** @class */ (function () {
function Radiostasis() {
var _this = this;
this.lastSearch = null;
this.debouncer = null;
this.playlist = new Playlist();
this.player = new Player(this.playlist);
this.main = document.getElementsByTagName('main').item(0);
// set up htmx event handlers
document.addEventListener('htmx:historyRestore', function () { return _this.wireLoadedFragment(); });
document.getElementsByTagName('main').item(0).addEventListener('htmx:afterSwap', function () { return _this.wireLoadedFragment(); });
}
Radiostasis.prototype.wireLoadedFragment = function () {
var _this = this;
// episode play and queue buttons
var episodes = this.main.getElementsByClassName('episode');
var _loop_1 = function (i) {
var el = episodes.item(i);
var episode = {
slug: el.dataset.slug,
title: el.getAttribute('title'),
file: el.dataset.file,
series: {
slug: el.dataset.sslug,
title: el.dataset.series,
cover: el.dataset.cover,
},
};
// play button
el.getElementsByTagName('a').item(0).addEventListener('click', function (e) {
_this.player.playEpisode(episode);
e.preventDefault();
});
// queue button
el.getElementsByTagName('a').item(1).addEventListener('click', function (e) {
_this.playlist.queueEpisode(episode);
e.preventDefault();
});
};
for (var i = 0; i < episodes.length; i++) {
_loop_1(i);
}
// series filter input
var filter = this.main
.getElementsByClassName('filter').item(0);
if (filter) {
if (this.lastSearch) {
filter.value = this.lastSearch;
this.lastSearch = null;
}
var allSeries_1 = this.main.getElementsByTagName('section');
filter.addEventListener('input', function () {
if (_this.debouncer)
clearTimeout(_this.debouncer);
_this.debouncer = setTimeout(function () {
_this.lastSearch = filter.value.toLowerCase();
var terms = _this.lastSearch.split(' ');
for (var i = 0; i < allSeries_1.length; i++) {
var series = allSeries_1.item(i);
var match = true;
for (var _i = 0, terms_1 = terms; _i < terms_1.length; _i++) {
var term = terms_1[_i];
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);
});
}
};
Radiostasis.prototype.initialize = function () {
var path = location.pathname == '/'
? '/partial/home.html'
: "/partial/".concat(location.pathname, ".html");
htmx.ajax('GET', path, 'main');
};
return Radiostasis;
}());
document.addEventListener('DOMContentLoaded', function () {
// start application
new Radiostasis().initialize();
});