From 5fc5387e40cef85636434203858442b2fd17ab9f Mon Sep 17 00:00:00 2001 From: Rudis Muiznieks Date: Sun, 23 Apr 2023 14:59:30 -0500 Subject: [PATCH] added now playing to top of playlist --- db/migrations/010-date_updated.sql | 11 ++++ site/index.html | 2 +- site/radiostasis.js | 90 ++++++++++++++++++++++------ src/Player.ts | 4 ++ src/Playlist.ts | 95 ++++++++++++++++++++++++------ 5 files changed, 167 insertions(+), 35 deletions(-) create mode 100644 db/migrations/010-date_updated.sql diff --git a/db/migrations/010-date_updated.sql b/db/migrations/010-date_updated.sql new file mode 100644 index 0000000..dca828a --- /dev/null +++ b/db/migrations/010-date_updated.sql @@ -0,0 +1,11 @@ +alter table series +add column date_updated timestamp; + +update series +set date_updated='2023-04-15'; + +pragma writable_schema=on; +update sqlite_master +set sql=replace(sql, 'date_updated timestamp', 'date_updated timestamp not null') +where type='table' and name='series'; +pragma writable_schema=off; diff --git a/site/index.html b/site/index.html index 6b2d57c..ebbddc3 100644 --- a/site/index.html +++ b/site/index.html @@ -96,7 +96,7 @@

Playlist

-

Up Next:

+

Now Playing:

