Skip to content

Commit

Permalink
fix: improved queue handling
Browse files Browse the repository at this point in the history
If an episode that is already in the queue is played, remove it from the queue. Don't appent newly
played episode to queue. If a new episode is set to play when there is another one currently
playing, add previous to queue.
  • Loading branch information
chhoumann committed Jul 28, 2022
1 parent 061a586 commit e6d32a7
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 99 deletions.
137 changes: 123 additions & 14 deletions src/store/index.ts
@@ -1,29 +1,114 @@
import { writable } from 'svelte/store';
import { get, writable } from 'svelte/store';
import type PodNotes from 'src/main';
import { Episode } from 'src/types/Episode';
import { PlayedEpisode } from 'src/types/PlayedEpisode';
import { PodcastFeed } from 'src/types/PodcastFeed';
import { Playlist } from 'src/types/Playlist';
import { ViewState } from 'src/types/ViewState';

export const plugin = writable<PodNotes>();
export const currentTime = writable<number>(0);
export const duration = writable<number>(0);
export const currentEpisode = writable<Episode>();

export const currentEpisode = function () {
const store = writable<Episode>();
const { subscribe, update } = store;

return {
subscribe,
set: (newEpisode: Episode) => {
update(previousEpisode => {
if (previousEpisode) {
addEpisodeToQueue(previousEpisode);
}

return newEpisode;
});
}
}
}();


export const isPaused = writable<boolean>(true);
export const playedEpisodes = writable<{
[key: string]: PlayedEpisode;
}>({});
export const savedFeeds = writable<{[podcastName: string]: PodcastFeed}>({});
export const playedEpisodes = function () {
const store = writable<{ [key: string]: PlayedEpisode }>({});
const { subscribe, update, set } = store;

export const episodeCache = writable<{[podcastName: string]: Episode[]}>({});
return {
subscribe,
set,
update,
add: (episode: Episode, time: number, duration: number, finished: boolean) => {
update(playedEpisodes => {
playedEpisodes[episode.title] = {
title: episode.title,
podcastName: episode.podcastName,
time,
duration,
finished,
};

export const queue = writable<Playlist>({
icon: 'list-ordered',
name: 'Queue',
episodes: [],
shouldEpisodeRemoveAfterPlay: true,
shouldRepeat: false,
});
return playedEpisodes;
});
},
markAsPlayed: (episode: Episode) => {
update(playedEpisodes => {
const playedEpisode = playedEpisodes[episode.title];

if (playedEpisode) {
playedEpisode.finished = true;
}

playedEpisodes[episode.title] = playedEpisode;
return playedEpisodes;
});
}
}
}();

export const savedFeeds = writable<{ [podcastName: string]: PodcastFeed }>({});

export const episodeCache = writable<{ [podcastName: string]: Episode[] }>({});

export const queue = function () {
const store = writable<Playlist>({
icon: 'list-ordered',
name: 'Queue',
episodes: [],
shouldEpisodeRemoveAfterPlay: true,
shouldRepeat: false,
});
const { subscribe, update, set } = store;

return {
subscribe,
update,
set,
add: (episode: Episode) => {
update(queue => {
queue.episodes.push(episode);
return queue;
});
},
remove: (episode: Episode) => {
update(queue => {
queue.episodes = queue.episodes.filter(e => e.title !== episode.title);
return queue;
});
},
playNext: () => {
update(queue => {
const nextEp = queue.episodes.shift();

if (nextEp) {
currentEpisode.set(nextEp);
}

return queue;
});
}
}
}();

