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
14 changes: 14 additions & 0 deletions .changeset/devices-solid2-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@solid-primitives/devices": major
---

Migrate to Solid.js v2.0 (beta.13). `createAccelerometer` and `createGyroscope` have been moved to the new `@solid-primitives/sensors` package.

Breaking changes:
- `solid-js` peer dependency updated to `^2.0.0-beta.13`
- `@solidjs/web` is now a required peer dependency
- `createAccelerometer` removed — use `@solid-primitives/sensors` instead
- `createGyroscope` removed — use `@solid-primitives/sensors` instead
- `createMemo` initialValue arg removed (Solid 2.0 API change)
- `isServer` imported from `@solidjs/web`
- `createStore` imported from `solid-js` (not `solid-js/store`)
10 changes: 10 additions & 0 deletions .changeset/sensors-new-package.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@solid-primitives/sensors": minor
---

New package. Provides `makeAccelerometer`, `createAccelerometer`, `makeGyroscope`, and `createGyroscope` following the Solid Primitives `make*`/`create*` convention.

- `makeAccelerometer(onChange, options?)` — raw event listener, returns cleanup, no Solid lifecycle
- `createAccelerometer(includeGravity?, interval?)` — reactive accessor backed by `devicemotion` events
- `makeGyroscope(onChange, options?)` — raw event listener, returns cleanup, no Solid lifecycle
- `createGyroscope(interval?)` — reactive store `{ alpha, beta, gamma }` backed by `deviceorientation` events
49 changes: 36 additions & 13 deletions packages/devices/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,70 @@
[![size](https://img.shields.io/npm/v/@solid-primitives/devices?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/devices)
[![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)

Creates a primitive to get a list of media devices (microphones, speakers, cameras). There are filtered primitives for convenience reasons.
Reactive primitives for enumerating and filtering media input/output devices (microphones, speakers, cameras).

> **Looking for accelerometer or gyroscope?** Motion and orientation sensor primitives have moved to [`@solid-primitives/sensors`](../sensors/README.md).

## Installation

```
npm install @solid-primitives/devices
# or
yarn add @solid-primitives/devices
pnpm add @solid-primitives/devices
```

## How to use it

### Media Devices
### `createDevices`

Returns a reactive accessor for the full list of [`MediaDeviceInfo`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo) objects available to the browser. The list updates automatically whenever devices are added or removed.

```ts
import { createDevices } from "@solid-primitives/devices";

const devices = createDevices();
// => Accessor<MediaDeviceInfo[]>

createEffect(() => console.log(devices()));
```

### `createMicrophones` / `createSpeakers` / `createCameras`

Filtered convenience primitives that each return an accessor for a specific device kind. Each one only re-runs downstream computations when a device of its own kind changes — devices of other kinds changing do not trigger updates.

```ts
import { createMicrophones, createSpeakers, createCameras } from "@solid-primitives/devices";

const microphones = createMicrophones();
// => Accessor<MediaDeviceInfo[]> (kind === "audioinput")

const speakers = createSpeakers();
// => Accessor<MediaDeviceInfo[]> (kind === "audiooutput")

const cameras = createCameras();
// => Accessor<MediaDeviceInfo[]> (kind === "videoinput")
```

The filtered primitives are build so that they only triggered if the devices of their own kind changed.

### Device Motion
## API

```ts
const accelerometer = createAccelerometer();
const gyroscope = createGyroscope();
function createDevices(): Accessor<MediaDeviceInfo[]>;

function createMicrophones(): Accessor<MediaDeviceInfo[]>;
function createSpeakers(): Accessor<MediaDeviceInfo[]>;
function createCameras(): Accessor<MediaDeviceInfo[]>;
```

All four primitives:
- Are SSR-safe — return an empty array on the server.
- Require no arguments.
- Subscribe to [`devicechange`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/devicechange_event) events and clean up automatically via `onCleanup`.

## Demo

You may view a working example here:
https://primitives.solidjs.community/playground/devices/

## Reference

`createAccelerometer` : [devicemotion event](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicemotion_event)
`createGyroscope` : [deviceorientation event](https://developer.mozilla.org/en-US/docs/Web/API/Window/deviceorientation_event)

## Changelog

See [CHANGELOG.md](./CHANGELOG.md)
13 changes: 6 additions & 7 deletions packages/devices/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@solid-primitives/devices",
"version": "1.3.1",
"description": "Primitive that enumerates media devices",
"version": "2.0.0",
"description": "Reactive primitives for enumerating and filtering media input/output devices (microphones, speakers, cameras).",
"author": "Alex Lohr <alex.lohr@logmein.com>",
"contributors": [
"Mohan <mohanavel15@protonmail.com>"
Expand All @@ -19,9 +19,7 @@
"createDevices",
"createMicrophones",
"createSpeakers",
"createCameras",
"createAccelerometer",
"createGyroscope"
"createCameras"
],
"category": "Display & Media"
},
Expand Down Expand Up @@ -55,10 +53,11 @@
],
"browser": {},
"peerDependencies": {
"solid-js": "^1.6.12"
"solid-js": "^2.0.0-beta.13"
},
"typesVersions": {},
"devDependencies": {
"solid-js": "^1.9.7"
"@solidjs/web": "2.0.0-beta.13",
"solid-js": "2.0.0-beta.13"
}
}
84 changes: 10 additions & 74 deletions packages/devices/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createMemo, createSignal, getOwner, onCleanup } from "solid-js";
import { isServer } from "solid-js/web";
import { createStore } from "solid-js/store";
import { createMemo, createSignal, onCleanup } from "solid-js";
import { isServer } from "@solidjs/web";

