parent
efe6ae38ce
commit
efa1cbe891
@ -0,0 +1,80 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte';
|
||||
import { setActiveUix, type ActiveUix } from '$active-uix';
|
||||
import { Soma } from '$soma/core/soma.svelte';
|
||||
import { ActiveEidos } from '$uix/eidos';
|
||||
import AudioPlayer from '../index';
|
||||
import { MediaPlayer } from '../../media-player';
|
||||
import type { AudioPlayerVariant } from '../types';
|
||||
import type { MediaPlayerSize } from '../../media-player/types';
|
||||
import type { SoundMediaMetadata } from '$sound';
|
||||
|
||||
let {
|
||||
uix,
|
||||
src,
|
||||
artworkSrc,
|
||||
metadata,
|
||||
onPlay,
|
||||
onPause,
|
||||
onEnded
|
||||
}: {
|
||||
uix: ActiveUix;
|
||||
src: string;
|
||||
artworkSrc: string;
|
||||
metadata?: SoundMediaMetadata;
|
||||
onPlay: () => void;
|
||||
onPause: () => void;
|
||||
onEnded: () => void;
|
||||
} = $props();
|
||||
// svelte-ignore state_referenced_locally
|
||||
setActiveUix(uix);
|
||||
Soma.create();
|
||||
const eidos = ActiveEidos.create({ applyDom: false });
|
||||
onDestroy(() => eidos.dispose());
|
||||
let variant = $state<AudioPlayerVariant>('card');
|
||||
let size = $state<MediaPlayerSize>('md');
|
||||
let dir = $state<'ltr' | 'rtl'>('ltr');
|
||||
let peaks = $state<number[] | undefined>(undefined);
|
||||
let loop = $state(false);
|
||||
let title = $state('Pista de prueba');
|
||||
</script>
|
||||
|
||||
<section style="width: 1000px; padding: 16px; background: white; color: black;">
|
||||
<h1>AudioPlayer audit</h1>
|
||||
<AudioPlayer
|
||||
{src}
|
||||
{artworkSrc}
|
||||
artworkAlt="Carátula de prueba"
|
||||
{title}
|
||||
artist="Autor de prueba"
|
||||
{metadata}
|
||||
{variant}
|
||||
{size}
|
||||
{dir}
|
||||
{peaks}
|
||||
{loop}
|
||||
seekStep={3}
|
||||
ariaLabel="Reproductor de prueba"
|
||||
{onPlay}
|
||||
{onPause}
|
||||
{onEnded}
|
||||
>
|
||||
<MediaPlayer.RestartButton />
|
||||
<MediaPlayer.SkipButton direction="previous" />
|
||||
<MediaPlayer.SkipButton direction="next" />
|
||||
</AudioPlayer>
|
||||
<div>
|
||||
{#each ['card', 'row', 'bar', 'inline'] as nextVariant}
|
||||
<button onclick={() => (variant = nextVariant as AudioPlayerVariant)}>
|
||||
Variant {nextVariant}
|
||||
</button>
|
||||
{/each}
|
||||
{#each ['sm', 'md', 'lg', 'xl'] as nextSize}
|
||||
<button onclick={() => (size = nextSize as MediaPlayerSize)}>Size {nextSize}</button>
|
||||
{/each}
|
||||
<button onclick={() => (dir = dir === 'ltr' ? 'rtl' : 'ltr')}>Toggle direction</button>
|
||||
<button onclick={() => (peaks = peaks ? undefined : [0, 1, 0.5, 0.25])}>Toggle peaks</button>
|
||||
<button onclick={() => (loop = !loop)}>Toggle loop</button>
|
||||
<button onclick={() => (title = 'Nueva pista')}>Change identity</button>
|
||||
</div>
|
||||
</section>
|
||||
@ -0,0 +1,95 @@
|
||||
import { flushSync, mount, unmount } from 'svelte';
|
||||
import { vi } from 'vitest';
|
||||
import { createActiveUix } from '$active-uix';
|
||||
import type { SoundMediaMetadata } from '$sound';
|
||||
import '../../../index.css';
|
||||
import AudioPlayerAuditHarness from './audio-player-audit-harness.svelte';
|
||||
|
||||
// A native PCM WAV with silent samples: media loading/playback is real,
|
||||
// while the fixture emits no audible content and needs no network server.
|
||||
function silentWav(seconds: number): Blob {
|
||||
const rate = 8000;
|
||||
const bytes = seconds * rate * 2;
|
||||
const buffer = new ArrayBuffer(44 + bytes);
|
||||
const data = new DataView(buffer);
|
||||
const text = (offset: number, value: string) => {
|
||||
for (let i = 0; i < value.length; i++) data.setUint8(offset + i, value.charCodeAt(i));
|
||||
};
|
||||
text(0, 'RIFF');
|
||||
data.setUint32(4, 36 + bytes, true);
|
||||
text(8, 'WAVE');
|
||||
text(12, 'fmt ');
|
||||
data.setUint32(16, 16, true);
|
||||
data.setUint16(20, 1, true);
|
||||
data.setUint16(22, 1, true);
|
||||
data.setUint32(24, rate, true);
|
||||
data.setUint32(28, rate * 2, true);
|
||||
data.setUint16(32, 2, true);
|
||||
data.setUint16(34, 16, true);
|
||||
text(36, 'data');
|
||||
data.setUint32(40, bytes, true);
|
||||
return new Blob([buffer], { type: 'audio/wav' });
|
||||
}
|
||||
|
||||
export function renderAudioPlayer(metadata?: SoundMediaMetadata) {
|
||||
const src = URL.createObjectURL(silentWav(20));
|
||||
const artworkSrc =
|
||||
'data:image/svg+xml,' +
|
||||
encodeURIComponent(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"><rect width="200" height="200" fill="#126878"/><circle cx="100" cy="100" r="60" fill="#e6af36"/></svg>'
|
||||
);
|
||||
const uix = createActiveUix({
|
||||
langs: { schema: {}, defaultLocale: 'es' },
|
||||
prefs: { storage: false },
|
||||
projectPrefs: false
|
||||
});
|
||||
const target = document.createElement('div');
|
||||
document.body.appendChild(target);
|
||||
const onPlay = vi.fn();
|
||||
const onPause = vi.fn();
|
||||
const onEnded = vi.fn();
|
||||
const component = mount(AudioPlayerAuditHarness, {
|
||||
target,
|
||||
props: { uix, src, artworkSrc, metadata, onPlay, onPause, onEnded }
|
||||
});
|
||||
flushSync();
|
||||
const root = target.querySelector<HTMLElement>('[data-media-player]')!;
|
||||
const audio = root.querySelector<HTMLAudioElement>('audio')!;
|
||||
return {
|
||||
target,
|
||||
root,
|
||||
audio,
|
||||
uix,
|
||||
artworkSrc,
|
||||
onPlay,
|
||||
onPause,
|
||||
onEnded,
|
||||
part(name: string) {
|
||||
return root.querySelector<HTMLElement>(`[data-media-player-${name}]`)!;
|
||||
},
|
||||
button(text: string) {
|
||||
return Array.from(target.querySelectorAll('button')).find(
|
||||
(button) => button.textContent?.trim() === text
|
||||
)!;
|
||||
},
|
||||
async ready() {
|
||||
await vi.waitFor(
|
||||
() => {
|
||||
expectLoaded(audio);
|
||||
},
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
},
|
||||
async dispose() {
|
||||
audio.pause();
|
||||
await unmount(component);
|
||||
uix.dispose();
|
||||
URL.revokeObjectURL(src);
|
||||
target.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function expectLoaded(audio: HTMLAudioElement) {
|
||||
if (audio.readyState < 3 || audio.duration !== 20) throw new Error('Native audio not ready');
|
||||
}
|
||||
Loading…
Reference in new issue