export const favorites = writable<Playlist>({
icon: 'lucide-star',
Expand All @@ -34,3 +119,27 @@ export const favorites = writable<Playlist>({
});

export const playlists = writable<{ [name: string]: Playlist }>({});

export const podcastView = writable<HTMLDivElement>();
export const viewState = function () {
const store = writable<ViewState>(ViewState.PodcastGrid);
const { subscribe, set } = store;

return {
subscribe,
set: (newState: ViewState) => {
set(newState);

get(podcastView)?.scrollIntoView();
}
}
}();

function addEpisodeToQueue(episode: Episode) {
queue.update(playlist => {
const newEpisodes = [episode, ...playlist.episodes];
playlist.episodes = newEpisodes;

return playlist;
});
}
65 changes: 8 additions & 57 deletions src/ui/PodcastView/EpisodePlayer.svelte
Expand Up @@ -17,8 +17,8 @@
import Loading from "./Loading.svelte";
import EpisodeList from "./EpisodeList.svelte";
import Progressbar from "../common/Progressbar.svelte";
import spawnEpisodeContextMenu from "./spawnEpisodeContextMenu";
import { Episode } from "src/types/Episode";
import spawnEpisodeContextMenu from "./spawnEpisodeContextMenu";
import { Episode } from "src/types/Episode";
// #region Circumventing the forced two-way binding of the playback rate.
class CircumentForcedTwoWayBinding {
Expand All @@ -45,21 +45,6 @@ import { Episode } from "src/types/Episode";
currentTime.set(percent * $duration);
}
function markEpisodeAsPlayed() {
playedEpisodes.update((playedEpisodes) => {
const currentEp = $currentEpisode;
playedEpisodes[currentEp.title] = {
...currentEp,
time: $currentTime,
duration: $duration,
finished: true,
};
return playedEpisodes;
});
}
function removeEpisodeFromPlaylists() {
playlists.update((lists) => {
Object.values(lists).forEach((playlist) => {
Expand All @@ -71,31 +56,14 @@ import { Episode } from "src/types/Episode";
return lists;
});
queue.update((q) => {
q.episodes = q.episodes.filter(
(ep) => ep.title !== $currentEpisode.title
);
return q;
});
}
function playNextInQueue() {
queue.update((q) => {
const nextEp = q.episodes.shift();
if (nextEp) {
currentEpisode.set(nextEp);
}
return q;
});
queue.remove($currentEpisode);
}
function onEpisodeEnded() {
markEpisodeAsPlayed();
playedEpisodes.markAsPlayed($currentEpisode);
removeEpisodeFromPlaylists();
playNextInQueue();
queue.playNext();
}
function onPlaybackRateChange(event: CustomEvent<{ value: number }>) {
Expand Down Expand Up @@ -141,33 +109,15 @@ import { Episode } from "src/types/Episode";
}
// #endregion
function addCurrentEpisodeToPlayedEpisodes() {
playedEpisodes.update((playedEpisodes) => {
const currentEp = $currentEpisode;
const curTime = $currentTime;
const dur = $duration;
playedEpisodes[currentEp.title] = {
title: currentEp.title,
podcastName: currentEp.podcastName,
time: curTime,
duration: dur,
finished: curTime === dur,
};
return playedEpisodes;
});
}
onDestroy(() => {
addCurrentEpisodeToPlayedEpisodes();
playedEpisodes.add($currentEpisode, $currentTime, $duration, ($currentTime === $duration));
isPaused.set(true);
});
function handleContextMenuEpisode({
detail: { event, episode },
}: CustomEvent<{ episode: Episode; event: MouseEvent }>) {
spawnEpisodeContextMenu(episode, event, () => {});
spawnEpisodeContextMenu(episode, event);
}
</script>

Expand Down Expand Up @@ -213,6 +163,7 @@ import { Episode } from "src/types/Episode";
on:ended={onEpisodeEnded}
on:loadedmetadata={onMetadataLoaded}
on:play|preventDefault
autoplay={true}
/>

<div class="status-container">
Expand Down
45 changes: 20 additions & 25 deletions src/ui/PodcastView/PodcastView.svelte
Expand Up @@ -8,6 +8,8 @@
playlists,
queue,
favorites,
podcastView,
viewState,
} from "src/store";
import EpisodePlayer from "./EpisodePlayer.svelte";
import EpisodeList from "./EpisodeList.svelte";
Expand All @@ -29,15 +31,6 @@
let displayedEpisodes: Episode[] = [];
let displayedPlaylists: Playlist[] = [];
let latestEpisodes: Episode[] = [];
let _viewState: ViewState;
let view: HTMLDivElement;
function updateViewState(viewState: ViewState) {
_viewState = viewState;
view.scrollIntoView();
}
onMount(async () => {
const unsubscribePlaylists = playlists.subscribe((pl) => {
Expand Down Expand Up @@ -115,22 +108,20 @@
selectedFeed = feed;
displayedEpisodes = await fetchEpisodes(feed);
updateViewState(ViewState.EpisodeList);
viewState.set(ViewState.EpisodeList);
}
function handleClickEpisode(event: CustomEvent<{ episode: Episode }>) {
const { episode } = event.detail;
currentEpisode.set(episode);
updateViewState(ViewState.Player);
viewState.set(ViewState.Player);
}
function handleContextMenuEpisode({
detail: { event, episode },
}: CustomEvent<{ episode: Episode; event: MouseEvent }>) {
spawnEpisodeContextMenu(episode, event, () => {
updateViewState(ViewState.Player);
});
spawnEpisodeContextMenu(episode, event);
}
async function handleClickRefresh() {
Expand All @@ -151,30 +142,32 @@
displayedEpisodes = searchEpisodes(query, latestEpisodes);
}, 250);
function handleClickPlaylist(event: CustomEvent<{event: MouseEvent, playlist: Playlist}>) {
function handleClickPlaylist(
event: CustomEvent<{ event: MouseEvent; playlist: Playlist }>
) {
const { event: clickEvent, playlist } = event.detail;
if (playlist.name === $queue.name && $queue.episodes.length > 0) {
currentEpisode.set($queue.episodes[0]);
updateViewState(ViewState.Player);
viewState.set(ViewState.Player);
} else {
selectedPlaylist = playlist;
displayedEpisodes = playlist.episodes;
updateViewState(ViewState.EpisodeList);
viewState.set(ViewState.EpisodeList);
}
}
</script>

<div class="podcast-view" bind:this={view}>
<div class="podcast-view" bind:this={$podcastView}>
<TopBar
bind:viewState={_viewState}
bind:viewState={$viewState}
canShowEpisodeList={true}
canShowPlayer={!!$currentEpisode}
/>

{#if _viewState === ViewState.Player}
{#if $viewState === ViewState.Player}
<EpisodePlayer />
{:else if _viewState === ViewState.EpisodeList}
{:else if $viewState === ViewState.EpisodeList}
<EpisodeList
episodes={displayedEpisodes}
showThumbnails={!selectedFeed || !selectedPlaylist}
Expand All @@ -190,7 +183,7 @@
on:click={() => {
selectedFeed = null;
displayedEpisodes = latestEpisodes;
updateViewState(ViewState.EpisodeList);
viewState.set(ViewState.EpisodeList);
}}
>
<Icon
Expand All @@ -212,7 +205,7 @@
on:click={() => {
selectedPlaylist = null;
displayedEpisodes = latestEpisodes;
updateViewState(ViewState.EpisodeList);
viewState.set(ViewState.EpisodeList);
}}
>
<Icon
Expand All @@ -224,7 +217,9 @@
size={20}
/> Latest Episodes
</span>
<div style="display: flex; align-items: center; justify-content: center;">
<div
style="display: flex; align-items: center; justify-content: center;"
>
<Icon
icon={selectedPlaylist.icon}
size={40}
Expand All @@ -237,7 +232,7 @@
{/if}
</svelte:fragment>
</EpisodeList>
{:else if _viewState === ViewState.PodcastGrid}
{:else if $viewState === ViewState.PodcastGrid}
<PodcastGrid
{feeds}
playlists={displayedPlaylists}
Expand Down

0 comments on commit e6d32a7

Please sign in to comment.