@solid-primitives/devices
Version:
Primitive that enumerates media devices
142 lines (141 loc) • 5.22 kB
JavaScript
import { createMemo, createSignal, getOwner, onCleanup } from "solid-js";
import { isServer } from "solid-js/web";
import { createStore } from "solid-js/store";
/**
* Creates a list of all media devices
*
* @returns () => MediaDeviceInfo[]
*
* If the permissions to use the media devices are not granted, you'll get a single device of that kind with empty ids and label to show that devices are available at all.
*
* If the array does not contain a device of a certain kind, you cannot get permissions, as requesting permissions requires requesting a stream on any device of the kind.
*/
export const createDevices = () => {
if (isServer) {
return () => [];
}
const [devices, setDevices] = createSignal([]);
const enumerate = () => {
navigator.mediaDevices.enumerateDevices().then(setDevices);
};
enumerate();
navigator.mediaDevices.addEventListener("devicechange", enumerate);
onCleanup(() => navigator.mediaDevices.removeEventListener("devicechange", enumerate));
return devices;
};
const equalDeviceLists = (prev, next) => prev.length === next.length && prev.every(device => next.includes(device));
/**
* Creates a list of all media devices that are microphones
*
* @returns () => MediaDeviceInfo[]
*
* If the microphone permissions are not granted, you'll get a single device with empty ids and label to show that devices are available at all.
*
* Without a device, you cannot get permissions, as requesting permissions requires requesting a stream on any device of the kind.
*/
export const createMicrophones = () => {
if (isServer) {
return () => [];
}
const devices = createDevices();
return createMemo(() => devices().filter(device => device.kind === "audioinput"), [], {
name: "microphones",
equals: equalDeviceLists,
});
};
/**
* Creates a list of all media devices that are speakers
*
* @returns () => MediaDeviceInfo[]
*
* If the speaker permissions are not granted, you'll get a single device with empty ids and label to show that devices are available at all.
*
* Microphone permissions automatically include speaker permissions. You can use the device id of the speaker to use the setSinkId-API of any audio tag.
*/
export const createSpeakers = () => {
if (isServer) {
return () => [];
}
const devices = createDevices();
return createMemo(() => devices().filter(device => device.kind === "audiooutput"), [], {
name: "speakers",
equals: equalDeviceLists,
});
};
/**
* Creates a list of all media devices that are cameras
*
* @returns () => MediaDeviceInfo[]
*
* If the camera permissions are not granted, you'll get a single device with empty ids and label to show that devices are available at all.
*
* Without a device, you cannot get permissions, as requesting permissions requires requesting a stream on any device of the kind.
*/
export const createCameras = () => {
if (isServer) {
return () => [];
}
const devices = createDevices();
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 = false, interval = 100) => {
if (isServer) {
return () => ({
x: 0,
y: 0,
z: 0,
});
}
const [acceleration, setAcceleration] = createSignal();
let throttled = false;
const accelerationEvent = (e) => {
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 = 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) => {
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;
};