media-devices 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
 
7
7
  ## [UNRELEASED]
8
8
 
9
+ ## [0.5.0] - 2024-04-28
10
+
11
+ ### Changed
12
+
13
+ - Reworked the build process to avoid an unmaintained plugin which affects how TypeScript definitions are published.
14
+ - Publish as ESM package. Legacy entrypoints are maintained for compatibility.
15
+ - Replaced packaged source files with a generated `.d.ts` definition for TypeScript.
16
+
17
+ ### Removed
18
+
19
+ - Source files are no longer distributed with the package. You should not notice a difference.
20
+
9
21
  ## [0.4.0] - 2022-05-30
10
22
 
11
23
  ### Removed
@@ -55,7 +67,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
67
  - A device list-diffing implementation of `ondevicechange`.
56
68
  - Support detection via `supportsMediaDevices()`.
57
69
 
58
- [Unreleased]: https://github.com/PsychoLlama/media-devices/compare/v0.4.0...HEAD
70
+ [Unreleased]: https://github.com/PsychoLlama/media-devices/compare/v0.5.0...HEAD
71
+ [0.5.0]: https://github.com/PsychoLlama/media-devices/compare/v0.4.0...v0.5.0
59
72
  [0.4.0]: https://github.com/PsychoLlama/media-devices/compare/v0.3.0...v0.4.0
60
73
  [0.3.0]: https://github.com/PsychoLlama/media-devices/compare/v0.2.0...v0.3.0
61
74
  [0.2.0]: https://github.com/PsychoLlama/media-devices/compare/v0.1.0...v0.2.0
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <div>
6
6
  <a href="https://github.com/PsychoLlama/media-devices/actions/workflows/main.yml">
7
- <img src="https://img.shields.io/github/workflow/status/PsychoLlama/media-devices/CI/main" alt="Build status" />
7
+ <img src="https://img.shields.io/github/actions/workflow/status/PsychoLlama/media-devices/main.yml?branch=main" alt="Build status" />
8
8
  </a>
9
9
  <img src="https://img.shields.io/npm/types/media-devices" alt="Build status" />
10
10
  <a href="https://www.npmjs.com/package/media-devices">
