Fix episode attributes (#34)
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
'svelte-podcast': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Improvements to audio stores:
|
||||||
|
|
||||||
|
- Adds missing `is_paused` value.
|
||||||
|
- Fixes to `start_at` value
|
||||||
|
- Refactors localStorage stores & user preferences
|
||||||
+5
-4
@@ -37,7 +37,8 @@
|
|||||||
"svelte": "^3.54.0"
|
"svelte": "^3.54.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"just-clamp": "^4.2.0"
|
"just-clamp": "^4.2.0",
|
||||||
|
"svelte-local-storage-store": "^0.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/cli": "^2.26.0",
|
"@changesets/cli": "^2.26.0",
|
||||||
@@ -47,14 +48,14 @@
|
|||||||
"@sveltejs/package": "^2.0.0",
|
"@sveltejs/package": "^2.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||||
"@typescript-eslint/parser": "^5.45.0",
|
"@typescript-eslint/parser": "^5.45.0",
|
||||||
|
"eslint": "^8.28.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-svelte3": "^4.0.0",
|
"eslint-plugin-svelte3": "^4.0.0",
|
||||||
"eslint": "^8.28.0",
|
|
||||||
"prettier-plugin-svelte": "^2.8.1",
|
|
||||||
"prettier": "^2.8.0",
|
"prettier": "^2.8.0",
|
||||||
|
"prettier-plugin-svelte": "^2.8.1",
|
||||||
"publint": "^0.1.9",
|
"publint": "^0.1.9",
|
||||||
"svelte-check": "^3.0.1",
|
|
||||||
"svelte": "^3.54.0",
|
"svelte": "^3.54.0",
|
||||||
|
"svelte-check": "^3.0.1",
|
||||||
"tslib": "^2.4.1",
|
"tslib": "^2.4.1",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^4.9.3",
|
||||||
"vite": "^4.0.0"
|
"vite": "^4.0.0"
|
||||||
|
|||||||
@@ -1,18 +1,27 @@
|
|||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
|
import { user_preferences } from '$lib/user';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { readable } from 'svelte/store';
|
import { get, readable } from 'svelte/store';
|
||||||
|
|
||||||
export const audio_element = readable<HTMLAudioElement | null>(null, (set) => {
|
export const audio_element = readable<HTMLAudioElement | null>(null, (set) => {
|
||||||
if (!browser) return;
|
if (!browser) return;
|
||||||
const ID = 'svelte-podcast-generated-audio-element';
|
const ID = 'svelte-podcast-generated-audio-element';
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const el = document.createElement('audio');
|
const existing_element = document.getElementById(ID) as HTMLAudioElement | null;
|
||||||
|
const el = existing_element || document.createElement('audio');
|
||||||
|
|
||||||
el.id = ID;
|
el.id = ID;
|
||||||
el.setAttribute('preload', 'metadata');
|
el.setAttribute('preload', 'metadata');
|
||||||
el.muted = false;
|
el.muted = false;
|
||||||
el.autoplay = true;
|
el.autoplay = true;
|
||||||
el.controls = true;
|
el.controls = false;
|
||||||
document.body.appendChild(el);
|
|
||||||
|
const preferences = get(user_preferences);
|
||||||
|
el.playbackRate = preferences.playback_rate;
|
||||||
|
el.volume = preferences.volume;
|
||||||
|
|
||||||
|
if (!existing_element) document.body.appendChild(el);
|
||||||
set(el);
|
set(el);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -21,5 +30,3 @@ export const audio_element = readable<HTMLAudioElement | null>(null, (set) => {
|
|||||||
element ? element.remove() : null;
|
element ? element.remove() : null;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AudioElementStore = typeof audio_element;
|
|
||||||
|
|||||||
@@ -1,13 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { audio_element } from '$lib/audio/audio-element';
|
import { audio_element } from '$lib/audio/audio-element';
|
||||||
import { load_podcast_state } from '$lib/utility';
|
import { user_preferences } from '$lib/user';
|
||||||
import { info } from '$lib/utility/package/log';
|
import { info, warn } from '$lib/utility/package/log';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
|
$: $audio_element;
|
||||||
|
/* onMount(() => {
|
||||||
|
return audio_element.subscribe((el) => {
|
||||||
|
if (el) info('mounted audio element :: ', el);
|
||||||
|
});
|
||||||
|
}); */
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
load_podcast_state();
|
return user_preferences.subscribe((prefs) => {
|
||||||
return audio_element.subscribe((el) => {
|
const el = get(audio_element);
|
||||||
info('loaded audio :: ', el);
|
if (!el) return warn('no audio element found');
|
||||||
|
info('setting preferences :: ', prefs);
|
||||||
|
el.volume = prefs.volume;
|
||||||
|
el.playbackRate = prefs.playback_rate;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
import { audio_element, type AudioElementStore } from '$lib/audio/audio-element';
|
import { audio_element } from '$lib/audio/audio-element';
|
||||||
import { episode_details, type EpisodeDetailsStore } from '$lib/audio/episode-details';
|
import { episode_details } from '$lib/audio/episode-details';
|
||||||
import { user_preferences } from '$lib/preferences';
|
|
||||||
import { podcast_progress } from '$lib/progress';
|
|
||||||
import type { EpisodeAttributes, EpisodeDetails } from '$lib/types';
|
import type { EpisodeAttributes, EpisodeDetails } from '$lib/types';
|
||||||
|
import { user_preferences, user_progress } from '$lib/user';
|
||||||
import { warn } from '$lib/utility/package/log';
|
import { warn } from '$lib/utility/package/log';
|
||||||
import clamp from 'just-clamp';
|
import clamp from 'just-clamp';
|
||||||
import { derived, get } from 'svelte/store';
|
import { derived, get, type Readable } from 'svelte/store';
|
||||||
|
|
||||||
const default_episode_attributes = {
|
const default_episode_attributes = {
|
||||||
will_autoplay: false,
|
will_autoplay: false,
|
||||||
is_muted: false,
|
is_paused: true,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
start_at: 0,
|
start_at: 0,
|
||||||
details: null,
|
details: null,
|
||||||
} satisfies Omit<EpisodeAttributes, 'src'>;
|
} satisfies Omit<EpisodeAttributes, 'src'>;
|
||||||
|
|
||||||
const episode_attributes = derived<
|
type EpisodeAttributesStore = Readable<EpisodeAttributes | null>;
|
||||||
[AudioElementStore, EpisodeDetailsStore],
|
|
||||||
EpisodeAttributes | null
|
const episode_attributes = derived([audio_element, episode_details], ([$audio, $details], set) => {
|
||||||
>([audio_element, episode_details], ([$audio, $details], set) => {
|
|
||||||
if (!$audio) return set(null);
|
if (!$audio) return set(null);
|
||||||
|
|
||||||
function set_value() {
|
function set_value() {
|
||||||
@@ -28,12 +26,22 @@ const episode_attributes = derived<
|
|||||||
src: $audio.src,
|
src: $audio.src,
|
||||||
duration: $audio.duration,
|
duration: $audio.duration,
|
||||||
details: $details,
|
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('loadeddata', set_value);
|
||||||
return () => $audio.removeEventListener('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);
|
||||||
|
};
|
||||||
|
}) satisfies EpisodeAttributesStore;
|
||||||
|
|
||||||
type HandleType = 'toggle' | 'set';
|
type HandleType = 'toggle' | 'set';
|
||||||
|
|
||||||
@@ -42,13 +50,17 @@ const no_element = (action: string) => warn(`could not ${action} :: no audio ele
|
|||||||
export const episode_audio = {
|
export const episode_audio = {
|
||||||
subscribe: episode_attributes.subscribe,
|
subscribe: episode_attributes.subscribe,
|
||||||
load: (src: string, details: EpisodeDetails) => {
|
load: (src: string, details: EpisodeDetails) => {
|
||||||
podcast_progress.stash_episode();
|
user_progress.save();
|
||||||
const el = get(audio_element);
|
const el = get(audio_element);
|
||||||
if (!el) return no_element('load');
|
if (!el) return no_element('load');
|
||||||
const progress = podcast_progress.get_episode(src);
|
|
||||||
el.src = src;
|
el.src = src;
|
||||||
el.currentTime = progress?.start_at || 0;
|
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);
|
const preferences = get(user_preferences);
|
||||||
el.playbackRate = preferences.playback_rate;
|
el.playbackRate = preferences.playback_rate;
|
||||||
el.volume = preferences.volume;
|
el.volume = preferences.volume;
|
||||||
@@ -56,7 +68,7 @@ export const episode_audio = {
|
|||||||
episode_details.set(details);
|
episode_details.set(details);
|
||||||
},
|
},
|
||||||
unload: () => {
|
unload: () => {
|
||||||
podcast_progress.stash_episode();
|
user_progress.save();
|
||||||
const el = get(audio_element);
|
const el = get(audio_element);
|
||||||
if (!el) return no_element('unload');
|
if (!el) return no_element('unload');
|
||||||
el.src = '';
|
el.src = '';
|
||||||
@@ -73,7 +85,7 @@ export const episode_audio = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
pause: (t?: HandleType) => {
|
pause: (t?: HandleType) => {
|
||||||
podcast_progress.stash_episode();
|
user_progress.save();
|
||||||
const el = get(audio_element);
|
const el = get(audio_element);
|
||||||
if (!el) return no_element('pause');
|
if (!el) return no_element('pause');
|
||||||
|
|
||||||
|
|||||||
@@ -2,4 +2,3 @@ import type { EpisodeDetails } from '$lib/types';
|
|||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
export const episode_details = writable<EpisodeDetails | null>(null);
|
export const episode_details = writable<EpisodeDetails | null>(null);
|
||||||
export type EpisodeDetailsStore = typeof episode_details;
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { audio_element, type AudioElementStore } from '$lib/audio/audio-element';
|
import { audio_element } from '$lib/audio/audio-element';
|
||||||
import type { EpisodeProgress } from '$lib/types';
|
import type { EpisodeProgress } from '$lib/types';
|
||||||
import { secondsToTimestamp } from '$lib/utility';
|
import { secondsToTimestamp } from '$lib/utility';
|
||||||
import { derived } from 'svelte/store';
|
import { derived, type Readable } from 'svelte/store';
|
||||||
|
|
||||||
const default_episode_progress = {
|
const default_episode_progress = {
|
||||||
current_time: 0,
|
current_time: 0,
|
||||||
@@ -9,9 +9,9 @@ const default_episode_progress = {
|
|||||||
has_ended: false,
|
has_ended: false,
|
||||||
} satisfies EpisodeProgress;
|
} satisfies EpisodeProgress;
|
||||||
|
|
||||||
export const episode_progress = derived<AudioElementStore, EpisodeProgress>(
|
type ProgressStore = Readable<EpisodeProgress>;
|
||||||
audio_element,
|
|
||||||
($audio, set) => {
|
export const episode_progress = derived(audio_element, ($audio, set) => {
|
||||||
if (!$audio) return set(default_episode_progress);
|
if (!$audio) return set(default_episode_progress);
|
||||||
|
|
||||||
function set_value() {
|
function set_value() {
|
||||||
@@ -25,5 +25,4 @@ export const episode_progress = derived<AudioElementStore, EpisodeProgress>(
|
|||||||
|
|
||||||
$audio.addEventListener('timeupdate', () => set_value());
|
$audio.addEventListener('timeupdate', () => set_value());
|
||||||
return () => $audio.removeEventListener('timeupdate', () => set_value());
|
return () => $audio.removeEventListener('timeupdate', () => set_value());
|
||||||
},
|
}) satisfies ProgressStore;
|
||||||
);
|
|
||||||
|
|||||||
+1
-2
@@ -1,4 +1,3 @@
|
|||||||
export * from '$lib/audio';
|
export * from '$lib/audio';
|
||||||
export * from '$lib/preferences';
|
export * from '$lib/user';
|
||||||
export * from '$lib/progress';
|
|
||||||
export * from '$lib/utility';
|
export * from '$lib/utility';
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import { browser } from '$app/environment';
|
|
||||||
import { user_preferences } from '$lib/preferences/db-state';
|
|
||||||
|
|
||||||
import type { UserPreferences } from '$lib/types';
|
|
||||||
import { info, warn } from '$lib/utility/package/log';
|
|
||||||
import { get } from 'svelte/store';
|
|
||||||
|
|
||||||
const USER_PREFERENCE_KEY = 'USER_PREFERENCE' as const;
|
|
||||||
|
|
||||||
export function save_preferences() {
|
|
||||||
if (!browser || !localStorage) {
|
|
||||||
warn('localStorage not available, skipping save');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const preferences = get(user_preferences);
|
|
||||||
info(`Saving user preferences to localStorage`, preferences);
|
|
||||||
localStorage.setItem(USER_PREFERENCE_KEY, JSON.stringify(preferences));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function load_preferences() {
|
|
||||||
if (!browser || !localStorage) {
|
|
||||||
warn('localStorage not available, skipping load');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = localStorage.getItem(USER_PREFERENCE_KEY);
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
info('No saved user preferences found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const preferences = JSON.parse(data) as UserPreferences;
|
|
||||||
user_preferences.edit(preferences);
|
|
||||||
|
|
||||||
info(`Loaded user preferences from localStorage`, preferences);
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import type { UserPreferences } from '$lib/types';
|
|
||||||
import clamp from 'just-clamp';
|
|
||||||
import { writable } from 'svelte/store';
|
|
||||||
import { load_preferences, save_preferences } from './db-local-storage';
|
|
||||||
|
|
||||||
const _default_user_preferences = { playback_rate: 1, volume: 1 } satisfies UserPreferences;
|
|
||||||
const _user_preferences = writable<UserPreferences>(_default_user_preferences);
|
|
||||||
|
|
||||||
const clamp_preferences = (prefs: UserPreferences) => ({
|
|
||||||
playback_rate: clamp(prefs.playback_rate, 0.5, 5),
|
|
||||||
volume: clamp(prefs.volume, 0, 1),
|
|
||||||
});
|
|
||||||
|
|
||||||
const edit = (prefs: Partial<UserPreferences>) => {
|
|
||||||
_user_preferences.update((prev) => clamp_preferences({ ...prev, ...prefs }));
|
|
||||||
save_preferences();
|
|
||||||
};
|
|
||||||
|
|
||||||
const clear = () => {
|
|
||||||
_user_preferences.set(_default_user_preferences);
|
|
||||||
save_preferences();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const user_preferences = {
|
|
||||||
subscribe: _user_preferences.subscribe,
|
|
||||||
edit,
|
|
||||||
clear,
|
|
||||||
load: load_preferences,
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './db-state';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export const _episode_progress_map = new Map<string, number>();
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { browser } from '$app/environment';
|
|
||||||
import { _episode_progress_map } from '$lib/progress/_private';
|
|
||||||
import { error, info, warn } from '$lib/utility/package/log';
|
|
||||||
|
|
||||||
type SavedEpisodeProgress = { src: string; current_time: number };
|
|
||||||
const EPISODE_PROGRESS_KEY = 'EPISODE_PROGRESS' as const;
|
|
||||||
|
|
||||||
export function save_podcast_progress() {
|
|
||||||
if (!browser || !localStorage) {
|
|
||||||
warn('localStorage not available, skipping save');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const items = [..._episode_progress_map];
|
|
||||||
|
|
||||||
console.log('items', items);
|
|
||||||
|
|
||||||
const episodes = items.reduce((prev, [src, current_time]) => {
|
|
||||||
return [...prev, { src, current_time }];
|
|
||||||
}, [] as SavedEpisodeProgress[]);
|
|
||||||
|
|
||||||
info(`Saving progress for ${episodes.length} episodes to localStorage`, episodes);
|
|
||||||
localStorage.setItem(EPISODE_PROGRESS_KEY, JSON.stringify(episodes));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function load_podcast_progress() {
|
|
||||||
if (!browser || !localStorage) {
|
|
||||||
warn('localStorage not available, skipping load');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = localStorage.getItem(EPISODE_PROGRESS_KEY);
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
info('No saved episode progress found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const episodes = JSON.parse(data) as SavedEpisodeProgress[];
|
|
||||||
|
|
||||||
try {
|
|
||||||
episodes.forEach(({ src, current_time }) => {
|
|
||||||
_episode_progress_map.set(src, current_time);
|
|
||||||
});
|
|
||||||
info(`Loaded ${_episode_progress_map.size} episodes progress from localStorage`);
|
|
||||||
} catch (e) {
|
|
||||||
error('Error loading episode progress', e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import { episode_audio, episode_progress } from '$lib/audio';
|
|
||||||
import { _episode_progress_map } from '$lib/progress/_private';
|
|
||||||
import type { AudioLoadOptions } from '$lib/types';
|
|
||||||
import { info } from '$lib/utility/package/log';
|
|
||||||
import { get } from 'svelte/store';
|
|
||||||
import { load_podcast_progress, save_podcast_progress } from './db-local-storage';
|
|
||||||
|
|
||||||
const get_src_pathname = (src: string) => {
|
|
||||||
if (src.startsWith('http')) return new URL(src).pathname;
|
|
||||||
else return new URL(src, 'https://svelte.dev').pathname;
|
|
||||||
};
|
|
||||||
|
|
||||||
const stash_episode_progress = () => {
|
|
||||||
const audio = get(episode_audio);
|
|
||||||
console.log('audio', audio);
|
|
||||||
if (!audio?.src) return;
|
|
||||||
info('saving progress: ', audio.src);
|
|
||||||
const pathname = get_src_pathname(audio.src);
|
|
||||||
_episode_progress_map.set(pathname, get(episode_progress).current_time);
|
|
||||||
save_podcast_progress();
|
|
||||||
};
|
|
||||||
|
|
||||||
const get_episode_progress = (src: string): Pick<AudioLoadOptions, 'start_at'> | null => {
|
|
||||||
const start_at = _episode_progress_map.get(src);
|
|
||||||
|
|
||||||
if (!start_at) return null;
|
|
||||||
|
|
||||||
info('found saved progress: ', src, start_at);
|
|
||||||
|
|
||||||
return { start_at: start_at };
|
|
||||||
};
|
|
||||||
|
|
||||||
const clear = () => {
|
|
||||||
_episode_progress_map.clear();
|
|
||||||
save_podcast_progress();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const podcast_progress = {
|
|
||||||
stash_episode: stash_episode_progress,
|
|
||||||
get_episode: get_episode_progress,
|
|
||||||
load: load_podcast_progress,
|
|
||||||
clear,
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './db-state';
|
|
||||||
+5
-1
@@ -6,7 +6,7 @@ export type EpisodeProgress = { current_time: number; timestamp: string; has_end
|
|||||||
|
|
||||||
export type EpisodeAttributes = {
|
export type EpisodeAttributes = {
|
||||||
will_autoplay: boolean;
|
will_autoplay: boolean;
|
||||||
is_muted: boolean;
|
is_paused: boolean;
|
||||||
duration: number;
|
duration: number;
|
||||||
src: string;
|
src: string;
|
||||||
start_at: number;
|
start_at: number;
|
||||||
@@ -22,3 +22,7 @@ export interface UserPreferences {
|
|||||||
playback_rate: number;
|
playback_rate: number;
|
||||||
volume: number;
|
volume: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UserProgress = {
|
||||||
|
[key: string]: number;
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './preferences';
|
||||||
|
export * from './progress';
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import type { UserPreferences } from '$lib/types';
|
||||||
|
import clamp from 'just-clamp';
|
||||||
|
import { persisted } from 'svelte-local-storage-store';
|
||||||
|
|
||||||
|
const _default_user_preferences = { playback_rate: 1, volume: 1 } satisfies UserPreferences;
|
||||||
|
const _user_preferences = persisted<UserPreferences>('USER_PREFERENCE', _default_user_preferences);
|
||||||
|
|
||||||
|
export const user_preferences = {
|
||||||
|
subscribe: _user_preferences.subscribe,
|
||||||
|
set: {
|
||||||
|
playback_rate: (value: number) => {
|
||||||
|
const playback_rate = clamp(value, 0.5, 5);
|
||||||
|
return _user_preferences.update((prefs) => ({ ...prefs, playback_rate }));
|
||||||
|
},
|
||||||
|
volume: (value: number) => {
|
||||||
|
const volume = clamp(value, 0, 1);
|
||||||
|
return _user_preferences.update((prefs) => ({ ...prefs, volume }));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
clear: () => _user_preferences.set(_default_user_preferences),
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { episode_audio, episode_progress } from '$lib/audio';
|
||||||
|
import type { UserProgress } from '$lib/types';
|
||||||
|
import { get_pathname_from_url } from '$lib/utility/get-pathname-from-url';
|
||||||
|
import { info } from '$lib/utility/package/log';
|
||||||
|
import { persisted } from 'svelte-local-storage-store';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
|
const _default_user_progress = {} satisfies UserProgress;
|
||||||
|
const _user_progress = persisted<UserProgress>('USER_PROGRESS', _default_user_progress);
|
||||||
|
|
||||||
|
const save = () => {
|
||||||
|
const audio = get(episode_audio);
|
||||||
|
if (!audio?.src) return;
|
||||||
|
|
||||||
|
info('saving progress: ', audio.src);
|
||||||
|
|
||||||
|
const pathname = get_pathname_from_url(audio.src);
|
||||||
|
const current_time = get(episode_progress).current_time;
|
||||||
|
_user_progress.update((prev) => ({ ...prev, [pathname]: current_time }));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const user_progress = {
|
||||||
|
subscribe: _user_progress.subscribe,
|
||||||
|
get: (src: string) => get(_user_progress)[get_pathname_from_url(src)],
|
||||||
|
save,
|
||||||
|
clear: () => _user_progress.set(_default_user_progress),
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export const get_pathname_from_url = (src: string) => {
|
||||||
|
if (src.startsWith('http')) return new URL(src).pathname;
|
||||||
|
else return new URL(src, 'https://svelte.dev').pathname;
|
||||||
|
};
|
||||||
@@ -1,2 +1 @@
|
|||||||
export * from './seconds-to-timestamp';
|
export * from './seconds-to-timestamp';
|
||||||
export * from './use-state';
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { user_preferences } from '$lib/preferences';
|
|
||||||
import { podcast_progress } from '$lib/progress';
|
|
||||||
|
|
||||||
export function load_podcast_state() {
|
|
||||||
podcast_progress.load();
|
|
||||||
user_preferences.load();
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { podcast_progress, user_preferences } from '$lib';
|
import { episode_audio, episode_progress, user_preferences, user_progress } from '$lib';
|
||||||
import { episode_audio, episode_progress } from '$lib/audio';
|
|
||||||
|
|
||||||
const sources = {
|
const sources = {
|
||||||
syntax: {
|
syntax: {
|
||||||
@@ -31,7 +30,7 @@
|
|||||||
|
|
||||||
<h1>Demo</h1>
|
<h1>Demo</h1>
|
||||||
<a href="/another-page">Another Page</a>
|
<a href="/another-page">Another Page</a>
|
||||||
<button type="button" on:click={podcast_progress.clear}>Clear progress for all episodes</button>
|
<button type="button" on:click={user_progress.clear}>Clear progress for all episodes</button>
|
||||||
<button type="button" on:click={user_preferences.clear}>Clear all preferences</button>
|
<button type="button" on:click={user_preferences.clear}>Clear all preferences</button>
|
||||||
|
|
||||||
<h5>Load Audio</h5>
|
<h5>Load Audio</h5>
|
||||||
@@ -77,7 +76,7 @@
|
|||||||
<h6>Playback Rate</h6>
|
<h6>Playback Rate</h6>
|
||||||
|
|
||||||
{#each [0.5, 1, 2, 3] as rate}
|
{#each [0.5, 1, 2, 3] as rate}
|
||||||
<button type="button" on:click={() => user_preferences.edit({ playback_rate: rate })}>
|
<button type="button" on:click={() => user_preferences.set.playback_rate(rate)}>
|
||||||
{rate}x
|
{rate}x
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -2799,6 +2799,11 @@ svelte-hmr@^0.15.1:
|
|||||||
resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.15.1.tgz#d11d878a0bbb12ec1cba030f580cd2049f4ec86b"
|
resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.15.1.tgz#d11d878a0bbb12ec1cba030f580cd2049f4ec86b"
|
||||||
integrity sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==
|
integrity sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==
|
||||||
|
|
||||||
|
svelte-local-storage-store@^0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/svelte-local-storage-store/-/svelte-local-storage-store-0.4.0.tgz#965e342582555b606102045e86662d6c5864e1e6"
|
||||||
|
integrity sha512-ctPykTt4S3BE5bF0mfV0jKiUR1qlmqLvnAkQvYHLeb9wRyO1MdIFDVI23X+TZEFleATHkTaOpYZswIvf3b2tWA==
|
||||||
|
|
||||||
svelte-preprocess@^5.0.0:
|
svelte-preprocess@^5.0.0:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/svelte-preprocess/-/svelte-preprocess-5.0.1.tgz#3dd21a17eb508347d4b26a0d98059d23e2d1b9a0"
|
resolved "https://registry.yarnpkg.com/svelte-preprocess/-/svelte-preprocess-5.0.1.tgz#3dd21a17eb508347d4b26a0d98059d23e2d1b9a0"
|
||||||
|
|||||||
Reference in New Issue
Block a user