@daugasauron/dolly-host-microphone 0.1.4 → 0.1.5-rc.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/abi.mjs CHANGED
@@ -17,4 +17,4 @@ export const DOLLY_MICROPHONE_CAPTURING = 2;
17
17
  export const DOLLY_MICROPHONE_DENIED = 3;
18
18
  export const DOLLY_MICROPHONE_UNAVAILABLE = 4;
19
19
  export const DOLLY_ERRNO = Object.freeze({ E2BIG: 1, EACCES: 2, EADDRINUSE: 3, EADDRNOTAVAIL: 4, EAFNOSUPPORT: 5, EAGAIN: 6, EALREADY: 7, EBADF: 8, EBADMSG: 9, EBUSY: 10, ECANCELED: 11, ECHILD: 12, ECONNABORTED: 13, ECONNREFUSED: 14, ECONNRESET: 15, EDEADLK: 16, EDESTADDRREQ: 17, EDOM: 18, EDQUOT: 19, EEXIST: 20, EFAULT: 21, EFBIG: 22, EHOSTUNREACH: 23, EIDRM: 24, EILSEQ: 25, EINPROGRESS: 26, EINTR: 27, EINVAL: 28, EIO: 29, EISCONN: 30, EISDIR: 31, ELOOP: 32, EMFILE: 33, EMLINK: 34, EMSGSIZE: 35, EMULTIHOP: 36, ENAMETOOLONG: 37, ENETDOWN: 38, ENETRESET: 39, ENETUNREACH: 40, ENFILE: 41, ENOBUFS: 42, ENODEV: 43, ENOENT: 44, ENOEXEC: 45, ENOLCK: 46, ENOLINK: 47, ENOMEM: 48, ENOMSG: 49, ENOPROTOOPT: 50, ENOSPC: 51, ENOSYS: 52, ENOTCONN: 53, ENOTDIR: 54, ENOTEMPTY: 55, ENOTRECOVERABLE: 56, ENOTSOCK: 57, ENOTTY: 59, ENXIO: 60, EOVERFLOW: 61, EOWNERDEAD: 62, EPERM: 63, EPIPE: 64, EPROTO: 65, EPROTONOSUPPORT: 66, EPROTOTYPE: 67, ERANGE: 68, EROFS: 69, ESPIPE: 70, ESRCH: 71, ESTALE: 72, ETIMEDOUT: 73, ETXTBSY: 74, EXDEV: 75, ENODATA: 116, ENOTSUP: 138 });
20
- export const DOLLY_MICROPHONE_ABI_DIGEST = "1e1a29020c1d2f288048903eb53d1807819f52264e6366a8f9a326483054e5b4";
20
+ export const DOLLY_MICROPHONE_ABI_DIGEST = "0efad528c1809a3a87a0889c2e68145ace79da86af746de2800f2bc215dc2f86";
package/microphone.mjs CHANGED
@@ -4,24 +4,19 @@ import { DOLLY_ERRNO as E } from "./abi.mjs";
4
4
  export { DOLLY_MICROPHONE_ABI_DIGEST as digest } from "./abi.mjs";
5
5
 
6
6
  // The device as the lease bridge sees it.