Clear Playlist
    diff --git a/site/radiostasis.js b/site/radiostasis.js index a8eb16e..55a6a35 100644 --- a/site/radiostasis.js +++ b/site/radiostasis.js @@ -40,6 +40,8 @@ var Player = /** @class */ (function () { var _this = this; this.playlist = playlist; this.playlist.setPlayEpisodeHandler(function (episode) { return _this.playEpisode(episode); }); + this.playlist.setNowPlayingEpisodeHandler(function () { return _this.currentEpisode(); }); + this.playlist.setStopHandler(function () { return _this.stopPlaybackAndResetUi(); }); var controls = getOrThrow(document.getElementById('controls')); var timeVolume = getOrThrow(document.getElementById('timeVolume')); this.nowPlaying = getOrThrow(document.getElementById('nowPlaying')); @@ -161,6 +163,7 @@ var Player = /** @class */ (function () { text: message, gravity: 'bottom', position: 'right', + stopOnFocus: false, style: { marginBottom: '10ex', background: '#a00', @@ -321,6 +324,8 @@ var Playlist = /** @class */ (function () { this.queueExpandedHeight = 'calc(100% - 6ex)'; // event handlers this.changedHandlers = []; + this.nowPlayingEpisodeHandler = null; + this.stopHandler = null; this.playEpisodeHandler = null; // the actual episode queue this.episodes = []; @@ -335,6 +340,7 @@ var Playlist = /** @class */ (function () { this.queueTab.addEventListener('click', function () { return _this.toggleQueueUI(); }); this.overlay.addEventListener('click', function () { return _this.toggleQueueUI(); }); this.clearButton.addEventListener('click', function () { return _this.clearPlaylist(); }); + this.unshiftCurrent(); } Playlist.prototype.addPlaylistChangedHandler = function (handler) { this.changedHandlers.push(handler); @@ -342,6 +348,12 @@ var Playlist = /** @class */ (function () { Playlist.prototype.setPlayEpisodeHandler = function (handler) { this.playEpisodeHandler = handler; }; + Playlist.prototype.setNowPlayingEpisodeHandler = function (handler) { + this.nowPlayingEpisodeHandler = handler; + }; + Playlist.prototype.setStopHandler = function (handler) { + this.stopHandler = handler; + }; Playlist.prototype.hasNextEpisode = function () { return this.episodes.length > 0; }; @@ -370,6 +382,16 @@ var Playlist = /** @class */ (function () { this.playlistChanged(); }; Playlist.prototype.unshiftEpisodes = function (episodes) { + this.shiftCurrent(); + var nowPlaying = this.nowPlayingEpisodeHandler + ? this.nowPlayingEpisodeHandler() + : null; + if (nowPlaying) { + var litem = this.createQueueListItem(nowPlaying); + this.episodes.unshift([nowPlaying, litem]); + this.episodeHash.add(nowPlaying.id); + this.queueList.prepend(litem); + } for (var i = episodes.length - 1; i >= 0; i--) { var episode = episodes[i]; if (this.isQueued(episode)) @@ -382,6 +404,7 @@ var Playlist = /** @class */ (function () { this.notify(episodes.length == 1 ? 'Episode added to queue.' : "".concat(episodes.length, " episodes added to queue.")); + this.unshiftCurrent(); this.playlistChanged(); }; Playlist.prototype.removeEpisode = function (episode, ignoreChanged, notify) { @@ -406,20 +429,50 @@ var Playlist = /** @class */ (function () { }; Playlist.prototype.clearPlaylist = function () { var removed = this.episodes.length; + if (this.nowPlayingEpisodeHandler && + this.nowPlayingEpisodeHandler() != null) { + if (this.stopHandler) + this.stopHandler(); + removed++; + } this.episodes.length = 0; this.episodeHash.clear(); this.queueList.innerHTML = ''; + this.unshiftCurrent(); this.playlistChanged(); if (removed > 0) { this.notify('Playlist cleared.'); } }; Playlist.prototype.playlistChanged = function () { + this.shiftCurrent(); + this.unshiftCurrent(); for (var _i = 0, _a = this.changedHandlers; _i < _a.length; _i++) { var handler = _a[_i]; handler(); } }; + Playlist.prototype.shiftCurrent = function () { + var _a; + (_a = this.queueList.firstChild) === null || _a === void 0 ? void 0 : _a.remove(); + }; + Playlist.prototype.unshiftCurrent = function () { + var currentEpisode = this.nowPlayingEpisodeHandler + ? this.nowPlayingEpisodeHandler() + : null; + if (currentEpisode) { + this.queueList.prepend(this.createQueueListItem(currentEpisode, false)); + } + else { + var litem = document.createElement('li'); + litem.classList.add('episode'); + litem.title = 'No episode playing'; + var label = document.createElement('label'); + label.innerHTML = 'No episode playing'; + litem.appendChild(label); + this.queueList.prepend(litem); + } + }; Playlist.prototype.toggleQueueUI = function () { if (this.queueContainer.style.height !== this.queueExpandedHeight) { this.queueContainer.style.height = this.queueExpandedHeight; @@ -438,8 +491,9 @@ var Playlist = /** @class */ (function () { this.queueTab.classList.remove('expanded'); } }; - Playlist.prototype.createQueueListItem = function (episode) { + Playlist.prototype.createQueueListItem = function (episode, withControls) { var _this = this; + if (withControls === void 0) { withControls = true; } var item = document.createElement('li'); item.classList.add('episode'); item.title = episode.title; @@ -449,24 +503,26 @@ var Playlist = /** @class */ (function () { sspan.innerHTML = episode.series.title; var aside = document.createElement('aside'); aside.appendChild(sspan); - var controls = document.createElement('div'); - controls.classList.add('controls'); - var playBtn = document.createElement('a'); - playBtn.href = '#'; - playBtn.innerHTML = 'Play Now'; - playBtn.addEventListener('click', function () { - if (_this.playEpisodeHandler) - _this.playEpisodeHandler(episode); - }); - var remBtn = document.createElement('a'); - remBtn.href = '#'; - remBtn.innerHTML = 'Remove'; - remBtn.addEventListener('click', function () { return _this.removeEpisode(episode); }); - controls.appendChild(playBtn); - controls.appendChild(remBtn); item.appendChild(label); item.appendChild(aside); - item.appendChild(controls); + if (withControls) { + var controls = document.createElement('div'); + controls.classList.add('controls'); + var playBtn = document.createElement('a'); + playBtn.href = '#'; + playBtn.innerHTML = 'Play Now'; + playBtn.addEventListener('click', function () { + if (_this.playEpisodeHandler) + _this.playEpisodeHandler(episode); + }); + var remBtn = document.createElement('a'); + remBtn.href = '#'; + remBtn.innerHTML = 'Remove'; + remBtn.addEventListener('click', function () { return _this.removeEpisode(episode); }); + controls.appendChild(playBtn); + controls.appendChild(remBtn); + item.appendChild(controls); + } return item; }; Playlist.prototype.notify = function (message) { diff --git a/src/Player.ts b/src/Player.ts index 6da93df..8178c21 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -26,6 +26,9 @@ class Player { public constructor(playlist: Playlist) { this.playlist = playlist; this.playlist.setPlayEpisodeHandler((episode) => this.playEpisode(episode)); + this.playlist.setNowPlayingEpisodeHandler(() => this.currentEpisode()); + this.playlist.setStopHandler(() => this.stopPlaybackAndResetUi()); + const controls = getOrThrow(document.getElementById('controls')); const timeVolume = getOrThrow(document.getElementById('timeVolume')); this.nowPlaying = getOrThrow(document.getElementById('nowPlaying')); @@ -163,6 +166,7 @@ class Player { text: message, gravity: 'bottom', position: 'right', + stopOnFocus: false, style: { marginBottom: '10ex', background: '#a00', diff --git a/src/Playlist.ts b/src/Playlist.ts index 6b52e0f..82e5acc 100644 --- a/src/Playlist.ts +++ b/src/Playlist.ts @@ -10,6 +10,8 @@ class Playlist { // event handlers private changedHandlers: Array<() => void> = []; + private nowPlayingEpisodeHandler: (() => Episode | null) | null = null; + private stopHandler: (() => void) | null = null; private playEpisodeHandler: ((episode: Episode) => void) | null = null; // the actual episode queue @@ -36,6 +38,8 @@ class Playlist { this.queueTab.addEventListener('click', () => this.toggleQueueUI()); this.overlay.addEventListener('click', () => this.toggleQueueUI()); this.clearButton.addEventListener('click', () => this.clearPlaylist()); + + this.unshiftCurrent(); } public addPlaylistChangedHandler(handler: () => void): void { @@ -46,6 +50,14 @@ class Playlist { this.playEpisodeHandler = handler; } + public setNowPlayingEpisodeHandler(handler: () => Episode | null): void { + this.nowPlayingEpisodeHandler = handler; + } + + public setStopHandler(handler: () => void): void { + this.stopHandler = handler; + } + public hasNextEpisode(): boolean { return this.episodes.length > 0; } @@ -78,6 +90,16 @@ class Playlist { } public unshiftEpisodes(episodes: Array): void { + this.shiftCurrent(); + const nowPlaying = this.nowPlayingEpisodeHandler + ? this.nowPlayingEpisodeHandler() + : null; + if (nowPlaying) { + const litem = this.createQueueListItem(nowPlaying); + this.episodes.unshift([nowPlaying, litem]); + this.episodeHash.add(nowPlaying.id); + this.queueList.prepend(litem); + } for (let i = episodes.length - 1; i >= 0; i--) { const episode = episodes[i]; if (this.isQueued(episode)) this.removeEpisode(episode, true, false); @@ -91,6 +113,7 @@ class Playlist { ? 'Episode added to queue.' : `${episodes.length} episodes added to queue.` ); + this.unshiftCurrent(); this.playlistChanged(); } @@ -118,10 +141,19 @@ class Playlist { } public clearPlaylist(): void { - const removed = this.episodes.length; + let removed = this.episodes.length; + if ( + this.nowPlayingEpisodeHandler && + this.nowPlayingEpisodeHandler() != null + ) { + if (this.stopHandler) this.stopHandler(); + removed++; + } + this.episodes.length = 0; this.episodeHash.clear(); this.queueList.innerHTML = ''; + this.unshiftCurrent(); this.playlistChanged(); if (removed > 0) { this.notify('Playlist cleared.'); @@ -129,11 +161,35 @@ class Playlist { } private playlistChanged(): void { + this.shiftCurrent(); + this.unshiftCurrent(); for (const handler of this.changedHandlers) { handler(); } } + private shiftCurrent(): void { + this.queueList.firstChild?.remove(); + } + + private unshiftCurrent(): void { + const currentEpisode = this.nowPlayingEpisodeHandler + ? this.nowPlayingEpisodeHandler() + : null; + + if (currentEpisode) { + this.queueList.prepend(this.createQueueListItem(currentEpisode, false)); + } else { + const litem = document.createElement('li'); + litem.classList.add('episode'); + litem.title = 'No episode playing'; + const label = document.createElement('label'); + label.innerHTML = 'No episode playing'; + litem.appendChild(label); + this.queueList.prepend(litem); + } + } + private toggleQueueUI(): void { if (this.queueContainer.style.height !== this.queueExpandedHeight) { this.queueContainer.style.height = this.queueExpandedHeight; @@ -152,7 +208,10 @@ class Playlist { } } - private createQueueListItem(episode: Episode): HTMLLIElement { + private createQueueListItem( + episode: Episode, + withControls = true + ): HTMLLIElement { const item = document.createElement('li'); item.classList.add('episode'); item.title = episode.title; @@ -162,23 +221,25 @@ class Playlist { sspan.innerHTML = episode.series.title; const aside = document.createElement('aside'); aside.appendChild(sspan); - const controls = document.createElement('div'); - controls.classList.add('controls'); - const playBtn = document.createElement('a'); - playBtn.href = '#'; - playBtn.innerHTML = 'Play Now'; - playBtn.addEventListener('click', () => { - if (this.playEpisodeHandler) this.playEpisodeHandler(episode); - }); - const remBtn = document.createElement('a'); - remBtn.href = '#'; - remBtn.innerHTML = 'Remove'; - remBtn.addEventListener('click', () => this.removeEpisode(episode)); - controls.appendChild(playBtn); - controls.appendChild(remBtn); item.appendChild(label); item.appendChild(aside); - item.appendChild(controls); + if (withControls) { + const controls = document.createElement('div'); + controls.classList.add('controls'); + const playBtn = document.createElement('a'); + playBtn.href = '#'; + playBtn.innerHTML = 'Play Now'; + playBtn.addEventListener('click', () => { + if (this.playEpisodeHandler) this.playEpisodeHandler(episode); + }); + const remBtn = document.createElement('a'); + remBtn.href = '#'; + remBtn.innerHTML = 'Remove'; + remBtn.addEventListener('click', () => this.removeEpisode(episode)); + controls.appendChild(playBtn); + controls.appendChild(remBtn); + item.appendChild(controls); + } return item; }