@dialtribe/react-sdk 0.1.0-alpha.21 → 0.1.0-alpha.23
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/{dialtribe-streamer-D9ulVBVb.d.ts → dialtribe-streamer-DOhD-r_F.d.ts} +8 -3
- package/dist/{dialtribe-streamer-DH23BseY.d.mts → dialtribe-streamer-Do-8Oavc.d.mts} +8 -3
- package/dist/dialtribe-streamer.d.mts +1 -1
- package/dist/dialtribe-streamer.d.ts +1 -1
- package/dist/dialtribe-streamer.js +101 -13
- package/dist/dialtribe-streamer.js.map +1 -1
- package/dist/dialtribe-streamer.mjs +101 -13
- package/dist/dialtribe-streamer.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +101 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +101 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2337,8 +2337,11 @@ var WebSocketStreamer = class {
|
|
|
2337
2337
|
* Set up canvas-based rendering pipeline for video streams.
|
|
2338
2338
|
* This allows seamless camera flips by changing the video source
|
|
2339
2339
|
* without affecting MediaRecorder (which records from the canvas).
|
|
2340
|
+
*
|
|
2341
|
+
* This is async to ensure the video is producing frames before returning,
|
|
2342
|
+
* which prevents black initial thumbnails.
|
|
2340
2343
|
*/
|
|
2341
|
-
setupCanvasRendering() {
|
|
2344
|
+
async setupCanvasRendering() {
|
|
2342
2345
|
console.log("\u{1F3A8} Setting up canvas-based rendering for seamless camera flips");
|
|
2343
2346
|
const videoTrack = this.mediaStream.getVideoTracks()[0];
|
|
2344
2347
|
const settings = videoTrack?.getSettings() || {};
|
|
@@ -2356,7 +2359,27 @@ var WebSocketStreamer = class {
|
|
|
2356
2359
|
videoElement.srcObject = this.mediaStream;
|
|
2357
2360
|
videoElement.muted = true;
|
|
2358
2361
|
videoElement.playsInline = true;
|
|
2359
|
-
|
|
2362
|
+
await new Promise((resolve) => {
|
|
2363
|
+
const checkReady = () => {
|
|
2364
|
+
if (videoElement.videoWidth > 0 && videoElement.videoHeight > 0) {
|
|
2365
|
+
console.log(`\u{1F4F9} Video ready: ${videoElement.videoWidth}x${videoElement.videoHeight}`);
|
|
2366
|
+
resolve();
|
|
2367
|
+
} else {
|
|
2368
|
+
requestAnimationFrame(checkReady);
|
|
2369
|
+
}
|
|
2370
|
+
};
|
|
2371
|
+
videoElement.addEventListener("loadeddata", () => {
|
|
2372
|
+
checkReady();
|
|
2373
|
+
}, { once: true });
|
|
2374
|
+
videoElement.play().catch((e) => {
|
|
2375
|
+
console.warn("Video autoplay warning:", e);
|
|
2376
|
+
resolve();
|
|
2377
|
+
});
|
|
2378
|
+
setTimeout(() => {
|
|
2379
|
+
console.warn("\u26A0\uFE0F Video ready timeout - continuing anyway");
|
|
2380
|
+
resolve();
|
|
2381
|
+
}, 2e3);
|
|
2382
|
+
});
|
|
2360
2383
|
const frameRate = settings.frameRate || 30;
|
|
2361
2384
|
const stream = canvas.captureStream(frameRate);
|
|
2362
2385
|
const audioTracks = this.mediaStream.getAudioTracks();
|
|
@@ -2379,13 +2402,28 @@ var WebSocketStreamer = class {
|
|
|
2379
2402
|
lastVideoWidth: 0,
|
|
2380
2403
|
lastVideoHeight: 0
|
|
2381
2404
|
};
|
|
2405
|
+
if (videoElement.videoWidth > 0 && videoElement.videoHeight > 0) {
|
|
2406
|
+
const vw = videoElement.videoWidth;
|
|
2407
|
+
const vh = videoElement.videoHeight;
|
|
2408
|
+
const cw = canvas.width;
|
|
2409
|
+
const ch = canvas.height;
|
|
2410
|
+
const scale = Math.min(cw / vw, ch / vh);
|
|
2411
|
+
const sw = vw * scale;
|
|
2412
|
+
const sh = vh * scale;
|
|
2413
|
+
const sx = (cw - sw) / 2;
|
|
2414
|
+
const sy = (ch - sh) / 2;
|
|
2415
|
+
ctx.fillStyle = "#000";
|
|
2416
|
+
ctx.fillRect(0, 0, cw, ch);
|
|
2417
|
+
ctx.drawImage(videoElement, sx, sy, sw, sh);
|
|
2418
|
+
console.log("\u{1F5BC}\uFE0F Drew first frame synchronously to prevent black thumbnail");
|
|
2419
|
+
}
|
|
2382
2420
|
const state = this.canvasState;
|
|
2383
2421
|
const renderFrame = () => {
|
|
2384
2422
|
if (!this.canvasState || state !== this.canvasState) return;
|
|
2385
2423
|
const { ctx: ctx2, canvas: canvas2, videoElement: videoElement2 } = state;
|
|
2386
2424
|
if (videoElement2.paused) {
|
|
2387
|
-
|
|
2388
|
-
|
|
2425
|
+
videoElement2.play().catch(() => {
|
|
2426
|
+
});
|
|
2389
2427
|
}
|
|
2390
2428
|
const canvasWidth = canvas2.width;
|
|
2391
2429
|
const canvasHeight = canvas2.height;
|
|
@@ -2520,7 +2558,7 @@ Please check encoder server logs and DATABASE_URL configuration.`
|
|
|
2520
2558
|
});
|
|
2521
2559
|
console.log("\u2705 WebSocket connected");
|
|
2522
2560
|
this.setupWebSocketHandlers();
|
|
2523
|
-
const streamToRecord = this.isVideo ? this.setupCanvasRendering() : this.mediaStream;
|
|
2561
|
+
const streamToRecord = this.isVideo ? await this.setupCanvasRendering() : this.mediaStream;
|
|
2524
2562
|
const recorderOptions = getMediaRecorderOptions(this.isVideo);
|
|
2525
2563
|
this.mimeType = recorderOptions.mimeType;
|
|
2526
2564
|
this.mediaRecorder = new MediaRecorder(streamToRecord, recorderOptions);
|
|
@@ -2598,20 +2636,70 @@ Please check encoder server logs and DATABASE_URL configuration.`
|
|
|
2598
2636
|
/**
|
|
2599
2637
|
* Replace the video track for camera flips.
|
|
2600
2638
|
*
|
|
2601
|
-
* When using canvas-based rendering (video streams), this
|
|
2602
|
-
*
|
|
2639
|
+
* When using canvas-based rendering (video streams), this preloads the new
|
|
2640
|
+
* camera in a temporary video element, waits for it to be ready, then swaps
|
|
2641
|
+
* it in. This ensures continuous frame output with no gaps.
|
|
2603
2642
|
*
|
|
2604
2643
|
* @param newVideoTrack - The new video track from the flipped camera
|
|
2644
|
+
* @returns Promise that resolves when the swap is complete
|
|
2605
2645
|
*/
|
|
2606
|
-
replaceVideoTrack(newVideoTrack) {
|
|
2646
|
+
async replaceVideoTrack(newVideoTrack) {
|
|
2607
2647
|
console.log("\u{1F504} Replacing video track");
|
|
2608
2648
|
if (this.canvasState) {
|
|
2609
|
-
console.log("\u{1F3A8} Using canvas-based swap (
|
|
2649
|
+
console.log("\u{1F3A8} Using canvas-based swap with preloading (no frame gaps)");
|
|
2610
2650
|
const audioTracks = this.mediaStream.getAudioTracks();
|
|
2611
2651
|
const newStream = new MediaStream([newVideoTrack, ...audioTracks]);
|
|
2652
|
+
const preloadVideo = document.createElement("video");
|
|
2653
|
+
preloadVideo.srcObject = newStream;
|
|
2654
|
+
preloadVideo.muted = true;
|
|
2655
|
+
preloadVideo.playsInline = true;
|
|
2656
|
+
await new Promise((resolve, reject) => {
|
|
2657
|
+
const timeout = setTimeout(() => {
|
|
2658
|
+
console.warn("\u26A0\uFE0F Video preload timeout - switching anyway");
|
|
2659
|
+
if (preloadVideo.paused) {
|
|
2660
|
+
preloadVideo.play().catch(() => {
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
resolve();
|
|
2664
|
+
}, 3e3);
|
|
2665
|
+
const checkFullyReady = () => {
|
|
2666
|
+
if (preloadVideo.videoWidth > 0 && preloadVideo.videoHeight > 0 && !preloadVideo.paused) {
|
|
2667
|
+
clearTimeout(timeout);
|
|
2668
|
+
console.log(`\u{1F4F9} New camera ready and playing: ${preloadVideo.videoWidth}x${preloadVideo.videoHeight}`);
|
|
2669
|
+
resolve();
|
|
2670
|
+
return true;
|
|
2671
|
+
}
|
|
2672
|
+
return false;
|
|
2673
|
+
};
|
|
2674
|
+
preloadVideo.addEventListener("loadeddata", () => {
|
|
2675
|
+
preloadVideo.play().then(() => {
|
|
2676
|
+
requestAnimationFrame(() => {
|
|
2677
|
+
if (!checkFullyReady()) {
|
|
2678
|
+
const pollPlaying = setInterval(() => {
|
|
2679
|
+
if (checkFullyReady()) {
|
|
2680
|
+
clearInterval(pollPlaying);
|
|
2681
|
+
}
|
|
2682
|
+
}, 50);
|
|
2683
|
+
setTimeout(() => clearInterval(pollPlaying), 2e3);
|
|
2684
|
+
}
|
|
2685
|
+
});
|
|
2686
|
+
}).catch((e) => {
|
|
2687
|
+
console.warn("Video preload play warning:", e);
|
|
2688
|
+
checkFullyReady();
|
|
2689
|
+
});
|
|
2690
|
+
}, { once: true });
|
|
2691
|
+
preloadVideo.addEventListener("error", (e) => {
|
|
2692
|
+
clearTimeout(timeout);
|
|
2693
|
+
reject(new Error(`Video preload failed: ${e}`));
|
|
2694
|
+
}, { once: true });
|
|
2695
|
+
preloadVideo.play().catch(() => {
|
|
2696
|
+
});
|
|
2697
|
+
});
|
|
2698
|
+
const oldVideoElement = this.canvasState.videoElement;
|
|
2699
|
+
this.canvasState.videoElement = preloadVideo;
|
|
2612
2700
|
this.mediaStream.getVideoTracks().forEach((track) => track.stop());
|
|
2613
|
-
|
|
2614
|
-
|
|
2701
|
+
oldVideoElement.pause();
|
|
2702
|
+
oldVideoElement.srcObject = null;
|
|
2615
2703
|
this.mediaStream = newStream;
|
|
2616
2704
|
this.invalidateScalingCache();
|
|
2617
2705
|
const settings = newVideoTrack.getSettings();
|
|
@@ -2675,7 +2763,7 @@ Please check encoder server logs and DATABASE_URL configuration.`
|
|
|
2675
2763
|
let streamToRecord = this.mediaStream;
|
|
2676
2764
|
if (this.isVideo) {
|
|
2677
2765
|
this.cleanupCanvasRendering();
|
|
2678
|
-
streamToRecord = this.setupCanvasRendering();
|
|
2766
|
+
streamToRecord = await this.setupCanvasRendering();
|
|
2679
2767
|
console.log("\u{1F3A8} Canvas rendering recreated for new stream");
|
|
2680
2768
|
}
|
|
2681
2769
|
const recorderOptions = getMediaRecorderOptions(this.isVideo);
|
|
@@ -3651,7 +3739,7 @@ function DialtribeStreamer({
|
|
|
3651
3739
|
console.log("\u{1F4F7} Got new camera stream:", newFacingMode);
|
|
3652
3740
|
const newVideoTrack = newStream.getVideoTracks()[0];
|
|
3653
3741
|
if (newVideoTrack) {
|
|
3654
|
-
streamer.replaceVideoTrack(newVideoTrack);
|
|
3742
|
+
await streamer.replaceVideoTrack(newVideoTrack);
|
|
3655
3743
|
}
|
|
3656
3744
|
const updatedStream = streamer.getMediaStream();
|
|
3657
3745
|
setMediaStream(updatedStream);
|