7
- export const lease = { type: "microphone", slots: M.DOLLY_MICROPHONE_SLOTS, packetBytes: M.DOLLY_MICROPHONE_PACKET_BYTES,
7
+ export const lease = { type: "microphone", version: M.DOLLY_MICROPHONE_VERSION, slots: M.DOLLY_MICROPHONE_SLOTS, packetBytes: M.DOLLY_MICROPHONE_PACKET_BYTES,
8
8
  replyBytes: M.DOLLY_MICROPHONE_REPLY_BYTES, open: M.DOLLY_MICROPHONE_OPEN, close: M.DOLLY_MICROPHONE_CLOSE };
9
9
 
10
- export function check() {
11
- return typeof navigator.mediaDevices?.getUserMedia === "function" && typeof globalThis.AudioWorkletNode === "function"
12
- ? null : "Microphone capture is unavailable in this browser";
13
- }
14
-
15
10
  // Trusted page DOM over the display, beside the browser's own recording mark:
16
11
  // shown for as long as a program has asked for or holds the microphone.
17
- function indicate(indicators, state) {
12
+ function indicate({ mount, indicators }, state) {
18
13
  let element = document.querySelector("#microphone-status");
19
14
  if (!element) {
20
- element = document.body.appendChild(document.createElement("div"));
15
+ element = mount.appendChild(document.createElement("div"));
21
16
  element.id = "microphone-status";
22
17
  element.className = "page-indicator";
23
18
  element.setAttribute("role", "status");
24
- element.style.cssText = "position:fixed;left:0.5rem;bottom:2rem;z-index:8;max-width:calc(100vw - 1rem);" +
19
+ element.style.cssText = "position:absolute;left:0.5rem;bottom:2rem;z-index:8;max-width:calc(100% - 1rem);" +
25
20
  "padding:0.15rem 0.45rem;background:#262626;border:1px solid #e98773;color:#e98773;font-size:13px;pointer-events:none";
26
21
  }
27
22
  const text = { waiting: "Microphone: waiting for permission", capturing: "Microphone on" }[state];
@@ -30,13 +25,13 @@ function indicate(indicators, state) {
30
25
  indicators.hold(element, Boolean(text));
31
26
  }
32
27
 
33
- export function browser({ send, indicators }) {
34
- const provider = createMicrophoneProvider(() => indicate(indicators, provider.status().state));
28
+ export function browser({ mount, send, indicators }) {
29
+ const provider = createMicrophoneProvider(() => indicate({ mount, indicators }, provider.status().state));
35
30
  return {
36
31
  page: { get microphone() { return provider.status(); } },
37
32
  messages: {
38
33
  "microphone-request"(message) {
39
- const result = provider.dispatch(message.packet);
34
+ const result = provider.dispatch(message);
40
35
  send({ type: "microphone-complete", scope: message.scope, sequence: message.sequence, ...result },
41
36
  [result.bytes.buffer]);
42
37
  },
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
- "version": "0.1.4",
2
+ "version": "0.1.5-rc.0",
3
3
  "type": "module",
4
4
  "name": "@daugasauron/dolly-host-microphone",
5
5
  "license": "MIT",
6
6
  "dependencies": {
7
- "@daugasauron/dolly": "0.1.4"
7
+ "@daugasauron/dolly": "0.1.5-rc.0"
8
8
  },
9
9
  "description": "Dolly's microphone@0 host module: the default microphone, behind the browser's permission prompt.",
10
10
  "repository": {
package/provider.mjs CHANGED
@@ -72,20 +72,17 @@ export function createMicrophoneProvider(changed = () => {}) {
72
72
  queuedFrames: capture?.length ?? 0, capturedFrames: capture?.captured ?? 0, droppedFrames: capture?.dropped ?? 0,
73
73
  liveTracks: capture?.stream?.getTracks().filter(track => track.readyState === "live").length ?? 0 };
74
74
  }
75
- function dispatch(packet) {
75
+ // A request the lease bridge checked: the packet's operation, scope and
76
+ // sequence (dolly_device_header) and its body.
77
+ function dispatch({ operation: op, scope, sequence, body }) {
76
78
  try {
77
- if (!(packet instanceof Uint8Array) || packet.byteLength < 32 || packet.byteLength > M.DOLLY_MICROPHONE_PACKET_BYTES) fail(E.EINVAL);
78
- packet = packet.slice();
79
- const view = new DataView(packet.buffer, packet.byteOffset, packet.byteLength);
80
- const op = view.getUint32(4, true), scope = view.getUint32(8, true), sequence = view.getUint32(16, true);
81
- if (view.getUint32(0, true) !== M.DOLLY_MICROPHONE_VERSION || view.getUint32(12, true) || view.getUint32(20, true) ||
82
- !scope || !sequence || view.getUint32(24, true) !== packet.length - 32 || view.getUint32(28, true)) fail(E.EINVAL);
79
+ const view = new DataView(body.buffer, body.byteOffset, body.byteLength);
83
80
  let result;
84
81
  if (op === M.DOLLY_MICROPHONE_OPEN) {
85
- if (packet.length !== 32) fail(E.EINVAL);
82
+ if (body.length) fail(E.EINVAL);
86
83
  if (scope <= generation) fail(E.ESTALE);
87
84
  if (capture) fail(E.EBUSY);
88
- if (typeof navigator.mediaDevices?.getUserMedia !== "function" || typeof AudioWorkletNode !== "function") fail(E.ENOSYS);
85
+ if (typeof navigator.mediaDevices?.getUserMedia !== "function" || typeof AudioWorkletNode !== "function") fail(E.ENODEV);
89
86
  capture = { scope, sequence, state: M.DOLLY_MICROPHONE_WAITING, released: false,
90
87
  samples: new Float32Array(M.DOLLY_MICROPHONE_QUEUE_FRAMES), start: 0, length: 0, captured: 0, dropped: 0 };
91
88
  generation = scope;
@@ -100,9 +97,9 @@ export function createMicrophoneProvider(changed = () => {}) {
100
97
  if (sequence <= capture.sequence) fail(E.ESTALE);
101
98
  capture.sequence = sequence;
102
99
  if (op === M.DOLLY_MICROPHONE_READ) {
103
- if (packet.length !== 40) fail(E.EINVAL);
104
- const frames = view.getUint32(32, true);
105
- if (!frames || frames > M.DOLLY_MICROPHONE_MAX_FRAMES || view.getUint32(36, true)) fail(E.EINVAL);
100
+ if (body.length !== 8) fail(E.EINVAL);
101
+ const frames = view.getUint32(0, true);
102
+ if (!frames || frames > M.DOLLY_MICROPHONE_MAX_FRAMES || view.getUint32(4, true)) fail(E.EINVAL);
106
103
  const count = Math.min(frames, capture.length), size = capture.samples.length;
107
104
  if (!count && capture.state === M.DOLLY_MICROPHONE_DENIED) fail(E.EACCES);
108
105
  if (!count && capture.state === M.DOLLY_MICROPHONE_UNAVAILABLE) fail(E.ENODEV);
@@ -112,13 +109,13 @@ export function createMicrophoneProvider(changed = () => {}) {
112
109
  for (let i = 0; i < count; ++i) output.setFloat32(8 + i * 4, capture.samples[(capture.start + i) % size], true);
113
110
  capture.start = (capture.start + count) % size; capture.length -= count;
114
111
  } else if (op === M.DOLLY_MICROPHONE_STATUS) {
115
- if (packet.length !== 32) fail(E.EINVAL);
112
+ if (body.length) fail(E.EINVAL);
116
113
  result = response(24);
117
114
  const output = new DataView(result.buffer);
118
115
  output.setUint32(0, capture.length, true); output.setUint32(4, capture.state, true);
119
116
  output.setBigUint64(8, BigInt(capture.captured), true); output.setBigUint64(16, BigInt(capture.dropped), true);
120
117
  } else if (op === M.DOLLY_MICROPHONE_CLOSE) {
121
- if (packet.length !== 32) fail(E.EINVAL);
118
+ if (body.length) fail(E.EINVAL);
122
119
  release(scope); result = response(0);
123
120
  } else fail(E.ENOSYS);
124
121
  }