/**
* Creates a list of all media devices
Expand All @@ -13,11 +12,11 @@ import { createStore } from "solid-js/store";
*/
export const createDevices = () => {
if (isServer) {
return () => [];
return () => [] as MediaDeviceInfo[];
}
const [devices, setDevices] = createSignal<MediaDeviceInfo[]>([]);
const enumerate = () => {
navigator.mediaDevices.enumerateDevices().then(setDevices);
navigator.mediaDevices.enumerateDevices().then(d => setDevices(d as MediaDeviceInfo[]));
};
enumerate();
navigator.mediaDevices.addEventListener("devicechange", enumerate);
Expand All @@ -39,10 +38,10 @@ const equalDeviceLists = (prev: MediaDeviceInfo[], next: MediaDeviceInfo[]) =>
*/
export const createMicrophones = () => {
if (isServer) {
return () => [];
return () => [] as MediaDeviceInfo[];
}
const devices = createDevices();
return createMemo(() => devices().filter(device => device.kind === "audioinput"), [], {
return createMemo(() => devices().filter(device => device.kind === "audioinput"), {
name: "microphones",
equals: equalDeviceLists,
});
Expand All @@ -59,10 +58,10 @@ export const createMicrophones = () => {
*/
export const createSpeakers = () => {
if (isServer) {
return () => [];
return () => [] as MediaDeviceInfo[];
}
const devices = createDevices();
return createMemo(() => devices().filter(device => device.kind === "audiooutput"), [], {
return createMemo(() => devices().filter(device => device.kind === "audiooutput"), {
name: "speakers",
equals: equalDeviceLists,
});
Expand All @@ -79,74 +78,11 @@ export const createSpeakers = () => {
*/
export const createCameras = () => {
if (isServer) {
return () => [];
return () => [] as MediaDeviceInfo[];
}
const devices = createDevices();
return createMemo(() => devices().filter(device => device.kind === "videoinput"), [], {
return createMemo(() => devices().filter(device => device.kind === "videoinput"), {
name: "cameras",
equals: equalDeviceLists,
});
};

/**
* Creates a reactive wrapper to get device acceleration
* @param includeGravity boolean. default value false
* @param interval number as ms. default value 100
* @returnValue Acceleration: Accessor<DeviceMotionEventAcceleration | undefined>
*/
export const createAccelerometer = (includeGravity: boolean = false, interval: number = 100) => {
if (isServer) {
return () => ({
x: 0,
y: 0,
z: 0,
});
}
const [acceleration, setAcceleration] = createSignal<DeviceMotionEventAcceleration>();
let throttled = false;

const accelerationEvent = (e: DeviceMotionEvent) => {
if (throttled) return;
throttled = true;
setTimeout(() => {
throttled = false;
}, interval);

const acceleration = includeGravity ? e.accelerationIncludingGravity : e.acceleration;
setAcceleration(acceleration ? acceleration : undefined);
};

addEventListener("devicemotion", accelerationEvent);
getOwner() && onCleanup(() => removeEventListener("devicemotion", accelerationEvent));
return acceleration;
};

/**
* Creates a reactive wrapper to get device orientation
* @param interval number as ms. default value 100
* @returnValue { alpha: 0, beta: 0, gamma: 0 }
*/
export const createGyroscope = (interval: number = 100) => {
if (isServer) {
return { alpha: 0, beta: 0, gamma: 0 };
}
const [orientation, setOrientation] = createStore({ alpha: 0, beta: 0, gamma: 0 });
let throttled = false;

const orientationEvent = (e: DeviceOrientationEvent) => {
if (throttled) return;
throttled = true;
setTimeout(() => {
throttled = false;
}, interval);
setOrientation({
alpha: e.alpha ? e.alpha : 0,
beta: e.beta ? e.beta : 0,
gamma: e.gamma ? e.gamma : 0,
});
};

addEventListener("deviceorientation", orientationEvent);
getOwner() && onCleanup(() => removeEventListener("deviceorientation", orientationEvent));
return orientation;
};
Loading