import { audio_autoplay, audio_current_time, audio_duration, audio_element, audio_ended, audio_loading, audio_muted, audio_paused, audio_playback_rate, audio_src, audio_start_at, audio_volume, } from '$lib/context/audio-internals'; import { audio_metadata, type AudioMetadata } from '$lib/context/audio-metadata'; import { secondsToTimestamp } from '$lib/utility/seconds-to-timestamp'; import { info, warn } from '$pkg/log'; import clamp from 'just-clamp'; import { derived } from 'svelte/store'; const audio_state = derived( [ audio_current_time, audio_duration, audio_ended, audio_loading, audio_paused, audio_start_at, audio_autoplay, audio_muted, audio_playback_rate, audio_src, audio_volume, audio_metadata, ], ([ $current_time, $duration, $ended, $loading, $paused, $start_at, $autoplay, $muted, $playback_rate, $src, $volume, $metadata, ]) => { return { current_time: $current_time, duration: $duration, ended: $ended, loading: $loading, paused: $paused, start_at: $start_at, autoplay: $autoplay, muted: $muted, playback_rate: $playback_rate, src: $src, volume: $volume, metadata: $metadata, timestamp: secondsToTimestamp($current_time), }; }, ); type HandleType = 'toggle' | 'set'; function play(type: HandleType = 'set') { info('play: ', type); return audio_element.subscribe((el) => { if (!el) return warn('no audio element'); if (type === 'toggle') { el.paused ? el.play() : el.pause(); } else { el.play(); } })(); } function pause(type: HandleType = 'set') { info('pause: ', type); return audio_element.subscribe((el) => { if (!el) return warn('no audio element'); if (type === 'toggle') { el.paused ? el.play() : el.pause(); } else { el.pause(); } })(); } function mute(type: HandleType = 'set') { info('mute: ', type); if (type === 'toggle') { audio_muted.update((muted) => !muted); } else { audio_muted.set(true); } } function unmute(type: HandleType = 'set') { info('unmute: ', type); if (type === 'toggle') { audio_muted.update((muted) => !muted); } else { audio_muted.set(false); } } export type AudioLoadData = AudioMetadata & { src: string }; export type AudioLoadOptions = { autoplay: boolean; start_at?: number; }; const load = (data: AudioLoadData, opts: AudioLoadOptions) => { const { src, ...metadata } = data; info('load: ', src); audio_autoplay.set(opts.autoplay); audio_volume.set(1); audio_src.set(src); audio_metadata.set(metadata); audio_start_at.set(opts.start_at || 0); // opts.current_time ? seek(opts.current_time) : null; //opts.autoplay && play(); }; function unload() { info('unload: '); pause(); audio_autoplay.set(false); audio_volume.set(1); audio_src.set(''); audio_metadata.set(null); audio_start_at.set(0); } function setPlaybackRate(rate: number) { info('setPlaybackRate: ', rate); audio_playback_rate.set(clamp(0, 10, rate)); } function seek(seconds: number, from: 'from-start' | 'from-end' = 'from-start') { info('seek: ', seconds, from); return audio_element.subscribe((el) => { if (!el) return warn('no audio element'); if (from === 'from-end') { el.currentTime = clamp(0, el.duration, el.duration - seconds); } else { el.currentTime = clamp(0, el.duration, seconds); } })(); } function skip(seconds: number, type: 'forward' | 'backward' = 'forward') { info('skip: ', seconds, type); return audio_element.subscribe((el) => { if (!el) return warn('no audio element'); if (type === 'backward') { el.currentTime = clamp(0, el.duration, el.currentTime - seconds); } else { el.currentTime = clamp(0, el.duration, el.currentTime + seconds); } })(); } export const audio = { subscribe: audio_state.subscribe, play, pause, mute, unmute, load, unload, setPlaybackRate, seek, skip, };