@@ -0,0 +1,129 @@
1
+ declare const _default: DeviceManager;
2
+ export default _default;
3
+
4
+ declare interface DeviceAddEvent {
5
+ type: OperationType.Add;
6
+ device: DeviceInfo;
7
+ }
8
+
9
+ export declare type DeviceChange = DeviceAddEvent | DeviceRemoveEvent | DeviceUpdateEvent;
10
+
11
+ declare interface DeviceChangeListener {
12
+ (update: {
13
+ changes: Array<DeviceChange>;
14
+ devices: Array<DeviceInfo>;
15
+ }): unknown;
16
+ }
17
+
18
+ export declare interface DeviceInfo {
19
+ /**
20
+ * The device list is obfuscated until you gain elevated permissions.
21
+ * Browsers will use an empty string for the device label until the first
22
+ * successful `getUserMedia(...)` request.
23
+ */
24
+ label: null | string;
25
+ /**
26
+ * A unique identifier persistent across sessions. Note: In Chromium
27
+ * browsers, this can be unset if you haven't received permission for the
28
+ * media resource yet.
29
+ */
30
+ deviceId: null | string;
31
+ /**
32
+ * A unique identifier grouping one or more devices together. Two devices
33
+ * with the same group ID symbolise that both devices belong to the same
34
+ * hardware, e.g. a webcam with an integrated microphone. Note: Safari
35
+ * doesn't support group IDs.
36
+ */
37
+ groupId: null | string;
38
+ /**
39
+ * Declares the type of media provided. This covers microphones, cameras,
40
+ * and speakers.
41
+ */
42
+ kind: DeviceKind;
43
+ }
44
+
45
+ export declare enum DeviceKind {
46
+ VideoInput = "videoinput",
47
+ AudioInput = "audioinput",
48
+ AudioOutput = "audiooutput"
49
+ }
50
+
51
+ /**
52
+ * Monitors the set of devices for changes and calculates convenient diffs
53
+ * between updates. Steps are taken to handle cross-browser quirks and
54
+ * attempts graceful integration with browser fingerprinting countermeasures.
55
+ */
56
+ declare class DeviceManager {
57
+ private _knownDevices;
58
+ private _gainedScreenAccessOnce;
59
+ /**
60
+ * Specifies a function to be called whenever the list of available devices
61
+ * changes.
62
+ *
63
+ * Note: this is different from the native event. It passes the changeset
64
+ * and full list of devices as a parameter.
65
+ */
66
+ ondevicechange: null | DeviceChangeListener;
67
+ constructor();
68
+ /**
69
+ * Request a live media stream from audio and/or video devices. Streams are
70
+ * configurable through constraints.
71
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
72
+ */
73
+ getUserMedia: (constraints: MediaStreamConstraints) => Promise<MediaStream>;
74
+ /**
75
+ * Ask the user to share their screen. Resolves with a media stream carrying
76
+ * video, and potentially audio from the application window.
77
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia
78
+ */
79
+ getDisplayMedia: (constraints?: MediaStreamConstraints) => Promise<MediaStream>;
80
+ /**
81
+ * Lists every available hardware device, including microphones, cameras,
82
+ * and speakers (depending on browser support). May contain redacted
83
+ * information depending on application permissions.
84
+ */
85
+ enumerateDevices: () => Promise<Array<DeviceInfo>>;
86
+ /**
87
+ * Returns an object containing every media constraint supported by the
88
+ * browser.
89
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getSupportedConstraints
90
+ */
91
+ getSupportedConstraints: () => MediaTrackSupportedConstraints;
92
+ private _checkForDeviceChanges;
93
+ /**
94
+ * Note: The device enumeration API may return null values for device IDs
95
+ * and labels. To avoid creating erroneous "Device Added" notifications,
96
+ * a best effort should be made to detect when devices are identical.
97
+ *
98
+ * Order is significant. Preferred devices are listed first, which helps
99
+ * correlate devices from permissioned requests with unpermissioned
100
+ * requests.
101
+ */
102
+ private _calculateDeviceDiff;
103
+ }
104
+
105
+ declare interface DeviceRemoveEvent {
106
+ type: OperationType.Remove;
107
+ device: DeviceInfo;
108
+ }
109
+
110
+ declare interface DeviceUpdateEvent {
111
+ type: OperationType.Update;
112
+ newInfo: DeviceInfo;
113
+ oldInfo: DeviceInfo;
114
+ }
115
+
116
+ export declare enum OperationType {
117
+ Add = "add",
118
+ Remove = "remove",
119
+ Update = "update"
120
+ }
121
+
122
+ /**
123
+ * Not all browsers support media devices, and some restrict access for
124
+ * insecure sites and private contexts. This is often reflected by removing
125
+ * the `mediaDevices` API entirely.
126
+ */
127
+ export declare function supportsMediaDevices(): boolean;
128
+
129
+ export { }
@@ -0,0 +1,136 @@
1
+ var f = Object.defineProperty;
2
+ var p = (e, t, n) => t in e ? f(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n;
3
+ var r = (e, t, n) => (p(e, typeof t != "symbol" ? t + "" : t, n), n);
4
+ function o() {
5
+ return typeof navigator < "u" && !!navigator.mediaDevices;
6
+ }
7
+ function c() {
8
+ if (!o())
9
+ throw new Error("The media devices API isn't supported here.");
10
+ return navigator.mediaDevices;
11
+ }
12
+ async function g() {
13
+ return (await c().enumerateDevices()).filter(h).map(m);
14
+ }
15
+ function h(e) {
16
+ return e.deviceId !== "default";
17
+ }
18
+ function m(e) {
19
+ return {
20
+ label: e.label || null,
21
+ kind: e.kind,
22
+ deviceId: e.deviceId || null,
23
+ groupId: e.groupId || null
24
+ };
25
+ }
26
+ var D = /* @__PURE__ */ ((e) => (e.VideoInput = "videoinput", e.AudioInput = "audioinput", e.AudioOutput = "audiooutput", e))(D || {});
27
+ async function I(e) {
28
+ return c().getUserMedia(e);
29
+ }
30
+ class y {
31
+ constructor() {
32
+ r(this, "_knownDevices", []);
33
+ r(this, "_gainedScreenAccessOnce", !1);
34
+ /**
35
+ * Specifies a function to be called whenever the list of available devices
36
+ * changes.
37
+ *
38
+ * Note: this is different from the native event. It passes the changeset
39
+ * and full list of devices as a parameter.
40
+ */
41
+ r(this, "ondevicechange", null);
42
+ /**
43
+ * Request a live media stream from audio and/or video devices. Streams are
44
+ * configurable through constraints.
45
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
46
+ */
47
+ r(this, "getUserMedia", async (t) => {
48
+ const n = await I(t);
49
+ return this.enumerateDevices(), n;
50
+ });
51
+ /**
52
+ * Ask the user to share their screen. Resolves with a media stream carrying
53
+ * video, and potentially audio from the application window.
54
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia
55
+ */
56
+ r(this, "getDisplayMedia", async (t) => {
57
+ const n = await c().getDisplayMedia(t);
58
+ return this._gainedScreenAccessOnce || (this._gainedScreenAccessOnce = !0, this.enumerateDevices()), n;
59
+ });
60
+ /**
61
+ * Lists every available hardware device, including microphones, cameras,
62
+ * and speakers (depending on browser support). May contain redacted
63
+ * information depending on application permissions.
64
+ */
65
+ r(this, "enumerateDevices", async () => {
66
+ const t = await g();
67
+ return this._checkForDeviceChanges(t), t;
68
+ });
69
+ /**
70
+ * Returns an object containing every media constraint supported by the
71
+ * browser.
72
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getSupportedConstraints
73
+ */
74
+ r(this, "getSupportedConstraints", () => c().getSupportedConstraints());
75
+ o() && c().addEventListener("devicechange", () => this.ondevicechange ? this.enumerateDevices() : Promise.resolve());
76
+ }
77
+ _checkForDeviceChanges(t) {
78
+ var a;
79
+ const n = this._knownDevices;
80
+ this._knownDevices = t;
81
+ const i = this._calculateDeviceDiff(
82
+ t,
83
+ n
84
+ );
85
+ i.length && ((a = this.ondevicechange) == null || a.call(this, { changes: i, devices: t }));
86
+ }
87
+ /**
88
+ * Note: The device enumeration API may return null values for device IDs
89
+ * and labels. To avoid creating erroneous "Device Added" notifications,
90
+ * a best effort should be made to detect when devices are identical.
91
+ *
92
+ * Order is significant. Preferred devices are listed first, which helps
93
+ * correlate devices from permissioned requests with unpermissioned
94
+ * requests.
95
+ */
96
+ _calculateDeviceDiff(t, n) {
97
+ const i = n.slice(), a = [], l = t.filter((s) => {
98
+ const d = i.findIndex((u) => _(s, u));
99
+ if (d > -1) {
100
+ const [u] = i.splice(d, 1);
101
+ if (s.label !== u.label) {
102
+ const v = {
103
+ type: "update",
104
+ newInfo: s,
105
+ oldInfo: u
106
+ };
107
+ a.push(v);
108
+ }
109
+ }
110
+ return d === -1;
111
+ });
112
+ return [
113
+ ...a,
114
+ // A device was just removed.
115
+ ...i.map((s) => ({ type: "remove", device: s })),
116
+ // A device was just plugged in.
117
+ ...l.map((s) => ({ type: "add", device: s }))
118
+ ];
119
+ }
120
+ }
121
+ function _(e, t) {
122
+ if (t.deviceId)
123
+ return e.deviceId === t.deviceId;
124
+ function n(i) {
125
+ return `${i.kind}:${i.groupId}`;
126
+ }
127
+ return n(e) === n(t);
128
+ }
129
+ var k = /* @__PURE__ */ ((e) => (e.Add = "add", e.Remove = "remove", e.Update = "update", e))(k || {});
130
+ const M = new y();
131
+ export {
132
+ D as DeviceKind,
133
+ k as OperationType,
134
+ M as default,
135
+ o as supportsMediaDevices
136
+ };
@@ -0,0 +1 @@
1
+ (function(t,i){typeof exports=="object"&&typeof module<"u"?i(exports):typeof define=="function"&&define.amd?define(["exports"],i):(t=typeof globalThis<"u"?globalThis:t||self,i(t["media-devices"]={}))})(this,function(t){"use strict";var k=Object.defineProperty;var A=(t,i,s)=>i in t?k(t,i,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[i]=s;var a=(t,i,s)=>(A(t,typeof i!="symbol"?i+"":i,s),s);function i(){return typeof navigator<"u"&&!!navigator.mediaDevices}function s(){if(!i())throw new Error("The media devices API isn't supported here.");return navigator.mediaDevices}async function p(){return(await s().enumerateDevices()).filter(h).map(g)}function h(e){return e.deviceId!=="default"}function g(e){return{label:e.label||null,kind:e.kind,deviceId:e.deviceId||null,groupId:e.groupId||null}}var f=(e=>(e.VideoInput="videoinput",e.AudioInput="audioinput",e.AudioOutput="audiooutput",e))(f||{});async function m(e){return s().getUserMedia(e)}class D{constructor(){a(this,"_knownDevices",[]);a(this,"_gainedScreenAccessOnce",!1);a(this,"ondevicechange",null);a(this,"getUserMedia",async n=>{const r=await m(n);return this.enumerateDevices(),r});a(this,"getDisplayMedia",async n=>{const r=await s().getDisplayMedia(n);return this._gainedScreenAccessOnce||(this._gainedScreenAccessOnce=!0,this.enumerateDevices()),r});a(this,"enumerateDevices",async()=>{const n=await p();return this._checkForDeviceChanges(n),n});a(this,"getSupportedConstraints",()=>s().getSupportedConstraints());i()&&s().addEventListener("devicechange",()=>this.ondevicechange?this.enumerateDevices():Promise.resolve())}_checkForDeviceChanges(n){var u;const r=this._knownDevices;this._knownDevices=n;const c=this._calculateDeviceDiff(n,r);c.length&&((u=this.ondevicechange)==null||u.call(this,{changes:c,devices:n}))}_calculateDeviceDiff(n,r){const c=r.slice(),u=[],_=n.filter(d=>{const l=c.findIndex(o=>I(d,o));if(l>-1){const[o]=c.splice(l,1);if(d.label!==o.label){const M={type:"update",newInfo:d,oldInfo:o};u.push(M)}}return l===-1});return[...u,...c.map(d=>({type:"remove",device:d})),..._.map(d=>({type:"add",device:d}))]}}function I(e,n){if(n.deviceId)return e.deviceId===n.deviceId;function r(c){return`${c.kind}:${c.groupId}`}return r(e)===r(n)}var v=(e=>(e.Add="add",e.Remove="remove",e.Update="update",e))(v||{});const y=new D;t.DeviceKind=f,t.OperationType=v,t.default=y,t.supportsMediaDevices=i,Object.defineProperties(t,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
package/package.json CHANGED
@@ -1,22 +1,24 @@
1
1
  {
2
2
  "name": "media-devices",
3
3
  "description": "Easily manage media devices in the browser.",
4
- "version": "0.4.0",
4
+ "version": "0.5.0",
5
5
  "license": "MIT",
6
6
  "author": "Jesse Gibson <JesseTheGibson@gmail.com>",
7
7
  "repository": "github:PsychoLlama/media-devices",
8
8
  "homepage": "https://github.com/PsychoLlama/media-devices#readme",
9
- "module": "./dist/media-devices.es.js",
10
- "main": "./dist/media-devices.umd.js",
9
+ "type": "module",
10
+ "module": "./dist/media-devices.js",
11
+ "main": "./dist/media-devices.umd.cjs",
12
+ "types": "./dist/media-devices.d.ts",
11
13
  "exports": {
12
14
  ".": {
13
- "require": "./dist/media-devices.umd.js",
14
- "import": "./dist/media-devices.es.js"
15
+ "types": "./dist/media-devices.d.ts",
16
+ "require": "./dist/media-devices.umd.cjs",
17
+ "import": "./dist/media-devices.js"
15
18
  }
16
19
  },
17
20
  "files": [
18
- "dist",
19
- "src"
21
+ "dist"
20
22
  ],
21
23
  "keywords": [
22
24
  "camera",
@@ -26,7 +28,7 @@
26
28
  "mediadevices"
27
29
  ],
28
30
  "scripts": {
29
- "prepare": "vite build --sourcemap",
31
+ "prepare": "vite build",
30
32
  "test": "./bin/run-tests",
31
33
  "test:unit": "jest --color",
32
34
  "test:lint": "eslint src --ext ts --color",
@@ -81,19 +83,18 @@
81
83
  }
82
84
  },
83
85
  "devDependencies": {
84
- "@types/jest": "27.5.1",
85
- "@typescript-eslint/eslint-plugin": "5.27.0",
86
- "@typescript-eslint/parser": "5.27.0",
87
- "eslint": "8.16.0",
88
- "husky": "8.0.1",
89
- "jest": "28.1.0",
90
- "jest-environment-jsdom": "28.1.0",
91
- "lint-staged": "12.4.3",
92
- "prettier": "2.6.2",
93
- "size-limit": "7.0.8",
94
- "ts-jest": "28.0.3",
95
- "typescript": "4.7.2",
96
- "vite": "2.9.9",
97
- "vite-dts": "1.0.4"
86
+ "@types/jest": "29.5.12",
87
+ "@typescript-eslint/eslint-plugin": "7.7.1",
88
+ "@typescript-eslint/parser": "7.7.1",
89
+ "eslint": "8.57.0",
90
+ "husky": "9.0.11",
91
+ "jest": "29.7.0",
92
+ "jest-environment-jsdom": "29.7.0",
93
+ "lint-staged": "15.2.2",
94
+ "prettier": "3.2.5",
95
+ "ts-jest": "29.1.2",
96
+ "typescript": "5.4.5",
97
+ "vite": "5.2.10",
98
+ "vite-plugin-dts": "^3.7.3"
98
99
  }
99
100
  }
@@ -1,2 +0,0 @@
1
- export * from "../src/index"
2
- export {default} from "../src/index"
@@ -1,132 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField = (obj, key, value) => {
4
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
- return value;
6
- };
7
- function supportsMediaDevices() {
8
- return typeof navigator !== "undefined" && !!navigator.mediaDevices;
9
- }
10
- function getMediaDevicesApi() {
11
- if (!supportsMediaDevices()) {
12
- throw new Error(`The media devices API isn't supported here.`);
13
- }
14
- return navigator.mediaDevices;
15
- }
16
- async function enumerateDevices() {
17
- const devices = await getMediaDevicesApi().enumerateDevices();
18
- return devices.filter(isPhysicalDevice).map(normalizeDeviceInfo);
19
- }
20
- function isPhysicalDevice(device) {
21
- return device.deviceId !== "default";
22
- }
23
- function normalizeDeviceInfo(device) {
24
- return {
25
- label: device.label || null,
26
- kind: device.kind,
27
- deviceId: device.deviceId || null,
28
- groupId: device.groupId || null
29
- };
30
- }
31
- var DeviceKind = /* @__PURE__ */ ((DeviceKind2) => {
32
- DeviceKind2["VideoInput"] = "videoinput";
33
- DeviceKind2["AudioInput"] = "audioinput";
34
- DeviceKind2["AudioOutput"] = "audiooutput";
35
- return DeviceKind2;
36
- })(DeviceKind || {});
37
- async function getUserMedia(constraints) {
38
- return getMediaDevicesApi().getUserMedia(constraints);
39
- }
40
- class DeviceManager {
41
- constructor() {
42
- __publicField(this, "_knownDevices", []);
43
- __publicField(this, "_gainedScreenAccessOnce", false);
44
- __publicField(this, "ondevicechange", null);
45
- __publicField(this, "getUserMedia", async (constraints) => {
46
- const stream = await getUserMedia(constraints);
47
- this.enumerateDevices();
48
- return stream;
49
- });
50
- __publicField(this, "getDisplayMedia", async (constraints) => {
51
- const stream = await getMediaDevicesApi().getDisplayMedia(constraints);
52
- if (!this._gainedScreenAccessOnce) {
53
- this._gainedScreenAccessOnce = true;
54
- this.enumerateDevices();
55
- }
56
- return stream;
57
- });
58
- __publicField(this, "enumerateDevices", async () => {
59
- const devices = await enumerateDevices();
60
- this._checkForDeviceChanges(devices);
61
- return devices;
62
- });
63
- __publicField(this, "getSupportedConstraints", () => {
64
- return getMediaDevicesApi().getSupportedConstraints();
65
- });
66
- if (supportsMediaDevices()) {
67
- getMediaDevicesApi().addEventListener("devicechange", () => {
68
- if (this.ondevicechange) {
69
- return this.enumerateDevices();
70
- }
71
- return Promise.resolve();
72
- });
73
- }
74
- }
75
- _checkForDeviceChanges(newDevices) {
76
- var _a;
77
- const oldDevices = this._knownDevices;
78
- this._knownDevices = newDevices;
79
- const changes = this._calculateDeviceDiff(newDevices, oldDevices);
80
- if (changes.length) {
81
- (_a = this.ondevicechange) == null ? void 0 : _a.call(this, { changes, devices: newDevices });
82
- }
83
- }
84
- _calculateDeviceDiff(newDevices, oldDevices) {
85
- const removals = oldDevices.slice();
86
- const updates = [];
87
- const additions = newDevices.filter((newDevice) => {
88
- const oldDeviceIndex = removals.findIndex((oldDevice) => {
89
- return isIdenticalDevice(newDevice, oldDevice);
90
- });
91
- if (oldDeviceIndex > -1) {
92
- const [oldDevice] = removals.splice(oldDeviceIndex, 1);
93
- if (newDevice.label !== oldDevice.label) {
94
- const update = {
95
- type: OperationType.Update,
96
- newInfo: newDevice,
97
- oldInfo: oldDevice
98
- };
99
- updates.push(update);
100
- }
101
- }
102
- return oldDeviceIndex === -1;
103
- });
104
- return [
105
- ...updates,
106
- ...removals.map((device) => {
107
- return { type: OperationType.Remove, device };
108
- }),
109
- ...additions.map((device) => {
110
- return { type: OperationType.Add, device };
111
- })
112
- ];
113
- }
114
- }
115
- function isIdenticalDevice(newDevice, oldDevice) {
116
- if (oldDevice.deviceId) {
117
- return newDevice.deviceId === oldDevice.deviceId;
118
- }
119
- function toCrudeId(device) {
120
- return `${device.kind}:${device.groupId}`;
121
- }
122
- return toCrudeId(newDevice) === toCrudeId(oldDevice);
123
- }
124
- var OperationType = /* @__PURE__ */ ((OperationType2) => {
125
- OperationType2["Add"] = "add";
126
- OperationType2["Remove"] = "remove";
127
- OperationType2["Update"] = "update";
128
- return OperationType2;
129
- })(OperationType || {});
130
- var index = new DeviceManager();
131
- export { DeviceKind, OperationType, index as default, supportsMediaDevices };
132
- //# sourceMappingURL=media-devices.es.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"media-devices.es.js","sources":["../src/support-detection.ts","../src/enumerate-devices.ts","../src/get-user-media.ts","../src/device-manager.ts","../src/index.ts"],"sourcesContent":["/**\n * Not all browsers support media devices, and some restrict access for\n * insecure sites and private contexts. This is often reflected by removing\n * the `mediaDevices` API entirely.\n */\nexport function supportsMediaDevices() {\n return typeof navigator !== 'undefined' && !!navigator.mediaDevices;\n}\n\nexport function getMediaDevicesApi() {\n if (!supportsMediaDevices()) {\n throw new Error(`The media devices API isn't supported here.`);\n }\n\n return navigator.mediaDevices;\n}\n","import { getMediaDevicesApi } from './support-detection';\n\n/**\n * A normalization layer over `MediaDevices.enumerateDevices()`:\n * https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo\n *\n * The API is fraught with cross-browser quirks and fingerprinting blocks.\n * This interface seeks to normalize some of those quirks and make the\n * security tradeoffs obvious.\n */\nexport default async function enumerateDevices(): Promise<Array<DeviceInfo>> {\n const devices = await getMediaDevicesApi().enumerateDevices();\n return devices.filter(isPhysicalDevice).map(normalizeDeviceInfo);\n}\n\n/**\n * Chromium does this really annoying thing where it duplicates preferred\n * devices by substituting the ID with \"default\". No other browser does this,\n * and preferred devices are already represented by list order.\n *\n * Since those meta-devices don't add relevant information and risk confusing\n * device UIs, I simply remove them.\n */\nfunction isPhysicalDevice(device: MediaDeviceInfo) {\n return device.deviceId !== 'default';\n}\n\n// Make nullable fields explicit.\nfunction normalizeDeviceInfo(device: MediaDeviceInfo): DeviceInfo {\n return {\n label: device.label || null,\n kind: device.kind as DeviceKind,\n deviceId: device.deviceId || null,\n groupId: device.groupId || null,\n };\n}\n\nexport interface DeviceInfo {\n /**\n * The device list is obfuscated until you gain elevated permissions.\n * Browsers will use an empty string for the device label until the first\n * successful `getUserMedia(...)` request.\n */\n label: null | string;\n\n /**\n * A unique identifier persistent across sessions. Note: In Chromium\n * browsers, this can be unset if you haven't received permission for the\n * media resource yet.\n */\n deviceId: null | string;\n\n /**\n * A unique identifier grouping one or more devices together. Two devices\n * with the same group ID symbolise that both devices belong to the same\n * hardware, e.g. a webcam with an integrated microphone. Note: Safari\n * doesn't support group IDs.\n */\n groupId: null | string;\n\n /**\n * Declares the type of media provided. This covers microphones, cameras,\n * and speakers.\n */\n kind: DeviceKind;\n}\n\nexport enum DeviceKind {\n VideoInput = 'videoinput',\n AudioInput = 'audioinput',\n AudioOutput = 'audiooutput',\n}\n","import { getMediaDevicesApi } from './support-detection';\n\nexport default async function getUserMedia(\n constraints: MediaStreamConstraints\n): Promise<MediaStream> {\n return getMediaDevicesApi().getUserMedia(constraints);\n}\n","import enumerateDevices, { DeviceInfo } from './enumerate-devices';\nimport { getMediaDevicesApi, supportsMediaDevices } from './support-detection';\nimport getUserMedia from './get-user-media';\n\n/**\n * Monitors the set of devices for changes and calculates convenient diffs\n * between updates. Steps are taken to handle cross-browser quirks and\n * attempts graceful integration with browser fingerprinting countermeasures.\n */\nexport default class DeviceManager {\n private _knownDevices: Array<DeviceInfo> = [];\n private _gainedScreenAccessOnce = false;\n\n /**\n * Specifies a function to be called whenever the list of available devices\n * changes.\n *\n * Note: this is different from the native event. It passes the changeset\n * and full list of devices as a parameter.\n */\n ondevicechange: null | DeviceChangeListener = null;\n\n constructor() {\n // Listen for changes at the OS level. If the device list changes and\n // someone's around to see it, refresh the device list. Refreshing has\n // a side effect of performing a diff and telling all subscribers about\n // the change.\n if (supportsMediaDevices()) {\n getMediaDevicesApi().addEventListener('devicechange', () => {\n if (this.ondevicechange) {\n return this.enumerateDevices();\n }\n\n return Promise.resolve();\n });\n }\n }\n\n /**\n * Request a live media stream from audio and/or video devices. Streams are\n * configurable through constraints.\n * See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia\n */\n getUserMedia = async (constraints: MediaStreamConstraints) => {\n const stream = await getUserMedia(constraints);\n\n // The browser considers us trusted after the first approved GUM query and\n // allows access to more information in the device list, which is an\n // implicit device change event. Refresh to update the cache.\n //\n // We do this for every GUM request because some browsers only allow\n // access to the subset of devices you've been approved for. While\n // reasonable from a security perspective, it means we're never sure if\n // the cache is stale.\n this.enumerateDevices();\n\n return stream;\n };\n\n /**\n * Ask the user to share their screen. Resolves with a media stream carrying\n * video, and potentially audio from the application window.\n * See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia\n */\n getDisplayMedia = async (\n constraints?: MediaStreamConstraints\n ): Promise<MediaStream> => {\n const stream = await getMediaDevicesApi().getDisplayMedia(constraints);\n\n // Similar to `getUserMedia(...)`, granting access to your screen implies\n // a certain level of trust. Some browsers will remove the fingerprinting\n // protections after the first successful call. However, it's unlikely\n // that another will tell us anything more, so we only refresh devices\n // after the first success.\n if (!this._gainedScreenAccessOnce) {\n this._gainedScreenAccessOnce = true;\n this.enumerateDevices();\n }\n\n return stream;\n };\n\n /**\n * Lists every available hardware device, including microphones, cameras,\n * and speakers (depending on browser support). May contain redacted\n * information depending on application permissions.\n */\n enumerateDevices = async (): Promise<Array<DeviceInfo>> => {\n const devices = await enumerateDevices();\n this._checkForDeviceChanges(devices);\n\n return devices;\n };\n\n /**\n * Returns an object containing every media constraint supported by the\n * browser.\n * See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getSupportedConstraints\n */\n getSupportedConstraints = (): MediaTrackSupportedConstraints => {\n return getMediaDevicesApi().getSupportedConstraints();\n };\n\n private _checkForDeviceChanges(newDevices: Array<DeviceInfo>) {\n const oldDevices = this._knownDevices;\n this._knownDevices = newDevices; // Replace the old devices.\n\n const changes: Array<DeviceChange> = this._calculateDeviceDiff(\n newDevices,\n oldDevices\n );\n\n if (changes.length) {\n this.ondevicechange?.({ changes, devices: newDevices });\n }\n }\n\n /**\n * Note: The device enumeration API may return null values for device IDs\n * and labels. To avoid creating erroneous \"Device Added\" notifications,\n * a best effort should be made to detect when devices are identical.\n *\n * Order is significant. Preferred devices are listed first, which helps\n * correlate devices from permissioned requests with unpermissioned\n * requests.\n */\n private _calculateDeviceDiff(\n newDevices: Array<DeviceInfo>,\n oldDevices: Array<DeviceInfo>\n ): Array<DeviceChange> {\n const removals = oldDevices.slice();\n const updates: Array<DeviceChange> = [];\n\n // If a \"new\" device exists in the list of old devices, then it obviously\n // wasn't just added and clearly we haven't removed it either. It's the\n // same device.\n const additions = newDevices.filter((newDevice) => {\n const oldDeviceIndex = removals.findIndex((oldDevice) => {\n return isIdenticalDevice(newDevice, oldDevice);\n });\n\n // Note: Nasty state mutation hides here.\n // Maps/Sets are out of the question due to poor TS support. Plus IDs\n // are far too unreliable in this context. Iteration and splice() are\n // ugly and gross, but they work.\n if (oldDeviceIndex > -1) {\n const [oldDevice] = removals.splice(oldDeviceIndex, 1);\n\n if (newDevice.label !== oldDevice.label) {\n const update: DeviceUpdateEvent = {\n type: OperationType.Update,\n newInfo: newDevice,\n oldInfo: oldDevice,\n };\n\n updates.push(update);\n }\n }\n\n // Only count it as an \"addition\" if we couldn't find the same device in\n // the older set.\n return oldDeviceIndex === -1;\n });\n\n return [\n ...updates,\n\n // A device was just removed.\n ...removals.map((device) => {\n return { type: OperationType.Remove, device } as DeviceRemoveEvent;\n }),\n\n // A device was just plugged in.\n ...additions.map((device) => {\n return { type: OperationType.Add, device } as DeviceAddEvent;\n }),\n ];\n }\n}\n\n/**\n * Due to fingerprinting countermeasures, the device ID might be an empty\n * string. We have to resort to vague comparisons. After the first successful\n * `getUserMedia(...)` query, the device ID for all related device kinds\n * should be revealed. In that case the new device will have an ID but the old\n * device won't. It should be safe to assume the inverse never happens.\n *\n * Note: Chromium browsers take a private stance by hiding your extra devices.\n * Even if you have a hundred cameras plugged in, until that first GUM query,\n * you'll only see the preferred one. Same for microphones and output devices.\n */\nfunction isIdenticalDevice(newDevice: DeviceInfo, oldDevice: DeviceInfo) {\n if (oldDevice.deviceId) {\n return newDevice.deviceId === oldDevice.deviceId;\n }\n\n // These are the only credible fields we have to go on. It may yield a false\n // positive if you're changing devices before the first GUM query, but since\n // the lists are ordered by priority, that should be unlikely. It's\n // certainly preferable to \"new device\" false positives.\n function toCrudeId(device: DeviceInfo) {\n return `${device.kind}:${device.groupId}`;\n }\n\n return toCrudeId(newDevice) === toCrudeId(oldDevice);\n}\n\nexport type DeviceChange =\n | DeviceAddEvent\n | DeviceRemoveEvent\n | DeviceUpdateEvent;\n\ninterface DeviceAddEvent {\n type: OperationType.Add;\n device: DeviceInfo;\n}\n\ninterface DeviceRemoveEvent {\n type: OperationType.Remove;\n device: DeviceInfo;\n}\n\ninterface DeviceUpdateEvent {\n type: OperationType.Update;\n newInfo: DeviceInfo;\n oldInfo: DeviceInfo;\n}\n\nexport enum OperationType {\n Add = 'add',\n Remove = 'remove',\n Update = 'update',\n}\n\ninterface DeviceChangeListener {\n (update: {\n changes: Array<DeviceChange>;\n devices: Array<DeviceInfo>;\n }): unknown;\n}\n","import DeviceManager from './device-manager';\n\nexport { supportsMediaDevices } from './support-detection';\nexport { DeviceKind } from './enumerate-devices';\nexport { OperationType } from './device-manager';\n\nexport type { DeviceInfo } from './enumerate-devices';\nexport type { DeviceChange } from './device-manager';\n\nexport default new DeviceManager();\n"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export * from "../src/index"
2
- export {default} from "../src/index"
@@ -1,2 +0,0 @@
1
- (function(t,i){typeof exports=="object"&&typeof module!="undefined"?i(exports):typeof define=="function"&&define.amd?define(["exports"],i):(t=typeof globalThis!="undefined"?globalThis:t||self,i(t["media-devices"]={}))})(this,function(t){"use strict";var A=Object.defineProperty;var k=(t,i,s)=>i in t?A(t,i,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[i]=s;var a=(t,i,s)=>(k(t,typeof i!="symbol"?i+"":i,s),s);function i(){return typeof navigator!="undefined"&&!!navigator.mediaDevices}function s(){if(!i())throw new Error("The media devices API isn't supported here.");return navigator.mediaDevices}async function p(){return(await s().enumerateDevices()).filter(h).map(g)}function h(e){return e.deviceId!=="default"}function g(e){return{label:e.label||null,kind:e.kind,deviceId:e.deviceId||null,groupId:e.groupId||null}}var f=(e=>(e.VideoInput="videoinput",e.AudioInput="audioinput",e.AudioOutput="audiooutput",e))(f||{});async function m(e){return s().getUserMedia(e)}class D{constructor(){a(this,"_knownDevices",[]);a(this,"_gainedScreenAccessOnce",!1);a(this,"ondevicechange",null);a(this,"getUserMedia",async n=>{const r=await m(n);return this.enumerateDevices(),r});a(this,"getDisplayMedia",async n=>{const r=await s().getDisplayMedia(n);return this._gainedScreenAccessOnce||(this._gainedScreenAccessOnce=!0,this.enumerateDevices()),r});a(this,"enumerateDevices",async()=>{const n=await p();return this._checkForDeviceChanges(n),n});a(this,"getSupportedConstraints",()=>s().getSupportedConstraints());i()&&s().addEventListener("devicechange",()=>this.ondevicechange?this.enumerateDevices():Promise.resolve())}_checkForDeviceChanges(n){var o;const r=this._knownDevices;this._knownDevices=n;const c=this._calculateDeviceDiff(n,r);c.length&&((o=this.ondevicechange)==null||o.call(this,{changes:c,devices:n}))}_calculateDeviceDiff(n,r){const c=r.slice(),o=[],_=n.filter(d=>{const v=c.findIndex(l=>I(d,l));if(v>-1){const[l]=c.splice(v,1);if(d.label!==l.label){const M={type:u.Update,newInfo:d,oldInfo:l};o.push(M)}}return v===-1});return[...o,...c.map(d=>({type:u.Remove,device:d})),..._.map(d=>({type:u.Add,device:d}))]}}function I(e,n){if(n.deviceId)return e.deviceId===n.deviceId;function r(c){return`${c.kind}:${c.groupId}`}return r(e)===r(n)}var u=(e=>(e.Add="add",e.Remove="remove",e.Update="update",e))(u||{}),y=new D;t.DeviceKind=f,t.OperationType=u,t.default=y,t.supportsMediaDevices=i,Object.defineProperties(t,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
2
- //# sourceMappingURL=media-devices.umd.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"media-devices.umd.js","sources":["../src/support-detection.ts","../src/enumerate-devices.ts","../src/get-user-media.ts","../src/device-manager.ts","../src/index.ts"],"sourcesContent":["/**\n * Not all browsers support media devices, and some restrict access for\n * insecure sites and private contexts. This is often reflected by removing\n * the `mediaDevices` API entirely.\n */\nexport function supportsMediaDevices() {\n return typeof navigator !== 'undefined' && !!navigator.mediaDevices;\n}\n\nexport function getMediaDevicesApi() {\n if (!supportsMediaDevices()) {\n throw new Error(`The media devices API isn't supported here.`);\n }\n\n return navigator.mediaDevices;\n}\n","import { getMediaDevicesApi } from './support-detection';\n\n/**\n * A normalization layer over `MediaDevices.enumerateDevices()`:\n * https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo\n *\n * The API is fraught with cross-browser quirks and fingerprinting blocks.\n * This interface seeks to normalize some of those quirks and make the\n * security tradeoffs obvious.\n */\nexport default async function enumerateDevices(): Promise<Array<DeviceInfo>> {\n const devices = await getMediaDevicesApi().enumerateDevices();\n return devices.filter(isPhysicalDevice).map(normalizeDeviceInfo);\n}\n\n/**\n * Chromium does this really annoying thing where it duplicates preferred\n * devices by substituting the ID with \"default\". No other browser does this,\n * and preferred devices are already represented by list order.\n *\n * Since those meta-devices don't add relevant information and risk confusing\n * device UIs, I simply remove them.\n */\nfunction isPhysicalDevice(device: MediaDeviceInfo) {\n return device.deviceId !== 'default';\n}\n\n// Make nullable fields explicit.\nfunction normalizeDeviceInfo(device: MediaDeviceInfo): DeviceInfo {\n return {\n label: device.label || null,\n kind: device.kind as DeviceKind,\n deviceId: device.deviceId || null,\n groupId: device.groupId || null,\n };\n}\n\nexport interface DeviceInfo {\n /**\n * The device list is obfuscated until you gain elevated permissions.\n * Browsers will use an empty string for the device label until the first\n * successful `getUserMedia(...)` request.\n */\n label: null | string;\n\n /**\n * A unique identifier persistent across sessions. Note: In Chromium\n * browsers, this can be unset if you haven't received permission for the\n * media resource yet.\n */\n deviceId: null | string;\n\n /**\n * A unique identifier grouping one or more devices together. Two devices\n * with the same group ID symbolise that both devices belong to the same\n * hardware, e.g. a webcam with an integrated microphone. Note: Safari\n * doesn't support group IDs.\n */\n groupId: null | string;\n\n /**\n * Declares the type of media provided. This covers microphones, cameras,\n * and speakers.\n */\n kind: DeviceKind;\n}\n\nexport enum DeviceKind {\n VideoInput = 'videoinput',\n AudioInput = 'audioinput',\n AudioOutput = 'audiooutput',\n}\n","import { getMediaDevicesApi } from './support-detection';\n\nexport default async function getUserMedia(\n constraints: MediaStreamConstraints\n): Promise<MediaStream> {\n return getMediaDevicesApi().getUserMedia(constraints);\n}\n","import enumerateDevices, { DeviceInfo } from './enumerate-devices';\nimport { getMediaDevicesApi, supportsMediaDevices } from './support-detection';\nimport getUserMedia from './get-user-media';\n\n/**\n * Monitors the set of devices for changes and calculates convenient diffs\n * between updates. Steps are taken to handle cross-browser quirks and\n * attempts graceful integration with browser fingerprinting countermeasures.\n */\nexport default class DeviceManager {\n private _knownDevices: Array<DeviceInfo> = [];\n private _gainedScreenAccessOnce = false;\n\n /**\n * Specifies a function to be called whenever the list of available devices\n * changes.\n *\n * Note: this is different from the native event. It passes the changeset\n * and full list of devices as a parameter.\n */\n ondevicechange: null | DeviceChangeListener = null;\n\n constructor() {\n // Listen for changes at the OS level. If the device list changes and\n // someone's around to see it, refresh the device list. Refreshing has\n // a side effect of performing a diff and telling all subscribers about\n // the change.\n if (supportsMediaDevices()) {\n getMediaDevicesApi().addEventListener('devicechange', () => {\n if (this.ondevicechange) {\n return this.enumerateDevices();\n }\n\n return Promise.resolve();\n });\n }\n }\n\n /**\n * Request a live media stream from audio and/or video devices. Streams are\n * configurable through constraints.\n * See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia\n */\n getUserMedia = async (constraints: MediaStreamConstraints) => {\n const stream = await getUserMedia(constraints);\n\n // The browser considers us trusted after the first approved GUM query and\n // allows access to more information in the device list, which is an\n // implicit device change event. Refresh to update the cache.\n //\n // We do this for every GUM request because some browsers only allow\n // access to the subset of devices you've been approved for. While\n // reasonable from a security perspective, it means we're never sure if\n // the cache is stale.\n this.enumerateDevices();\n\n return stream;\n };\n\n /**\n * Ask the user to share their screen. Resolves with a media stream carrying\n * video, and potentially audio from the application window.\n * See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia\n */\n getDisplayMedia = async (\n constraints?: MediaStreamConstraints\n ): Promise<MediaStream> => {\n const stream = await getMediaDevicesApi().getDisplayMedia(constraints);\n\n // Similar to `getUserMedia(...)`, granting access to your screen implies\n // a certain level of trust. Some browsers will remove the fingerprinting\n // protections after the first successful call. However, it's unlikely\n // that another will tell us anything more, so we only refresh devices\n // after the first success.\n if (!this._gainedScreenAccessOnce) {\n this._gainedScreenAccessOnce = true;\n this.enumerateDevices();\n }\n\n return stream;\n };\n\n /**\n * Lists every available hardware device, including microphones, cameras,\n * and speakers (depending on browser support). May contain redacted\n * information depending on application permissions.\n */\n enumerateDevices = async (): Promise<Array<DeviceInfo>> => {\n const devices = await enumerateDevices();\n this._checkForDeviceChanges(devices);\n\n return devices;\n };\n\n /**\n * Returns an object containing every media constraint supported by the\n * browser.\n * See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getSupportedConstraints\n */\n getSupportedConstraints = (): MediaTrackSupportedConstraints => {\n return getMediaDevicesApi().getSupportedConstraints();\n };\n\n private _checkForDeviceChanges(newDevices: Array<DeviceInfo>) {\n const oldDevices = this._knownDevices;\n this._knownDevices = newDevices; // Replace the old devices.\n\n const changes: Array<DeviceChange> = this._calculateDeviceDiff(\n newDevices,\n oldDevices\n );\n\n if (changes.length) {\n this.ondevicechange?.({ changes, devices: newDevices });\n }\n }\n\n /**\n * Note: The device enumeration API may return null values for device IDs\n * and labels. To avoid creating erroneous \"Device Added\" notifications,\n * a best effort should be made to detect when devices are identical.\n *\n * Order is significant. Preferred devices are listed first, which helps\n * correlate devices from permissioned requests with unpermissioned\n * requests.\n */\n private _calculateDeviceDiff(\n newDevices: Array<DeviceInfo>,\n oldDevices: Array<DeviceInfo>\n ): Array<DeviceChange> {\n const removals = oldDevices.slice();\n const updates: Array<DeviceChange> = [];\n\n // If a \"new\" device exists in the list of old devices, then it obviously\n // wasn't just added and clearly we haven't removed it either. It's the\n // same device.\n const additions = newDevices.filter((newDevice) => {\n const oldDeviceIndex = removals.findIndex((oldDevice) => {\n return isIdenticalDevice(newDevice, oldDevice);\n });\n\n // Note: Nasty state mutation hides here.\n // Maps/Sets are out of the question due to poor TS support. Plus IDs\n // are far too unreliable in this context. Iteration and splice() are\n // ugly and gross, but they work.\n if (oldDeviceIndex > -1) {\n const [oldDevice] = removals.splice(oldDeviceIndex, 1);\n\n if (newDevice.label !== oldDevice.label) {\n const update: DeviceUpdateEvent = {\n type: OperationType.Update,\n newInfo: newDevice,\n oldInfo: oldDevice,\n };\n\n updates.push(update);\n }\n }\n\n // Only count it as an \"addition\" if we couldn't find the same device in\n // the older set.\n return oldDeviceIndex === -1;\n });\n\n return [\n ...updates,\n\n // A device was just removed.\n ...removals.map((device) => {\n return { type: OperationType.Remove, device } as DeviceRemoveEvent;\n }),\n\n // A device was just plugged in.\n ...additions.map((device) => {\n return { type: OperationType.Add, device } as DeviceAddEvent;\n }),\n ];\n }\n}\n\n/**\n * Due to fingerprinting countermeasures, the device ID might be an empty\n * string. We have to resort to vague comparisons. After the first successful\n * `getUserMedia(...)` query, the device ID for all related device kinds\n * should be revealed. In that case the new device will have an ID but the old\n * device won't. It should be safe to assume the inverse never happens.\n *\n * Note: Chromium browsers take a private stance by hiding your extra devices.\n * Even if you have a hundred cameras plugged in, until that first GUM query,\n * you'll only see the preferred one. Same for microphones and output devices.\n */\nfunction isIdenticalDevice(newDevice: DeviceInfo, oldDevice: DeviceInfo) {\n if (oldDevice.deviceId) {\n return newDevice.deviceId === oldDevice.deviceId;\n }\n\n // These are the only credible fields we have to go on. It may yield a false\n // positive if you're changing devices before the first GUM query, but since\n // the lists are ordered by priority, that should be unlikely. It's\n // certainly preferable to \"new device\" false positives.\n function toCrudeId(device: DeviceInfo) {\n return `${device.kind}:${device.groupId}`;\n }\n\n return toCrudeId(newDevice) === toCrudeId(oldDevice);\n}\n\nexport type DeviceChange =\n | DeviceAddEvent\n | DeviceRemoveEvent\n | DeviceUpdateEvent;\n\ninterface DeviceAddEvent {\n type: OperationType.Add;\n device: DeviceInfo;\n}\n\ninterface DeviceRemoveEvent {\n type: OperationType.Remove;\n device: DeviceInfo;\n}\n\ninterface DeviceUpdateEvent {\n type: OperationType.Update;\n newInfo: DeviceInfo;\n oldInfo: DeviceInfo;\n}\n\nexport enum OperationType {\n Add = 'add',\n Remove = 'remove',\n Update = 'update',\n}\n\ninterface DeviceChangeListener {\n (update: {\n changes: Array<DeviceChange>;\n devices: Array<DeviceInfo>;\n }): unknown;\n}\n","import DeviceManager from './device-manager';\n\nexport { supportsMediaDevices } from './support-detection';\nexport { DeviceKind } from './enumerate-devices';\nexport { OperationType } from './device-manager';\n\nexport type { DeviceInfo } from './enumerate-devices';\nexport type { DeviceChange } from './device-manager';\n\nexport default new DeviceManager();\n"],"names":[],"mappings":"kaAKuC,YAAA,CACrC,MAAO,OAAO,YAAc,aAAe,CAAC,CAAC,UAAU,YACzD,CAEqC,YAAA,CAC/B,GAAA,CAAC,IACG,KAAA,IAAI,OAAM,6CAA6C,EAG/D,MAAO,WAAU,YACnB,CCL6E,kBAAA,CAE3E,MAAO,AADS,MAAM,GAAmB,EAAE,iBAAiB,GAC7C,OAAO,CAAgB,EAAE,IAAI,CAAmB,CACjE,CAUA,WAA0B,EAAyB,CACjD,MAAO,GAAO,WAAa,SAC7B,CAGA,WAA6B,EAAqC,CACzD,MAAA,CACL,MAAO,EAAO,OAAS,KACvB,KAAM,EAAO,KACb,SAAU,EAAO,UAAY,KAC7B,QAAS,EAAO,SAAW,IAAA,CAE/B,CAgCY,GAAA,IAAA,GACG,GAAA,WAAA,aACA,EAAA,WAAA,aACC,EAAA,YAAA,cAHJ,IAAA,GAAA,CAAA,CAAA,ECjEZ,iBACE,EACsB,CACf,MAAA,GAAqB,EAAA,aAAa,CAAW,CACtD,CCGA,MAAqB,CAAc,CAajC,aAAc,CAZN,uBAAmC,CAAA,GAC3C,iCAAkC,IASlC,wBAA8C,MAuB9C,sBAAe,KAAO,IAAwC,CACtD,KAAA,GAAS,KAAM,GAAa,CAAW,EAU7C,YAAK,iBAAiB,EAEf,CAAA,GAQT,yBAAkB,KAChB,IACyB,CACzB,KAAM,GAAS,KAAM,GAAmB,EAAE,gBAAgB,CAAW,EAOjE,MAAC,MAAK,yBACR,MAAK,wBAA0B,GAC/B,KAAK,iBAAiB,GAGjB,CAAA,GAQT,0BAAmB,SAAwC,CACnD,KAAA,GAAU,KAAM,KACtB,YAAK,uBAAuB,CAAO,EAE5B,CAAA,GAQT,iCAA0B,IACjB,EAAA,EAAqB,2BAzE5B,AAAI,KACiB,EAAA,EAAE,iBAAiB,eAAgB,IAChD,KAAK,eACA,KAAK,mBAGP,QAAQ,SAChB,CAEL,CAmEQ,uBAAuB,EAA+B,OAC5D,KAAM,GAAa,KAAK,cACxB,KAAK,cAAgB,EAErB,KAAM,GAA+B,KAAK,qBACxC,EACA,CACF,EAEA,AAAI,EAAQ,QACV,SAAK,iBAAL,kBAAsB,CAAE,UAAS,QAAS,CAAY,GAE1D,CAWQ,qBACN,EACA,EACqB,CACf,KAAA,GAAW,EAAW,QACtB,EAA+B,CAAA,EAK/B,EAAY,EAAW,OAAO,AAAC,GAAc,CACjD,KAAM,GAAiB,EAAS,UAAU,AAAC,GAClC,EAAkB,EAAW,CAAS,CAC9C,EAMD,GAAI,EAAiB,GAAI,CACvB,KAAM,CAAC,GAAa,EAAS,OAAO,EAAgB,CAAC,EAEjD,GAAA,EAAU,QAAU,EAAU,MAAO,CACvC,KAAM,GAA4B,CAChC,KAAM,EAAc,OACpB,QAAS,EACT,QAAS,CAAA,EAGX,EAAQ,KAAK,CAAM,CACrB,CACF,CAIA,MAAO,KAAmB,EAAA,CAC3B,EAEM,MAAA,CACL,GAAG,EAGH,GAAG,EAAS,IAAI,AAAC,GACR,EAAE,KAAM,EAAc,OAAQ,QAAO,EAC7C,EAGD,GAAG,EAAU,IAAI,AAAC,GACT,EAAE,KAAM,EAAc,IAAK,QAAO,EAC1C,CAAA,CAEL,CACF,CAaA,WAA2B,EAAuB,EAAuB,CACvE,GAAI,EAAU,SACL,MAAA,GAAU,WAAa,EAAU,SAO1C,WAAmB,EAAoB,CAC9B,MAAA,GAAG,EAAO,QAAQ,EAAO,SAClC,CAEA,MAAO,GAAU,CAAS,IAAM,EAAU,CAAS,CACrD,CAuBY,GAAA,IAAA,GACJ,GAAA,IAAA,MACG,EAAA,OAAA,SACA,EAAA,OAAA,SAHC,IAAA,GAAA,CAAA,CAAA,EC3NG,EAAA,GAAI"}