[READ-ONLY] Mirror of https://github.com/flo-bit/room. tiny 3d rooms saved locally or in your bluesky account, svelte/threlte flo-bit.dev/room/
0

Configure Feed

Select the types of activity you want to include in your feed.

room / src / lib / components / audio / waveform-audio-player / WaveformAudioPlayer.svelte
4.3 kB 167 lines
1<script lang="ts"> 2 import NumberFlow, { NumberFlowGroup } from '@number-flow/svelte'; 3 import { onMount } from 'svelte'; 4 import WaveSurfer from 'wavesurfer.js'; 5 6 import { ThemeWatcher } from '$lib/helper/ThemeWatcher.svelte'; 7 8 let wavesurfer: WaveSurfer | null = $state(null); 9 10 let { url, darkMode = 'class' }: { 11 url: string; 12 darkMode?: 'class' | 'mediaQuery'; 13 } = $props(); 14 15 let audioContainer: HTMLElement | null = $state(null); 16 17 let audioDuration = $state(0); 18 let currentTime = $state(0); 19 let remainingTime = $derived(Math.round(audioDuration - currentTime)); 20 let remainingSeconds = $derived(remainingTime % 60); 21 let remainingMinutes = $derived(Math.floor(remainingTime / 60)); 22 23 let isPlaying = $state(false); 24 let isLoaded = $state(false); 25 26 let themeWatcher: ThemeWatcher | null = $state(null); 27 28 function isDarkMode() { 29 return themeWatcher?.isDarkMode() ?? false; 30 } 31 32 function getCSSVar(variable: string) { 33 return themeWatcher?.getCSSVar(variable) ?? ''; 34 } 35 36 onMount(() => { 37 if (!audioContainer) return; 38 39 themeWatcher = new ThemeWatcher(updateWaveformColors, darkMode); 40 41 wavesurfer = WaveSurfer.create({ 42 container: audioContainer, 43 cursorWidth: 2, 44 height: 16, 45 barWidth: 2, 46 barGap: 1, 47 barRadius: 2, 48 dragToSeek: { 49 debounceTime: 100 50 }, 51 fillParent: true, 52 normalize: true, 53 hideScrollbar: true 54 }); 55 56 updateWaveformColors(); 57 58 wavesurfer?.load(url); 59 60 wavesurfer?.on('ready', (duration: number) => { 61 audioDuration = duration; 62 isLoaded = true; 63 }); 64 65 wavesurfer?.on('timeupdate', (time: number) => { 66 currentTime = time; 67 }); 68 69 wavesurfer?.on('play', () => { 70 isPlaying = true; 71 }); 72 73 wavesurfer?.on('pause', () => { 74 isPlaying = false; 75 }); 76 77 return () => { 78 wavesurfer?.pause(); 79 wavesurfer?.destroy(); 80 81 themeWatcher?.destroy(); 82 }; 83 }); 84 85 function updateWaveformColors() { 86 wavesurfer?.setOptions({ 87 waveColor: isDarkMode() ? getCSSVar('--color-accent-950') : getCSSVar('--color-accent-200'), 88 progressColor: isDarkMode() ? getCSSVar('--color-accent-500') : getCSSVar('--color-accent-500') 89 }); 90 } 91 92 function playAudio() { 93 wavesurfer?.play(); 94 } 95 96 function pauseAudio() { 97 wavesurfer?.pause(); 98 } 99</script> 100 101<div class="relative flex h-24 w-full items-center justify-center gap-4 overflow-hidden"> 102 <button 103 onclick={() => { 104 isPlaying ? pauseAudio() : playAudio(); 105 }} 106 class="bg-accent-600 hover:bg-accent-500 inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-30 md:size-14" 107 > 108 {#if isPlaying} 109 <svg 110 xmlns="http://www.w3.org/2000/svg" 111 viewBox="0 0 24 24" 112 fill="currentColor" 113 class="size-6 md:size-9 text-base-50 dark:text-base-950" 114 > 115 <path 116 fill-rule="evenodd" 117 d="M6.75 5.25a.75.75 0 0 1 .75-.75H9a.75.75 0 0 1 .75.75v13.5a.75.75 0 0 1-.75.75H7.5a.75.75 0 0 1-.75-.75V5.25Zm7.5 0A.75.75 0 0 1 15 4.5h1.5a.75.75 0 0 1 .75.75v13.5a.75.75 0 0 1-.75.75H15a.75.75 0 0 1-.75-.75V5.25Z" 118 clip-rule="evenodd" 119 /> 120 </svg> 121 {:else} 122 <svg 123 xmlns="http://www.w3.org/2000/svg" 124 viewBox="0 0 24 24" 125 fill="currentColor" 126 class="ml-1 size-6 md:ml-1.5 md:size-9 text-base-50 dark:text-base-950" 127 > 128 <path 129 fill-rule="evenodd" 130 d="M4.5 5.653c0-1.427 1.529-2.33 2.779-1.643l11.54 6.347c1.295.712 1.295 2.573 0 3.286L7.28 19.99c-1.25.687-2.779-.217-2.779-1.643V5.653Z" 131 clip-rule="evenodd" 132 /> 133 </svg> 134 {/if} 135 <span class="sr-only">{isPlaying ? 'Pause' : 'Play'}</span> 136 </button> 137 <div bind:this={audioContainer} class="h-4 w-full grow"> 138 {#if !isLoaded} 139 <div class="m-1.5 h-1 w-full dark:bg-accent-950 bg-accent-100 rounded-full"></div> 140 {/if} 141 </div> 142 {#if isLoaded} 143 <NumberFlowGroup> 144 <div 145 class="text-accent-500 flex items-end justify-end font-semibold" 146 style="font-variant-numeric: tabular-nums;" 147 > 148 -<NumberFlow 149 value={remainingMinutes} 150 trend={-1} 151 format={{ minimumIntegerDigits: 2 }} 152 digits={{ 1: { max: 5 } }} 153 /> 154 <NumberFlow 155 value={remainingSeconds} 156 trend={-1} 157 format={{ minimumIntegerDigits: 2 }} 158 digits={{ 1: { max: 5 } }} 159 prefix=":" 160 /> 161 </div> 162 </NumberFlowGroup> 163 {:else} 164 165 <div class="text-accent-500 flex items-end justify-end text-sm font-medium">loading</div> 166 {/if} 167</div>