@skippr/live-agent-sdk 0.97.0 → 0.99.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/lib-exports.js +233 -50
- package/dist/skippr-sdk.js +156 -156
- package/dist/types/capture/a11yTree.d.ts +2 -0
- package/dist/types/capture/snapdom.d.ts +11 -1
- package/dist/types/components/DomCapture.d.ts +3 -0
- package/dist/types/hooks/useSessionHold.d.ts +2 -0
- package/package.json +2 -2
package/dist/esm/lib-exports.js
CHANGED
|
@@ -3146,6 +3146,7 @@ function useSessionHold({
|
|
|
3146
3146
|
const [countdown, setCountdown] = useState9(timeoutSeconds);
|
|
3147
3147
|
const screenWasSharedBeforeHoldRef = useRef11(false);
|
|
3148
3148
|
const holdTransitionInFlightRef = useRef11(false);
|
|
3149
|
+
const clearPausedCaptureRef = useRef11(null);
|
|
3149
3150
|
const publish = useCallback10(async (type) => {
|
|
3150
3151
|
await localParticipant.publishData(textEncoder2.encode(JSON.stringify({ type })), {
|
|
3151
3152
|
reliable: true,
|
|
@@ -3192,6 +3193,7 @@ function useSessionHold({
|
|
|
3192
3193
|
try {
|
|
3193
3194
|
await localParticipant.setMicrophoneEnabled(true);
|
|
3194
3195
|
await resumeScreenShareOnResume();
|
|
3196
|
+
clearPausedCaptureRef.current?.();
|
|
3195
3197
|
await publish("resume");
|
|
3196
3198
|
setIsHeld(false);
|
|
3197
3199
|
} catch (error) {
|
|
@@ -3223,7 +3225,7 @@ function useSessionHold({
|
|
|
3223
3225
|
holdTransitionInFlightRef.current = false;
|
|
3224
3226
|
});
|
|
3225
3227
|
}, [isHeld, hold, release]);
|
|
3226
|
-
return { isHeld, countdown, toggle };
|
|
3228
|
+
return { isHeld, countdown, toggle, clearPausedCaptureRef };
|
|
3227
3229
|
}
|
|
3228
3230
|
|
|
3229
3231
|
// src/context/SessionHoldContext.tsx
|
|
@@ -3231,7 +3233,8 @@ import { jsx as jsx13 } from "react/jsx-runtime";
|
|
|
3231
3233
|
var SessionHoldContext = createContext3({
|
|
3232
3234
|
isHeld: false,
|
|
3233
3235
|
countdown: 0,
|
|
3234
|
-
toggle: () => {}
|
|
3236
|
+
toggle: () => {},
|
|
3237
|
+
clearPausedCaptureRef: { current: null }
|
|
3235
3238
|
});
|
|
3236
3239
|
function SessionHoldProvider({ children }) {
|
|
3237
3240
|
const { pauseSession } = useLiveAgent();
|
|
@@ -4976,6 +4979,7 @@ function accessibleName(element, options = {}) {
|
|
|
4976
4979
|
var REF_PREFIX = "r-";
|
|
4977
4980
|
var VALUE_MAX_CHARS = 80;
|
|
4978
4981
|
var HREF_MAX_CHARS = 60;
|
|
4982
|
+
var TABLE_ROW_MAX_CHARS = 160;
|
|
4979
4983
|
var PAGE_CONTENT_MAX_BYTES = 14000;
|
|
4980
4984
|
var NODE_LIMIT = 1500;
|
|
4981
4985
|
var SENSITIVE_IFRAME_HOSTS = [
|
|
@@ -5321,8 +5325,9 @@ function ensureStableRef(element) {
|
|
|
5321
5325
|
function formatNodeAsLine(entry) {
|
|
5322
5326
|
const indent = " ".repeat(entry.depth);
|
|
5323
5327
|
const namePart = entry.name ? ` "${escapeAttributeValue(entry.name)}"` : "";
|
|
5328
|
+
const refPart = entry.ref ? ` ${entry.ref}` : "";
|
|
5324
5329
|
const attrsPart = entry.attrs.length > 0 ? ` ${entry.attrs.join(" ")}` : "";
|
|
5325
|
-
return `${indent}${entry.role}${namePart}
|
|
5330
|
+
return `${indent}${entry.role}${namePart}${refPart}${attrsPart}`;
|
|
5326
5331
|
}
|
|
5327
5332
|
function shouldSkipSubtree(element) {
|
|
5328
5333
|
if (element.id === WIDGET_ROOT_ID)
|
|
@@ -5350,13 +5355,66 @@ function getSameOriginIframeBody(element) {
|
|
|
5350
5355
|
return null;
|
|
5351
5356
|
}
|
|
5352
5357
|
}
|
|
5353
|
-
function
|
|
5358
|
+
function normalizeText(text) {
|
|
5359
|
+
return (text ?? "").replace(/\s+/g, " ").trim();
|
|
5360
|
+
}
|
|
5361
|
+
function formatTableRowText(row) {
|
|
5362
|
+
const cellValues = Array.from(row.children).filter((cell) => cell.tagName === "TD" || cell.tagName === "TH").map((cell) => normalizeText(cell.textContent).slice(0, VALUE_MAX_CHARS));
|
|
5363
|
+
return cellValues.join(" | ").slice(0, TABLE_ROW_MAX_CHARS).trim();
|
|
5364
|
+
}
|
|
5365
|
+
function isTableHeaderRow(row) {
|
|
5366
|
+
return Array.from(row.children).some((cell) => cell.tagName === "TH");
|
|
5367
|
+
}
|
|
5368
|
+
function directTextOfElement(element) {
|
|
5369
|
+
let combinedText = "";
|
|
5370
|
+
for (const childNode of Array.from(element.childNodes)) {
|
|
5371
|
+
if (childNode.nodeType === Node.TEXT_NODE)
|
|
5372
|
+
combinedText += `${childNode.textContent} `;
|
|
5373
|
+
}
|
|
5374
|
+
return normalizeText(combinedText);
|
|
5375
|
+
}
|
|
5376
|
+
var REFLESS_TEXT_ENTRY_REF = "";
|
|
5377
|
+
function collectRefLessTextLeaf(text, depth, rect, entries) {
|
|
5378
|
+
entries.push({
|
|
5379
|
+
depth,
|
|
5380
|
+
role: "text",
|
|
5381
|
+
name: text.slice(0, VALUE_MAX_CHARS),
|
|
5382
|
+
ref: REFLESS_TEXT_ENTRY_REF,
|
|
5383
|
+
attrs: [],
|
|
5384
|
+
area: rect.width * rect.height
|
|
5385
|
+
});
|
|
5386
|
+
}
|
|
5387
|
+
function collectTableRowAsSingleEntry(row, depth, rect, entries) {
|
|
5388
|
+
const rowText = formatTableRowText(row);
|
|
5389
|
+
if (!rowText)
|
|
5390
|
+
return false;
|
|
5391
|
+
entries.push({
|
|
5392
|
+
depth,
|
|
5393
|
+
role: "row",
|
|
5394
|
+
name: rowText,
|
|
5395
|
+
ref: ensureStableRef(row),
|
|
5396
|
+
attrs: isTableHeaderRow(row) ? ["header"] : [],
|
|
5397
|
+
area: rect.width * rect.height
|
|
5398
|
+
});
|
|
5399
|
+
for (const child of Array.from(row.children)) {
|
|
5400
|
+
walkAndCollect(child, depth + 1, entries, true);
|
|
5401
|
+
}
|
|
5402
|
+
return true;
|
|
5403
|
+
}
|
|
5404
|
+
function walkAndCollect(element, depth, entries, suppressTextLeaves = false) {
|
|
5354
5405
|
if (shouldSkipSubtree(element))
|
|
5355
5406
|
return;
|
|
5356
5407
|
const rect = element.getBoundingClientRect();
|
|
5408
|
+
if (element.tagName === "TR") {
|
|
5409
|
+
if (!isVisible(element, rect))
|
|
5410
|
+
return;
|
|
5411
|
+
if (collectTableRowAsSingleEntry(element, depth, rect, entries))
|
|
5412
|
+
return;
|
|
5413
|
+
}
|
|
5357
5414
|
const role = inferRole(element) ?? (isCursorClickable(element, rect) ? "button" : null);
|
|
5358
5415
|
const isVisibleAndEmittable = role !== null && isVisible(element, rect) && shouldEmitElement(element, role);
|
|
5359
5416
|
let childDepth = depth;
|
|
5417
|
+
let childSuppressTextLeaves = suppressTextLeaves;
|
|
5360
5418
|
if (isVisibleAndEmittable && role) {
|
|
5361
5419
|
const ref = ensureStableRef(element);
|
|
5362
5420
|
const name = accessibleName(element);
|
|
@@ -5369,12 +5427,20 @@ function walkAndCollect(element, depth, entries) {
|
|
|
5369
5427
|
area: rect.width * rect.height
|
|
5370
5428
|
});
|
|
5371
5429
|
childDepth = depth + 1;
|
|
5430
|
+
const nameAlreadyCoversSubtreeText = name !== "";
|
|
5431
|
+
if (nameAlreadyCoversSubtreeText)
|
|
5432
|
+
childSuppressTextLeaves = true;
|
|
5433
|
+
} else if (!suppressTextLeaves) {
|
|
5434
|
+
const ownText = directTextOfElement(element);
|
|
5435
|
+
if (ownText && isVisible(element, rect)) {
|
|
5436
|
+
collectRefLessTextLeaf(ownText, depth, rect, entries);
|
|
5437
|
+
}
|
|
5372
5438
|
}
|
|
5373
5439
|
if (role === "iframe") {
|
|
5374
5440
|
const iframeBody = getSameOriginIframeBody(element);
|
|
5375
5441
|
if (iframeBody) {
|
|
5376
5442
|
for (const child of Array.from(iframeBody.children)) {
|
|
5377
|
-
walkAndCollect(child, childDepth, entries);
|
|
5443
|
+
walkAndCollect(child, childDepth, entries, childSuppressTextLeaves);
|
|
5378
5444
|
}
|
|
5379
5445
|
}
|
|
5380
5446
|
return;
|
|
@@ -5382,15 +5448,19 @@ function walkAndCollect(element, depth, entries) {
|
|
|
5382
5448
|
if (role && isBlindRegion(role))
|
|
5383
5449
|
return;
|
|
5384
5450
|
for (const child of Array.from(element.children)) {
|
|
5385
|
-
walkAndCollect(child, childDepth, entries);
|
|
5451
|
+
walkAndCollect(child, childDepth, entries, childSuppressTextLeaves);
|
|
5386
5452
|
}
|
|
5387
5453
|
}
|
|
5388
|
-
|
|
5454
|
+
var TRUNCATION_DROP_ORDER_BY_ROLE = { text: 0, row: 1 };
|
|
5455
|
+
function truncationDropOrder(entry) {
|
|
5456
|
+
return TRUNCATION_DROP_ORDER_BY_ROLE[entry.role] ?? 2;
|
|
5457
|
+
}
|
|
5458
|
+
function dropInformationalEntriesFirstUntilUnderLimit(entries) {
|
|
5389
5459
|
if (entries.length <= NODE_LIMIT)
|
|
5390
5460
|
return entries;
|
|
5391
5461
|
const indexedEntries = entries.map((entry, index2) => ({ entry, index: index2 }));
|
|
5392
|
-
const
|
|
5393
|
-
const indicesToDrop = new Set(
|
|
5462
|
+
const entriesMostDroppableFirst = indexedEntries.slice(1).sort((a, b) => truncationDropOrder(a.entry) - truncationDropOrder(b.entry) || a.entry.area - b.entry.area);
|
|
5463
|
+
const indicesToDrop = new Set(entriesMostDroppableFirst.slice(0, entries.length - NODE_LIMIT).map((indexed) => indexed.index));
|
|
5394
5464
|
const keptEntries = [];
|
|
5395
5465
|
for (let i = 0;i < entries.length; i++) {
|
|
5396
5466
|
if (!indicesToDrop.has(i))
|
|
@@ -5415,7 +5485,7 @@ function buildAccessibilityTree() {
|
|
|
5415
5485
|
}
|
|
5416
5486
|
}
|
|
5417
5487
|
const exceededNodeLimit = collectedEntries.length > NODE_LIMIT;
|
|
5418
|
-
const finalEntries = exceededNodeLimit ?
|
|
5488
|
+
const finalEntries = exceededNodeLimit ? dropInformationalEntriesFirstUntilUnderLimit(collectedEntries) : collectedEntries;
|
|
5419
5489
|
const treeLines = finalEntries.map(formatNodeAsLine);
|
|
5420
5490
|
let pageContent = treeLines.join(`
|
|
5421
5491
|
`);
|
|
@@ -5790,21 +5860,46 @@ function paintCursorOverlay(ctx, transform, brand = DEFAULT_BRAND_PALETTE) {
|
|
|
5790
5860
|
}
|
|
5791
5861
|
|
|
5792
5862
|
// src/capture/snapdom.ts
|
|
5863
|
+
var UNLOADABLE_IMAGE_PLACEHOLDER_FILL = "#e2e8f0";
|
|
5864
|
+
function placeholderForUnloadableImage({
|
|
5865
|
+
width = 100,
|
|
5866
|
+
height = 100
|
|
5867
|
+
}) {
|
|
5868
|
+
const placeholderSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"><rect width="100%" height="100%" fill="${UNLOADABLE_IMAGE_PLACEHOLDER_FILL}"/></svg>`;
|
|
5869
|
+
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(placeholderSvg)}`;
|
|
5870
|
+
}
|
|
5793
5871
|
var cachedSnapdomModule = null;
|
|
5794
5872
|
function loadSnapdom() {
|
|
5795
|
-
if (!cachedSnapdomModule)
|
|
5873
|
+
if (!cachedSnapdomModule) {
|
|
5796
5874
|
cachedSnapdomModule = import("@zumer/snapdom");
|
|
5875
|
+
}
|
|
5797
5876
|
return cachedSnapdomModule;
|
|
5798
5877
|
}
|
|
5878
|
+
function preCacheSnapshotResources() {
|
|
5879
|
+
loadSnapdom().then(({ preCache }) => preCache(document, { embedFonts: true })).catch(() => {
|
|
5880
|
+
return;
|
|
5881
|
+
});
|
|
5882
|
+
}
|
|
5799
5883
|
async function snapToCanvas(element, options = {}) {
|
|
5800
5884
|
const snapdomModule = await loadSnapdom();
|
|
5801
|
-
|
|
5885
|
+
const optionsWithEngineDefaults = {
|
|
5886
|
+
embedFonts: true,
|
|
5887
|
+
fallbackURL: placeholderForUnloadableImage,
|
|
5888
|
+
...options,
|
|
5889
|
+
snap: snapdomModule.snapdom
|
|
5890
|
+
};
|
|
5891
|
+
return snapdomModule.snapdom.toCanvas(element, optionsWithEngineDefaults);
|
|
5802
5892
|
}
|
|
5803
5893
|
|
|
5804
5894
|
// src/components/DomCapture.tsx
|
|
5805
5895
|
var SNAPSHOT_INTERVAL_MS = 3000;
|
|
5806
5896
|
var A11Y_PUBLISH_INTERVAL_MS = 2000;
|
|
5807
5897
|
var FIRST_SNAPSHOT_DELAY_MS = 400;
|
|
5898
|
+
var RASTERIZE_TIMEOUT_MS = 1e4;
|
|
5899
|
+
var FULL_QUALITY_TIMEOUTS_BEFORE_COOLDOWN = 1;
|
|
5900
|
+
var FAST_CAPTURES_BEFORE_FULL_QUALITY_RETRY = 10;
|
|
5901
|
+
var RASTERIZE_TIMED_OUT = Symbol("rasterize-timed-out");
|
|
5902
|
+
var SCROLL_SETTLE_MS = 300;
|
|
5808
5903
|
var CAPTURE_PRESET = ScreenSharePresets2.h1080fps30;
|
|
5809
5904
|
var CAPTURE_FPS = CAPTURE_PRESET.encoding.maxFramerate ?? 30;
|
|
5810
5905
|
var CAPTURE_BITRATE = 4000000;
|
|
@@ -5812,15 +5907,20 @@ var DOM_SNAPSHOT_GZIP_THRESHOLD_BYTES = 14000;
|
|
|
5812
5907
|
var MAX_CANVAS_DPR = 1.5;
|
|
5813
5908
|
var CONSECUTIVE_FAILURES_BEFORE_REPORT = 3;
|
|
5814
5909
|
var textEncoder4 = new TextEncoder;
|
|
5910
|
+
var SNAPSHOT_EXCLUDE_SELECTORS = [`#${WIDGET_ROOT_ID}`, `[${PRIVATE_ATTR}]`];
|
|
5911
|
+
function isCapturableIframe(element) {
|
|
5912
|
+
return isSameOriginIframe(element) && !isSensitiveIframe(element);
|
|
5913
|
+
}
|
|
5815
5914
|
function shouldIncludeInSnapshot(element) {
|
|
5816
|
-
if (
|
|
5817
|
-
return true;
|
|
5818
|
-
if (element.id === WIDGET_ROOT_ID)
|
|
5819
|
-
return false;
|
|
5820
|
-
if (element.hasAttribute?.(PRIVATE_ATTR))
|
|
5915
|
+
if (element instanceof HTMLIFrameElement && !isCapturableIframe(element))
|
|
5821
5916
|
return false;
|
|
5822
5917
|
return true;
|
|
5823
5918
|
}
|
|
5919
|
+
function shouldIncludeInFirstFrame(element) {
|
|
5920
|
+
if (element.tagName === "IMG")
|
|
5921
|
+
return false;
|
|
5922
|
+
return shouldIncludeInSnapshot(element);
|
|
5923
|
+
}
|
|
5824
5924
|
function createPublishingCanvas() {
|
|
5825
5925
|
const dprBoost = Math.min(window.devicePixelRatio || 1, MAX_CANVAS_DPR);
|
|
5826
5926
|
const canvas = document.createElement("canvas");
|
|
@@ -5841,22 +5941,23 @@ function getCanvasCaptureStream(canvas, fps) {
|
|
|
5841
5941
|
}
|
|
5842
5942
|
return captureStreamFn.call(canvas, fps);
|
|
5843
5943
|
}
|
|
5844
|
-
async function rasterizeViewportToPageLayer(width, height) {
|
|
5845
|
-
const
|
|
5944
|
+
async function rasterizeViewportToPageLayer(width, height, { skipCrossOriginImageSettling, includeImages }) {
|
|
5945
|
+
const requestedDpr = Math.min(window.devicePixelRatio || 1, MAX_CANVAS_DPR);
|
|
5946
|
+
const viewportWidthAtCaptureStart = window.innerWidth;
|
|
5846
5947
|
const snapshotCanvas = await snapToCanvas(document.documentElement, {
|
|
5847
|
-
|
|
5848
|
-
|
|
5948
|
+
exclude: SNAPSHOT_EXCLUDE_SELECTORS,
|
|
5949
|
+
excludeMode: "remove",
|
|
5950
|
+
filter: includeImages ? shouldIncludeInSnapshot : shouldIncludeInFirstFrame,
|
|
5951
|
+
filterMode: "hide",
|
|
5849
5952
|
backgroundColor: "#ffffff",
|
|
5850
|
-
fast:
|
|
5851
|
-
dpr
|
|
5953
|
+
fast: skipCrossOriginImageSettling,
|
|
5954
|
+
dpr: requestedDpr,
|
|
5955
|
+
clip: "viewport"
|
|
5852
5956
|
});
|
|
5853
|
-
const
|
|
5854
|
-
const
|
|
5855
|
-
const
|
|
5856
|
-
const
|
|
5857
|
-
const fitScale = Math.min(width / sourceWidth, height / sourceHeight);
|
|
5858
|
-
const destWidth = sourceWidth * fitScale;
|
|
5859
|
-
const destHeight = sourceHeight * fitScale;
|
|
5957
|
+
const canvasPixelsPerCssPixel = viewportWidthAtCaptureStart > 0 ? snapshotCanvas.width / viewportWidthAtCaptureStart : requestedDpr;
|
|
5958
|
+
const fitScale = Math.min(width / snapshotCanvas.width, height / snapshotCanvas.height);
|
|
5959
|
+
const destWidth = snapshotCanvas.width * fitScale;
|
|
5960
|
+
const destHeight = snapshotCanvas.height * fitScale;
|
|
5860
5961
|
const destX = (width - destWidth) / 2;
|
|
5861
5962
|
const destY = (height - destHeight) / 2;
|
|
5862
5963
|
const layer = document.createElement("canvas");
|
|
@@ -5867,10 +5968,14 @@ async function rasterizeViewportToPageLayer(width, height) {
|
|
|
5867
5968
|
throw new Error("Failed to get 2D context for page layer");
|
|
5868
5969
|
layerCtx.fillStyle = "#ffffff";
|
|
5869
5970
|
layerCtx.fillRect(0, 0, width, height);
|
|
5870
|
-
layerCtx.drawImage(snapshotCanvas,
|
|
5971
|
+
layerCtx.drawImage(snapshotCanvas, destX, destY, destWidth, destHeight);
|
|
5871
5972
|
return {
|
|
5872
5973
|
canvas: layer,
|
|
5873
|
-
viewportToCanvas: {
|
|
5974
|
+
viewportToCanvas: {
|
|
5975
|
+
scale: canvasPixelsPerCssPixel * fitScale,
|
|
5976
|
+
offsetX: destX,
|
|
5977
|
+
offsetY: destY
|
|
5978
|
+
}
|
|
5874
5979
|
};
|
|
5875
5980
|
}
|
|
5876
5981
|
function compositePageAndCursorOverlay(ctx, canvas, cachedPage, brand) {
|
|
@@ -5894,6 +5999,12 @@ async function gzipIfBeneficial(snapshotBytes) {
|
|
|
5894
5999
|
return { bytes: snapshotBytes, compressionFlag: 0 };
|
|
5895
6000
|
}
|
|
5896
6001
|
}
|
|
6002
|
+
function frameWithCompressionFlag(compressionFlag, payload) {
|
|
6003
|
+
const frame = new Uint8Array(payload.byteLength + 1);
|
|
6004
|
+
frame[0] = compressionFlag;
|
|
6005
|
+
frame.set(payload, 1);
|
|
6006
|
+
return frame;
|
|
6007
|
+
}
|
|
5897
6008
|
async function buildDomSnapshotFrame() {
|
|
5898
6009
|
const a11yTree = buildAccessibilityTree();
|
|
5899
6010
|
const snapshotJson = JSON.stringify({
|
|
@@ -5906,14 +6017,12 @@ async function buildDomSnapshotFrame() {
|
|
|
5906
6017
|
});
|
|
5907
6018
|
const snapshotBytes = textEncoder4.encode(snapshotJson);
|
|
5908
6019
|
const { bytes, compressionFlag } = await gzipIfBeneficial(snapshotBytes);
|
|
5909
|
-
|
|
5910
|
-
frame[0] = compressionFlag;
|
|
5911
|
-
frame.set(bytes, 1);
|
|
5912
|
-
return frame;
|
|
6020
|
+
return frameWithCompressionFlag(compressionFlag, bytes);
|
|
5913
6021
|
}
|
|
5914
6022
|
function reportCaptureError(localParticipant) {
|
|
5915
6023
|
try {
|
|
5916
|
-
|
|
6024
|
+
const payload = textEncoder4.encode(JSON.stringify({ type: "capture_error" }));
|
|
6025
|
+
localParticipant.publishData(frameWithCompressionFlag(0, payload), {
|
|
5917
6026
|
reliable: true,
|
|
5918
6027
|
topic: DOM_SNAPSHOT_TOPIC
|
|
5919
6028
|
}).catch(() => {
|
|
@@ -5930,7 +6039,7 @@ async function unpublishAndStopTrack(localParticipant, videoTrack) {
|
|
|
5930
6039
|
function DomCapture({ pushOrTapMicMode = false }) {
|
|
5931
6040
|
const { localParticipant } = useLocalParticipant5();
|
|
5932
6041
|
const connectionState = useConnectionState2();
|
|
5933
|
-
const { isHeld } = useSessionHoldContext();
|
|
6042
|
+
const { isHeld, clearPausedCaptureRef } = useSessionHoldContext();
|
|
5934
6043
|
const accentColor = useContext9(LiveAgentContext)?.appearance?.accentColor ?? null;
|
|
5935
6044
|
const brand = useMemo4(() => resolveBrandPalette(accentColor), [accentColor]);
|
|
5936
6045
|
const brandRef = useRef17(brand);
|
|
@@ -5956,12 +6065,18 @@ function DomCapture({ pushOrTapMicMode = false }) {
|
|
|
5956
6065
|
return;
|
|
5957
6066
|
videoTrack.contentHint = "text";
|
|
5958
6067
|
didStartRef.current = true;
|
|
6068
|
+
preCacheSnapshotResources();
|
|
5959
6069
|
let cancelled = false;
|
|
5960
6070
|
let snapshotInFlight = false;
|
|
5961
6071
|
let a11yPublishInFlight = false;
|
|
5962
6072
|
let a11yPublishPending = false;
|
|
5963
6073
|
let consecutiveCaptureFailures = 0;
|
|
5964
6074
|
let cachedPage = null;
|
|
6075
|
+
let rasterizeSequence = 0;
|
|
6076
|
+
let lastCommittedSequence = 0;
|
|
6077
|
+
let firstFrameCommitted = false;
|
|
6078
|
+
let capturesUntilFullQualityRetry = 0;
|
|
6079
|
+
let consecutiveFullQualityTimeouts = 0;
|
|
5965
6080
|
if (!pushOrTapMicMode && !localParticipant.isMicrophoneEnabled) {
|
|
5966
6081
|
localParticipant.setMicrophoneEnabled(true).catch((error) => console.error("Failed to enable microphone:", error));
|
|
5967
6082
|
}
|
|
@@ -5977,16 +6092,61 @@ function DomCapture({ pushOrTapMicMode = false }) {
|
|
|
5977
6092
|
for (const track of canvasStream.getTracks())
|
|
5978
6093
|
track.stop();
|
|
5979
6094
|
});
|
|
5980
|
-
const
|
|
5981
|
-
if (cancelled ||
|
|
6095
|
+
const commitPageLayerIfNewest = (sequence, page) => {
|
|
6096
|
+
if (cancelled || pausedRef.current || sequence <= lastCommittedSequence)
|
|
6097
|
+
return false;
|
|
6098
|
+
lastCommittedSequence = sequence;
|
|
6099
|
+
cachedPage = page;
|
|
6100
|
+
compositePageAndCursorOverlay(ctx, canvas, page, brandRef.current);
|
|
6101
|
+
firstFrameCommitted = true;
|
|
6102
|
+
return true;
|
|
6103
|
+
};
|
|
6104
|
+
const rasterizeAndCommitWaitingOutTimeout = async (options) => {
|
|
6105
|
+
const sequence = ++rasterizeSequence;
|
|
6106
|
+
const captureInProgress = rasterizeViewportToPageLayer(canvas.width, canvas.height, options);
|
|
6107
|
+
const captureOrTimeout = await Promise.race([
|
|
6108
|
+
captureInProgress,
|
|
6109
|
+
new Promise((resolve) => setTimeout(() => resolve(RASTERIZE_TIMED_OUT), RASTERIZE_TIMEOUT_MS))
|
|
6110
|
+
]);
|
|
6111
|
+
if (captureOrTimeout === RASTERIZE_TIMED_OUT) {
|
|
6112
|
+
captureInProgress.then((lateCapture) => commitPageLayerIfNewest(sequence, lateCapture)).catch(() => {
|
|
6113
|
+
return;
|
|
6114
|
+
});
|
|
6115
|
+
return { timedOut: true };
|
|
6116
|
+
}
|
|
6117
|
+
commitPageLayerIfNewest(sequence, captureOrTimeout);
|
|
6118
|
+
return { timedOut: false };
|
|
6119
|
+
};
|
|
6120
|
+
let followUpRefreshQueued = false;
|
|
6121
|
+
const refreshPageAndComposite = async ({ queueWhenBusy = false } = {}) => {
|
|
6122
|
+
if (cancelled || snapshotInFlight || pausedRef.current) {
|
|
6123
|
+
if (snapshotInFlight && !cancelled && queueWhenBusy)
|
|
6124
|
+
followUpRefreshQueued = true;
|
|
5982
6125
|
return;
|
|
6126
|
+
}
|
|
5983
6127
|
snapshotInFlight = true;
|
|
5984
6128
|
try {
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
6129
|
+
const isImagelessFirstFrame = !firstFrameCommitted;
|
|
6130
|
+
const useFullQuality = firstFrameCommitted && capturesUntilFullQualityRetry === 0;
|
|
6131
|
+
const { timedOut } = await rasterizeAndCommitWaitingOutTimeout({
|
|
6132
|
+
skipCrossOriginImageSettling: !useFullQuality,
|
|
6133
|
+
includeImages: !isImagelessFirstFrame
|
|
6134
|
+
});
|
|
6135
|
+
if (isImagelessFirstFrame && firstFrameCommitted)
|
|
6136
|
+
followUpRefreshQueued = true;
|
|
5989
6137
|
consecutiveCaptureFailures = 0;
|
|
6138
|
+
if (useFullQuality) {
|
|
6139
|
+
if (timedOut) {
|
|
6140
|
+
consecutiveFullQualityTimeouts += 1;
|
|
6141
|
+
if (consecutiveFullQualityTimeouts >= FULL_QUALITY_TIMEOUTS_BEFORE_COOLDOWN) {
|
|
6142
|
+
capturesUntilFullQualityRetry = FAST_CAPTURES_BEFORE_FULL_QUALITY_RETRY;
|
|
6143
|
+
}
|
|
6144
|
+
} else {
|
|
6145
|
+
consecutiveFullQualityTimeouts = 0;
|
|
6146
|
+
}
|
|
6147
|
+
} else if (capturesUntilFullQualityRetry > 0) {
|
|
6148
|
+
capturesUntilFullQualityRetry -= 1;
|
|
6149
|
+
}
|
|
5990
6150
|
} catch {
|
|
5991
6151
|
if (cancelled)
|
|
5992
6152
|
return;
|
|
@@ -5996,6 +6156,10 @@ function DomCapture({ pushOrTapMicMode = false }) {
|
|
|
5996
6156
|
}
|
|
5997
6157
|
} finally {
|
|
5998
6158
|
snapshotInFlight = false;
|
|
6159
|
+
if (followUpRefreshQueued && !cancelled) {
|
|
6160
|
+
followUpRefreshQueued = false;
|
|
6161
|
+
refreshPageAndComposite();
|
|
6162
|
+
}
|
|
5999
6163
|
}
|
|
6000
6164
|
};
|
|
6001
6165
|
let overlayAnimationFrame = null;
|
|
@@ -6061,36 +6225,55 @@ function DomCapture({ pushOrTapMicMode = false }) {
|
|
|
6061
6225
|
});
|
|
6062
6226
|
const cleanupSnapshotRequest = onSnapshotRequest(() => {
|
|
6063
6227
|
tickA11yPublish();
|
|
6064
|
-
refreshPageAndComposite();
|
|
6228
|
+
refreshPageAndComposite({ queueWhenBusy: true });
|
|
6065
6229
|
});
|
|
6230
|
+
let scrollSettleTimer = null;
|
|
6231
|
+
const refreshOnceScrollSettles = () => {
|
|
6232
|
+
if (scrollSettleTimer)
|
|
6233
|
+
clearTimeout(scrollSettleTimer);
|
|
6234
|
+
scrollSettleTimer = setTimeout(() => {
|
|
6235
|
+
tickA11yPublish();
|
|
6236
|
+
refreshPageAndComposite({ queueWhenBusy: true });
|
|
6237
|
+
}, SCROLL_SETTLE_MS);
|
|
6238
|
+
};
|
|
6239
|
+
window.addEventListener("scroll", refreshOnceScrollSettles, { passive: true, capture: true });
|
|
6066
6240
|
let snapshotTimer = null;
|
|
6067
6241
|
const scheduleNextSnapshot = (delay) => {
|
|
6068
6242
|
if (cancelled)
|
|
6069
6243
|
return;
|
|
6070
|
-
snapshotTimer = setTimeout(
|
|
6071
|
-
|
|
6244
|
+
snapshotTimer = setTimeout(() => {
|
|
6245
|
+
refreshPageAndComposite();
|
|
6072
6246
|
scheduleNextSnapshot(SNAPSHOT_INTERVAL_MS);
|
|
6073
6247
|
}, delay);
|
|
6074
6248
|
};
|
|
6075
6249
|
scheduleNextSnapshot(FIRST_SNAPSHOT_DELAY_MS);
|
|
6076
6250
|
tickA11yPublish();
|
|
6077
|
-
const a11yPublishTimer = setInterval(tickA11yPublish, A11Y_PUBLISH_INTERVAL_MS);
|
|
6251
|
+
const a11yPublishTimer = setInterval(() => void tickA11yPublish(), A11Y_PUBLISH_INTERVAL_MS);
|
|
6078
6252
|
freezeCaptureRef.current = () => {
|
|
6079
6253
|
pausedRef.current = true;
|
|
6080
6254
|
drawPausedOverlay(canvas, ctx);
|
|
6081
6255
|
};
|
|
6082
|
-
|
|
6256
|
+
const repaintCleanFrameFromCachedPage = () => {
|
|
6083
6257
|
pausedRef.current = false;
|
|
6258
|
+
compositePageAndCursorOverlay(ctx, canvas, cachedPage, brandRef.current);
|
|
6259
|
+
};
|
|
6260
|
+
resumeCaptureRef.current = () => {
|
|
6261
|
+
repaintCleanFrameFromCachedPage();
|
|
6084
6262
|
tickA11yPublish();
|
|
6085
6263
|
refreshPageAndComposite();
|
|
6086
6264
|
};
|
|
6265
|
+
clearPausedCaptureRef.current = repaintCleanFrameFromCachedPage;
|
|
6087
6266
|
return () => {
|
|
6088
6267
|
cancelled = true;
|
|
6089
6268
|
didStartRef.current = false;
|
|
6090
6269
|
freezeCaptureRef.current = null;
|
|
6091
6270
|
resumeCaptureRef.current = null;
|
|
6271
|
+
clearPausedCaptureRef.current = null;
|
|
6092
6272
|
if (snapshotTimer)
|
|
6093
6273
|
clearTimeout(snapshotTimer);
|
|
6274
|
+
if (scrollSettleTimer)
|
|
6275
|
+
clearTimeout(scrollSettleTimer);
|
|
6276
|
+
window.removeEventListener("scroll", refreshOnceScrollSettles, { capture: true });
|
|
6094
6277
|
clearInterval(a11yPublishTimer);
|
|
6095
6278
|
if (overlayAnimationFrame !== null)
|
|
6096
6279
|
cancelAnimationFrame(overlayAnimationFrame);
|
|
@@ -6101,7 +6284,7 @@ function DomCapture({ pushOrTapMicMode = false }) {
|
|
|
6101
6284
|
for (const track of canvasStream.getTracks())
|
|
6102
6285
|
track.stop();
|
|
6103
6286
|
};
|
|
6104
|
-
}, [connectionState, localParticipant, pushOrTapMicMode]);
|
|
6287
|
+
}, [connectionState, localParticipant, pushOrTapMicMode, clearPausedCaptureRef]);
|
|
6105
6288
|
useEffect22(() => {
|
|
6106
6289
|
if (isHeld) {
|
|
6107
6290
|
freezeCaptureRef.current?.();
|