Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions configs/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ export default defineConfig(({ mode }) => {
}),
},
resolve: {
alias: {
"solid-js/web": "@solidjs/web",
},
conditions: testSSR
? ["@solid-primitives/source", "node"]
: ["@solid-primitives/source", "browser", "development"],
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
"rehype-highlight": "^7.0.2",
"rehype-slug": "^6.0.0",
"remark-gfm": "^4.0.1",
"solid-js": "^1.9.7",
"@solidjs/web": "^2.0.0-experimental.16",
"solid-js": "^2.0.0-beta.6",
"typescript": "^5.8.3",
"vinxi": "^0.5.7",
"vite": "^6.3.5",
Expand Down
9 changes: 9 additions & 0 deletions packages/audio/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# @solid-primitives/audio

## 2.0.0

### Major Changes

- Updated to Solid.js 2.0 API:
- Replaced `onMount` + `onCleanup` with `onSettled` (returning cleanup function)
- Migrated `createEffect` calls to the split compute/apply model
- Updated peer dependency to `solid-js@^2.0.0`

## 1.4.4

### Patch Changes
Expand Down
118 changes: 70 additions & 48 deletions packages/audio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
[![size](https://img.shields.io/npm/v/@solid-primitives/audio?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/audio)
[![stage](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-3.json)](https://github.com/solidjs-community/solid-primitives#contribution-process)

Primitive to manage audio playback in the browser. The primitives are easily composable and extended. To create your own audio element, consider using makeAudioPlayer which allows you to supply a player instance that matches the built-in standard Audio API.
Primitives to manage audio playback in the browser. The primitives are layered: `make*` variants are non-reactive base primitives that require no Solid owner, while `createAudio` integrates with Solid's reactive system.

Each primitive also exposes the audio instance for further custom extensions. Within an SSR context this audio primitive performs noops but never interrupts the process. Time values and durations are zero'd and in LOADING state.
Within an SSR context these primitives perform noops and never interrupt the process.

## Installation

Expand All @@ -24,102 +24,124 @@ yarn add @solid-primitives/audio

### makeAudio

A foundational primitive with no player controls but exposes the raw player object.
A foundational non-reactive primitive that creates a raw `HTMLAudioElement` with optional event handlers. No Solid owner required.

```ts
const player = makeAudio("example.mp3");
const [player, cleanup] = makeAudio("example.mp3");
// later:
cleanup();
```

#### Definition

```ts
function makeAudio(src: AudioSource, handlers: AudioEventHandlers = {}): HTMLAudioElement;
function makeAudio(
src: AudioSource | HTMLAudioElement,
handlers?: AudioEventHandlers,
): [player: HTMLAudioElement, cleanup: VoidFunction];
```

### makeAudioPlayer

Provides a very basic interface for wrapping listeners to a supplied or default audio player.
Wraps `makeAudio` with simple playback controls. No Solid owner required.

```ts
const { play, pause, seek } = makeAudioPlayer("example.mp3");
const [{ play, pause, seek, setVolume, player }, cleanup] = makeAudioPlayer("example.mp3");
play();
seek(30);
cleanup();
```

#### Definition

```ts
function makeAudioPlayer(
src: AudioSource,
handlers: AudioEventHandlers = {},
): {
play: VoidFunction;
src: AudioSource | HTMLAudioElement,
handlers?: AudioEventHandlers,
): [controls: AudioControls, cleanup: VoidFunction];
```

`AudioControls`:

```ts
type AudioControls = {
play: () => Promise<void>;
pause: VoidFunction;
seek: (time: number) => void;
setVolume: (volume: number) => void;
player: HTMLAudioElement;
};
```

The seek function falls back to fastSeek when on [supporting browsers](https://caniuse.com/?search=fastseek).
The `seek` function uses `fastSeek` on [supporting browsers](https://caniuse.com/?search=fastseek).

### createAudio

Creates a very basic audio/sound player with reactive properties to control the audio. Be careful not to destructure the value properties provided by the primitive as it will likely break reactivity.
A reactive audio primitive. Returns a flat object with writable signal accessors for `playing` and `volume`, a reactive `currentTime`, and an async `duration` that suspends until audio metadata is loaded — integrating with `<Suspense>` / `<Loading>`.

```ts
const [playing, setPlaying] = createSignal(false);
const [volume, setVolume] = createSignal(false);
const [audio, controls] = createAudio("sample.mp3", playing, volume);
setPlaying(true); // or controls.play()
controls.seek(4000);
const audio = createAudio("example.mp3");

audio.playing() // boolean
audio.setPlaying(true) // plays
audio.volume() // 0–1
audio.setVolume(0.5)
audio.currentTime() // seconds
audio.seek(30)
```

The audio primitive exports an reactive properties that provides you access to state, duration and current time.
The `duration` accessor returns a Promise, so wrap it in `<Suspense>`:

```tsx
<Suspense fallback="Loading...">
<span>{audio.duration()}s</span>
</Suspense>
```

_Note:_ Initializing the primitive with `playing` as true works, however note that the user has to interact with the page first (on a fresh page load).
The `src` argument can be a reactive accessor — switching sources replaces the track and seeks to the start:

```ts
function createAudio(
src: AudioSource | Accessor<AudioSource>,
playing?: Accessor<boolean>,
volume?: Accessor<number>,
): [
{
state: AudioState;
currentTime: number;
duration: number;
volume: number;
player: HTMLAudioElement;
},
{
seek: (time: number) => void;
setVolume: (volume: number) => void;
play: VoidFunction;
pause: VoidFunction;
},
];
const [src, setSrc] = createSignal("track1.mp3");
const audio = createAudio(src);
setSrc("track2.mp3");
```

#### Dynamic audio changes
#### Definition

```ts
function createAudio(src: AudioSource | Accessor<AudioSource>): AudioReturn;
```

The source property can be a signal as well as a media source. Upon switching the source via a signal it will continue playing from the head.
`AudioReturn`:

```ts
const [src, setSrc] = createSignal("sample.mp3");
const audio = createAudio(src);
setSrc("sample2.mp3");
type AudioReturn = {
player: HTMLAudioElement;
playing: Accessor<boolean>;
setPlaying: (v: boolean) => void;
volume: Accessor<number>;
setVolume: (v: number) => void;
currentTime: Accessor<number>;
duration: Accessor<number>; // async — suspends until loaded
seek: (time: number) => void;
};
```

### Audio Source
## Audio Source

`createAudio` as well as `makeAudio` and `makeAudioPlayer` all accept MediaSource as a property.
All primitives accept `AudioSource` as their `src` argument:

```ts
type AudioSource = string | undefined | MediaProvider;
```

This includes `MediaSource` and `MediaStream`, enabling streamed or Blob-backed audio:

```ts
const media = new MediaSource();
const audio = createAudio(URL.createObjectURL(media));
```

This allows you to managed streamed or Blob supplied media. In essence the primitives in this package are very flexible and allow direct access to the base browser API.

## Demo

You may view a working example here: https://stackblitz.com/edit/vitejs-vite-zwfs6h?file=src%2Fmain.tsx
Expand Down
Loading
Loading