@burdenoff/website-sdk 2026.1105.1 → 2026.1105.3
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/README.md +94 -1
- package/dist/avatar-live/index.d.mts +2 -2
- package/dist/avatar-live/index.d.ts +2 -2
- package/dist/avatar-live/index.js +11 -1
- package/dist/avatar-live/index.js.map +1 -1
- package/dist/avatar-live/index.mjs +11 -1
- package/dist/avatar-live/index.mjs.map +1 -1
- package/dist/avatar-live-view/index.d.mts +2 -2
- package/dist/avatar-live-view/index.d.ts +2 -2
- package/dist/avatar-live-view/index.js +915 -103
- package/dist/avatar-live-view/index.js.map +1 -1
- package/dist/avatar-live-view/index.mjs +908 -96
- package/dist/avatar-live-view/index.mjs.map +1 -1
- package/dist/avatar-mic/index.d.mts +187 -62
- package/dist/avatar-mic/index.d.ts +187 -62
- package/dist/avatar-mic/index.js +457 -5
- package/dist/avatar-mic/index.js.map +1 -1
- package/dist/avatar-mic/index.mjs +457 -6
- package/dist/avatar-mic/index.mjs.map +1 -1
- package/dist/{chunk-XIZE37IB.js → chunk-3AIAJ2L4.js} +2 -2
- package/dist/chunk-3AIAJ2L4.js.map +1 -0
- package/dist/{chunk-MVNN6LLO.mjs → chunk-JGU6JW4N.mjs} +2 -2
- package/dist/chunk-JGU6JW4N.mjs.map +1 -0
- package/dist/{explore-avatar-live-config-C-OwXNje.d.ts → explore-avatar-live-config-1zeXdMer.d.ts} +7 -4
- package/dist/{explore-avatar-live-config-OO0hvQCK.d.mts → explore-avatar-live-config-BxiE66Im.d.mts} +7 -4
- package/dist/index.d.mts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/{types-CXqaLlNB.d.mts → types-CTUJNvQu.d.mts} +10 -0
- package/dist/{types-CXqaLlNB.d.ts → types-CTUJNvQu.d.ts} +10 -0
- package/package.json +1 -1
- package/dist/chunk-MVNN6LLO.mjs.map +0 -1
- package/dist/chunk-XIZE37IB.js.map +0 -1
package/README.md
CHANGED
|
@@ -420,6 +420,98 @@ publishes audio, video or data, and it sends the avatar nothing to say.
|
|
|
420
420
|
site's main bundle and the view's chunk carry none of it. On alpha, a browser
|
|
421
421
|
WebSocket cannot carry Cloudflare Access service-token headers, so the
|
|
422
422
|
stream needs the Access cookie for the gateway host.
|
|
423
|
+
- **The mic through the browser's own speech recognition (BOFF-9797 Stage C,
|
|
424
|
+
the operator's decision of 2026-11-05, dark).** The same `mic: true` opt-in
|
|
425
|
+
(default unset: no mic); the server's availability decides how the mic works.
|
|
426
|
+
Where the last `ok` answer carries `mic: {"mode": "browser",
|
|
427
|
+
"maxPressSeconds": 30}` (contract v1.6, the avatar service's
|
|
428
|
+
`AVATAR_WEB_MIC=browser`; read strictly: any other value is `unreadable` and
|
|
429
|
+
means no mic) and the consent version's words describe it (the new
|
|
430
|
+
`AvatarConsentCopy.micNotice`), the mic uses `SpeechRecognition` /
|
|
431
|
+
`webkitSpeechRecognition`, as the product assistant does: the browser's own
|
|
432
|
+
speech service turns the voice into text, and **nothing of a press reaches
|
|
433
|
+
our server**; the text goes into the box and leaves only on Send. Offered
|
|
434
|
+
only in a secure context with a recognizer, and never in Firefox (`Firefox/`,
|
|
435
|
+
`FxiOS/`: desktop Firefox gains an on-device recognizer in release 160) or
|
|
436
|
+
Brave (`navigator.brave`: no speech service behind its recognizer). The
|
|
437
|
+
gateway's stream is never offered beside a `mic` the server sent (readable or
|
|
438
|
+
not), nor under words that name a `micNotice`; without a `mic` it is offered
|
|
439
|
+
exactly as before. The first tap shows the notice the words name,
|
|
440
|
+
`web-mic-2026-11-21-c` (714 bytes, SHA-256 `0a15bf5d…8e06de8f`); agreement is
|
|
441
|
+
kept per notice version, so another version is shown again. Two new consent
|
|
442
|
+
versions, DRAFTS for the privacy review that no server names yet:
|
|
443
|
+
`web-consent-2026-11-21-c` (bot check `turnstile`, 1,761 bytes, SHA-256
|
|
444
|
+
`824f389a…111790db`) and `web-consent-2026-11-22-c` (bot check `off`, 1,717
|
|
445
|
+
bytes, SHA-256 `d84a038b…1eb6200c`), Stage D's words with the mic's sentences
|
|
446
|
+
added. Tap to start, tap to stop on every platform: the recognizer
|
|
447
|
+
(`en-IN`, one `en-US` retry on `language-not-supported`, interim results,
|
|
448
|
+
`continuous` except on a touch device) starts inside the tap where the
|
|
449
|
+
browser reports the microphone granted; otherwise the tap first opens the
|
|
450
|
+
microphone and releases it at once (`getUserMedia`, nothing kept or sent),
|
|
451
|
+
which is what raises the permission prompt on Android Chrome and iOS, and
|
|
452
|
+
the recognizer starts 250 ms later (the Chat dictation's mobile handling,
|
|
453
|
+
`use-speech-input`). The 30 s limit counts from its `onstart`, and an engine
|
|
454
|
+
that ends by itself (a pause, a phone's single utterance, `no-speech`) is
|
|
455
|
+
replaced in the same press, its words joined: at once on a computer, after
|
|
456
|
+
120 ms on a touch device, after a 2 s cooldown after a throw (or, on a
|
|
457
|
+
computer, an instant end); only 8 instant ends in a row, or 60 recognizers
|
|
458
|
+
in all, end a press by itself. The last words after `stop()` are waited for
|
|
459
|
+
(3 s), and a tap meanwhile does nothing. A refused microphone
|
|
460
|
+
(`NotAllowedError`, or `not-allowed` where the microphone was not
|
|
461
|
+
confirmed) says it is blocked (the mic stays); a refusal right after a grant
|
|
462
|
+
is retried once after 350 ms; refused again, or `service-not-allowed`,
|
|
463
|
+
`audio-capture`, no microphone, or a second `network` in a row, it says it
|
|
464
|
+
can't be used here and goes for the page. Words heard before any failure are
|
|
465
|
+
kept. Inside a Stage D session a tap while the avatar speaks sends
|
|
466
|
+
`interrupt`, recording sends `listening` marks from `onstart`, never within
|
|
467
|
+
5 s of the one before (at most 12 a minute, within the session's 30 turns a
|
|
468
|
+
minute); the mic goes the moment a question is sent and is back once its
|
|
469
|
+
answer has gone to the session, so the avatar never starts an answer into
|
|
470
|
+
an open microphone (a tap while it speaks sends `interrupt` first; how fast
|
|
471
|
+
the cut lands, and whether to mute the avatar locally on that tap, are open
|
|
472
|
+
Stage D items). What is heard and the box carry rrweb's `rr-block`/`rr-mask` (the box also `rr-ignore`), so a site's
|
|
473
|
+
session replay records none of it before Send, and nothing in the mic calls
|
|
474
|
+
the site's analytics. **Before alpha is switched on:** each of the four sites'
|
|
475
|
+
session replay must be off, or honour those classes, and each privacy notice
|
|
476
|
+
(`/privacy#live-avatar`) needs its microphone paragraph.
|
|
477
|
+
- **The answers after the first (BOFF-9797 Stage D, dark).** Only where the
|
|
478
|
+
create's 201 carries `followUps: {maxAnswers, idleSeconds,
|
|
479
|
+
maxAnswerWaitSeconds}` (the avatar service adds it only under its
|
|
480
|
+
`AVATAR_WEB_MULTI_ANSWER`, read strictly into `AvatarSession.followUps`;
|
|
481
|
+
absent or unreadable, the session reads its one answer exactly as before),
|
|
482
|
+
the visitor agreed to consent copy whose words describe them (`followUps:
|
|
483
|
+
true`: `web-consent-2026-11-19-d` with the bot check, `web-consent-2026-11-20-d`
|
|
484
|
+
without, DRAFTS no server names yet), and the tab has its own box. The box
|
|
485
|
+
then stays under the live avatar, and a question asked there (typed, or by
|
|
486
|
+
the mic where it is offered) is the next one of the same conversation, sent
|
|
487
|
+
with `surface: "AVATAR"`; the same session reads its answer. The view tells
|
|
488
|
+
the session through the gateway's `POST /api/web-avatar/v1/sessions/{id}/turn`
|
|
489
|
+
(`{secret, kind, nonce, messageId?, idempotencyKey?}`, a simple request like
|
|
490
|
+
the end, `requestTurn`): `asking` as Send is pressed, `listening` while the
|
|
491
|
+
mic records (once the microphone is open and its stream ready, and as it
|
|
492
|
+
stops), `answer` with the answer's id once it is written, and `interrupt`
|
|
493
|
+
when the mic is tapped while the avatar speaks. The session reads one answer
|
|
494
|
+
at a time and refuses a later one `busy` while the one before plays, so an
|
|
495
|
+
answer written while the avatar speaks is sent once that answer has played
|
|
496
|
+
out here, and a `busy` is asked again with the same key after the refusal's
|
|
497
|
+
`retryAfterSeconds` (doubling up to 8 s, at most 24 times, never past the
|
|
498
|
+
session's end; this also covers a room that tells no voice). An answer that
|
|
499
|
+
got no reply is asked again once with the same key. An answer the session
|
|
500
|
+
does not read stays in Chat, said in one line (`no_time`: "There isn't
|
|
501
|
+
enough time left in this session to read that answer. It's in Chat.";
|
|
502
|
+
`not_eligible`: "This answer stays in Chat as text. …"; also `max_answers`,
|
|
503
|
+
`answer_claimed`, `limits`, `paused`, `busy` past its retries), and the box
|
|
504
|
+
keeps asking while answers are left. An answer this browser can tell will
|
|
505
|
+
not be read (a failed answer, or one its checks refuse) is sent all the same
|
|
506
|
+
with this browser's line shown: only the session's refusal ends the wait an
|
|
507
|
+
`asking` mark holds open. When an answer has played out is the worker's voice going quiet for
|
|
508
|
+
2.5 s (the room's new `speaking` event, from the media server's active
|
|
509
|
+
speakers); the status line then says "The avatar ends after about thirty
|
|
510
|
+
seconds without a question.", and the server ends the session after its
|
|
511
|
+
idle period (the room closing ends it here, as any server end). Under
|
|
512
|
+
`endUnattended`, a session out of sight ends once its answer has played out
|
|
513
|
+
and nothing is on its way. Stop, an interrupt and a correction never create a
|
|
514
|
+
session or ask for an answer twice.
|
|
423
515
|
- **One answer: the first, to a suggested question.** The avatar service
|
|
424
516
|
accepts a conversation only when it holds exactly two messages: the first user
|
|
425
517
|
message (a suggested question) and the first assistant answer, completed. The
|
|
@@ -628,7 +720,8 @@ Testing hooks (`data-boff-explore`): `avatar-live` (with `data-phase`),
|
|
|
628
720
|
`avatar-live-start`, `avatar-live-not-now`, `avatar-live-stop`,
|
|
629
721
|
`avatar-live-video`, `avatar-live-audio`, `avatar-live-badge`,
|
|
630
722
|
`avatar-live-captions`, `avatar-live-countdown`, `avatar-live-sound`,
|
|
631
|
-
`avatar-live-continue`, `avatar-live-back`, `avatar-live-bot-check
|
|
723
|
+
`avatar-live-continue`, `avatar-live-back`, `avatar-live-bot-check`,
|
|
724
|
+
`avatar-live-idle` (Stage D: the idle line under a refusal's).
|
|
632
725
|
|
|
633
726
|
#### Site assistant dock (`SiteAssistantDock`, every product site)
|
|
634
727
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as ReceiveOnlyJoinOptions, a as ReceiveOnlySession } from '../types-
|
|
2
|
-
export { A as AvatarLiveModule, b as ReceiveOnlyEvent } from '../types-
|
|
1
|
+
import { R as ReceiveOnlyJoinOptions, a as ReceiveOnlySession } from '../types-CTUJNvQu.mjs';
|
|
2
|
+
export { A as AvatarLiveModule, b as ReceiveOnlyEvent } from '../types-CTUJNvQu.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Join the room in `options.url` with the receive-only `options.token`, and play the avatar worker's video and voice
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as ReceiveOnlyJoinOptions, a as ReceiveOnlySession } from '../types-
|
|
2
|
-
export { A as AvatarLiveModule, b as ReceiveOnlyEvent } from '../types-
|
|
1
|
+
import { R as ReceiveOnlyJoinOptions, a as ReceiveOnlySession } from '../types-CTUJNvQu.js';
|
|
2
|
+
export { A as AvatarLiveModule, b as ReceiveOnlyEvent } from '../types-CTUJNvQu.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Join the room in `options.url` with the receive-only `options.token`, and play the avatar worker's video and voice
|
|
@@ -61,6 +61,16 @@ async function joinReceiveOnly(options) {
|
|
|
61
61
|
sound: !room.canPlaybackAudio
|
|
62
62
|
});
|
|
63
63
|
};
|
|
64
|
+
let workerSpeaking = false;
|
|
65
|
+
const reportSpeakers = (speakers) => {
|
|
66
|
+
if (leaving) return;
|
|
67
|
+
const speaking = speakers.some(
|
|
68
|
+
(speaker) => speaker.identity === workerIdentity
|
|
69
|
+
);
|
|
70
|
+
if (speaking === workerSpeaking) return;
|
|
71
|
+
workerSpeaking = speaking;
|
|
72
|
+
onEvent({ type: "speaking", speaking });
|
|
73
|
+
};
|
|
64
74
|
room.on(livekitClient.RoomEvent.TrackPublished, consider).on(
|
|
65
75
|
livekitClient.RoomEvent.TrackSubscribed,
|
|
66
76
|
(track, publication, participant) => {
|
|
@@ -93,7 +103,7 @@ async function joinReceiveOnly(options) {
|
|
|
93
103
|
} else {
|
|
94
104
|
voiceGone(participant);
|
|
95
105
|
}
|
|
96
|
-
}).on(livekitClient.RoomEvent.AudioPlaybackStatusChanged, reportPlayback).on(livekitClient.RoomEvent.VideoPlaybackStatusChanged, reportPlayback).on(livekitClient.RoomEvent.Disconnected, (reason) => {
|
|
106
|
+
}).on(livekitClient.RoomEvent.AudioPlaybackStatusChanged, reportPlayback).on(livekitClient.RoomEvent.VideoPlaybackStatusChanged, reportPlayback).on(livekitClient.RoomEvent.ActiveSpeakersChanged, reportSpeakers).on(livekitClient.RoomEvent.Disconnected, (reason) => {
|
|
97
107
|
if (leaving) return;
|
|
98
108
|
onEvent({
|
|
99
109
|
type: "disconnected",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/avatar-live/receive-only.ts"],"names":["DisconnectReason","Track","Room","RoomEvent"],"mappings":";;;;;AAgCA,IAAM,gBAAA,uBAAsD,GAAA,CAAI;AAAA,EAC9DA,8BAAA,CAAiB,YAAA;AAAA,EACjBA,8BAAA,CAAiB,WAAA;AAAA,EACjBA,8BAAA,CAAiB;AACnB,CAAC,CAAA;AAED,SAAS,QAAQ,WAAA,EAA8C;AAC7D,EAAA,OACE,WAAA,CAAY,SAASC,mBAAA,CAAM,IAAA,CAAK,SAChC,WAAA,CAAY,IAAA,KAASA,oBAAM,IAAA,CAAK,KAAA;AAEpC;AAOA,eAAsB,gBACpB,OAAA,EAC6B;AAC7B,EAAA,MAAM,EAAE,GAAA,EAAK,KAAA,EAAO,gBAAgB,KAAA,EAAO,KAAA,EAAO,SAAQ,GAAI,OAAA;AAC9D,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAI,IAAI,GAAG,CAAA;AAAA,EACtB,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,MAAM,4CAA4C,CAAA;AAAA,EAC9D;AACA,EAAA,IAAI,MAAA,CAAO,aAAa,MAAA,EAAQ;AAC9B,IAAA,MAAM,IAAI,MAAM,4CAA4C,CAAA;AAAA,EAC9D;AACA,EAAA,IAAI,CAAC,KAAA,IAAS,CAAC,cAAA,EAAgB;AAC7B,IAAA,MAAM,IAAI,MAAM,mDAAmD,CAAA;AAAA,EACrE;AAEA,EAAA,MAAM,IAAA,GAAO,IAAIC,kBAAA,CAAK;AAAA;AAAA,IAEpB,cAAA,EAAgB,KAAA;AAAA,IAChB,QAAA,EAAU,KAAA;AAAA;AAAA;AAAA,IAGV,qBAAA,EAAuB;AAAA,GACxB,CAAA;AACD,EAAA,IAAI,OAAA,GAAU,KAAA;AAEd,EAAA,MAAM,SAAA,GAAY,CAAC,WAAA,KACjB,WAAA,CAAY,QAAA,KAAa,cAAA;AAM3B,EAAA,MAAM,MAAA,uBAAa,GAAA,EAAY;AAC/B,EAAA,IAAI,WAAA,GAAc,KAAA;AAGlB,EAAA,MAAM,QAAA,GAAW,CACf,WAAA,EACA,WAAA,KACG;AACH,IAAA,IAAI,OAAA,EAAS;AACb,IAAA,IAAI,CAAC,SAAA,CAAU,WAAW,CAAA,EAAG;AAC3B,MAAA,IAAI,WAAA,CAAY,IAAA,KAASD,mBAAA,CAAM,IAAA,CAAK,KAAA,EAAO;AACzC,QAAA,MAAA,CAAO,GAAA,CAAI,YAAY,QAAQ,CAAA;AAAA,MACjC;AACA,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAC,OAAA,CAAQ,WAAW,CAAA,EAAG;AAC3B,IAAA,IAAI,CAAC,WAAA,CAAY,YAAA,EAAc,WAAA,CAAY,cAAc,IAAI,CAAA;AAAA,EAC/D,CAAA;AAGA,EAAA,MAAM,SAAA,GAAY,CAAC,WAAA,KAAmC;AACpD,IAAA,IAAI,WAAW,WAAA,IAAe,CAAC,OAAO,GAAA,CAAI,WAAA,CAAY,QAAQ,CAAA,EAAG;AACjE,IAAA,WAAA,GAAc,IAAA;AACd,IAAA,OAAA,CAAQ,EAAE,IAAA,EAAM,cAAA,EAAgB,CAAA;AAAA,EAClC,CAAA;AAEA,EAAA,MAAM,iBAAiB,MAAM;AAC3B,IAAA,IAAI,OAAA,EAAS;AACb,IAAA,OAAA,CAAQ;AAAA,MACN,IAAA,EAAM,kBAAA;AAAA,MACN,OAAA,EAAS,CAAC,IAAA,CAAK,gBAAA,IAAoB,CAAC,IAAA,CAAK,gBAAA;AAAA,MACzC,KAAA,EAAO,CAAC,IAAA,CAAK;AAAA,KACd,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,IAAA,CACG,EAAA,CAAGE,uBAAA,CAAU,cAAA,EAAgB,QAAQ,CAAA,CACrC,EAAA;AAAA,IACCA,uBAAA,CAAU,eAAA;AAAA,IACV,CACE,KAAA,EACA,WAAA,EACA,WAAA,KACG;AACH,MAAA,IAAI,OAAA,EAAS;AACb,MAAA,IAAI,CAAC,SAAA,CAAU,WAAW,KAAK,CAAC,OAAA,CAAQ,WAAW,CAAA,EAAG;AAEpD,QAAA,WAAA,CAAY,cAAc,KAAK,CAAA;AAC/B,QAAA;AAAA,MACF;AACA,MAAA,IAAI,KAAA,CAAM,IAAA,KAASF,mBAAA,CAAM,IAAA,CAAK,KAAA,EAAO;AACnC,QAAA,KAAA,CAAM,OAAO,KAAK,CAAA;AAClB,QAAA,OAAA,CAAQ,EAAE,IAAA,EAAM,eAAA,EAAiB,IAAA,EAAM,SAAS,CAAA;AAAA,MAClD,CAAA,MAAO;AACL,QAAA,KAAA,CAAM,OAAO,KAAK,CAAA;AAClB,QAAA,OAAA,CAAQ,EAAE,IAAA,EAAM,eAAA,EAAiB,IAAA,EAAM,SAAS,CAAA;AAAA,MAClD;AAAA,IACF;AAAA,GACF,CACC,EAAA,CAAGE,uBAAA,CAAU,iBAAA,EAAmB,CAAC,KAAA,KAAuB;AACvD,IAAA,KAAA,CAAM,MAAA,EAAO;AAAA,EACf,CAAC,CAAA,CACA,EAAA;AAAA,IACCA,uBAAA,CAAU,gBAAA;AAAA,IACV,CAAC,aAAqC,WAAA,KAAmC;AACvE,MAAA,IAAI,CAAC,UAAU,WAAW,CAAA,IAAK,YAAY,IAAA,KAASF,mBAAA,CAAM,KAAK,KAAA,EAAO;AACpE,QAAA,SAAA,CAAU,WAAW,CAAA;AAAA,MACvB;AAAA,IACF;AAAA,GACF,CACC,EAAA,CAAGE,uBAAA,CAAU,uBAAA,EAAyB,CAAC,WAAA,KAAmC;AACzE,IAAA,IAAI,OAAA,EAAS;AACb,IAAA,IAAI,SAAA,CAAU,WAAW,CAAA,EAAG;AAC1B,MAAA,OAAA,CAAQ,EAAE,IAAA,EAAM,cAAA,EAAgB,CAAA;AAAA,IAClC,CAAA,MAAO;AACL,MAAA,SAAA,CAAU,WAAW,CAAA;AAAA,IACvB;AAAA,EACF,CAAC,CAAA,CACA,EAAA,CAAGA,uBAAA,CAAU,0BAAA,EAA4B,cAAc,CAAA,CACvD,EAAA,CAAGA,uBAAA,CAAU,0BAAA,EAA4B,cAAc,CAAA,CACvD,EAAA,CAAGA,uBAAA,CAAU,YAAA,EAAc,CAAC,MAAA,KAA8B;AACzD,IAAA,IAAI,OAAA,EAAS;AACb,IAAA,OAAA,CAAQ;AAAA,MACN,IAAA,EAAM,cAAA;AAAA,MACN,QAAA,EAAU,MAAA,KAAW,MAAA,IAAa,gBAAA,CAAiB,IAAI,MAAM;AAAA,KAC9D,CAAA;AAAA,EACH,CAAC,CAAA;AAEH,EAAA,MAAM,YAAY,MAAM;AACtB,IAAA,KAAA,MAAW,WAAA,IAAe,IAAA,CAAK,kBAAA,CAAmB,MAAA,EAAO,EAAG;AAC1D,MAAA,KAAA,MAAW,WAAA,IAAe,WAAA,CAAY,iBAAA,CAAkB,MAAA,EAAO,EAAG;AAChE,QAAA,WAAA,CAAY,OAAO,MAAA,EAAO;AAAA,MAC5B;AAAA,IACF;AACA,IAAA,KAAA,CAAM,SAAA,GAAY,IAAA;AAClB,IAAA,KAAA,CAAM,SAAA,GAAY,IAAA;AAAA,EACpB,CAAA;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,KAAK,OAAA,CAAQ,GAAA,EAAK,OAAO,EAAE,aAAA,EAAe,OAAO,CAAA;AAAA,EACzD,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,GAAU,IAAA;AACV,IAAA,IAAA,CAAK,kBAAA,EAAmB;AACxB,IAAA,SAAA,EAAU;AACV,IAAA,MAAM,KAAA;AAAA,EACR;AAGA,EAAA,KAAA,MAAW,WAAA,IAAe,IAAA,CAAK,kBAAA,CAAmB,MAAA,EAAO,EAAG;AAC1D,IAAA,KAAA,MAAW,WAAA,IAAe,WAAA,CAAY,iBAAA,CAAkB,MAAA,EAAO,EAAG;AAChE,MAAA,QAAA,CAAS,aAAa,WAAW,CAAA;AAAA,IACnC;AAAA,EACF;AACA,EAAA,cAAA,EAAe;AAEf,EAAA,OAAO;AAAA,IACL,gBAAgB,YAAY;AAG1B,MAAA,MAAM,OAAA,CAAQ,WAAW,CAAC,IAAA,CAAK,YAAW,EAAG,IAAA,CAAK,UAAA,EAAY,CAAC,CAAA;AAC/D,MAAA,cAAA,EAAe;AAAA,IACjB,CAAA;AAAA,IACA,OAAO,YAAY;AACjB,MAAA,IAAI,OAAA,EAAS;AACb,MAAA,OAAA,GAAU,IAAA;AACV,MAAA,SAAA,EAAU;AACV,MAAA,IAAI;AACF,QAAA,MAAM,KAAK,UAAA,EAAW;AAAA,MACxB,CAAA,SAAE;AACA,QAAA,IAAA,CAAK,kBAAA,EAAmB;AAAA,MAC1B;AAAA,IACF;AAAA,GACF;AACF","file":"index.js","sourcesContent":["/**\n * BOFF-9797. The live avatar's room, joined to watch and listen only.\n *\n * A visitor's browser on a product website holds a subscribe-only room token: it can receive the avatar worker's\n * video and voice and do nothing else. This adapter keeps to that from its side too:\n *\n * - **Nothing is subscribed automatically** (`autoSubscribe: false`). Only the tracks of `workerIdentity`, the avatar\n * worker the create answer named, are subscribed, and only their video and audio. Any other participant's track\n * (the server's own speaker included) is never asked for, and is dropped again should the server send it anyway.\n * - **Nothing is published.** No microphone, no camera, no data, no RPC method: this file never calls a publishing\n * API, and the token would refuse one.\n * - **Safari and iOS** hold sound back until a gesture. The room reports it (`playback-blocked`, with `sound` when the\n * sound is held back and not only the video) and the panel offers \"Tap to turn on sound\", whose tap calls\n * `enablePlayback`.\n * - **The end of the speech** is told (`speech-ended`) from what any viewer of the room sees: the other voice in it, the\n * server's speaker that the worker follows, leaving the room or unpublishing its voice. Its track is never subscribed.\n *\n * Written for the website from the media library's public API: nothing here is copied from the product apps.\n */\nimport {\n DisconnectReason,\n Room,\n RoomEvent,\n Track,\n type RemoteParticipant,\n type RemoteTrack,\n type RemoteTrackPublication,\n} from \"livekit-client\";\n\nimport type { ReceiveOnlyJoinOptions, ReceiveOnlySession } from \"./types\";\n\n/** The reasons a room closes because the server closed it, rather than because the connection was lost. */\nconst CLOSED_BY_SERVER: ReadonlySet<DisconnectReason> = new Set([\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.ROOM_CLOSED,\n DisconnectReason.PARTICIPANT_REMOVED,\n]);\n\nfunction isMedia(publication: RemoteTrackPublication): boolean {\n return (\n publication.kind === Track.Kind.Video ||\n publication.kind === Track.Kind.Audio\n );\n}\n\n/**\n * Join the room in `options.url` with the receive-only `options.token`, and play the avatar worker's video and voice\n * in `options.video` and `options.audio`. Resolves once connected; rejects when the room cannot be joined (the panel\n * then ends the session).\n */\nexport async function joinReceiveOnly(\n options: ReceiveOnlyJoinOptions,\n): Promise<ReceiveOnlySession> {\n const { url, token, workerIdentity, video, audio, onEvent } = options;\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(\"live avatar: the room address is not a URL\");\n }\n if (parsed.protocol !== \"wss:\") {\n throw new Error(\"live avatar: the room address must be wss:\");\n }\n if (!token || !workerIdentity) {\n throw new Error(\"live avatar: the room token or speaker is missing\");\n }\n\n const room = new Room({\n // One small video, always shown while the session lasts: no quality switching, no layer pausing.\n adaptiveStream: false,\n dynacast: false,\n // Closing the tab or leaving the page leaves the room at once. Belt and braces with the panel's own page-hide\n // handler, which also asks the server to end the session: either may run first, and both are safe to repeat.\n disconnectOnPageLeave: true,\n });\n let leaving = false;\n\n const isSpeaker = (participant: RemoteParticipant) =>\n participant.identity === workerIdentity;\n\n /**\n * Everyone else in the room seen publishing a voice: the server's speaker, whose speech the worker voices. Noted\n * only, never subscribed to, so that the end of that speech can be told (`speech-ended`).\n */\n const voiced = new Set<string>();\n let speechEnded = false;\n\n /** Subscribe to `publication` when it is the speaker's video or audio; leave everything else alone. */\n const consider = (\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving) return;\n if (!isSpeaker(participant)) {\n if (publication.kind === Track.Kind.Audio) {\n voiced.add(participant.identity);\n }\n return;\n }\n if (!isMedia(publication)) return;\n if (!publication.isSubscribed) publication.setSubscribed(true);\n };\n\n /** A voice seen published has gone (its track, or its participant): the speech has ended at its source. Once. */\n const voiceGone = (participant: RemoteParticipant) => {\n if (leaving || speechEnded || !voiced.has(participant.identity)) return;\n speechEnded = true;\n onEvent({ type: \"speech-ended\" });\n };\n\n const reportPlayback = () => {\n if (leaving) return;\n onEvent({\n type: \"playback-blocked\",\n blocked: !room.canPlaybackAudio || !room.canPlaybackVideo,\n sound: !room.canPlaybackAudio,\n });\n };\n\n room\n .on(RoomEvent.TrackPublished, consider)\n .on(\n RoomEvent.TrackSubscribed,\n (\n track: RemoteTrack,\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving) return;\n if (!isSpeaker(participant) || !isMedia(publication)) {\n // Never asked for: drop it rather than play it.\n publication.setSubscribed(false);\n return;\n }\n if (track.kind === Track.Kind.Video) {\n track.attach(video);\n onEvent({ type: \"speaker-track\", kind: \"video\" });\n } else {\n track.attach(audio);\n onEvent({ type: \"speaker-track\", kind: \"audio\" });\n }\n },\n )\n .on(RoomEvent.TrackUnsubscribed, (track: RemoteTrack) => {\n track.detach();\n })\n .on(\n RoomEvent.TrackUnpublished,\n (publication: RemoteTrackPublication, participant: RemoteParticipant) => {\n if (!isSpeaker(participant) && publication.kind === Track.Kind.Audio) {\n voiceGone(participant);\n }\n },\n )\n .on(RoomEvent.ParticipantDisconnected, (participant: RemoteParticipant) => {\n if (leaving) return;\n if (isSpeaker(participant)) {\n onEvent({ type: \"speaker-left\" });\n } else {\n voiceGone(participant);\n }\n })\n .on(RoomEvent.AudioPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.VideoPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.Disconnected, (reason?: DisconnectReason) => {\n if (leaving) return;\n onEvent({\n type: \"disconnected\",\n byServer: reason !== undefined && CLOSED_BY_SERVER.has(reason),\n });\n });\n\n const detachAll = () => {\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n publication.track?.detach();\n }\n }\n video.srcObject = null;\n audio.srcObject = null;\n };\n\n try {\n await room.connect(url, token, { autoSubscribe: false });\n } catch (error) {\n leaving = true;\n room.removeAllListeners();\n detachAll();\n throw error;\n }\n\n // Whoever was in the room before this viewer joined: their tracks were published before the listener above.\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n consider(publication, participant);\n }\n }\n reportPlayback();\n\n return {\n enablePlayback: async () => {\n // Both started inside the visitor's gesture, before anything is awaited: Safari allows playback only there.\n // A refusal leaves the room as it was, and the report below keeps the button offered.\n await Promise.allSettled([room.startAudio(), room.startVideo()]);\n reportPlayback();\n },\n leave: async () => {\n if (leaving) return;\n leaving = true;\n detachAll();\n try {\n await room.disconnect();\n } finally {\n room.removeAllListeners();\n }\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/avatar-live/receive-only.ts"],"names":["DisconnectReason","Track","Room","RoomEvent"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * BOFF-9797. The live avatar's room, joined to watch and listen only.\n *\n * A visitor's browser on a product website holds a subscribe-only room token: it can receive the avatar worker's\n * video and voice and do nothing else. This adapter keeps to that from its side too:\n *\n * - **Nothing is subscribed automatically** (`autoSubscribe: false`). Only the tracks of `workerIdentity`, the avatar\n * worker the create answer named, are subscribed, and only their video and audio. Any other participant's track\n * (the server's own speaker included) is never asked for, and is dropped again should the server send it anyway.\n * - **Nothing is published.** No microphone, no camera, no data, no RPC method: this file never calls a publishing\n * API, and the token would refuse one.\n * - **Safari and iOS** hold sound back until a gesture. The room reports it (`playback-blocked`, with `sound` when the\n * sound is held back and not only the video) and the panel offers \"Tap to turn on sound\", whose tap calls\n * `enablePlayback`.\n * - **The end of the speech** is told (`speech-ended`) from what any viewer of the room sees: the other voice in it, the\n * server's speaker that the worker follows, leaving the room or unpublishing its voice. Its track is never subscribed.\n * - **Whether the worker's voice is heard** is told (`speaking`) each time it changes, from the room server's active\n * speakers, which it reports to a speaker's subscribers.\n *\n * Written for the website from the media library's public API: nothing here is copied from the product apps.\n */\nimport {\n DisconnectReason,\n Room,\n RoomEvent,\n Track,\n type Participant,\n type RemoteParticipant,\n type RemoteTrack,\n type RemoteTrackPublication,\n} from \"livekit-client\";\n\nimport type { ReceiveOnlyJoinOptions, ReceiveOnlySession } from \"./types\";\n\n/** The reasons a room closes because the server closed it, rather than because the connection was lost. */\nconst CLOSED_BY_SERVER: ReadonlySet<DisconnectReason> = new Set([\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.ROOM_CLOSED,\n DisconnectReason.PARTICIPANT_REMOVED,\n]);\n\nfunction isMedia(publication: RemoteTrackPublication): boolean {\n return (\n publication.kind === Track.Kind.Video ||\n publication.kind === Track.Kind.Audio\n );\n}\n\n/**\n * Join the room in `options.url` with the receive-only `options.token`, and play the avatar worker's video and voice\n * in `options.video` and `options.audio`. Resolves once connected; rejects when the room cannot be joined (the panel\n * then ends the session).\n */\nexport async function joinReceiveOnly(\n options: ReceiveOnlyJoinOptions,\n): Promise<ReceiveOnlySession> {\n const { url, token, workerIdentity, video, audio, onEvent } = options;\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(\"live avatar: the room address is not a URL\");\n }\n if (parsed.protocol !== \"wss:\") {\n throw new Error(\"live avatar: the room address must be wss:\");\n }\n if (!token || !workerIdentity) {\n throw new Error(\"live avatar: the room token or speaker is missing\");\n }\n\n const room = new Room({\n // One small video, always shown while the session lasts: no quality switching, no layer pausing.\n adaptiveStream: false,\n dynacast: false,\n // Closing the tab or leaving the page leaves the room at once. Belt and braces with the panel's own page-hide\n // handler, which also asks the server to end the session: either may run first, and both are safe to repeat.\n disconnectOnPageLeave: true,\n });\n let leaving = false;\n\n const isSpeaker = (participant: RemoteParticipant) =>\n participant.identity === workerIdentity;\n\n /**\n * Everyone else in the room seen publishing a voice: the server's speaker, whose speech the worker voices. Noted\n * only, never subscribed to, so that the end of that speech can be told (`speech-ended`).\n */\n const voiced = new Set<string>();\n let speechEnded = false;\n\n /** Subscribe to `publication` when it is the speaker's video or audio; leave everything else alone. */\n const consider = (\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving) return;\n if (!isSpeaker(participant)) {\n if (publication.kind === Track.Kind.Audio) {\n voiced.add(participant.identity);\n }\n return;\n }\n if (!isMedia(publication)) return;\n if (!publication.isSubscribed) publication.setSubscribed(true);\n };\n\n /** A voice seen published has gone (its track, or its participant): the speech has ended at its source. Once. */\n const voiceGone = (participant: RemoteParticipant) => {\n if (leaving || speechEnded || !voiced.has(participant.identity)) return;\n speechEnded = true;\n onEvent({ type: \"speech-ended\" });\n };\n\n const reportPlayback = () => {\n if (leaving) return;\n onEvent({\n type: \"playback-blocked\",\n blocked: !room.canPlaybackAudio || !room.canPlaybackVideo,\n sound: !room.canPlaybackAudio,\n });\n };\n\n /** Whether the worker's voice is heard, as last told: `speaking` goes out only when it changes. */\n let workerSpeaking = false;\n const reportSpeakers = (speakers: Participant[]) => {\n if (leaving) return;\n const speaking = speakers.some(\n (speaker) => speaker.identity === workerIdentity,\n );\n if (speaking === workerSpeaking) return;\n workerSpeaking = speaking;\n onEvent({ type: \"speaking\", speaking });\n };\n\n room\n .on(RoomEvent.TrackPublished, consider)\n .on(\n RoomEvent.TrackSubscribed,\n (\n track: RemoteTrack,\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving) return;\n if (!isSpeaker(participant) || !isMedia(publication)) {\n // Never asked for: drop it rather than play it.\n publication.setSubscribed(false);\n return;\n }\n if (track.kind === Track.Kind.Video) {\n track.attach(video);\n onEvent({ type: \"speaker-track\", kind: \"video\" });\n } else {\n track.attach(audio);\n onEvent({ type: \"speaker-track\", kind: \"audio\" });\n }\n },\n )\n .on(RoomEvent.TrackUnsubscribed, (track: RemoteTrack) => {\n track.detach();\n })\n .on(\n RoomEvent.TrackUnpublished,\n (publication: RemoteTrackPublication, participant: RemoteParticipant) => {\n if (!isSpeaker(participant) && publication.kind === Track.Kind.Audio) {\n voiceGone(participant);\n }\n },\n )\n .on(RoomEvent.ParticipantDisconnected, (participant: RemoteParticipant) => {\n if (leaving) return;\n if (isSpeaker(participant)) {\n onEvent({ type: \"speaker-left\" });\n } else {\n voiceGone(participant);\n }\n })\n .on(RoomEvent.AudioPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.VideoPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.ActiveSpeakersChanged, reportSpeakers)\n .on(RoomEvent.Disconnected, (reason?: DisconnectReason) => {\n if (leaving) return;\n onEvent({\n type: \"disconnected\",\n byServer: reason !== undefined && CLOSED_BY_SERVER.has(reason),\n });\n });\n\n const detachAll = () => {\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n publication.track?.detach();\n }\n }\n video.srcObject = null;\n audio.srcObject = null;\n };\n\n try {\n await room.connect(url, token, { autoSubscribe: false });\n } catch (error) {\n leaving = true;\n room.removeAllListeners();\n detachAll();\n throw error;\n }\n\n // Whoever was in the room before this viewer joined: their tracks were published before the listener above.\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n consider(publication, participant);\n }\n }\n reportPlayback();\n\n return {\n enablePlayback: async () => {\n // Both started inside the visitor's gesture, before anything is awaited: Safari allows playback only there.\n // A refusal leaves the room as it was, and the report below keeps the button offered.\n await Promise.allSettled([room.startAudio(), room.startVideo()]);\n reportPlayback();\n },\n leave: async () => {\n if (leaving) return;\n leaving = true;\n detachAll();\n try {\n await room.disconnect();\n } finally {\n room.removeAllListeners();\n }\n },\n };\n}\n"]}
|
|
@@ -59,6 +59,16 @@ async function joinReceiveOnly(options) {
|
|
|
59
59
|
sound: !room.canPlaybackAudio
|
|
60
60
|
});
|
|
61
61
|
};
|
|
62
|
+
let workerSpeaking = false;
|
|
63
|
+
const reportSpeakers = (speakers) => {
|
|
64
|
+
if (leaving) return;
|
|
65
|
+
const speaking = speakers.some(
|
|
66
|
+
(speaker) => speaker.identity === workerIdentity
|
|
67
|
+
);
|
|
68
|
+
if (speaking === workerSpeaking) return;
|
|
69
|
+
workerSpeaking = speaking;
|
|
70
|
+
onEvent({ type: "speaking", speaking });
|
|
71
|
+
};
|
|
62
72
|
room.on(RoomEvent.TrackPublished, consider).on(
|
|
63
73
|
RoomEvent.TrackSubscribed,
|
|
64
74
|
(track, publication, participant) => {
|
|
@@ -91,7 +101,7 @@ async function joinReceiveOnly(options) {
|
|
|
91
101
|
} else {
|
|
92
102
|
voiceGone(participant);
|
|
93
103
|
}
|
|
94
|
-
}).on(RoomEvent.AudioPlaybackStatusChanged, reportPlayback).on(RoomEvent.VideoPlaybackStatusChanged, reportPlayback).on(RoomEvent.Disconnected, (reason) => {
|
|
104
|
+
}).on(RoomEvent.AudioPlaybackStatusChanged, reportPlayback).on(RoomEvent.VideoPlaybackStatusChanged, reportPlayback).on(RoomEvent.ActiveSpeakersChanged, reportSpeakers).on(RoomEvent.Disconnected, (reason) => {
|
|
95
105
|
if (leaving) return;
|
|
96
106
|
onEvent({
|
|
97
107
|
type: "disconnected",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/avatar-live/receive-only.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * BOFF-9797. The live avatar's room, joined to watch and listen only.\n *\n * A visitor's browser on a product website holds a subscribe-only room token: it can receive the avatar worker's\n * video and voice and do nothing else. This adapter keeps to that from its side too:\n *\n * - **Nothing is subscribed automatically** (`autoSubscribe: false`). Only the tracks of `workerIdentity`, the avatar\n * worker the create answer named, are subscribed, and only their video and audio. Any other participant's track\n * (the server's own speaker included) is never asked for, and is dropped again should the server send it anyway.\n * - **Nothing is published.** No microphone, no camera, no data, no RPC method: this file never calls a publishing\n * API, and the token would refuse one.\n * - **Safari and iOS** hold sound back until a gesture. The room reports it (`playback-blocked`, with `sound` when the\n * sound is held back and not only the video) and the panel offers \"Tap to turn on sound\", whose tap calls\n * `enablePlayback`.\n * - **The end of the speech** is told (`speech-ended`) from what any viewer of the room sees: the other voice in it, the\n * server's speaker that the worker follows, leaving the room or unpublishing its voice. Its track is never subscribed.\n *\n * Written for the website from the media library's public API: nothing here is copied from the product apps.\n */\nimport {\n DisconnectReason,\n Room,\n RoomEvent,\n Track,\n type RemoteParticipant,\n type RemoteTrack,\n type RemoteTrackPublication,\n} from \"livekit-client\";\n\nimport type { ReceiveOnlyJoinOptions, ReceiveOnlySession } from \"./types\";\n\n/** The reasons a room closes because the server closed it, rather than because the connection was lost. */\nconst CLOSED_BY_SERVER: ReadonlySet<DisconnectReason> = new Set([\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.ROOM_CLOSED,\n DisconnectReason.PARTICIPANT_REMOVED,\n]);\n\nfunction isMedia(publication: RemoteTrackPublication): boolean {\n return (\n publication.kind === Track.Kind.Video ||\n publication.kind === Track.Kind.Audio\n );\n}\n\n/**\n * Join the room in `options.url` with the receive-only `options.token`, and play the avatar worker's video and voice\n * in `options.video` and `options.audio`. Resolves once connected; rejects when the room cannot be joined (the panel\n * then ends the session).\n */\nexport async function joinReceiveOnly(\n options: ReceiveOnlyJoinOptions,\n): Promise<ReceiveOnlySession> {\n const { url, token, workerIdentity, video, audio, onEvent } = options;\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(\"live avatar: the room address is not a URL\");\n }\n if (parsed.protocol !== \"wss:\") {\n throw new Error(\"live avatar: the room address must be wss:\");\n }\n if (!token || !workerIdentity) {\n throw new Error(\"live avatar: the room token or speaker is missing\");\n }\n\n const room = new Room({\n // One small video, always shown while the session lasts: no quality switching, no layer pausing.\n adaptiveStream: false,\n dynacast: false,\n // Closing the tab or leaving the page leaves the room at once. Belt and braces with the panel's own page-hide\n // handler, which also asks the server to end the session: either may run first, and both are safe to repeat.\n disconnectOnPageLeave: true,\n });\n let leaving = false;\n\n const isSpeaker = (participant: RemoteParticipant) =>\n participant.identity === workerIdentity;\n\n /**\n * Everyone else in the room seen publishing a voice: the server's speaker, whose speech the worker voices. Noted\n * only, never subscribed to, so that the end of that speech can be told (`speech-ended`).\n */\n const voiced = new Set<string>();\n let speechEnded = false;\n\n /** Subscribe to `publication` when it is the speaker's video or audio; leave everything else alone. */\n const consider = (\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving) return;\n if (!isSpeaker(participant)) {\n if (publication.kind === Track.Kind.Audio) {\n voiced.add(participant.identity);\n }\n return;\n }\n if (!isMedia(publication)) return;\n if (!publication.isSubscribed) publication.setSubscribed(true);\n };\n\n /** A voice seen published has gone (its track, or its participant): the speech has ended at its source. Once. */\n const voiceGone = (participant: RemoteParticipant) => {\n if (leaving || speechEnded || !voiced.has(participant.identity)) return;\n speechEnded = true;\n onEvent({ type: \"speech-ended\" });\n };\n\n const reportPlayback = () => {\n if (leaving) return;\n onEvent({\n type: \"playback-blocked\",\n blocked: !room.canPlaybackAudio || !room.canPlaybackVideo,\n sound: !room.canPlaybackAudio,\n });\n };\n\n room\n .on(RoomEvent.TrackPublished, consider)\n .on(\n RoomEvent.TrackSubscribed,\n (\n track: RemoteTrack,\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving) return;\n if (!isSpeaker(participant) || !isMedia(publication)) {\n // Never asked for: drop it rather than play it.\n publication.setSubscribed(false);\n return;\n }\n if (track.kind === Track.Kind.Video) {\n track.attach(video);\n onEvent({ type: \"speaker-track\", kind: \"video\" });\n } else {\n track.attach(audio);\n onEvent({ type: \"speaker-track\", kind: \"audio\" });\n }\n },\n )\n .on(RoomEvent.TrackUnsubscribed, (track: RemoteTrack) => {\n track.detach();\n })\n .on(\n RoomEvent.TrackUnpublished,\n (publication: RemoteTrackPublication, participant: RemoteParticipant) => {\n if (!isSpeaker(participant) && publication.kind === Track.Kind.Audio) {\n voiceGone(participant);\n }\n },\n )\n .on(RoomEvent.ParticipantDisconnected, (participant: RemoteParticipant) => {\n if (leaving) return;\n if (isSpeaker(participant)) {\n onEvent({ type: \"speaker-left\" });\n } else {\n voiceGone(participant);\n }\n })\n .on(RoomEvent.AudioPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.VideoPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.Disconnected, (reason?: DisconnectReason) => {\n if (leaving) return;\n onEvent({\n type: \"disconnected\",\n byServer: reason !== undefined && CLOSED_BY_SERVER.has(reason),\n });\n });\n\n const detachAll = () => {\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n publication.track?.detach();\n }\n }\n video.srcObject = null;\n audio.srcObject = null;\n };\n\n try {\n await room.connect(url, token, { autoSubscribe: false });\n } catch (error) {\n leaving = true;\n room.removeAllListeners();\n detachAll();\n throw error;\n }\n\n // Whoever was in the room before this viewer joined: their tracks were published before the listener above.\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n consider(publication, participant);\n }\n }\n reportPlayback();\n\n return {\n enablePlayback: async () => {\n // Both started inside the visitor's gesture, before anything is awaited: Safari allows playback only there.\n // A refusal leaves the room as it was, and the report below keeps the button offered.\n await Promise.allSettled([room.startAudio(), room.startVideo()]);\n reportPlayback();\n },\n leave: async () => {\n if (leaving) return;\n leaving = true;\n detachAll();\n try {\n await room.disconnect();\n } finally {\n room.removeAllListeners();\n }\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/avatar-live/receive-only.ts"],"names":[],"mappings":";;;AAmCA,IAAM,gBAAA,uBAAsD,GAAA,CAAI;AAAA,EAC9D,gBAAA,CAAiB,YAAA;AAAA,EACjB,gBAAA,CAAiB,WAAA;AAAA,EACjB,gBAAA,CAAiB;AACnB,CAAC,CAAA;AAED,SAAS,QAAQ,WAAA,EAA8C;AAC7D,EAAA,OACE,WAAA,CAAY,SAAS,KAAA,CAAM,IAAA,CAAK,SAChC,WAAA,CAAY,IAAA,KAAS,MAAM,IAAA,CAAK,KAAA;AAEpC;AAOA,eAAsB,gBACpB,OAAA,EAC6B;AAC7B,EAAA,MAAM,EAAE,GAAA,EAAK,KAAA,EAAO,gBAAgB,KAAA,EAAO,KAAA,EAAO,SAAQ,GAAI,OAAA;AAC9D,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAI,IAAI,GAAG,CAAA;AAAA,EACtB,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,MAAM,4CAA4C,CAAA;AAAA,EAC9D;AACA,EAAA,IAAI,MAAA,CAAO,aAAa,MAAA,EAAQ;AAC9B,IAAA,MAAM,IAAI,MAAM,4CAA4C,CAAA;AAAA,EAC9D;AACA,EAAA,IAAI,CAAC,KAAA,IAAS,CAAC,cAAA,EAAgB;AAC7B,IAAA,MAAM,IAAI,MAAM,mDAAmD,CAAA;AAAA,EACrE;AAEA,EAAA,MAAM,IAAA,GAAO,IAAI,IAAA,CAAK;AAAA;AAAA,IAEpB,cAAA,EAAgB,KAAA;AAAA,IAChB,QAAA,EAAU,KAAA;AAAA;AAAA;AAAA,IAGV,qBAAA,EAAuB;AAAA,GACxB,CAAA;AACD,EAAA,IAAI,OAAA,GAAU,KAAA;AAEd,EAAA,MAAM,SAAA,GAAY,CAAC,WAAA,KACjB,WAAA,CAAY,QAAA,KAAa,cAAA;AAM3B,EAAA,MAAM,MAAA,uBAAa,GAAA,EAAY;AAC/B,EAAA,IAAI,WAAA,GAAc,KAAA;AAGlB,EAAA,MAAM,QAAA,GAAW,CACf,WAAA,EACA,WAAA,KACG;AACH,IAAA,IAAI,OAAA,EAAS;AACb,IAAA,IAAI,CAAC,SAAA,CAAU,WAAW,CAAA,EAAG;AAC3B,MAAA,IAAI,WAAA,CAAY,IAAA,KAAS,KAAA,CAAM,IAAA,CAAK,KAAA,EAAO;AACzC,QAAA,MAAA,CAAO,GAAA,CAAI,YAAY,QAAQ,CAAA;AAAA,MACjC;AACA,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAC,OAAA,CAAQ,WAAW,CAAA,EAAG;AAC3B,IAAA,IAAI,CAAC,WAAA,CAAY,YAAA,EAAc,WAAA,CAAY,cAAc,IAAI,CAAA;AAAA,EAC/D,CAAA;AAGA,EAAA,MAAM,SAAA,GAAY,CAAC,WAAA,KAAmC;AACpD,IAAA,IAAI,WAAW,WAAA,IAAe,CAAC,OAAO,GAAA,CAAI,WAAA,CAAY,QAAQ,CAAA,EAAG;AACjE,IAAA,WAAA,GAAc,IAAA;AACd,IAAA,OAAA,CAAQ,EAAE,IAAA,EAAM,cAAA,EAAgB,CAAA;AAAA,EAClC,CAAA;AAEA,EAAA,MAAM,iBAAiB,MAAM;AAC3B,IAAA,IAAI,OAAA,EAAS;AACb,IAAA,OAAA,CAAQ;AAAA,MACN,IAAA,EAAM,kBAAA;AAAA,MACN,OAAA,EAAS,CAAC,IAAA,CAAK,gBAAA,IAAoB,CAAC,IAAA,CAAK,gBAAA;AAAA,MACzC,KAAA,EAAO,CAAC,IAAA,CAAK;AAAA,KACd,CAAA;AAAA,EACH,CAAA;AAGA,EAAA,IAAI,cAAA,GAAiB,KAAA;AACrB,EAAA,MAAM,cAAA,GAAiB,CAAC,QAAA,KAA4B;AAClD,IAAA,IAAI,OAAA,EAAS;AACb,IAAA,MAAM,WAAW,QAAA,CAAS,IAAA;AAAA,MACxB,CAAC,OAAA,KAAY,OAAA,CAAQ,QAAA,KAAa;AAAA,KACpC;AACA,IAAA,IAAI,aAAa,cAAA,EAAgB;AACjC,IAAA,cAAA,GAAiB,QAAA;AACjB,IAAA,OAAA,CAAQ,EAAE,IAAA,EAAM,UAAA,EAAY,QAAA,EAAU,CAAA;AAAA,EACxC,CAAA;AAEA,EAAA,IAAA,CACG,EAAA,CAAG,SAAA,CAAU,cAAA,EAAgB,QAAQ,CAAA,CACrC,EAAA;AAAA,IACC,SAAA,CAAU,eAAA;AAAA,IACV,CACE,KAAA,EACA,WAAA,EACA,WAAA,KACG;AACH,MAAA,IAAI,OAAA,EAAS;AACb,MAAA,IAAI,CAAC,SAAA,CAAU,WAAW,KAAK,CAAC,OAAA,CAAQ,WAAW,CAAA,EAAG;AAEpD,QAAA,WAAA,CAAY,cAAc,KAAK,CAAA;AAC/B,QAAA;AAAA,MACF;AACA,MAAA,IAAI,KAAA,CAAM,IAAA,KAAS,KAAA,CAAM,IAAA,CAAK,KAAA,EAAO;AACnC,QAAA,KAAA,CAAM,OAAO,KAAK,CAAA;AAClB,QAAA,OAAA,CAAQ,EAAE,IAAA,EAAM,eAAA,EAAiB,IAAA,EAAM,SAAS,CAAA;AAAA,MAClD,CAAA,MAAO;AACL,QAAA,KAAA,CAAM,OAAO,KAAK,CAAA;AAClB,QAAA,OAAA,CAAQ,EAAE,IAAA,EAAM,eAAA,EAAiB,IAAA,EAAM,SAAS,CAAA;AAAA,MAClD;AAAA,IACF;AAAA,GACF,CACC,EAAA,CAAG,SAAA,CAAU,iBAAA,EAAmB,CAAC,KAAA,KAAuB;AACvD,IAAA,KAAA,CAAM,MAAA,EAAO;AAAA,EACf,CAAC,CAAA,CACA,EAAA;AAAA,IACC,SAAA,CAAU,gBAAA;AAAA,IACV,CAAC,aAAqC,WAAA,KAAmC;AACvE,MAAA,IAAI,CAAC,UAAU,WAAW,CAAA,IAAK,YAAY,IAAA,KAAS,KAAA,CAAM,KAAK,KAAA,EAAO;AACpE,QAAA,SAAA,CAAU,WAAW,CAAA;AAAA,MACvB;AAAA,IACF;AAAA,GACF,CACC,EAAA,CAAG,SAAA,CAAU,uBAAA,EAAyB,CAAC,WAAA,KAAmC;AACzE,IAAA,IAAI,OAAA,EAAS;AACb,IAAA,IAAI,SAAA,CAAU,WAAW,CAAA,EAAG;AAC1B,MAAA,OAAA,CAAQ,EAAE,IAAA,EAAM,cAAA,EAAgB,CAAA;AAAA,IAClC,CAAA,MAAO;AACL,MAAA,SAAA,CAAU,WAAW,CAAA;AAAA,IACvB;AAAA,EACF,CAAC,EACA,EAAA,CAAG,SAAA,CAAU,4BAA4B,cAAc,CAAA,CACvD,GAAG,SAAA,CAAU,0BAAA,EAA4B,cAAc,CAAA,CACvD,EAAA,CAAG,UAAU,qBAAA,EAAuB,cAAc,EAClD,EAAA,CAAG,SAAA,CAAU,YAAA,EAAc,CAAC,MAAA,KAA8B;AACzD,IAAA,IAAI,OAAA,EAAS;AACb,IAAA,OAAA,CAAQ;AAAA,MACN,IAAA,EAAM,cAAA;AAAA,MACN,QAAA,EAAU,MAAA,KAAW,MAAA,IAAa,gBAAA,CAAiB,IAAI,MAAM;AAAA,KAC9D,CAAA;AAAA,EACH,CAAC,CAAA;AAEH,EAAA,MAAM,YAAY,MAAM;AACtB,IAAA,KAAA,MAAW,WAAA,IAAe,IAAA,CAAK,kBAAA,CAAmB,MAAA,EAAO,EAAG;AAC1D,MAAA,KAAA,MAAW,WAAA,IAAe,WAAA,CAAY,iBAAA,CAAkB,MAAA,EAAO,EAAG;AAChE,QAAA,WAAA,CAAY,OAAO,MAAA,EAAO;AAAA,MAC5B;AAAA,IACF;AACA,IAAA,KAAA,CAAM,SAAA,GAAY,IAAA;AAClB,IAAA,KAAA,CAAM,SAAA,GAAY,IAAA;AAAA,EACpB,CAAA;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,KAAK,OAAA,CAAQ,GAAA,EAAK,OAAO,EAAE,aAAA,EAAe,OAAO,CAAA;AAAA,EACzD,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,GAAU,IAAA;AACV,IAAA,IAAA,CAAK,kBAAA,EAAmB;AACxB,IAAA,SAAA,EAAU;AACV,IAAA,MAAM,KAAA;AAAA,EACR;AAGA,EAAA,KAAA,MAAW,WAAA,IAAe,IAAA,CAAK,kBAAA,CAAmB,MAAA,EAAO,EAAG;AAC1D,IAAA,KAAA,MAAW,WAAA,IAAe,WAAA,CAAY,iBAAA,CAAkB,MAAA,EAAO,EAAG;AAChE,MAAA,QAAA,CAAS,aAAa,WAAW,CAAA;AAAA,IACnC;AAAA,EACF;AACA,EAAA,cAAA,EAAe;AAEf,EAAA,OAAO;AAAA,IACL,gBAAgB,YAAY;AAG1B,MAAA,MAAM,OAAA,CAAQ,WAAW,CAAC,IAAA,CAAK,YAAW,EAAG,IAAA,CAAK,UAAA,EAAY,CAAC,CAAA;AAC/D,MAAA,cAAA,EAAe;AAAA,IACjB,CAAA;AAAA,IACA,OAAO,YAAY;AACjB,MAAA,IAAI,OAAA,EAAS;AACb,MAAA,OAAA,GAAU,IAAA;AACV,MAAA,SAAA,EAAU;AACV,MAAA,IAAI;AACF,QAAA,MAAM,KAAK,UAAA,EAAW;AAAA,MACxB,CAAA,SAAE;AACA,QAAA,IAAA,CAAK,kBAAA,EAAmB;AAAA,MAC1B;AAAA,IACF;AAAA,GACF;AACF","file":"index.mjs","sourcesContent":["/**\n * BOFF-9797. The live avatar's room, joined to watch and listen only.\n *\n * A visitor's browser on a product website holds a subscribe-only room token: it can receive the avatar worker's\n * video and voice and do nothing else. This adapter keeps to that from its side too:\n *\n * - **Nothing is subscribed automatically** (`autoSubscribe: false`). Only the tracks of `workerIdentity`, the avatar\n * worker the create answer named, are subscribed, and only their video and audio. Any other participant's track\n * (the server's own speaker included) is never asked for, and is dropped again should the server send it anyway.\n * - **Nothing is published.** No microphone, no camera, no data, no RPC method: this file never calls a publishing\n * API, and the token would refuse one.\n * - **Safari and iOS** hold sound back until a gesture. The room reports it (`playback-blocked`, with `sound` when the\n * sound is held back and not only the video) and the panel offers \"Tap to turn on sound\", whose tap calls\n * `enablePlayback`.\n * - **The end of the speech** is told (`speech-ended`) from what any viewer of the room sees: the other voice in it, the\n * server's speaker that the worker follows, leaving the room or unpublishing its voice. Its track is never subscribed.\n * - **Whether the worker's voice is heard** is told (`speaking`) each time it changes, from the room server's active\n * speakers, which it reports to a speaker's subscribers.\n *\n * Written for the website from the media library's public API: nothing here is copied from the product apps.\n */\nimport {\n DisconnectReason,\n Room,\n RoomEvent,\n Track,\n type Participant,\n type RemoteParticipant,\n type RemoteTrack,\n type RemoteTrackPublication,\n} from \"livekit-client\";\n\nimport type { ReceiveOnlyJoinOptions, ReceiveOnlySession } from \"./types\";\n\n/** The reasons a room closes because the server closed it, rather than because the connection was lost. */\nconst CLOSED_BY_SERVER: ReadonlySet<DisconnectReason> = new Set([\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.ROOM_CLOSED,\n DisconnectReason.PARTICIPANT_REMOVED,\n]);\n\nfunction isMedia(publication: RemoteTrackPublication): boolean {\n return (\n publication.kind === Track.Kind.Video ||\n publication.kind === Track.Kind.Audio\n );\n}\n\n/**\n * Join the room in `options.url` with the receive-only `options.token`, and play the avatar worker's video and voice\n * in `options.video` and `options.audio`. Resolves once connected; rejects when the room cannot be joined (the panel\n * then ends the session).\n */\nexport async function joinReceiveOnly(\n options: ReceiveOnlyJoinOptions,\n): Promise<ReceiveOnlySession> {\n const { url, token, workerIdentity, video, audio, onEvent } = options;\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(\"live avatar: the room address is not a URL\");\n }\n if (parsed.protocol !== \"wss:\") {\n throw new Error(\"live avatar: the room address must be wss:\");\n }\n if (!token || !workerIdentity) {\n throw new Error(\"live avatar: the room token or speaker is missing\");\n }\n\n const room = new Room({\n // One small video, always shown while the session lasts: no quality switching, no layer pausing.\n adaptiveStream: false,\n dynacast: false,\n // Closing the tab or leaving the page leaves the room at once. Belt and braces with the panel's own page-hide\n // handler, which also asks the server to end the session: either may run first, and both are safe to repeat.\n disconnectOnPageLeave: true,\n });\n let leaving = false;\n\n const isSpeaker = (participant: RemoteParticipant) =>\n participant.identity === workerIdentity;\n\n /**\n * Everyone else in the room seen publishing a voice: the server's speaker, whose speech the worker voices. Noted\n * only, never subscribed to, so that the end of that speech can be told (`speech-ended`).\n */\n const voiced = new Set<string>();\n let speechEnded = false;\n\n /** Subscribe to `publication` when it is the speaker's video or audio; leave everything else alone. */\n const consider = (\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving) return;\n if (!isSpeaker(participant)) {\n if (publication.kind === Track.Kind.Audio) {\n voiced.add(participant.identity);\n }\n return;\n }\n if (!isMedia(publication)) return;\n if (!publication.isSubscribed) publication.setSubscribed(true);\n };\n\n /** A voice seen published has gone (its track, or its participant): the speech has ended at its source. Once. */\n const voiceGone = (participant: RemoteParticipant) => {\n if (leaving || speechEnded || !voiced.has(participant.identity)) return;\n speechEnded = true;\n onEvent({ type: \"speech-ended\" });\n };\n\n const reportPlayback = () => {\n if (leaving) return;\n onEvent({\n type: \"playback-blocked\",\n blocked: !room.canPlaybackAudio || !room.canPlaybackVideo,\n sound: !room.canPlaybackAudio,\n });\n };\n\n /** Whether the worker's voice is heard, as last told: `speaking` goes out only when it changes. */\n let workerSpeaking = false;\n const reportSpeakers = (speakers: Participant[]) => {\n if (leaving) return;\n const speaking = speakers.some(\n (speaker) => speaker.identity === workerIdentity,\n );\n if (speaking === workerSpeaking) return;\n workerSpeaking = speaking;\n onEvent({ type: \"speaking\", speaking });\n };\n\n room\n .on(RoomEvent.TrackPublished, consider)\n .on(\n RoomEvent.TrackSubscribed,\n (\n track: RemoteTrack,\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (leaving) return;\n if (!isSpeaker(participant) || !isMedia(publication)) {\n // Never asked for: drop it rather than play it.\n publication.setSubscribed(false);\n return;\n }\n if (track.kind === Track.Kind.Video) {\n track.attach(video);\n onEvent({ type: \"speaker-track\", kind: \"video\" });\n } else {\n track.attach(audio);\n onEvent({ type: \"speaker-track\", kind: \"audio\" });\n }\n },\n )\n .on(RoomEvent.TrackUnsubscribed, (track: RemoteTrack) => {\n track.detach();\n })\n .on(\n RoomEvent.TrackUnpublished,\n (publication: RemoteTrackPublication, participant: RemoteParticipant) => {\n if (!isSpeaker(participant) && publication.kind === Track.Kind.Audio) {\n voiceGone(participant);\n }\n },\n )\n .on(RoomEvent.ParticipantDisconnected, (participant: RemoteParticipant) => {\n if (leaving) return;\n if (isSpeaker(participant)) {\n onEvent({ type: \"speaker-left\" });\n } else {\n voiceGone(participant);\n }\n })\n .on(RoomEvent.AudioPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.VideoPlaybackStatusChanged, reportPlayback)\n .on(RoomEvent.ActiveSpeakersChanged, reportSpeakers)\n .on(RoomEvent.Disconnected, (reason?: DisconnectReason) => {\n if (leaving) return;\n onEvent({\n type: \"disconnected\",\n byServer: reason !== undefined && CLOSED_BY_SERVER.has(reason),\n });\n });\n\n const detachAll = () => {\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n publication.track?.detach();\n }\n }\n video.srcObject = null;\n audio.srcObject = null;\n };\n\n try {\n await room.connect(url, token, { autoSubscribe: false });\n } catch (error) {\n leaving = true;\n room.removeAllListeners();\n detachAll();\n throw error;\n }\n\n // Whoever was in the room before this viewer joined: their tracks were published before the listener above.\n for (const participant of room.remoteParticipants.values()) {\n for (const publication of participant.trackPublications.values()) {\n consider(publication, participant);\n }\n }\n reportPlayback();\n\n return {\n enablePlayback: async () => {\n // Both started inside the visitor's gesture, before anything is awaited: Safari allows playback only there.\n // A refusal leaves the room as it was, and the report below keeps the button offered.\n await Promise.allSettled([room.startAudio(), room.startVideo()]);\n reportPlayback();\n },\n leave: async () => {\n if (leaving) return;\n leaving = true;\n detachAll();\n try {\n await room.disconnect();\n } finally {\n room.removeAllListeners();\n }\n },\n };\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { E as ExplorePanelAvatarLive, a as ExploreLocale, A as AvatarConversation } from '../explore-avatar-live-config-
|
|
4
|
-
import '../types-
|
|
3
|
+
import { E as ExplorePanelAvatarLive, a as ExploreLocale, A as AvatarConversation } from '../explore-avatar-live-config-BxiE66Im.mjs';
|
|
4
|
+
import '../types-CTUJNvQu.mjs';
|
|
5
5
|
import '../use-explore-chat-CbfohA9O.mjs';
|
|
6
6
|
import '../explore-types-Ol1Mm3dW.mjs';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { E as ExplorePanelAvatarLive, a as ExploreLocale, A as AvatarConversation } from '../explore-avatar-live-config-
|
|
4
|
-
import '../types-
|
|
3
|
+
import { E as ExplorePanelAvatarLive, a as ExploreLocale, A as AvatarConversation } from '../explore-avatar-live-config-1zeXdMer.js';
|
|
4
|
+
import '../types-CTUJNvQu.js';
|
|
5
5
|
import '../use-explore-chat-B3dOOvJN.js';
|
|
6
6
|
import '../explore-types-Ol1Mm3dW.js';
|
|
7
7
|
|