Simplify setup (#46)

This commit is contained in:
Ollie Taylor
2023-07-13 00:27:14 +01:00
committed by GitHub
parent 29ef65733e
commit d18b0c78d0
68 changed files with 1236 additions and 1470 deletions
+165
View File
@@ -0,0 +1,165 @@
import { BROWSER } from 'esm-env';
import clamp from 'just-clamp';
import { get } from 'svelte/store';
import { user_preferences, user_progress } from '../user';
import { audio_attributes, use_audio_element } from './audio-element';
import { audio_element_source } from './audio-element-source';
import { audio_metadata } from './audio-metadata';
/**
* Load audio
* @param {string} src - Audio source
* @param {import('./audio-metadata').AudioMetadata } metadata - Audio metadata
* @returns {void}
*/
const load = (src, metadata = {}, autoplay = false) => {
if (!BROWSER) return;
// Save the current progress if audio is already loaded
user_progress.save();
// Get user preferences
const preferences = get(user_preferences);
// Set audio element source
audio_element_source.set({
src,
start_at: user_progress.get(src) || 0,
playback_rate: preferences.playback_rate,
volume: preferences.volume,
autoplay: autoplay,
});
// Set metadata for current audio
audio_metadata.set(metadata);
};
/**
* Unload audio
* @returns {void}
*/
const unload = () => {
if (!BROWSER) return;
// Save the current progress if audio is already loaded
user_progress.save();
// Unset audio element source
audio_element_source.set(null);
// Unset metadata
audio_metadata.set(null);
};
/**
* @typedef {'toggle' | 'set'} HANDLE_TYPE
*/
/**
* Play audio
* @param {HANDLE_TYPE} t - Handle type
* @returns {void}
*/
const play = (t = 'set') => {
const el = use_audio_element('play');
if (t === 'toggle') {
el.paused ? el.play() : el.pause();
} else {
el.play();
}
};
/**
* Pause audio
* @param {HANDLE_TYPE} t - Handle type
* @returns {void}
*/
const pause = (t = 'set') => {
user_progress.save();
const el = use_audio_element('pause');
if (t === 'toggle') {
el.paused ? el.play() : el.pause();
} else {
el.pause();
}
};
/**
* Mute audio
* @param {HANDLE_TYPE} t - Handle type
* @returns {void}
*/
const mute = (t = 'set') => {
const el = use_audio_element('mute');
if (t === 'toggle') {
el.muted = !el.muted;
} else {
el.muted = true;
}
};
/**
* Unmute audio
* @param {HANDLE_TYPE} t - Handle type
* @returns {void}
*/
const unmute = (t = 'set') => {
const el = use_audio_element('unmute');
if (t === 'toggle') {
el.muted = !el.muted;
} else {
el.muted = false;
}
};
/**
* Seek to time audio
* @param {number} seconds - Seconds to seek
* @param {'from-start' | 'from-end'} [from="from-start"] - Seek from start or end
* @returns {void}
*/
const seek = (seconds, from = 'from-start') => {
const el = use_audio_element('seek');
if (from === 'from-end') {
el.currentTime = clamp(0, el.duration, el.duration - seconds);
} else {
el.currentTime = clamp(0, el.duration, seconds);
}
};
/**
* Skip by about audio
* @param {number} seconds - Seconds to skip
* @param {'forward' | 'backward'} [type='forward'] - Skip forward or backward
* @returns {void}
*/
const skip = (seconds, type = 'forward') => {
const el = use_audio_element('skip');
if (type === 'backward') {
el.currentTime = clamp(0, el.duration, el.currentTime - seconds);
} else {
el.currentTime = clamp(0, el.duration, el.currentTime + seconds);
}
};
/**
* Audio controls
*/
export const audio = {
subscribe: audio_attributes.subscribe,
load,
unload,
play,
pause,
mute,
unmute,
seek,
skip,
};
+15
View File
@@ -0,0 +1,15 @@
import { writable } from 'svelte/store';
// TODO: implement autoplay
/**
* @typedef {Object} AudioSource
* @property {string} src - A path or URL to the audio file
* @property {number} start_at - The starting point of the audio
* @property {number} [playback_rate=1] - The playback speed of the audio
* @property {number} [volume=1] - The volume of the audio
* @property {boolean} [autoplay=false] - Whether the audio should play as soon as it's loaded
*/
/** @type {import('svelte/store').Writable<AudioSource | null>} */
export const audio_element_source = writable(null);
+132
View File
@@ -0,0 +1,132 @@
import { BROWSER } from 'esm-env';
import { derived, get } from 'svelte/store';
import { announce } from '../internal';
import { seconds_to_timestamp } from '../utility';
import { audio_element_source } from './audio-element-source';
/**
* @typedef {import('svelte/store').Readable<HTMLAudioElement | undefined>} ReadableAudioElement
*/
/**
* @typedef {import('../user').UserPreferences} Preferences
*/
/**
* @typedef {(value: HTMLAudioElement | undefined) => void} SetAudioElement
*/
const ELEMENT_ID = 'svpod--generated-audio-element';
/** @type {ReadableAudioElement} */
export const audio_element = derived(
[audio_element_source],
([$audio_element_src], /** @type {SetAudioElement} */ set) => {
// If not in a browser environment, return early.
if (BROWSER) {
// Find an existing HTMLAudioElement or create a new one.
const found = document.getElementById(ELEMENT_ID);
const el =
found instanceof HTMLAudioElement
? found
: document.createElement('audio');
// Set the HTMLAudioElement's attributes.
el.id = ELEMENT_ID;
el.setAttribute('preload', 'metadata');
el.muted = false;
el.autoplay = false;
el.controls = false;
if ($audio_element_src) {
el.src = $audio_element_src.src;
el.currentTime = $audio_element_src.start_at;
el.playbackRate = $audio_element_src.playback_rate || 1;
el.volume = $audio_element_src.volume || 1;
el.autoplay = $audio_element_src.autoplay || false;
}
// If a new HTMLAudioElement was created, append it to the body.
if (!found) document.body.appendChild(el);
// Define a function to set the HTMLAudioElement and call it.
const handle_update = () => {
set(el);
$audio_element_src?.autoplay && el.play();
announce.info('Updating audio element');
};
handle_update();
// Add event listeners to the HTMLAudioElement to update the store when it changes.
el.addEventListener('loadeddata', handle_update);
el.addEventListener('pause', handle_update);
el.addEventListener('playing', handle_update);
el.addEventListener('timeupdate', handle_update);
// Return a function to clean up the HTMLAudioElement when the store unsubscribes.
return () => {
// Remove the event listeners from the HTMLAudioElement.
el.removeEventListener('loadeddata', handle_update);
el.removeEventListener('pause', handle_update);
el.removeEventListener('playing', handle_update);
el.removeEventListener('timeupdate', handle_update);
// Remove the HTMLAudioElement from the DOM.
el ? el.remove() : null;
};
} else {
set(undefined);
return () => null;
}
},
undefined,
);
/**
* Use audio element
* @param {string} action - Action being performed
* @returns {HTMLAudioElement} - Audio element
* @throws {string} - Error message if no audio element exists
*/
export const use_audio_element = (action) => {
const el = get(audio_element);
if (!el) {
throw announce.error(`could not ${action} :: no audio element found`);
}
return el;
};
/**
* @typedef {Object} AudioAttributes
* @property {boolean} is_loaded - Whether the audio element is loaded or not
* @property {boolean} is_paused - Whether the audio element is paused or not
* @property {number} current_time - The current time of the audio element in seconds
* @property {number} duration - The duration of the audio element in seconds
* @property {string} timestamp_current - The current time of the audio element in timestamp format (hh:mm:ss)
* @property {string} timestamp_end - The end time of the audio element in timestamp format (hh:mm:ss)
*/
/** @type {import('svelte/store').Readable<AudioAttributes>} */
export const audio_attributes = derived(audio_element, ($el, set) => {
if (!$el) {
return set({
is_loaded: false,
is_paused: true,
current_time: 0,
duration: 0,
timestamp_current: seconds_to_timestamp(0),
timestamp_end: seconds_to_timestamp(0),
});
}
set({
is_loaded: Boolean($el.src),
is_paused: $el.paused,
current_time: $el.currentTime,
duration: $el.duration,
timestamp_current: seconds_to_timestamp($el.currentTime),
timestamp_end: seconds_to_timestamp($el.duration),
});
});
+12
View File
@@ -0,0 +1,12 @@
import { writable } from 'svelte/store';
/**
* @typedef {Object.<string, unknown>} AudioMetadata
*/
/**
* Audio metadata store.
* @type {import('svelte/store').Writable<AudioMetadata | null>}
* @description This store holds the metadata for a single audio source.
*/
export const audio_metadata = writable(null);
-116
View File
@@ -1,116 +0,0 @@
<script>
import { onMount } from 'svelte';
import { get } from 'svelte/store';
import { announce } from '../internal';
import { user_preferences } from '../user';
import { audio_element } from './stores/audio-element';
$: $audio_element;
onMount(() => {
return user_preferences.subscribe((prefs) => {
const el = get(audio_element);
if (!el) return announce.warn('no audio element found');
announce.info('setting preferences :: ', prefs);
el.volume = prefs.volume;
el.playbackRate = prefs.playback_rate;
});
});
</script>
<!-- TODO: move styles to headless -->
<svelte:head>
<style>
:root {
--svpod--font: ui-sans-serif, system-ui, -apple-system,
BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
--svpod--surface--darker: rgb(0, 0, 0);
--svpod--surface--base: rgb(40, 40, 40);
--svpod--surface--lighter: rgb(60, 60, 60);
--svpod--content--darker: rgb(150, 150, 150);
--svpod--content--base: rgb(200, 200, 200);
--svpod--content--lighter: rgb(255, 255, 255);
--svpod--accent--darker: rgb(75, 15, 0);
--svpod--accent--base: rgb(180, 40, 0);
--svpod--accent--lighter: rgb(255, 55, 25);
--bg: var(--svpod--surface--darker);
--fg: var(--svpod--content--darker);
--svpod--radius--rounded: 9999px;
/* element : timeline */
--svpod--timeline-track--shape--height: 20px;
--svpod--timeline-track--shape--radius: 0;
--svpod--timeline-track--shape--border: 0;
--svpod--timeline-thumb--shape--width: 8px;
--svpod--timeline-thumb--shape--height: 32px;
--svpod--timeline-thumb--shape--radius: 6px;
--svpod--timeline-thumb--shape--border: 2px;
}
.svpod--reset {
text-transform: none;
font-style: normal;
text-indent: 0;
text-shadow: none;
text-align: left;
margin: 0;
padding: 0;
}
.svpod--a11y-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.svpod--button,
.svpod--icon-button {
--fg: var(--svpod--content--base);
--bg: var(--svpod--surface--base);
position: relative;
line-height: 1em;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: 16px;
border: 0px solid transparent;
border-radius: var(--svpod--radius--rounded);
background-color: var(--bg);
color: var(--fg);
cursor: pointer;
}
.svpod--button:hover,
.svpod--icon-button:hover {
--fg: var(--svpod--content--lighter);
--bg: var(--svpod--surface--lighter);
}
.svpod--button {
padding: 4px calc(1em);
}
.svpod--icon-button {
padding: 8px;
min-width: 48px;
min-height: 48px;
}
</style>
</svelte:head>
+4 -2
View File
@@ -3,7 +3,9 @@
* @module audio
*/
export { default as AudioContext } from './context.svelte';
export * from './actions';
export * from './audio-element';
export * from './audio-element-source';
export * from './audio-metadata';
export { default as AudioPlayer } from './player.svelte';
export { default as AudioProgress } from './progress.svelte';
export { episode_audio, episode_details, episode_progress } from './stores';
+11 -41
View File
@@ -1,71 +1,48 @@
<script>
import { onMount } from 'svelte';
import { AudioProgress } from '.';
import { episode_audio, episode_progress } from '../audio';
import { AudioProgress, audio_attributes } from '.';
import { audio } from '../audio';
import { user_preferences } from '../user';
import { seconds_to_timestamp } from '../utility';
/** @type {string|undefined} */
export let src;
/** @type {import('../audio/stores').EpisodeDetails} */
/** @type {import('./audio-metadata').AudioMetadata} */
export let metadata = {};
let mounted = false;
$: is_loaded = Boolean($episode_audio?.src);
$: is_playing = $episode_audio?.is_paused === false;
$: current_time = $episode_progress?.current_time || 0;
$: duration = $episode_audio?.duration || 0;
$: timestamp_hours = Boolean(
$episode_audio?.duration && $episode_audio.duration >= 3600,
);
$: timestamp = duration && seconds_to_timestamp(duration);
onMount(() => {
if (!src) {
mounted = true;
return;
}
episode_audio.load(src, metadata || {});
mounted = true;
});
$: mounted && src && episode_audio.load(src, metadata || {});
$: src && audio.load(src, metadata || {});
/**
* Skips the audio forward by a specified number of seconds.
* @param {number} seconds - The number of seconds to skip forward.
*/
const skip_forward = (seconds) => episode_audio.skip(seconds, 'forward');
const skip_forward = (seconds) => audio.skip(seconds, 'forward');
/**
* Skips the audio backward by a specified number of seconds.
* @param {number} seconds - The number of seconds to skip backward.
*/
const skip_back = (seconds) => episode_audio.skip(seconds, 'backward');
const skip_back = (seconds) => audio.skip(seconds, 'backward');
/**
* Seeks to a specific time in the audio.
* @param {number} value - The time to seek to, in seconds.
*/
const seek_to = (value) => episode_audio.seek(value);
const seek_to = (value) => audio.seek(value);
/**
* Toggles the audio playback between play and pause.
*/
const toggle = () => episode_audio.play('toggle');
const toggle = () => audio.play('toggle');
/**
* Starts playing the audio.
*/
const play = () => episode_audio.play('set');
const play = () => audio.play('set');
/**
* Pauses the audio.
*/
const pause = () => episode_audio.pause('set');
const pause = () => audio.pause('set');
</script>
<slot
@@ -80,12 +57,5 @@
play,
pause,
}}
episode={{
is_loaded,
is_playing,
current_time,
duration,
timestamp_hours,
timestamp,
}}
attributes={$audio_attributes}
/>
+22 -9
View File
@@ -1,6 +1,7 @@
<script>
import { episode_audio, episode_progress } from '../audio';
import clamp from 'just-clamp';
import { announce } from '../internal';
import { audio_attributes, audio_element } from './audio-element';
export let step = 10;
@@ -11,8 +12,8 @@
*/
function handle_drag_start(t) {
announce.info('drag_start :: ', t);
was_paused = $episode_audio?.is_paused || true;
episode_audio.pause();
was_paused = $audio_attributes.is_paused;
$audio_element?.pause();
}
/**
@@ -21,21 +22,33 @@
function handle_drag_end(t) {
announce.info('drag_end :: ', t);
if (was_paused) return;
else episode_audio.play();
else $audio_element?.play();
}
/**
* Seek to time audio
* @param {number} seconds - Seconds to seek
* @returns {void}
*/
const handle_seek_to = (seconds) => {
const el = $audio_element;
if (!el) return;
el.currentTime = clamp(0, el.duration, seconds);
};
</script>
{#if $episode_audio && $episode_progress}
{#if $audio_attributes.is_loaded}
<input
class={$$restProps.class}
style="display:block; width:100%;"
type="range"
data-paused={$episode_audio.is_paused ? 'true' : 'false'}
data-paused={$audio_attributes.is_paused ? 'true' : 'false'}
min={0}
{step}
max={$episode_audio.duration || step}
value={$episode_progress.current_time}
on:change={(e) => episode_audio.seek(e.currentTarget.valueAsNumber)}
max={$audio_attributes.duration}
value={$audio_attributes.current_time}
on:change={(e) => handle_seek_to(e.currentTarget.valueAsNumber)}
on:touchstart={() => handle_drag_start('touchstart')}
on:mousedown={() => handle_drag_start('mousedown')}
on:touchend={() => handle_drag_end('touchend')}
-54
View File
@@ -1,54 +0,0 @@
/**
* @typedef {Object} UserPreferences
* @property {number} playback_rate - The playback rate of the audio element.
* @property {number} volume - The volume of the audio element.
*/
import { BROWSER } from 'esm-env';
import { onMount } from 'svelte';
import { get, readable } from 'svelte/store';
import { user_preferences } from '../../user';
/**
* A readable store that provides an HTMLAudioElement instance.
* @type {import('svelte/store').Readable<HTMLAudioElement | undefined>}
* @exports audio_element
*/
export const audio_element = readable(undefined, (set) => {
if (!BROWSER) return;
const ID = 'svpod--generated-audio-element';
onMount(() => {
const existing_element = document.getElementById(ID);
/** @type {HTMLAudioElement} */
const el =
existing_element instanceof HTMLAudioElement
? existing_element
: document.createElement('audio');
el.id = ID;
el.setAttribute('preload', 'metadata');
el.muted = false;
el.autoplay = false;
el.controls = false;
/** @type {UserPreferences} */
const preferences = get(user_preferences);
el.playbackRate = preferences.playback_rate;
el.volume = preferences.volume;
if (!existing_element) document.body.appendChild(el);
/*
BUG: This is a hack to get around poor types.
The input and output types are correct, so this is safe for now.
*/
// @ts-expect-error - I'm not sure how to fix this using JSDOC
set(el);
});
return () => {
const element = document.getElementById(ID);
element ? element.remove() : null;
};
});
-225
View File
@@ -1,225 +0,0 @@
import clamp from 'just-clamp';
import { derived, get } from 'svelte/store';
import { user_preferences, user_progress } from '../../user';
import { announce } from '../../internal';
import { audio_element } from './audio-element';
import { episode_details } from './episode-details';
/**
* Episode state object
* @typedef {Object} EpisodeState
* @property {boolean} will_autoplay - Whether the episode will autoplay
* @property {boolean} is_paused - Whether the episode is paused
* @property {number} duration - The duration of the episode
* @property {string} src - The source of the episode
* @property {number} start_at - The starting point of the episode
* @property {import('./episode-details').EpisodeDetails | null} details - The details of the episode or null if there are none
*/
/** @type {Omit<EpisodeState, 'src'>} */
const default_episode_state = {
will_autoplay: false,
is_paused: true,
duration: 0,
start_at: 0,
details: null,
};
/**
* Episode state store
* @type {import('svelte/store').Readable<EpisodeState | null>}
*/
const episode_state = derived(
[audio_element, episode_details],
([$audio, $details], set) => {
if (!$audio) return set(null);
function set_value() {
if (!$audio?.src) return null;
set({
...default_episode_state,
src: $audio.src,
duration: $audio.duration,
details: $details,
will_autoplay: $audio.autoplay,
is_paused: $audio.paused,
start_at: user_progress.get($audio.src) ?? 0,
});
}
$audio.addEventListener('loadeddata', set_value);
$audio.addEventListener('pause', set_value);
$audio.addEventListener('playing', set_value);
return () => {
$audio.removeEventListener('loadeddata', set_value);
$audio.removeEventListener('pause', set_value);
$audio.removeEventListener('playing', set_value);
};
},
);
/**
* @typedef {'toggle' | 'set'} HANDLE_TYPE
*/
/**
* Use audio element
* @param {string} action - Action to perform
* @returns {HTMLAudioElement} - Audio element
* @throws {string} - Error message if no audio element exists
*/
const use_element = (action) => {
const el = get(audio_element);
if (!el)
throw announce.warn(`could not ${action} :: no audio element exists yet`);
return el;
};
/**
* Load audio
* @param {string} src - Audio source
* @param {import('./episode-details').EpisodeDetails } details - Episode details
* @returns {void}
*/
const load_audio = (src, details) => {
user_progress.save();
const el = use_element('load');
el.src = src;
el.muted = false;
// using user_progress
const start_at = user_progress.get(src) || 0;
el.currentTime = start_at;
// using user_preferences
const preferences = get(user_preferences);
el.playbackRate = preferences.playback_rate;
el.volume = preferences.volume;
episode_details.set(details);
};
/**
* Unload audio
* @returns {void}
*/
const unload_audio = () => {
user_progress.save();
const el = use_element('unload');
el.src = '';
episode_details.set(null);
};
/**
* Play audio
* @param {HANDLE_TYPE} t - Handle type
* @returns {void}
*/
const play_audio = (t = 'set') => {
const el = use_element('play');
if (t === 'toggle') {
el.paused ? el.play() : el.pause();
} else {
el.play();
}
};
/**
* Pause audio
* @param {HANDLE_TYPE} t - Handle type
* @returns {void}
*/
const pause_audio = (t = 'set') => {
user_progress.save();
const el = use_element('pause');
if (t === 'toggle') {
el.paused ? el.play() : el.pause();
} else {
el.pause();
}
};
/**
* Mute audio
* @param {HANDLE_TYPE} t - Handle type
* @returns {void}
*/
const mute_audio = (t = 'set') => {
const el = use_element('mute');
if (t === 'toggle') {
el.muted = !el.muted;
} else {
el.muted = true;
}
};
/**
* Unmute audio
* @param {HANDLE_TYPE} t - Handle type
* @returns {void}
*/
const unmute_audio = (t = 'set') => {
const el = use_element('unmute');
if (t === 'toggle') {
el.muted = !el.muted;
} else {
el.muted = false;
}
};
/**
* Seek to time audio
* @param {number} seconds - Seconds to seek
* @param {'from-start' | 'from-end'} [from="from-start"] - Seek from start or end
* @returns {void}
*/
const seek_audio = (seconds, from = 'from-start') => {
const el = use_element('seek');
if (from === 'from-end') {
el.currentTime = clamp(0, el.duration, el.duration - seconds);
} else {
el.currentTime = clamp(0, el.duration, seconds);
}
};
/**
* Skip by about audio
* @param {number} seconds - Seconds to skip
* @param {'forward' | 'backward'} [type='forward'] - Skip forward or backward
* @returns {void}
*/
const skip_audio = (seconds, type = 'forward') => {
const el = use_element('skip');
if (type === 'backward') {
el.currentTime = clamp(0, el.duration, el.currentTime - seconds);
} else {
el.currentTime = clamp(0, el.duration, el.currentTime + seconds);
}
};
/**
* Episode audio object
*/
export const episode_audio = {
/**
* Subscribes to episode audio store
*/
subscribe: episode_state.subscribe,
load: load_audio,
unload: unload_audio,
play: play_audio,
pause: pause_audio,
mute: mute_audio,
unmute: unmute_audio,
seek: seek_audio,
skip: skip_audio,
};
-12
View File
@@ -1,12 +0,0 @@
import { writable } from 'svelte/store';
/**
* @typedef {Object.<string, unknown>} EpisodeDetails
*/
/**
* Episode details store.
* @type {import('svelte/store').Writable<EpisodeDetails | null>}
* @description This store holds the metadata for a single episode.
*/
export const episode_details = writable(null);
-48
View File
@@ -1,48 +0,0 @@
import { seconds_to_timestamp } from 'svelte-podcast/utility';
import { audio_element } from './audio-element';
import { derived } from 'svelte/store';
/**
* Episode progress object.
* @typedef {Object} EpisodeProgress
* @property {number} current_time - The current time of the episode in seconds.
* @property {string} timestamp - The current time of the episode in the format 'mm:ss'.
* @property {boolean} has_ended - Whether the episode has ended or not.
*/
/**
* The default episode progress object.
* @type {EpisodeProgress}
*/
const default_episode_progress = {
current_time: 0,
timestamp: '00:00',
has_ended: false,
};
/**
* The default episode progress object.
* @type {import('svelte/store').Readable<EpisodeProgress>}
*/
export const episode_progress = derived(audio_element, ($audio, set) => {
if (!$audio) return set(default_episode_progress);
/**
* Set episode progress value.
*/
function set_value() {
if (!$audio) return;
set({
current_time: $audio.currentTime,
timestamp: seconds_to_timestamp($audio.currentTime),
has_ended: $audio.ended,
});
}
$audio.addEventListener('timeupdate', () => set_value());
/**
* Remove event listener.
*/
return () => $audio.removeEventListener('timeupdate', () => set_value());
});
-4
View File
@@ -1,4 +0,0 @@
export * from './audio-element';
export * from './episode-data';
export * from './episode-details';
export * from './episode-progress';
+6 -3
View File
@@ -1,4 +1,7 @@
export * from './audio';
export { AudioPlayer as default } from './audio';
import { default as AudioPlayer } from './audio/player.svelte';
export * from './audio/actions';
export * from './user';
export * from './utility';
export { AudioPlayer };
export default AudioPlayer;
+5 -5
View File
@@ -1,7 +1,7 @@
import { persisted } from 'svelte-local-storage-store';
import { get } from 'svelte/store';
import { episode_audio, episode_progress } from '../audio';
import { use_url, announce } from '../internal';
import { audio_element } from '../audio';
import { announce, use_url } from '../internal';
/**
* User progress object type.
@@ -14,7 +14,7 @@ import { use_url, announce } from '../internal';
* @type {UserProgress}
*/
const DEFAULT_USER_PROGRESS = {};
import {} from 'svelte-local-storage-store';
/**
* User progress store
* @type {import('svelte/store').Writable<UserProgress>}
@@ -27,13 +27,13 @@ const USER_PROGRESS_STORE = persisted('USER_PROGRESS', DEFAULT_USER_PROGRESS);
* @returns {void}
*/
const save_user_progress = () => {
const audio = get(episode_audio);
const audio = get(audio_element);
if (!audio?.src) return;
announce.info('saving progress: ', audio.src);
const pathname = use_url(audio.src).pathname;
const current_time = get(episode_progress).current_time;
const current_time = audio.currentTime;
USER_PROGRESS_STORE.update((prev) => ({
...prev,
[pathname]: current_time,