Cleanup lib exports (#50)

This commit is contained in:
Ollie Taylor
2023-07-29 17:57:14 +01:00
committed by GitHub
parent bba24215eb
commit 528457fe59
59 changed files with 697 additions and 853 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
<meta name="theme-color" content="#f97316" />
<meta name="color-scheme" content="light" />
<link rel="bookmark" title="svelte-podcast Docs" />
<link rel="bookmark" title="Svelte Podcast Docs" />
%sveltekit.head%
</head>
+14 -14
View File
@@ -1,4 +1,4 @@
<script>
<script lang="ts">
import {
LoadingSpinner,
Pause,
@@ -8,14 +8,8 @@
} from '$src/components/icon';
import { AudioPlayer, user_preferences } from 'svelte-podcast';
/** @type {string | undefined} */
export let src;
export let skip_back = 30;
export let skip_forward = 10;
/** @type {import('svelte-podcast/audio').AudioMetadata} */
export let metadata = {};
export let playback_rate_values = [1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.4];
</script>
@@ -37,14 +31,20 @@
</style>
</svelte:head>
<AudioPlayer {src} {metadata} let:Player let:action let:attributes>
<AudioPlayer
let:PlayerProgress
let:skip_by
let:play
let:preference
let:attributes
>
<div
class="svpod-container {$$restProps.class}"
data-loaded={attributes.is_loaded ? 'true' : 'false'}
>
<!-- Player Action: Skip back -->
<button
on:click={() => action.skip_back(skip_back)}
on:click={() => skip_by(skip_back, 'backward')}
class="svpod-skip"
type="button"
aria-label="Skip back {skip_back} seconds"
@@ -62,7 +62,7 @@
<!-- Player Action: Toggle play / pause -->
{#if attributes.is_loaded}
<button
on:click={() => action.toggle()}
on:click={() => play('toggle')}
class="svpod-toggle"
type="button"
aria-label={attributes.is_paused ? 'Play' : 'Pause'}
@@ -70,7 +70,7 @@
<svelte:component this={attributes.is_paused ? Play : Pause} />
</button>
{:else}
<div class="svpod-toggle">
<div class="svpod-toggle text-white">
<LoadingSpinner />
<span class="svpod--a11y">Waiting for audio...</span>
</div>
@@ -78,7 +78,7 @@
<!-- Player Action: Skip forward -->
<button
on:click={() => action.skip_forward(skip_forward)}
on:click={() => skip_by(skip_forward, 'forward')}
class="svpod-skip"
type="button"
aria-label="Skip forward {skip_forward} seconds"
@@ -102,7 +102,7 @@
<!-- Player Component: Timeline -->
<div class="svpod--timeline">
<Player.Progress />
<PlayerProgress />
</div>
<!-- Player Data: Total duration timestamp -->
@@ -119,7 +119,7 @@
value={$user_preferences.playback_rate}
on:change={(e) => {
const value = parseFloat(e.currentTarget.value);
action.set_playback_rate(value);
preference.set_playback_rate(value);
}}
>
{#each playback_rate_values as value}
+32
View File
@@ -0,0 +1,32 @@
<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte';
import { AudioProgress } from '.';
import { use_audio_element } from './_internal_/audio-element';
import { audio } from './actions';
import { audio_attributes } from './attributes';
import { user_preferences } from './user-preferences';
import { user_progress } from './user-progress';
const dispatch = createEventDispatcher();
onMount(() => {
use_audio_element('initialise');
dispatch('ready');
});
$: $audio;
</script>
<slot
PlayerProgress={AudioProgress}
attributes={$audio_attributes}
preference={{
set_playback_rate: user_preferences.set_playback_rate,
set_volume: user_preferences.set_volume,
save_progress: user_progress.save,
}}
play={audio.play}
mute={audio.mute}
seek_to={audio.seek_to}
skip_by={audio.skip_by}
/>
@@ -1,8 +1,8 @@
<script>
<script lang="ts">
import clamp from 'just-clamp';
import { announce } from '../internal';
import { audio_attributes, audio_element } from './audio-element';
import { announce } from './_internal_/announce';
import { audio_element } from './_internal_/audio-element';
import { audio_attributes } from './attributes';
export let step = 10;
let was_paused = true;
@@ -10,7 +10,7 @@
/**
* @param {string} t - The type of event that triggered the drag end.
*/
function handle_drag_start(t) {
function handle_drag_start(t: string) {
announce.info('drag_start :: ', t);
was_paused = $audio_attributes.is_paused;
$audio_element?.pause();
@@ -19,7 +19,7 @@
/**
* @param {string} t - The type of event that triggered the drag end.
*/
function handle_drag_end(t) {
function handle_drag_end(t: string) {
announce.info('drag_end :: ', t);
if (was_paused) return;
else $audio_element?.play();
@@ -30,7 +30,7 @@
* @param {number} seconds - Seconds to seek
* @returns {void}
*/
const handle_seek_to = (seconds) => {
const handle_seek_to = (seconds: number) => {
const el = $audio_element;
if (!el) return;
+43
View File
@@ -0,0 +1,43 @@
import { DEV } from 'esm-env';
const use_logger = Object.freeze({
info: console.info,
error: console.error,
warn: console.warn,
});
const base_styles = `padding: 4px 8px; border-radius:4px;`;
const logger_styles = {
error: `color: #FFFFFF; background-color: #FF0055; ${base_styles}`,
warn: `color: #662200; background-color: #FFBB33; ${base_styles}`,
info: `color: #80EAFF; background-color: #132686; ${base_styles}`,
} satisfies { [K in keyof typeof use_logger]: string };
function log(type: keyof typeof use_logger, ...content: unknown[]) {
// If type is info and not in dev mode, return early
if (type === 'info' && !DEV) return;
const logger = use_logger[type];
const styles = logger_styles[type];
// Log message
logger('%c🔊 svelte-podcast:', styles, ...content);
}
export const announce = {
/**
* Log an info message.
* @param {...unknown} content - Content to log.
*/
info: (...content: unknown[]) => log('info', ...content),
/**
* Log a warning message.
* @param {...unknown} content - Content to log.
*/
warn: (...content: unknown[]) => log('warn', ...content),
/**
* Log an error message.
* @param {...unknown} content - Content to log.
*/
error: (...content: unknown[]) => log('error', ...content),
} as const;
+84
View File
@@ -0,0 +1,84 @@
import { BROWSER } from 'esm-env';
import { derived, get, type Readable } from 'svelte/store';
import { announce } from './announce';
import { audio_state } from './audio-state';
const ELEMENT_ID = 'svpod--generated-audio-element';
/** A derived Svelte store containing an HTMLAudioElement or undefined. */
export const audio_element: Readable<HTMLAudioElement | null> = derived(
[audio_state],
([$audio_state], set) => {
// If not in a browser environment, return early.
if (!BROWSER) {
set(null);
return () => null;
}
// 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_state) {
el.src = $audio_state.src;
el.currentTime = $audio_state.start_at;
el.playbackRate = $audio_state.playback_rate || 1;
el.volume = $audio_state.volume || 1;
el.autoplay = $audio_state.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_state?.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;
};
},
null as HTMLAudioElement | null,
);
/**
* Use audio element
* @param action - Action being performed
*/
export const use_audio_element = (action: string): HTMLAudioElement => {
const el = get(audio_element);
if (!el) {
throw announce.error(`could not ${action} :: no audio element found`);
}
return el;
};
+5
View File
@@ -0,0 +1,5 @@
import { writable, type Writable } from 'svelte/store';
import type { AudioMetadata } from '../metadata';
/** A writable Svelte store containing the audio metadata. */
export const audio_metadata: Writable<AudioMetadata | null> = writable(null);
+17
View File
@@ -0,0 +1,17 @@
import { writable, type Writable } from 'svelte/store';
// TODO: implement autoplay
/**
* An object representing the state of an audio player.
*/
interface AudioState {
src: string;
start_at: number;
playback_rate?: number;
volume?: number;
autoplay?: boolean;
}
/** A writable Svelte store containing the audio state. */
export const audio_state: Writable<AudioState | null> = writable(null);
+8
View File
@@ -0,0 +1,8 @@
/**
* Utility functions module.
* @module Core
*/
export * from './audio-element';
export * from './audio-metadata';
export * from './audio-state';
+33
View File
@@ -0,0 +1,33 @@
/**
* An object representing a relative URL.
*/
interface RelativeURL {
pathname: string;
search: string;
hash: string;
searchParams: URLSearchParams;
}
/**
* Converts a string to a URL object. If the string is not a valid URL, it will be appended to the Svelte website URL.
* @param href - The string to convert to a URL object.
* @returns A URL object.
*/
function string_to_url(href: string): URL {
try {
return new URL(href);
} catch (e) {
return new URL(href, 'https://svelte.dev');
}
}
/**
* Parses a URL string and returns an object containing its pathname, search, hash, and searchParams.
* @param href - The URL string to parse.
* @returns An object containing the pathname, search, hash, and searchParams of the URL.
*/
export function use_url(href: string): RelativeURL {
const { pathname, search, hash, searchParams } = string_to_url(href);
return { pathname, search, hash, searchParams };
}
+134
View File
@@ -0,0 +1,134 @@
import { BROWSER } from 'esm-env';
import clamp from 'just-clamp';
import { get } from 'svelte/store';
import { use_audio_element } from './_internal_/audio-element';
import { audio_metadata } from './_internal_/audio-metadata';
import { audio_state } from './_internal_/audio-state';
import { audio_attributes } from './attributes';
import type { AudioMetadata } from './metadata';
import { user_preferences } from './user-preferences';
import { user_progress } from './user-progress';
/**
* Load audio
* @param src - Audio source
* @param metadata - Audio metadata
*/
const load_src = (src: string, metadata: AudioMetadata, 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_state.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
*/
const unload_src = () => {
if (!BROWSER) return;
// Save the current progress if audio is already loaded
user_progress.save();
// Unset audio element source
audio_state.set(null);
// Unset metadata
audio_metadata.set(null);
};
/**
* Play / Pause audio
* @param playing - Set or toggle the play state
*/
const play = (playing: boolean | 'toggle' = 'toggle') => {
const el = use_audio_element('play');
if (typeof playing === 'boolean') {
playing ? el.play() : el.pause();
}
if (playing === 'toggle') {
el.paused ? el.play() : el.pause();
}
};
/**
* Mute / Unmute audio
* @param muted - Set or toggle the mute state
*/
const mute = (muted: boolean | 'toggle' = 'toggle') => {
const el = use_audio_element('mute');
if (typeof muted === 'boolean') {
el.muted = muted;
}
if (muted === 'toggle') {
el.muted = !el.muted;
}
};
/**
* Seeks to a specific time in the audio.
* @param seconds Timestamp in seconds
* @param from Seek from-start or from-end
*/
const seek_to = (
seconds: number,
from: 'from-start' | 'from-end' = '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);
}
};
/**
* Skips the audio forward or backward by a specified number of seconds.
* @param seconds - Time to skip in seconds
* @param type [type='forward'] - Skip forward or backward
*/
const skip_by = (seconds: number, type: 'forward' | 'backward' = '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,
src: {
load: load_src,
unload: unload_src,
},
play,
mute,
seek_to,
skip_by,
};
+46
View File
@@ -0,0 +1,46 @@
import { derived, type Readable } from 'svelte/store';
import { audio_element } from './_internal_/audio-element';
import { audio_metadata } from './_internal_/audio-metadata';
import type { AudioMetadata } from './metadata';
import { format_seconds } from './utility/format-seconds';
/**
* An object representing the attributes of an audio element.
*/
export interface AudioAttributes {
is_loaded: boolean;
is_paused: boolean;
current_time: number;
duration: number;
timestamp_current: string;
timestamp_end: string;
metadata: AudioMetadata | null;
}
/** A readable Svelte store containing the audio attributes. */
export const audio_attributes: Readable<AudioAttributes> = derived(
[audio_element, audio_metadata],
([$el, $meta], set) => {
if (!$el) {
return set({
is_loaded: false,
is_paused: true,
current_time: 0,
duration: 0,
timestamp_current: format_seconds.to_timestamp(0),
timestamp_end: format_seconds.to_timestamp(0),
metadata: null,
} satisfies AudioAttributes);
}
set({
is_loaded: Boolean($el.src),
is_paused: $el.paused,
current_time: $el.currentTime,
duration: $el.duration,
timestamp_current: format_seconds.to_timestamp($el.currentTime),
timestamp_end: format_seconds.to_timestamp($el.duration),
metadata: $meta,
} satisfies AudioAttributes);
},
);
-165
View File
@@ -1,165 +0,0 @@
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
@@ -1,15 +0,0 @@
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
@@ -1,132 +0,0 @@
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
@@ -1,12 +0,0 @@
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);
-11
View File
@@ -1,11 +0,0 @@
/**
* Audio context module.
* @module audio
*/
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';
-61
View File
@@ -1,61 +0,0 @@
<script>
import { AudioProgress, audio_attributes } from '.';
import { audio } from '../audio';
import { user_preferences } from '../user';
/** @type {string|undefined} */
export let src;
/** @type {import('./audio-metadata').AudioMetadata} */
export let 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) => 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) => 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) => audio.seek(value);
/**
* Toggles the audio playback between play and pause.
*/
const toggle = () => audio.play('toggle');
/**
* Starts playing the audio.
*/
const play = () => audio.play('set');
/**
* Pauses the audio.
*/
const pause = () => audio.pause('set');
</script>
<slot
Player={{ Progress: AudioProgress }}
action={{
set_playback_rate: user_preferences.set_playback_rate,
set_volume: user_preferences.set_volume,
skip_forward,
skip_back,
seek_to,
toggle,
play,
pause,
}}
attributes={$audio_attributes}
/>
-7
View File
@@ -1,7 +0,0 @@
import { default as AudioPlayer } from './audio/player.svelte';
export * from './audio/actions';
export * from './user';
export { AudioPlayer };
export default AudioPlayer;
+10
View File
@@ -0,0 +1,10 @@
export { default as AudioPlayer, default as default } from './Player.svelte';
export { default as AudioProgress } from './Progress.svelte';
export * from './actions';
export * from './attributes';
export * from './metadata';
export * from './user-preferences';
export * from './user-progress';
export * from './utility/format-seconds';
// export type { AudioMetadata } from './audio-metadata';
-67
View File
@@ -1,67 +0,0 @@
import { DEV } from 'esm-env';
/**
* Object containing logger functions.
* @typedef {Object} Announce
* @property {function} info - Log info like events and data.
* @property {function} warn - Log warnings for developers.
* @property {function} error - Log errors for critical failures.
*/
/**
* Type of logger.
* @typedef {('info' | 'error' | 'warn')} Logger
*/
/**
* Object containing Logger functions.
*
* @type {Readonly<{[K in Logger]: Function}>}
*/
const use_logger = Object.freeze({
info: console.info,
error: console.error,
warn: console.warn,
});
/**
* Log function.
* @param {Logger} type - Type of logger.
* @param {...unknown} content - Content to log.
*/
function log(type, ...content) {
const logger = use_logger[type];
// If type is info and not in dev mode, return early
if (type === 'info' && !DEV) return;
// Log message
logger(
'%c🔊 svelte-podcast:',
'color: #FF3E00; background-color: rgba(255, 62, 0, 0.15); padding: 4px 8px; border-radius:4px;',
...content,
);
}
/**
* Object containing logger functions.
* @type {Announce}
* @exports announce
*/
export const announce = {
/**
* Log an info message.
* @param {...unknown} content - Content to log.
*/
info: (...content) => log('info', ...content),
/**
* Log a warning message.
* @param {...unknown} content - Content to log.
*/
warn: (...content) => log('warn', ...content),
/**
* Log an error message.
* @param {...unknown} content - Content to log.
*/
error: (...content) => log('error', ...content),
};
-7
View File
@@ -1,7 +0,0 @@
/**
* Utility functions module.
* @module internal
*/
export * from './announce';
export * from './use-url';
-31
View File
@@ -1,31 +0,0 @@
/**
* @typedef {Object} RelativeURL
* @property {string} pathname - The pathname of the URL.
* @property {string} search - The search string of the URL.
* @property {string} hash - The hash string of the URL.
* @property {URLSearchParams} searchParams - The search parameters of the URL.
*/
/**
* Converts a string to a URL object. If the string is not a valid URL, it will be appended to the Svelte website URL.
* @param {string} href - The string to convert to a URL object.
* @returns {URL} A URL object.
*/
const string_to_url = (href) => {
try {
return new URL(href);
} catch (e) {
return new URL(href, 'https://svelte.dev');
}
};
/**
* Parses a URL string and returns an object containing its pathname, search, hash, and searchParams.
* @param {string} href - The URL string to parse.
* @returns {RelativeURL} An object containing the pathname, search, hash, and searchParams of the URL.
*/
export const use_url = (href) => {
const { pathname, search, hash, searchParams } = string_to_url(href);
return { pathname, search, hash, searchParams };
};
+6
View File
@@ -0,0 +1,6 @@
/**
* An object representing the metadata for an audio source.
*/
export interface AudioMetadata {
[key: string]: unknown;
}
+59
View File
@@ -0,0 +1,59 @@
import clamp from 'just-clamp';
import { persisted } from 'svelte-local-storage-store';
export interface UserPreferences {
playback_rate: number;
volume: number;
}
/**
* The default user preferences
*/
const DEFAULT_USER_PREFERENCES: UserPreferences = {
playback_rate: 1,
volume: 1,
};
/**
* The user preferences store
*/
export const USER_PREFERENCES_STORE = persisted<UserPreferences>(
'USER_PREFERENCE',
DEFAULT_USER_PREFERENCES,
);
/**
* Sets the playback rate for the user
* @param value - The playback rate value
*/
const set_playback_rate = (value: number) => {
const playback_rate = clamp(value, 0.5, 5);
return USER_PREFERENCES_STORE.update((prefs) => ({
...prefs,
playback_rate,
}));
};
/**
* Sets the volume for the user
* @param value - The volume value
*/
const set_volume = (value: number) => {
const volume = clamp(value, 0, 1);
return USER_PREFERENCES_STORE.update((prefs) => ({ ...prefs, volume }));
};
/**
* Clears user preferences store
*/
const clear_store = () => USER_PREFERENCES_STORE.set(DEFAULT_USER_PREFERENCES);
/**
* The user preferences object
*/
export const user_preferences = {
subscribe: USER_PREFERENCES_STORE.subscribe,
set_playback_rate,
set_volume,
clear: clear_store,
};
+63
View File
@@ -0,0 +1,63 @@
import { persisted } from 'svelte-local-storage-store';
import { get } from 'svelte/store';
import { announce } from './_internal_/announce';
import { audio_element } from './_internal_/audio-element';
import { use_url } from './_internal_/safe-use-url';
export interface UserProgress {
[key: string]: number;
}
const DEFAULT_USER_PROGRESS: UserProgress = {};
/**
* User progress store
*/
const USER_PROGRESS_STORE = persisted<UserProgress>(
'USER_PROGRESS',
DEFAULT_USER_PROGRESS,
);
/**
* Saves user progress
*/
const save_user_progress = () => {
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 = audio.currentTime;
USER_PROGRESS_STORE.update((prev) => ({
...prev,
[pathname]: current_time,
}));
};
/**
* Gets user progress for a given audio source
* @param src - Audio source
*/
const get_user_progress = (src: string) => {
const pathname = use_url(src).pathname;
const store: UserProgress = get(USER_PROGRESS_STORE);
const value = store[pathname];
return value;
};
/**
* Clears user progress store
*/
const clear_user_progress = () =>
USER_PROGRESS_STORE.set(DEFAULT_USER_PROGRESS);
export const user_progress = {
subscribe: USER_PROGRESS_STORE.subscribe,
get: get_user_progress,
clear: clear_user_progress,
save: save_user_progress,
};
-7
View File
@@ -1,7 +0,0 @@
/**
* User storage module.
* @module user
*/
export * from './user-preferences';
export * from './user-progress';
-88
View File
@@ -1,88 +0,0 @@
import clamp from 'just-clamp';
import { persisted } from 'svelte-local-storage-store';
/**
* @typedef {Object} UserPreferences
* @property {number} playback_rate - The audio player playback rate (speed)
* @property {number} volume - The audio player volume
*/
/**
* The default user preferences
* @type {UserPreferences}
*/
const DEFAULT_USER_PREFERENCES = { playback_rate: 1, volume: 1 };
/**
* The user preferences store
* @type {import('svelte/store').Writable<UserPreferences>}
*/
const USER_PREFERENCES_STORE = persisted(
'USER_PREFERENCE',
DEFAULT_USER_PREFERENCES,
);
/**
* Sets the playback rate for the user
* @param {number} value - The playback rate value
* @returns {void}
*/
const set_playback_rate = (value) => {
const playback_rate = clamp(value, 0.5, 5);
return USER_PREFERENCES_STORE.update((prefs) => ({
...prefs,
playback_rate,
}));
};
/**
* Sets the volume for the user
* @param {number} value - The volume value
* @returns {void}
*/
const set_volume = (value) => {
const volume = clamp(value, 0, 1);
return USER_PREFERENCES_STORE.update((prefs) => ({ ...prefs, volume }));
};
/**
* Clears user preferences store
*/
const clear_store = () => USER_PREFERENCES_STORE.set(DEFAULT_USER_PREFERENCES);
/**
* @typedef {Object} UserPreferencesObject
* @property {typeof USER_PREFERENCES_STORE.subscribe} subscribe - Subscribes to the user preferences store
* @property {typeof set_playback_rate} set_playback_rate - Sets the playback rate for the user
* @property {typeof set_volume} set_volume - Sets the volume for the user
* @property {typeof clear_store} clear - Clears saved user preferences
*/
/**
* The user preferences object
* @type {UserPreferencesObject}
*/
export const user_preferences = {
/**
* Subscribes to user preferences store
*/
subscribe: USER_PREFERENCES_STORE.subscribe,
/**
* Sets the playback rate for the user
* @type {typeof set_playback_rate}
*/
set_playback_rate,
/**
* Sets the volume for the user
* @type {typeof set_volume}
*/
set_volume,
/**
* Clears user preferences store
* @type {typeof clear_store}
*/
clear: clear_store,
};
-81
View File
@@ -1,81 +0,0 @@
import { persisted } from 'svelte-local-storage-store';
import { get } from 'svelte/store';
import { audio_element } from '../audio';
import { announce, use_url } from '../internal';
/**
* User progress object type.
* This object acts as a dictionary with string keys and number values.
* @typedef {Object.<string, number>} UserProgress
*/
/**
* Default user progress object
* @type {UserProgress}
*/
const DEFAULT_USER_PROGRESS = {};
/**
* User progress store
* @type {import('svelte/store').Writable<UserProgress>}
*/
const USER_PROGRESS_STORE = persisted('USER_PROGRESS', DEFAULT_USER_PROGRESS);
/**
* Saves user progress
* @function
* @returns {void}
*/
const save_user_progress = () => {
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 = audio.currentTime;
USER_PROGRESS_STORE.update((prev) => ({
...prev,
[pathname]: current_time,
}));
};
/**
* Gets user progress for a given audio source
* @function
* @param {string} src - Audio source
* @returns {number|undefined} - User progress for the given audio source, or undefined if not found
*/
const get_user_progress = (src) => {
const pathname = use_url(src).pathname;
const store = get(USER_PROGRESS_STORE);
/**@type {number|undefined} */
const value = store[pathname];
return value;
};
/**
* Clears user progress store
* @function
* @returns {void}
*/
const clear_user_progress = () =>
USER_PROGRESS_STORE.set(DEFAULT_USER_PROGRESS);
/**
* User progress object
* @namespace
* @property {function} subscribe - Subscribes to user progress store
* @property {function} get - Gets user progress for a given audio source
* @property {function} clear - Clears user progress store
* @property {function} save - Saves user progress
*/
export const user_progress = {
subscribe: USER_PROGRESS_STORE.subscribe,
get: get_user_progress,
clear: clear_user_progress,
save: save_user_progress,
};
+40
View File
@@ -0,0 +1,40 @@
import { announce } from '../_internal_/announce';
/**
* Returns a string timestamp in the format of "hh:mm:ss" from a given number of seconds.
* @param seconds - The number of seconds to convert to a timestamp.
* @param force_hours - Whether to always include hours in the timestamp, even if it's 0.
* @param seperator - The seperator to use between the hours, minutes, and seconds.
* @returns A string timestamp in the format of "hh:mm:ss".
*/
function seconds_to_timestamp(
seconds: number,
force_hours = false,
seperator = ':',
): string {
const safe_seconds = Number(seconds) || 0;
announce.info({ seconds, force_hours, seperator, safe_seconds });
const hours = Math.floor(safe_seconds / 3600);
const hours_remainder = safe_seconds % 3600;
const minutes = Math.floor(hours_remainder / 60);
const minutes_remainder = hours_remainder % 60;
const secs = Math.floor(minutes_remainder);
const show_hours = force_hours || hours > 0;
const HH = hours.toString().padStart(2, '0');
const MM = minutes.toString().padStart(2, '0');
const SS = secs.toString().padStart(2, '0');
const values = show_hours ? [HH, MM, SS] : [MM, SS];
return values.join(seperator);
}
export const format_seconds = Object.freeze({
to_timestamp: seconds_to_timestamp,
});
-6
View File
@@ -1,6 +0,0 @@
/**
* Utility functions module.
* @module utility
*/
export * from './seconds-to-timestamp';
-31
View File
@@ -1,31 +0,0 @@
/**
* Returns a string timestamp in the format of "hh:mm:ss" from a given number of seconds.
* @param {number} seconds - The number of seconds to convert to a timestamp.
* @param {boolean} [force_hours=false] - Whether to always include hours in the timestamp, even if it's 0.
* @returns {string} A string timestamp in the format of "hh:mm:ss".
*/
export function seconds_to_timestamp(seconds, force_hours = false) {
const hours = Math.floor(seconds / 3600);
const hours_remainder = seconds % 3600;
const minutes = Math.floor(hours_remainder / 60);
const minutes_remainder = hours_remainder % 60;
const secs = Math.floor(minutes_remainder);
const hours_str = force_hours || hours > 0 ? `${section(hours)}:` : '';
const minutes_str = minutes ? `${section(minutes)}:` : '00:';
const seconds_str = secs ? `${section(secs)}` : '00';
return `${hours_str}${minutes_str}${seconds_str}`;
}
/**
* Returns a string representation of a number with a leading 0 if it's less than 10.
* @param {number} num - The number to format.
* @returns {string} A string representation of the number with a leading 0 if it's less than 10.
*/
function section(num) {
if (num < 10) return `0${num}`;
return num.toString();
}
+6 -4
View File
@@ -51,12 +51,9 @@
{ label: 'audio', anchor: 'audio' },
{ label: 'audio_data', anchor: 'audio_data' },
{ label: 'audio.subscribe', anchor: 'audio.subscribe' },
{ label: 'audio.load', anchor: 'audio.load' },
{ label: 'audio.unload', anchor: 'audio.unload' },
{ label: 'audio.src', anchor: 'audio.src' },
{ label: 'audio.play', anchor: 'audio.play' },
{ label: 'audio.pause', anchor: 'audio.pause' },
{ label: 'audio.mute', anchor: 'audio.mute' },
{ label: 'audio.unmute', anchor: 'audio.unmute' },
{ label: 'audio.seek', anchor: 'audio.seek' },
{ label: 'audio.skip', anchor: 'audio.skip' },
{ label: 'user_preferences', anchor: 'user_preferences' },
@@ -115,6 +112,11 @@
href: 'https://syntax.fm/',
src: '/syntax.png',
},
{
label: 'PodStack',
href: 'https://podstack.club/',
src: '/podstack.png',
},
]);
let is_menu_open = false;
+5 -3
View File
@@ -3,9 +3,12 @@
import { PlayerWidget } from '$src/components/player';
import { episodes } from '$src/content/episodes';
import { DocsPage } from '$src/layout/page';
import { audio } from '../lib/actions';
/** @type { string | undefined} */
let audio_src = episodes.knomii.src;
audio.src.load(episodes.syntax.src, {
title: episodes.syntax.title,
artwork: episodes.syntax.artwork,
});
</script>
<DocsPage title="Docs" let:Section>
@@ -87,7 +90,6 @@
class="-m-2 grid grid-cols-1 grid-rows-1 rounded-xl border border-mono-100 bg-white p-1 shadow-2xl shadow-mono-200 sm:p-2 lg:-m-4 lg:rounded-2xl lg:p-4"
>
<PlayerWidget
src={audio_src}
class="w-full border border-mono-100"
options={{
duration: true,
+4 -27
View File
@@ -5,13 +5,10 @@
import {
audio_load,
audio_mute,
audio_pause,
audio_play,
audio_seek,
audio_skip,
audio_subscribe,
audio_unload,
audio_unmute,
seconds_to_timestamp,
user_preferences_clear,
user_preferences_set_playback_rate,
@@ -87,23 +84,17 @@
</div>
</SectionArticle>
<SectionArticle title="audio.load">
<SectionArticle title="audio.src">
<p>
Load a new audio source. This will stop the current audio source and
replace it with the new one.
</p>
<div class="not-prose codeblock">
<Highlight code={audio_load} language={lang_ts} />
</div>
</SectionArticle>
<SectionArticle title="audio.unload">
<p>
Unload the current audio source. This will stop the current audio
source and remove it from the audio state.
Alternatively, unload the current audio source. This will stop the
current audio source and remove it from the audio state.
</p>
<div class="not-prose codeblock">
<Highlight code={audio_unload} language={lang_ts} />
<Highlight code={audio_load} language={lang_ts} />
</div>
</SectionArticle>
@@ -114,13 +105,6 @@
</div>
</SectionArticle>
<SectionArticle title="audio.pause">
<p>Set or toggle the pause state.</p>
<div class="not-prose codeblock">
<Highlight code={audio_pause} language={lang_ts} />
</div>
</SectionArticle>
<SectionArticle title="audio.mute">
<p>Set or toggle the mute state.</p>
<div class="not-prose codeblock">
@@ -128,13 +112,6 @@
</div>
</SectionArticle>
<SectionArticle title="audio.unmute">
<p>Set or toggle the unmute state.</p>
<div class="not-prose codeblock">
<Highlight code={audio_unmute} language={lang_ts} />
</div>
</SectionArticle>
<SectionArticle title="audio.seek">
<p>
Seek to a specific time in the audio. The `from` parameter
+9 -4
View File
@@ -1,17 +1,22 @@
import { audio } from 'svelte-podcast';
// load a new audio source
audio.load(
// load a new audio source into the player
audio.src.load(
// path to audio file
'/episode-377.mp3',
// custom metadata
// custom metadata defined by you
{
title: 'Supper Club × Rich Harris, Author of Svelte',
artwork:
'https://ssl-static.libsyn.com/p/assets/b/3/c/d/b3cdf28da11ad39fe5bbc093207a2619/Syntax_-_499.jpg',
guests: ['Rich Harris'],
hosts: ['Wes Bos', 'Scott Tolinski'],
},
// autoplay
true,
false,
);
// unload the current audio source
audio.src.unload();
+5 -4
View File
@@ -1,9 +1,10 @@
import { audio } from 'svelte-podcast';
// mute the current audio source
// do nothing if it's already muted
audio.mute('set');
audio.mute(true);
// mute the current audio if it's unmuted
// unmute the current audio if it's muted
// unmute the current audio source
audio.mute(false);
// invert the mute state of the current audio source
audio.mute('toggle');
-9
View File
@@ -1,9 +0,0 @@
import { audio } from 'svelte-podcast';
// pause the current audio source
// do nothing if it's already paused
audio.pause('set');
// pause the current audio if it's playing
// play the current audio if it's paused
audio.pause('toggle');
+5 -4
View File
@@ -1,9 +1,10 @@
import { audio } from 'svelte-podcast';
// play the current audio source
// do nothing if it's already playing
audio.play('set');
audio.play(true);
// play the current audio if it's paused
// pause the current audio if it's playing
// pause the current audio source
audio.play(false);
// invert the play state of the current audio source
audio.play('toggle');
+2 -2
View File
@@ -1,7 +1,7 @@
import { audio } from 'svelte-podcast';
// go to 200 seconds from the start
audio.seek(200);
audio.seek_to(200);
// go to 200 seconds before the end
audio.seek(200, 'from-end');
audio.seek_to(200, 'from-end');
+2 -2
View File
@@ -1,7 +1,7 @@
import { audio } from 'svelte-podcast';
// skip forward 30 seconds from the current position
audio.skip(30);
audio.skip_by(30);
// skip backward 30 seconds from the current position
audio.skip(30, 'backward');
audio.skip_by(30, 'backward');
-4
View File
@@ -1,4 +0,0 @@
import { audio } from 'svelte-podcast';
// unload the current audio source
audio.unload();
-9
View File
@@ -1,9 +0,0 @@
import { audio } from 'svelte-podcast';
// unmute the current audio source
// do nothing if it's already unmuted
audio.unmute('set');
// unmute the current audio if it's muted
// mute the current audio if it's unmuted
audio.unmute('toggle');
+5 -11
View File
@@ -1,13 +1,10 @@
import { format_code } from '../../../layout/helper';
import { default as audio_load_src } from './audio-load.js?raw';
import { default as audio_mute_src } from './audio-mute.js?raw';
import { default as audio_pause_src } from './audio-pause.js?raw';
import { default as audio_play_src } from './audio-play.js?raw';
import { default as audio_seek_src } from './audio-seek.js?raw';
import { default as audio_skip_src } from './audio-skip.js?raw';
import { default as audio_load_src } from './audio-load?raw';
import { default as audio_mute_src } from './audio-mute?raw';
import { default as audio_play_src } from './audio-play?raw';
import { default as audio_seek_src } from './audio-seek?raw';
import { default as audio_skip_src } from './audio-skip?raw';
import { default as audio_subscribe_src } from './audio-subscribe.svelte?raw';
import { default as audio_unload_src } from './audio-unload.js?raw';
import { default as audio_unmute_src } from './audio-unmute.js?raw';
import { default as load_audio_after_click_src } from './load-audio-after-click.svelte?raw';
import { default as load_audio_after_mount_src } from './load-audio-after-mount.svelte?raw';
import { default as seconds_to_timestamp_src } from './seconds-to-timestamp?raw';
@@ -25,12 +22,9 @@ export const load_audio_after_mount = format_code(load_audio_after_mount_src);
export const seconds_to_timestamp = format_code(seconds_to_timestamp_src);
export const audio_load = format_code(audio_load_src);
export const audio_play = format_code(audio_play_src);
export const audio_unload = format_code(audio_unload_src);
export const audio_pause = format_code(audio_pause_src);
export const audio_skip = format_code(audio_skip_src);
export const audio_seek = format_code(audio_seek_src);
export const audio_mute = format_code(audio_mute_src);
export const audio_unmute = format_code(audio_unmute_src);
export const user_preferences_clear = format_code(user_preferences_clear_src);
export const user_preferences_set_playback_rate = format_code(
user_preferences_set_playback_rate_src,
@@ -5,12 +5,14 @@
<!-- load the episode on click -->
<button
on:click={() =>
audio.load('/episode-audio.mp3', {
/* optional metadata */
audio.src.load('/episode-audio.mp3', {
/* your metadata */
title: 'Episode Title',
artwork: '/artwork.png',
})}
>
Load Episode
</button>
<!-- unload the episode on click -->
<button on:click={() => audio.unload()}>Unload Episode</button>
<button on:click={() => audio.src.unload()}>Unload Episode</button>
@@ -4,8 +4,10 @@
onMount(() => {
// load the episode on mount without any metadata
audio.load('/episode-audio.mp3', {
/* optional metadata */
audio.src.load('/episode-audio.mp3', {
/* your metadata */
title: 'Episode Title',
artwork: '/artwork.png',
});
});
</script>
+7 -5
View File
@@ -1,8 +1,10 @@
import { seconds_to_timestamp } from 'svelte-podcast/utility';
import { format_seconds } from 'svelte-podcast/utility/format-seconds';
// Example
seconds_to_timestamp(5173); // 01:26:13
format_seconds.to_timestamp(5173); // '01:26:13';
// force hours
seconds_to_timestamp(2700, true); // 00:45:00
seconds_to_timestamp(2700, false); // 45:00 (default)
// force hours to be displayed
format_seconds.to_timestamp(2700, true); // '00:45:00';
// only display hours if there are any
format_seconds.to_timestamp(2700, false); // '45:00';
@@ -1,4 +1,4 @@
import { user_preferences } from '../../../lib/user';
import { user_preferences } from 'svelte-podcast';
// Clears all user preferences
user_preferences.clear();
@@ -1,4 +1,4 @@
import { user_preferences } from '../../../lib/user';
import { user_preferences } from 'svelte-podcast';
// Sets the playback speed to 150%
user_preferences.set_playback_rate(1.5);
@@ -1,4 +1,4 @@
import { user_preferences } from '../../../lib/user';
import { user_preferences } from 'svelte-podcast';
// Sets the volume to 75%
user_preferences.set_volume(0.75);
@@ -1,5 +1,5 @@
<script>
import { AudioProgress } from 'svelte-podcast/audio';
import { AudioProgress } from 'svelte-podcast';
</script>
<AudioProgress step={10} />
+1 -1
View File
@@ -70,7 +70,7 @@
Along side your audio source, you can optionally define arbitrary
metadata for you to use in your player, and determine if the episode
should autoplay once loaded. Learn more in the <a
href="/api/#audio.load">api docs</a
href="/api/#audio.src">api docs</a
>
</p>
+9 -7
View File
@@ -1,14 +1,16 @@
import { audio } from 'svelte-podcast';
audio.load('/episode-audio.mp3');
audio.src.load(
// Audio file
'/episode-audio.mp3',
audio.load(
'https://media.transistor.fm/27a058c9/27b595e2.mp3',
// Your custom metadata
{
title: 'SvelteKit-superforms with Andreas Söderlund',
artwork:
'https://images.transistor.fm/file/transistor/images/show/12899/medium_1597678946-artwork.jpg',
guest_name: 'Andreas Söderlund',
title: 'A deep dive into Svelte Podcast',
artwork: '/artwork.png',
guest_name: 'OllieJT',
},
// Autoplay once loaded
false,
);
+4 -1
View File
@@ -1,3 +1,6 @@
import { audio } from 'svelte-podcast';
audio.load('/episode-audio.mp3');
audio.src.load('/episode-audio.mp3', {
title: 'Episode Title',
artwork: '/artwork.png',
});
+4 -1
View File
@@ -1,3 +1,6 @@
import { audio } from 'svelte-podcast';
audio.load('https://media.transistor.fm/27a058c9/27b595e2.mp3');
audio.src.load('https://media.transistor.fm/27a058c9/27b595e2.mp3', {
title: 'Episode Title',
artwork: '/artwork.png',
});
@@ -1,10 +1,9 @@
// include this in your /src/app.d.ts file
declare module 'svelte-podcast' {
interface EpisodeDetails {
interface AudioMetadata {
title: string;
artwork: string;
guest_name: string;
artwork: string | null;
}
}