@ai-matrx/meet 0.11.29 → 0.11.30
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 +2 -2
- package/dist/core.cjs +407 -30
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +186 -5
- package/dist/core.d.ts +186 -5
- package/dist/core.js +407 -30
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +407 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +407 -30
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +498 -30
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -9
- package/dist/react.d.ts +1 -9
- package/dist/react.js +498 -30
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +250 -22
- package/dist/skins/meet.cjs.map +1 -1
- package/dist/skins/meet.js +250 -22
- package/dist/skins/meet.js.map +1 -1
- package/dist/styles.css +46 -0
- package/package.json +4 -4
package/dist/react.js
CHANGED
|
@@ -2183,13 +2183,19 @@ function recordingLabel(state) {
|
|
|
2183
2183
|
}
|
|
2184
2184
|
|
|
2185
2185
|
// src/core/attendance.ts
|
|
2186
|
+
var IDLE_MEDIA_REGION = {
|
|
2187
|
+
permission: { microphone: "unknown", camera: "unknown" },
|
|
2188
|
+
audioOutput: "ok",
|
|
2189
|
+
notice: null,
|
|
2190
|
+
offer: null
|
|
2191
|
+
};
|
|
2186
2192
|
var IDLE_TRACK = { enabled: false, unavailable: null };
|
|
2187
2193
|
var IDLE_MEDIA = {
|
|
2188
2194
|
microphone: IDLE_TRACK,
|
|
2189
2195
|
camera: IDLE_TRACK
|
|
2190
2196
|
};
|
|
2191
|
-
function unavailableFrom(error) {
|
|
2192
|
-
return { code: error.code, message: error.message, remedy: error.remedy };
|
|
2197
|
+
function unavailableFrom(error, reason) {
|
|
2198
|
+
return reason === void 0 ? { code: error.code, message: error.message, remedy: error.remedy } : { code: error.code, message: error.message, remedy: error.remedy, reason };
|
|
2193
2199
|
}
|
|
2194
2200
|
var LISTEN_ONLY_NOTICE = "You're attending without a microphone \u2014 others can't hear you";
|
|
2195
2201
|
function isListenOnly(media) {
|
|
@@ -3637,6 +3643,7 @@ var EMPTY = {
|
|
|
3637
3643
|
wrapUp: null,
|
|
3638
3644
|
noteTakerRefusal: null,
|
|
3639
3645
|
media: IDLE_MEDIA,
|
|
3646
|
+
mediaRegion: IDLE_MEDIA_REGION,
|
|
3640
3647
|
failure: null,
|
|
3641
3648
|
collab: EMPTY_COLLAB,
|
|
3642
3649
|
breakoutRoomId: null
|
|
@@ -3854,7 +3861,7 @@ function createMeetStore(initial = {}) {
|
|
|
3854
3861
|
},
|
|
3855
3862
|
setLocalTrack(kind, next) {
|
|
3856
3863
|
const current = state.media[kind];
|
|
3857
|
-
if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.message === next.unavailable?.message) {
|
|
3864
|
+
if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.reason === next.unavailable?.reason && current.unavailable?.message === next.unavailable?.message) {
|
|
3858
3865
|
return;
|
|
3859
3866
|
}
|
|
3860
3867
|
commit({ ...state, media: { ...state.media, [kind]: next } });
|
|
@@ -4441,6 +4448,342 @@ function createMeetSessionIdentity(options) {
|
|
|
4441
4448
|
});
|
|
4442
4449
|
}
|
|
4443
4450
|
|
|
4451
|
+
// src/core/media-supervisor.ts
|
|
4452
|
+
function classifyDeviceFailure(error, permission) {
|
|
4453
|
+
if (permission === "denied") return "denied";
|
|
4454
|
+
switch (error.code) {
|
|
4455
|
+
case "permission-denied":
|
|
4456
|
+
if (/by system/i.test(error.message) || permission === "granted") return "blocked_os";
|
|
4457
|
+
return "denied";
|
|
4458
|
+
case "device-not-found":
|
|
4459
|
+
return "none";
|
|
4460
|
+
case "device-in-use":
|
|
4461
|
+
return "busy";
|
|
4462
|
+
case "insecure-context":
|
|
4463
|
+
return "blocked_site";
|
|
4464
|
+
default:
|
|
4465
|
+
return "failed";
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
function deviceNoticeCode(kind, reason) {
|
|
4469
|
+
if (reason === "denied" || reason === "blocked_site" || reason === "blocked_os") {
|
|
4470
|
+
return kind === "microphone" ? "mic-blocked" : "camera-blocked";
|
|
4471
|
+
}
|
|
4472
|
+
return "device-lost";
|
|
4473
|
+
}
|
|
4474
|
+
function deviceUnavailableReason(track, permission) {
|
|
4475
|
+
if (track.enabled) return null;
|
|
4476
|
+
if (permission === "denied") return "denied";
|
|
4477
|
+
const refused = track.unavailable;
|
|
4478
|
+
if (refused === null) return null;
|
|
4479
|
+
return refused.reason ?? classifyDeviceFailure({ code: refused.code, message: refused.message }, permission);
|
|
4480
|
+
}
|
|
4481
|
+
function deviceChipLabel(kind, reason) {
|
|
4482
|
+
const what = kind === "microphone" ? "Mic" : "Camera";
|
|
4483
|
+
switch (reason) {
|
|
4484
|
+
case "denied":
|
|
4485
|
+
case "blocked_site":
|
|
4486
|
+
case "blocked_os":
|
|
4487
|
+
return `${what} blocked`;
|
|
4488
|
+
case "none":
|
|
4489
|
+
return kind === "microphone" ? "No microphone found" : "No camera found";
|
|
4490
|
+
case "busy":
|
|
4491
|
+
return `${what} in use by another app`;
|
|
4492
|
+
case "revoked":
|
|
4493
|
+
return `${what} access was turned off`;
|
|
4494
|
+
default:
|
|
4495
|
+
return `${what} didn't start`;
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
function deviceFixSteps(kind, reason, where) {
|
|
4499
|
+
const device = kind === "microphone" ? "Microphone" : "Camera";
|
|
4500
|
+
const lower = device.toLowerCase();
|
|
4501
|
+
switch (reason) {
|
|
4502
|
+
case "denied":
|
|
4503
|
+
case "revoked":
|
|
4504
|
+
case "blocked_site":
|
|
4505
|
+
switch (where.browser) {
|
|
4506
|
+
case "firefox":
|
|
4507
|
+
return [`Click the crossed-out ${lower} icon in the address bar`, "Clear the Blocked setting", "Press Try again and choose Allow"];
|
|
4508
|
+
case "safari":
|
|
4509
|
+
return where.os === "ios" ? [`Settings \u203A Apps \u203A Safari \u203A ${device}`, "Choose Allow", "Return here and press Try again"] : ["Safari \u203A Settings for This Website", `Set ${device} to Allow`, "Press Try again"];
|
|
4510
|
+
default:
|
|
4511
|
+
return ["Click the icon left of the web address", `Set ${device} to Allow`, "Press Try again"];
|
|
4512
|
+
}
|
|
4513
|
+
case "blocked_os": {
|
|
4514
|
+
const app = where.browser === "chrome" ? "Google Chrome" : where.browser === "edge" ? "Microsoft Edge" : where.browser === "firefox" ? "Firefox" : "your browser";
|
|
4515
|
+
if (where.os === "mac") return [`System Settings \u203A Privacy & Security \u203A ${device}`, `Turn on ${app}`, "Reopen the browser if asked, then Try again"];
|
|
4516
|
+
if (where.os === "windows") return [`Settings \u203A Privacy & security \u203A ${device}`, `Turn on ${device} access for apps`, "Press Try again"];
|
|
4517
|
+
return [`Allow ${lower} access for ${app} in your system settings`, "Press Try again"];
|
|
4518
|
+
}
|
|
4519
|
+
case "busy":
|
|
4520
|
+
return [`Close other apps using the ${lower} (Zoom, Teams, another tab)`, "Press Try again"];
|
|
4521
|
+
case "none":
|
|
4522
|
+
return [`Connect a ${lower}`, "Press Try again"];
|
|
4523
|
+
default:
|
|
4524
|
+
return [`Pick another ${lower} in Settings`, "Press Try again"];
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
4527
|
+
function detectBrowser(userAgent) {
|
|
4528
|
+
if (/Edg\//.test(userAgent)) return "edge";
|
|
4529
|
+
if (/Firefox\//.test(userAgent)) return "firefox";
|
|
4530
|
+
if (/Chrome\/|Chromium\/|CriOS\//.test(userAgent)) return "chrome";
|
|
4531
|
+
if (/Safari\//.test(userAgent)) return "safari";
|
|
4532
|
+
return "other";
|
|
4533
|
+
}
|
|
4534
|
+
function detectOs(userAgent, maxTouchPoints = 0) {
|
|
4535
|
+
if (/iPhone|iPad|iPod/.test(userAgent)) return "ios";
|
|
4536
|
+
if (/Macintosh/.test(userAgent)) return maxTouchPoints > 1 ? "ios" : "mac";
|
|
4537
|
+
if (/Android/.test(userAgent)) return "android";
|
|
4538
|
+
if (/Windows/.test(userAgent)) return "windows";
|
|
4539
|
+
if (/Linux|CrOS/.test(userAgent)) return "linux";
|
|
4540
|
+
return "other";
|
|
4541
|
+
}
|
|
4542
|
+
function webMediaPlatform() {
|
|
4543
|
+
const scope = globalThis;
|
|
4544
|
+
const nav = scope.navigator;
|
|
4545
|
+
if (nav === void 0) return null;
|
|
4546
|
+
const ua = nav.userAgent ?? "";
|
|
4547
|
+
const reading = (state) => state === "granted" || state === "denied" || state === "prompt" ? state : "unknown";
|
|
4548
|
+
const query = async (kind) => {
|
|
4549
|
+
try {
|
|
4550
|
+
return await nav.permissions?.query({ name: kind }) ?? null;
|
|
4551
|
+
} catch {
|
|
4552
|
+
return null;
|
|
4553
|
+
}
|
|
4554
|
+
};
|
|
4555
|
+
return {
|
|
4556
|
+
browser: detectBrowser(ua),
|
|
4557
|
+
os: detectOs(ua, nav.maxTouchPoints ?? 0),
|
|
4558
|
+
async queryPermission(kind) {
|
|
4559
|
+
return reading((await query(kind))?.state);
|
|
4560
|
+
},
|
|
4561
|
+
watchPermission(kind, onChange) {
|
|
4562
|
+
let status = null;
|
|
4563
|
+
let stopped = false;
|
|
4564
|
+
void query(kind).then((found) => {
|
|
4565
|
+
if (stopped || found === null) return;
|
|
4566
|
+
status = found;
|
|
4567
|
+
found.onchange = () => onChange(reading(found.state));
|
|
4568
|
+
});
|
|
4569
|
+
return () => {
|
|
4570
|
+
stopped = true;
|
|
4571
|
+
if (status !== null) status.onchange = null;
|
|
4572
|
+
};
|
|
4573
|
+
},
|
|
4574
|
+
async listDevices() {
|
|
4575
|
+
try {
|
|
4576
|
+
return await nav.mediaDevices?.enumerateDevices() ?? [];
|
|
4577
|
+
} catch {
|
|
4578
|
+
return [];
|
|
4579
|
+
}
|
|
4580
|
+
},
|
|
4581
|
+
onVisible(callback) {
|
|
4582
|
+
const doc = scope.document;
|
|
4583
|
+
if (doc === void 0) return () => void 0;
|
|
4584
|
+
const handler = () => {
|
|
4585
|
+
if (doc.visibilityState === "visible") callback();
|
|
4586
|
+
};
|
|
4587
|
+
doc.addEventListener("visibilitychange", handler);
|
|
4588
|
+
return () => doc.removeEventListener("visibilitychange", handler);
|
|
4589
|
+
}
|
|
4590
|
+
};
|
|
4591
|
+
}
|
|
4592
|
+
var INPUT_KIND = { microphone: "audioinput", camera: "videoinput" };
|
|
4593
|
+
function isAlias(deviceId2) {
|
|
4594
|
+
return deviceId2 === "default" || deviceId2 === "communications" || deviceId2 === "";
|
|
4595
|
+
}
|
|
4596
|
+
function createMediaSupervisor(options) {
|
|
4597
|
+
const { store, platform } = options;
|
|
4598
|
+
let unsubscribers = [];
|
|
4599
|
+
let started = false;
|
|
4600
|
+
let noticeId = 0;
|
|
4601
|
+
let known = null;
|
|
4602
|
+
let inUse = { audioinput: null, videoinput: null };
|
|
4603
|
+
function region() {
|
|
4604
|
+
return store.snapshot().mediaRegion;
|
|
4605
|
+
}
|
|
4606
|
+
function setRegion(next) {
|
|
4607
|
+
const current = region();
|
|
4608
|
+
const merged = { ...current, ...next };
|
|
4609
|
+
if (merged.audioOutput === current.audioOutput && merged.notice === current.notice && merged.offer === current.offer && merged.permission.microphone === current.permission.microphone && merged.permission.camera === current.permission.camera) {
|
|
4610
|
+
return;
|
|
4611
|
+
}
|
|
4612
|
+
store.patch({ mediaRegion: merged });
|
|
4613
|
+
}
|
|
4614
|
+
function setPermission(kind, reading) {
|
|
4615
|
+
const current = region().permission;
|
|
4616
|
+
if (current[kind] === reading) return;
|
|
4617
|
+
setRegion({ permission: { ...current, [kind]: reading } });
|
|
4618
|
+
const track = store.snapshot().media[kind];
|
|
4619
|
+
if (reading === "granted" && !track.enabled && track.unavailable?.reason === "denied") {
|
|
4620
|
+
store.setLocalTrack(kind, {
|
|
4621
|
+
enabled: false,
|
|
4622
|
+
unavailable: { ...track.unavailable, reason: "failed", remedy: "Access is allowed now. Press Try again." }
|
|
4623
|
+
});
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
async function snapshotDevices() {
|
|
4627
|
+
const list = platform === null ? [] : await platform.listDevices();
|
|
4628
|
+
const out = { audioinput: /* @__PURE__ */ new Map(), videoinput: /* @__PURE__ */ new Map() };
|
|
4629
|
+
for (const device of list) {
|
|
4630
|
+
if (device.kind === "audioinput" || device.kind === "videoinput") out[device.kind].set(device.deviceId, device.label);
|
|
4631
|
+
}
|
|
4632
|
+
return out;
|
|
4633
|
+
}
|
|
4634
|
+
function readInUse() {
|
|
4635
|
+
const driver = options.driver();
|
|
4636
|
+
if (driver === null) return;
|
|
4637
|
+
const active = driver.activeDevices();
|
|
4638
|
+
inUse = { audioinput: active.audioinput, videoinput: active.videoinput };
|
|
4639
|
+
}
|
|
4640
|
+
async function handleDevicesChanged() {
|
|
4641
|
+
if (platform === null) return;
|
|
4642
|
+
const before = known;
|
|
4643
|
+
const now = await snapshotDevices();
|
|
4644
|
+
known = now;
|
|
4645
|
+
const driver = options.driver();
|
|
4646
|
+
if (driver === null || before === null) {
|
|
4647
|
+
readInUse();
|
|
4648
|
+
return;
|
|
4649
|
+
}
|
|
4650
|
+
for (const kind of ["microphone", "camera"]) {
|
|
4651
|
+
const deviceKind = INPUT_KIND[kind];
|
|
4652
|
+
const previous = inUse[deviceKind];
|
|
4653
|
+
const track = store.snapshot().media[kind];
|
|
4654
|
+
const gone = previous !== null && !isAlias(previous) && before[deviceKind].has(previous) && !now[deviceKind].has(previous);
|
|
4655
|
+
if (gone) {
|
|
4656
|
+
const from = before[deviceKind].get(previous) ?? "";
|
|
4657
|
+
const remaining = [...now[deviceKind].entries()].filter(([id]) => !isAlias(id));
|
|
4658
|
+
if (remaining.length === 0) {
|
|
4659
|
+
store.setLocalTrack(kind, {
|
|
4660
|
+
enabled: false,
|
|
4661
|
+
unavailable: {
|
|
4662
|
+
code: "device-not-found",
|
|
4663
|
+
message: `meet.${kind}: the ${kind} was disconnected and no other is connected`,
|
|
4664
|
+
remedy: `Connect a ${kind === "microphone" ? "microphone" : "camera"}, then press Try again.`,
|
|
4665
|
+
reason: "none"
|
|
4666
|
+
}
|
|
4667
|
+
});
|
|
4668
|
+
setRegion({ notice: { id: ++noticeId, kind, outcome: "lost", from, to: null } });
|
|
4669
|
+
continue;
|
|
4670
|
+
}
|
|
4671
|
+
if (kind === "camera") {
|
|
4672
|
+
const [nextId, nextLabel] = remaining[0];
|
|
4673
|
+
try {
|
|
4674
|
+
await driver.switchDevice("videoinput", nextId);
|
|
4675
|
+
if (track.enabled || track.unavailable !== null) {
|
|
4676
|
+
await driver.setCameraEnabled(true);
|
|
4677
|
+
store.setLocalTrack("camera", { enabled: true, unavailable: null });
|
|
4678
|
+
}
|
|
4679
|
+
setRegion({ notice: { id: ++noticeId, kind, outcome: "switched", from, to: nextLabel } });
|
|
4680
|
+
} catch (error) {
|
|
4681
|
+
const refused = await unavailable(error, "camera", "meet.camera(fallback)");
|
|
4682
|
+
store.setLocalTrack("camera", { enabled: false, unavailable: refused });
|
|
4683
|
+
setRegion({ notice: { id: ++noticeId, kind, outcome: "lost", from, to: null } });
|
|
4684
|
+
}
|
|
4685
|
+
} else {
|
|
4686
|
+
const active = driver.activeDevices().audioinput;
|
|
4687
|
+
const to = (active !== null ? now.audioinput.get(active) : void 0) ?? now.audioinput.get("default") ?? remaining[0][1];
|
|
4688
|
+
setRegion({ notice: { id: ++noticeId, kind, outcome: "switched", from, to } });
|
|
4689
|
+
}
|
|
4690
|
+
continue;
|
|
4691
|
+
}
|
|
4692
|
+
const added = [...now[deviceKind].entries()].find(([id]) => !isAlias(id) && !before[deviceKind].has(id));
|
|
4693
|
+
if (added !== void 0 && added[0] !== driver.activeDevices()[deviceKind]) {
|
|
4694
|
+
setRegion({ offer: { kind, deviceId: added[0], label: added[1] } });
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
4697
|
+
readInUse();
|
|
4698
|
+
}
|
|
4699
|
+
async function unavailable(error, kind, operation) {
|
|
4700
|
+
const normalized = normalizeDeviceError(error, operation);
|
|
4701
|
+
const permission = platform === null ? "unknown" : await platform.queryPermission(kind);
|
|
4702
|
+
if (permission !== "unknown") setPermission(kind, permission);
|
|
4703
|
+
return unavailableFrom(normalized, classifyDeviceFailure(normalized, permission));
|
|
4704
|
+
}
|
|
4705
|
+
return {
|
|
4706
|
+
start() {
|
|
4707
|
+
if (started || platform === null) return;
|
|
4708
|
+
started = true;
|
|
4709
|
+
for (const kind of ["microphone", "camera"]) {
|
|
4710
|
+
void platform.queryPermission(kind).then((reading) => setPermission(kind, reading));
|
|
4711
|
+
unsubscribers.push(platform.watchPermission(kind, (reading) => setPermission(kind, reading)));
|
|
4712
|
+
}
|
|
4713
|
+
unsubscribers.push(
|
|
4714
|
+
platform.onVisible(() => {
|
|
4715
|
+
const driver = options.driver();
|
|
4716
|
+
if (driver?.startAudio === void 0 || region().audioOutput !== "blocked") return;
|
|
4717
|
+
driver.startAudio().catch(() => void 0);
|
|
4718
|
+
})
|
|
4719
|
+
);
|
|
4720
|
+
},
|
|
4721
|
+
stop() {
|
|
4722
|
+
for (const off of unsubscribers) off();
|
|
4723
|
+
unsubscribers = [];
|
|
4724
|
+
started = false;
|
|
4725
|
+
},
|
|
4726
|
+
unavailable,
|
|
4727
|
+
onAudioPlayback(canPlay) {
|
|
4728
|
+
setRegion({ audioOutput: canPlay ? "ok" : "blocked" });
|
|
4729
|
+
},
|
|
4730
|
+
onDevicesChanged() {
|
|
4731
|
+
void handleDevicesChanged().catch((error) => {
|
|
4732
|
+
options.report("warn", `meet: device change could not be handled: ${String(error)}`, "Pick the device in Settings.");
|
|
4733
|
+
});
|
|
4734
|
+
},
|
|
4735
|
+
onDeviceError(error, deviceKind) {
|
|
4736
|
+
if (deviceKind === null) return;
|
|
4737
|
+
const kind = deviceKind === "audioinput" ? "microphone" : "camera";
|
|
4738
|
+
void unavailable(error, kind, `meet.${kind}`).then((refused) => {
|
|
4739
|
+
const track = store.snapshot().media[kind];
|
|
4740
|
+
if (track.enabled) return;
|
|
4741
|
+
store.setLocalTrack(kind, { enabled: false, unavailable: refused });
|
|
4742
|
+
});
|
|
4743
|
+
},
|
|
4744
|
+
onConnected() {
|
|
4745
|
+
void snapshotDevices().then((now) => {
|
|
4746
|
+
known = now;
|
|
4747
|
+
readInUse();
|
|
4748
|
+
});
|
|
4749
|
+
},
|
|
4750
|
+
onDisconnected() {
|
|
4751
|
+
known = null;
|
|
4752
|
+
inUse = { audioinput: null, videoinput: null };
|
|
4753
|
+
setRegion({ audioOutput: "ok", notice: null, offer: null });
|
|
4754
|
+
},
|
|
4755
|
+
async enableAudio() {
|
|
4756
|
+
const driver = options.driver();
|
|
4757
|
+
if (driver?.startAudio === void 0) return;
|
|
4758
|
+
try {
|
|
4759
|
+
await driver.startAudio();
|
|
4760
|
+
setRegion({ audioOutput: "ok" });
|
|
4761
|
+
} catch (error) {
|
|
4762
|
+
options.report("warn", `meet: sound could not be enabled: ${String(error)}`, "Click Enable sound again, or check the tab is not muted.");
|
|
4763
|
+
}
|
|
4764
|
+
},
|
|
4765
|
+
async acceptOffer() {
|
|
4766
|
+
const offer = region().offer;
|
|
4767
|
+
const driver = options.driver();
|
|
4768
|
+
setRegion({ offer: null });
|
|
4769
|
+
if (offer === null || driver === null) return;
|
|
4770
|
+
try {
|
|
4771
|
+
await driver.switchDevice(INPUT_KIND[offer.kind], offer.deviceId);
|
|
4772
|
+
readInUse();
|
|
4773
|
+
} catch (error) {
|
|
4774
|
+
const refused = await unavailable(error, offer.kind, "meet.switchDevice");
|
|
4775
|
+
options.report("warn", refused.message, refused.remedy);
|
|
4776
|
+
}
|
|
4777
|
+
},
|
|
4778
|
+
dismissOffer() {
|
|
4779
|
+
setRegion({ offer: null });
|
|
4780
|
+
},
|
|
4781
|
+
dismissNotice() {
|
|
4782
|
+
setRegion({ notice: null });
|
|
4783
|
+
}
|
|
4784
|
+
};
|
|
4785
|
+
}
|
|
4786
|
+
|
|
4444
4787
|
// src/core/room-events.ts
|
|
4445
4788
|
var RECORDING_STATES = [
|
|
4446
4789
|
"idle",
|
|
@@ -4895,9 +5238,9 @@ function createRoomEngine(options) {
|
|
|
4895
5238
|
const departures = /* @__PURE__ */ new Map();
|
|
4896
5239
|
function requestAsNow() {
|
|
4897
5240
|
if (lastRequest === null) return null;
|
|
4898
|
-
const
|
|
5241
|
+
const media2 = store.snapshot().media;
|
|
4899
5242
|
if (store.snapshot().phase !== "in_call") return lastRequest;
|
|
4900
|
-
return { ...lastRequest, mode:
|
|
5243
|
+
return { ...lastRequest, mode: media2.camera.enabled ? "video" : "audio", microphone: media2.microphone.enabled };
|
|
4901
5244
|
}
|
|
4902
5245
|
async function dropRoom() {
|
|
4903
5246
|
const target = driver;
|
|
@@ -4908,6 +5251,7 @@ function createRoomEngine(options) {
|
|
|
4908
5251
|
}
|
|
4909
5252
|
store.setLocalTrack("microphone", IDLE_TRACK);
|
|
4910
5253
|
store.setLocalTrack("camera", IDLE_TRACK);
|
|
5254
|
+
media.onDisconnected();
|
|
4911
5255
|
store.patch({ share: NO_SHARE, connection: "stable" });
|
|
4912
5256
|
clearGhosts();
|
|
4913
5257
|
await target?.disconnect();
|
|
@@ -4973,7 +5317,22 @@ function createRoomEngine(options) {
|
|
|
4973
5317
|
report("error", normalized.message, normalized.remedy);
|
|
4974
5318
|
return normalized;
|
|
4975
5319
|
}
|
|
5320
|
+
const media = createMediaSupervisor({
|
|
5321
|
+
store,
|
|
5322
|
+
platform: options.mediaPlatform === void 0 ? webMediaPlatform() : options.mediaPlatform,
|
|
5323
|
+
driver: () => driver,
|
|
5324
|
+
report
|
|
5325
|
+
});
|
|
4976
5326
|
const handlers = {
|
|
5327
|
+
audioPlayback(canPlay) {
|
|
5328
|
+
media.onAudioPlayback(canPlay);
|
|
5329
|
+
},
|
|
5330
|
+
devicesChanged() {
|
|
5331
|
+
media.onDevicesChanged();
|
|
5332
|
+
},
|
|
5333
|
+
mediaDeviceError(error, kind) {
|
|
5334
|
+
media.onDeviceError(error, kind);
|
|
5335
|
+
},
|
|
4977
5336
|
participants(list) {
|
|
4978
5337
|
const joinedAt = new Date(now()).toISOString();
|
|
4979
5338
|
const before = new Set(store.snapshot().participants.map((p) => p.identity));
|
|
@@ -5239,6 +5598,7 @@ function createRoomEngine(options) {
|
|
|
5239
5598
|
store.dispatch({ type: "connected" });
|
|
5240
5599
|
store.setLocalTrack("microphone", IDLE_TRACK);
|
|
5241
5600
|
store.setLocalTrack("camera", IDLE_TRACK);
|
|
5601
|
+
media.start();
|
|
5242
5602
|
const effects = options.effectsStorage?.load() ?? store.snapshot().effects;
|
|
5243
5603
|
store.patch({ effects });
|
|
5244
5604
|
if (!effects.noiseSuppression || effects.background !== "none") {
|
|
@@ -5263,6 +5623,7 @@ function createRoomEngine(options) {
|
|
|
5263
5623
|
if (request.microphone !== false) {
|
|
5264
5624
|
await enableTrack(created, "microphone", true);
|
|
5265
5625
|
}
|
|
5626
|
+
media.onConnected();
|
|
5266
5627
|
}
|
|
5267
5628
|
async function enableTrack(target, kind, enabled) {
|
|
5268
5629
|
try {
|
|
@@ -5272,10 +5633,10 @@ function createRoomEngine(options) {
|
|
|
5272
5633
|
store.setLocalTrack(kind, state);
|
|
5273
5634
|
return state;
|
|
5274
5635
|
} catch (error) {
|
|
5275
|
-
const
|
|
5276
|
-
const state = { enabled: false, unavailable:
|
|
5636
|
+
const refused = await media.unavailable(error, kind, `meet.${kind}`);
|
|
5637
|
+
const state = { enabled: false, unavailable: refused };
|
|
5277
5638
|
store.setLocalTrack(kind, state);
|
|
5278
|
-
report("warn",
|
|
5639
|
+
report("warn", refused.message, refused.remedy);
|
|
5279
5640
|
return state;
|
|
5280
5641
|
}
|
|
5281
5642
|
}
|
|
@@ -5584,6 +5945,10 @@ function createRoomEngine(options) {
|
|
|
5584
5945
|
if (driver === null) return;
|
|
5585
5946
|
await driver.switchDevice(kind, deviceId2);
|
|
5586
5947
|
},
|
|
5948
|
+
enableAudio: () => media.enableAudio(),
|
|
5949
|
+
acceptDeviceOffer: () => media.acceptOffer(),
|
|
5950
|
+
dismissDeviceOffer: () => media.dismissOffer(),
|
|
5951
|
+
dismissDeviceNotice: () => media.dismissNotice(),
|
|
5587
5952
|
pin(who) {
|
|
5588
5953
|
store.patch({ pinned: who, layout: who === null ? "grid" : "speaker" });
|
|
5589
5954
|
},
|
|
@@ -5598,6 +5963,7 @@ function createRoomEngine(options) {
|
|
|
5598
5963
|
async dispose() {
|
|
5599
5964
|
disposed = true;
|
|
5600
5965
|
supervisor.dispose();
|
|
5966
|
+
media.stop();
|
|
5601
5967
|
clearGhosts();
|
|
5602
5968
|
if (driver !== null) {
|
|
5603
5969
|
const target = driver;
|
|
@@ -6031,6 +6397,12 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
6031
6397
|
roster();
|
|
6032
6398
|
}).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, roster).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ParticipantNameChanged, roster).on(RoomEvent.ParticipantAttributesChanged, roster).on(RoomEvent.ParticipantMetadataChanged, roster).on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
|
6033
6399
|
handlers.roomMetadata?.(metadata ?? "");
|
|
6400
|
+
}).on(RoomEvent.AudioPlaybackStatusChanged, (canPlay) => {
|
|
6401
|
+
handlers.audioPlayback?.(canPlay);
|
|
6402
|
+
}).on(RoomEvent.MediaDevicesChanged, () => {
|
|
6403
|
+
handlers.devicesChanged?.();
|
|
6404
|
+
}).on(RoomEvent.MediaDevicesError, (error, kind) => {
|
|
6405
|
+
handlers.mediaDeviceError?.(error, kind === "audioinput" || kind === "videoinput" ? kind : null);
|
|
6034
6406
|
}).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
|
6035
6407
|
handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
|
|
6036
6408
|
}).on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {
|
|
@@ -6127,6 +6499,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
6127
6499
|
}
|
|
6128
6500
|
projectRoster?.();
|
|
6129
6501
|
handlers.roomMetadata?.(target.metadata ?? "");
|
|
6502
|
+
handlers.audioPlayback?.(target.canPlaybackAudio);
|
|
6130
6503
|
},
|
|
6131
6504
|
async disconnect() {
|
|
6132
6505
|
if (room === null) return;
|
|
@@ -6253,6 +6626,11 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
6253
6626
|
} catch (error) {
|
|
6254
6627
|
throw normalizeDeviceError(error, "meet.switchDevice");
|
|
6255
6628
|
}
|
|
6629
|
+
},
|
|
6630
|
+
async startAudio() {
|
|
6631
|
+
const target = room;
|
|
6632
|
+
if (target === null) return;
|
|
6633
|
+
await target.startAudio();
|
|
6256
6634
|
}
|
|
6257
6635
|
};
|
|
6258
6636
|
}
|
|
@@ -6660,22 +7038,10 @@ function observedRole(role, standing) {
|
|
|
6660
7038
|
if (role === "host" || role === "cohost") return role;
|
|
6661
7039
|
return isLinkFollower(standing) ? "guest" : "participant";
|
|
6662
7040
|
}
|
|
6663
|
-
function observedDevice(track) {
|
|
7041
|
+
function observedDevice(track, permission = "unknown") {
|
|
6664
7042
|
if (track.enabled) return "on";
|
|
6665
|
-
const
|
|
6666
|
-
|
|
6667
|
-
switch (refused.code) {
|
|
6668
|
-
case "permission-denied":
|
|
6669
|
-
return "unavailable:denied";
|
|
6670
|
-
case "device-not-found":
|
|
6671
|
-
return "unavailable:none";
|
|
6672
|
-
case "device-in-use":
|
|
6673
|
-
return "unavailable:busy";
|
|
6674
|
-
case "insecure-context":
|
|
6675
|
-
return "unavailable:blocked_site";
|
|
6676
|
-
default:
|
|
6677
|
-
return "unavailable:failed";
|
|
6678
|
-
}
|
|
7043
|
+
const reason = deviceUnavailableReason(track, permission);
|
|
7044
|
+
return reason === null ? "muted_by_me" : `unavailable:${reason}`;
|
|
6679
7045
|
}
|
|
6680
7046
|
function endedRecord(snapshot, given) {
|
|
6681
7047
|
const record = snapshot?.meeting ?? given;
|
|
@@ -6708,9 +7074,10 @@ function observe(input) {
|
|
|
6708
7074
|
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot !== null && isWaitingPhase(snapshot.phase) ? snapshot.waiting?.admittersPresent ?? null : null,
|
|
6709
7075
|
lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
|
|
6710
7076
|
recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
7077
|
+
// S6c: the autoplay region is known only inside the room (no remote audio exists elsewhere).
|
|
7078
|
+
audioBlocked: inCall && snapshot !== null ? snapshot.mediaRegion.audioOutput === "blocked" : null,
|
|
7079
|
+
camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera) : null,
|
|
7080
|
+
microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone) : null
|
|
6714
7081
|
};
|
|
6715
7082
|
}
|
|
6716
7083
|
function staticObservation(phase, reason = null) {
|
|
@@ -6775,10 +7142,10 @@ function participantAttributes(p) {
|
|
|
6775
7142
|
if (quality !== null) attrs["data-meet-participant-connection"] = quality;
|
|
6776
7143
|
return attrs;
|
|
6777
7144
|
}
|
|
6778
|
-
function microphoneNoticeCode(track) {
|
|
6779
|
-
const
|
|
6780
|
-
if (
|
|
6781
|
-
return
|
|
7145
|
+
function microphoneNoticeCode(track, permission = "unknown") {
|
|
7146
|
+
const reason = deviceUnavailableReason(track, permission);
|
|
7147
|
+
if (reason === null) return null;
|
|
7148
|
+
return deviceNoticeCode("microphone", reason) === "mic-blocked" ? "mic-blocked" : "device-lost";
|
|
6782
7149
|
}
|
|
6783
7150
|
|
|
6784
7151
|
// src/react/root.tsx
|
|
@@ -7879,6 +8246,26 @@ function useConsentNotice() {
|
|
|
7879
8246
|
if (agent === null) return null;
|
|
7880
8247
|
return `${agent.displayName} is in this meeting: it is transcribing what is said and writing notes everyone here can read.`;
|
|
7881
8248
|
}
|
|
8249
|
+
function useMediaSupervisor() {
|
|
8250
|
+
const host = useMeetHost();
|
|
8251
|
+
const snapshot = useMeetSnapshot();
|
|
8252
|
+
const engine = host?.engine ?? null;
|
|
8253
|
+
return {
|
|
8254
|
+
region: snapshot?.mediaRegion ?? IDLE_MEDIA_REGION,
|
|
8255
|
+
where: useMemo3(() => {
|
|
8256
|
+
const platform = webMediaPlatform();
|
|
8257
|
+
return { browser: platform?.browser ?? "other", os: platform?.os ?? "other" };
|
|
8258
|
+
}, []),
|
|
8259
|
+
enableAudio: useCallback2(async () => engine?.enableAudio(), [engine]),
|
|
8260
|
+
acceptOffer: useCallback2(async () => engine?.acceptDeviceOffer(), [engine]),
|
|
8261
|
+
dismissOffer: useCallback2(() => engine?.dismissDeviceOffer(), [engine]),
|
|
8262
|
+
dismissNotice: useCallback2(() => engine?.dismissDeviceNotice(), [engine]),
|
|
8263
|
+
retry: useCallback2(
|
|
8264
|
+
async (kind) => kind === "microphone" ? engine?.setMicrophoneEnabled(true) : engine?.setCameraEnabled(true),
|
|
8265
|
+
[engine]
|
|
8266
|
+
)
|
|
8267
|
+
};
|
|
8268
|
+
}
|
|
7882
8269
|
function useLocalMedia() {
|
|
7883
8270
|
const snapshot = useMeetSnapshot();
|
|
7884
8271
|
return snapshot?.media ?? IDLE_MEDIA;
|
|
@@ -10727,12 +11114,83 @@ function Toast({ text: text3, failed, onDone }) {
|
|
|
10727
11114
|
/* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: onDone, children: /* @__PURE__ */ jsx6(CloseIcon, { width: 14, height: 14 }) })
|
|
10728
11115
|
] });
|
|
10729
11116
|
}
|
|
11117
|
+
function MediaNotices() {
|
|
11118
|
+
const media = useLocalMedia();
|
|
11119
|
+
const { region, where, enableAudio, acceptOffer, dismissOffer, dismissNotice, retry } = useMediaSupervisor();
|
|
11120
|
+
const [fixing, setFixing] = useState4(null);
|
|
11121
|
+
const chips = ["microphone", "camera"].flatMap((kind) => {
|
|
11122
|
+
const reason = deviceUnavailableReason(media[kind], region.permission[kind]);
|
|
11123
|
+
return reason === null ? [] : [{ kind, reason }];
|
|
11124
|
+
});
|
|
11125
|
+
const fixReason = fixing === null ? null : chips.find((chip) => chip.kind === fixing)?.reason ?? null;
|
|
11126
|
+
const notice = region.notice;
|
|
11127
|
+
const offer = region.offer;
|
|
11128
|
+
const name = (kind) => kind === "microphone" ? "Microphone" : "Camera";
|
|
11129
|
+
if (chips.length === 0 && region.audioOutput !== "blocked" && notice === null && offer === null) return null;
|
|
11130
|
+
return /* @__PURE__ */ jsxs3("div", { className: "mx-meet__media-notices", children: [
|
|
11131
|
+
region.audioOutput === "blocked" ? /* @__PURE__ */ jsxs3("div", { className: "mx-meet__media-pill", role: "alert", "data-meet-notice": "audio-blocked", children: [
|
|
11132
|
+
/* @__PURE__ */ jsx6("span", { children: "Sound is paused in this tab" }),
|
|
11133
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__primary mx-meet__primary--small", onClick: () => void enableAudio(), children: "Enable sound" })
|
|
11134
|
+
] }) : null,
|
|
11135
|
+
chips.map(({ kind, reason }) => /* @__PURE__ */ jsxs3("div", { className: "mx-meet__media-pill", role: "status", "data-meet-notice": deviceNoticeCode(kind, reason), children: [
|
|
11136
|
+
/* @__PURE__ */ jsx6("span", { children: deviceChipLabel(kind, reason) }),
|
|
11137
|
+
/* @__PURE__ */ jsx6(
|
|
11138
|
+
"button",
|
|
11139
|
+
{
|
|
11140
|
+
type: "button",
|
|
11141
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
11142
|
+
"aria-label": kind === "microphone" ? "Fix microphone" : "Fix camera",
|
|
11143
|
+
"aria-expanded": fixing === kind,
|
|
11144
|
+
onClick: () => setFixing(fixing === kind ? null : kind),
|
|
11145
|
+
children: "Fix"
|
|
11146
|
+
}
|
|
11147
|
+
)
|
|
11148
|
+
] }, kind)),
|
|
11149
|
+
fixing !== null && fixReason !== null ? /* @__PURE__ */ jsxs3("div", { className: "mx-meet__popover mx-meet__fix-sheet", role: "dialog", "aria-label": `Fix ${name(fixing).toLowerCase()}`, children: [
|
|
11150
|
+
/* @__PURE__ */ jsx6("ol", { className: "mx-meet__fix-steps", children: deviceFixSteps(fixing, fixReason, where).map((step) => /* @__PURE__ */ jsx6("li", { children: step }, step)) }),
|
|
11151
|
+
/* @__PURE__ */ jsxs3("div", { className: "mx-meet__fix-actions", children: [
|
|
11152
|
+
/* @__PURE__ */ jsx6(
|
|
11153
|
+
"button",
|
|
11154
|
+
{
|
|
11155
|
+
type: "button",
|
|
11156
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
11157
|
+
onClick: () => {
|
|
11158
|
+
const kind = fixing;
|
|
11159
|
+
setFixing(null);
|
|
11160
|
+
void retry(kind);
|
|
11161
|
+
},
|
|
11162
|
+
children: "Try again"
|
|
11163
|
+
}
|
|
11164
|
+
),
|
|
11165
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Close", onClick: () => setFixing(null), children: /* @__PURE__ */ jsx6(CloseIcon, { width: 14, height: 14 }) })
|
|
11166
|
+
] })
|
|
11167
|
+
] }) : null,
|
|
11168
|
+
notice !== null ? /* @__PURE__ */ jsxs3(
|
|
11169
|
+
"div",
|
|
11170
|
+
{
|
|
11171
|
+
className: "mx-meet__media-pill",
|
|
11172
|
+
role: "status",
|
|
11173
|
+
...notice.outcome === "lost" ? { "data-meet-notice": "device-lost" } : {},
|
|
11174
|
+
children: [
|
|
11175
|
+
/* @__PURE__ */ jsx6("span", { children: notice.outcome === "switched" ? `${notice.from || name(notice.kind)} disconnected \xB7 now using ${notice.to || "the default"}` : `${notice.from || name(notice.kind)} disconnected` }),
|
|
11176
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismissNotice, children: /* @__PURE__ */ jsx6(CloseIcon, { width: 14, height: 14 }) })
|
|
11177
|
+
]
|
|
11178
|
+
}
|
|
11179
|
+
) : null,
|
|
11180
|
+
offer !== null ? /* @__PURE__ */ jsxs3("div", { className: "mx-meet__media-pill", role: "status", children: [
|
|
11181
|
+
/* @__PURE__ */ jsx6("span", { children: `New ${offer.kind}: ${offer.label || name(offer.kind)}` }),
|
|
11182
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__primary mx-meet__primary--small", onClick: () => void acceptOffer(), children: "Use it" }),
|
|
11183
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismissOffer, children: /* @__PURE__ */ jsx6(CloseIcon, { width: 14, height: 14 }) })
|
|
11184
|
+
] }) : null
|
|
11185
|
+
] });
|
|
11186
|
+
}
|
|
10730
11187
|
function MeetingOverlays() {
|
|
10731
11188
|
const reactions = useReactions();
|
|
10732
11189
|
const { notice, dismiss } = useModerationNotice();
|
|
10733
11190
|
const room = useRoom();
|
|
10734
11191
|
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
10735
11192
|
/* @__PURE__ */ jsx6(ReactionOverlay, { reactions }),
|
|
11193
|
+
/* @__PURE__ */ jsx6(MediaNotices, {}),
|
|
10736
11194
|
notice !== null && notice.kind !== "removed" ? (
|
|
10737
11195
|
// A host's request ("the host is asking you to unmute"), not an error:
|
|
10738
11196
|
// an assertive status, so it is announced at once without being filed
|
|
@@ -13011,6 +13469,7 @@ export {
|
|
|
13011
13469
|
GUEST_RECORD_NOT_SHARED,
|
|
13012
13470
|
GuestRecordAccessControl,
|
|
13013
13471
|
IDLE_MEDIA,
|
|
13472
|
+
IDLE_MEDIA_REGION,
|
|
13014
13473
|
IDLE_PHASES,
|
|
13015
13474
|
IDLE_TRACK,
|
|
13016
13475
|
IncomingCallCard,
|
|
@@ -13090,6 +13549,7 @@ export {
|
|
|
13090
13549
|
captionSegment,
|
|
13091
13550
|
captureHeldOf,
|
|
13092
13551
|
changesPatch,
|
|
13552
|
+
classifyDeviceFailure,
|
|
13093
13553
|
clockPosition,
|
|
13094
13554
|
collabFromState,
|
|
13095
13555
|
computeStage,
|
|
@@ -13101,6 +13561,7 @@ export {
|
|
|
13101
13561
|
createDeviceManager,
|
|
13102
13562
|
createInRoomClient,
|
|
13103
13563
|
createLiveKitDriver,
|
|
13564
|
+
createMediaSupervisor,
|
|
13104
13565
|
createMeetAi,
|
|
13105
13566
|
createMeetApi,
|
|
13106
13567
|
createMeetRepository,
|
|
@@ -13120,6 +13581,12 @@ export {
|
|
|
13120
13581
|
createWebResumeStorage,
|
|
13121
13582
|
createWebRoomTokenStorage,
|
|
13122
13583
|
defaultCaptionLanguage,
|
|
13584
|
+
detectBrowser,
|
|
13585
|
+
detectOs,
|
|
13586
|
+
deviceChipLabel,
|
|
13587
|
+
deviceFixSteps,
|
|
13588
|
+
deviceNoticeCode,
|
|
13589
|
+
deviceUnavailableReason,
|
|
13123
13590
|
displayNameFor,
|
|
13124
13591
|
fileDownloadPath,
|
|
13125
13592
|
focusFromRow,
|
|
@@ -13276,6 +13743,7 @@ export {
|
|
|
13276
13743
|
useTrack,
|
|
13277
13744
|
userTopic,
|
|
13278
13745
|
wasRefused,
|
|
13746
|
+
webMediaPlatform,
|
|
13279
13747
|
withItem,
|
|
13280
13748
|
wrapUpStatus
|
|
13281
13749
|
};
|