@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
|
@@ -387,8 +387,11 @@ var WebSocketStreamer = class {
|
|
|
387
387
|
* Set up canvas-based rendering pipeline for video streams.
|
|
388
388
|
* This allows seamless camera flips by changing the video source
|
|
389
389
|
* without affecting MediaRecorder (which records from the canvas).
|
|
390
|
+
*
|
|
391
|
+
* This is async to ensure the video is producing frames before returning,
|
|
392
|
+
* which prevents black initial thumbnails.
|
|
390
393
|
*/
|
|
391
|
-
setupCanvasRendering() {
|
|
394
|
+
async setupCanvasRendering() {
|
|
392
395
|
console.log("\u{1F3A8} Setting up canvas-based rendering for seamless camera flips");
|
|
393
396
|
const videoTrack = this.mediaStream.getVideoTracks()[0];
|
|
394
397
|
const settings = videoTrack?.getSettings() || {};
|
|
@@ -406,7 +409,27 @@ var WebSocketStreamer = class {
|
|
|
406
409
|
videoElement.srcObject = this.mediaStream;
|
|
407
410
|
videoElement.muted = true;
|
|
408
411
|
videoElement.playsInline = true;
|
|
409
|
-
|
|
412
|
+
await new Promise((resolve) => {
|
|
413
|
+
const checkReady = () => {
|
|
414
|
+
if (videoElement.videoWidth > 0 && videoElement.videoHeight > 0) {
|
|
415
|
+
console.log(`\u{1F4F9} Video ready: ${videoElement.videoWidth}x${videoElement.videoHeight}`);
|
|
416
|
+
resolve();
|
|
417
|
+
} else {
|
|
418
|
+
requestAnimationFrame(checkReady);
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
videoElement.addEventListener("loadeddata", () => {
|
|
422
|
+
checkReady();
|
|
423
|
+
}, { once: true });
|
|
424
|
+
videoElement.play().catch((e) => {
|
|
425
|
+
console.warn("Video autoplay warning:", e);
|
|
426
|
+
resolve();
|
|
427
|
+
});
|
|
428
|
+
setTimeout(() => {
|
|
429
|
+
console.warn("\u26A0\uFE0F Video ready timeout - continuing anyway");
|
|
430
|
+
resolve();
|
|
431
|
+
}, 2e3);
|
|
432
|
+
});
|
|
410
433
|
const frameRate = settings.frameRate || 30;
|
|
411
434
|
const stream = canvas.captureStream(frameRate);
|
|
412
435
|
const audioTracks = this.mediaStream.getAudioTracks();
|
|
@@ -429,13 +452,28 @@ var WebSocketStreamer = class {
|
|
|
429
452
|
lastVideoWidth: 0,
|
|
430
453
|
lastVideoHeight: 0
|
|
431
454
|
};
|
|
455
|
+
if (videoElement.videoWidth > 0 && videoElement.videoHeight > 0) {
|
|
456
|
+
const vw = videoElement.videoWidth;
|
|
457
|
+
const vh = videoElement.videoHeight;
|
|
458
|
+
const cw = canvas.width;
|
|
459
|
+
const ch = canvas.height;
|
|
460
|
+
const scale = Math.min(cw / vw, ch / vh);
|
|
461
|
+
const sw = vw * scale;
|
|
462
|
+
const sh = vh * scale;
|
|
463
|
+
const sx = (cw - sw) / 2;
|
|
464
|
+
const sy = (ch - sh) / 2;
|
|
465
|
+
ctx.fillStyle = "#000";
|
|
466
|
+
ctx.fillRect(0, 0, cw, ch);
|
|
467
|
+
ctx.drawImage(videoElement, sx, sy, sw, sh);
|
|
468
|
+
console.log("\u{1F5BC}\uFE0F Drew first frame synchronously to prevent black thumbnail");
|
|
469
|
+
}
|
|
432
470
|
const state = this.canvasState;
|
|
433
471
|
const renderFrame = () => {
|
|
434
472
|
if (!this.canvasState || state !== this.canvasState) return;
|
|
435
473
|
const { ctx: ctx2, canvas: canvas2, videoElement: videoElement2 } = state;
|
|
436
474
|
if (videoElement2.paused) {
|
|
437
|
-
|
|
438
|
-
|
|
475
|
+
videoElement2.play().catch(() => {
|
|
476
|
+
});
|
|
439
477
|
}
|
|
440
478
|
const canvasWidth = canvas2.width;
|
|
441
479
|
const canvasHeight = canvas2.height;
|
|
@@ -570,7 +608,7 @@ Please check encoder server logs and DATABASE_URL configuration.`
|
|
|
570
608
|
});
|
|
571
609
|
console.log("\u2705 WebSocket connected");
|
|
572
610
|
this.setupWebSocketHandlers();
|
|
573
|
-
const streamToRecord = this.isVideo ? this.setupCanvasRendering() : this.mediaStream;
|
|
611
|
+
const streamToRecord = this.isVideo ? await this.setupCanvasRendering() : this.mediaStream;
|
|
574
612
|
const recorderOptions = getMediaRecorderOptions(this.isVideo);
|
|
575
613
|
this.mimeType = recorderOptions.mimeType;
|
|
576
614
|
this.mediaRecorder = new MediaRecorder(streamToRecord, recorderOptions);
|
|
@@ -648,20 +686,70 @@ Please check encoder server logs and DATABASE_URL configuration.`
|
|
|
648
686
|
/**
|
|
649
687
|
* Replace the video track for camera flips.
|
|
650
688
|
*
|
|
651
|
-
* When using canvas-based rendering (video streams), this
|
|
652
|
-
*
|
|
689
|
+
* When using canvas-based rendering (video streams), this preloads the new
|
|
690
|
+
* camera in a temporary video element, waits for it to be ready, then swaps
|
|
691
|
+
* it in. This ensures continuous frame output with no gaps.
|
|
653
692
|
*
|
|
654
693
|
* @param newVideoTrack - The new video track from the flipped camera
|
|
694
|
+
* @returns Promise that resolves when the swap is complete
|
|
655
695
|
*/
|
|
656
|
-
replaceVideoTrack(newVideoTrack) {
|
|
696
|
+
async replaceVideoTrack(newVideoTrack) {
|
|
657
697
|
console.log("\u{1F504} Replacing video track");
|
|
658
698
|
if (this.canvasState) {
|
|
659
|
-
console.log("\u{1F3A8} Using canvas-based swap (
|
|
699
|
+
console.log("\u{1F3A8} Using canvas-based swap with preloading (no frame gaps)");
|
|
660
700
|
const audioTracks = this.mediaStream.getAudioTracks();
|
|
661
701
|
const newStream = new MediaStream([newVideoTrack, ...audioTracks]);
|
|
702
|
+
const preloadVideo = document.createElement("video");
|
|
703
|
+
preloadVideo.srcObject = newStream;
|
|
704
|
+
preloadVideo.muted = true;
|
|
705
|
+
preloadVideo.playsInline = true;
|
|
706
|
+
await new Promise((resolve, reject) => {
|
|
707
|
+
const timeout = setTimeout(() => {
|
|
708
|
+
console.warn("\u26A0\uFE0F Video preload timeout - switching anyway");
|
|
709
|
+
if (preloadVideo.paused) {
|
|
710
|
+
preloadVideo.play().catch(() => {
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
resolve();
|
|
714
|
+
}, 3e3);
|
|
715
|
+
const checkFullyReady = () => {
|
|
716
|
+
if (preloadVideo.videoWidth > 0 && preloadVideo.videoHeight > 0 && !preloadVideo.paused) {
|
|
717
|
+
clearTimeout(timeout);
|
|
718
|
+
console.log(`\u{1F4F9} New camera ready and playing: ${preloadVideo.videoWidth}x${preloadVideo.videoHeight}`);
|
|
719
|
+
resolve();
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
return false;
|
|
723
|
+
};
|
|
724
|
+
preloadVideo.addEventListener("loadeddata", () => {
|
|
725
|
+
preloadVideo.play().then(() => {
|
|
726
|
+
requestAnimationFrame(() => {
|
|
727
|
+
if (!checkFullyReady()) {
|
|
728
|
+
const pollPlaying = setInterval(() => {
|
|
729
|
+
if (checkFullyReady()) {
|
|
730
|
+
clearInterval(pollPlaying);
|
|
731
|
+
}
|
|
732
|
+
}, 50);
|
|
733
|
+
setTimeout(() => clearInterval(pollPlaying), 2e3);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
}).catch((e) => {
|
|
737
|
+
console.warn("Video preload play warning:", e);
|
|
738
|
+
checkFullyReady();
|
|
739
|
+
});
|
|
740
|
+
}, { once: true });
|
|
741
|
+
preloadVideo.addEventListener("error", (e) => {
|
|
742
|
+
clearTimeout(timeout);
|
|
743
|
+
reject(new Error(`Video preload failed: ${e}`));
|
|
744
|
+
}, { once: true });
|
|
745
|
+
preloadVideo.play().catch(() => {
|
|
746
|
+
});
|
|
747
|
+
});
|
|
748
|
+
const oldVideoElement = this.canvasState.videoElement;
|
|
749
|
+
this.canvasState.videoElement = preloadVideo;
|
|
662
750
|
this.mediaStream.getVideoTracks().forEach((track) => track.stop());
|
|
663
|
-
|
|
664
|
-
|
|
751
|
+
oldVideoElement.pause();
|
|
752
|
+
oldVideoElement.srcObject = null;
|
|
665
753
|
this.mediaStream = newStream;
|
|
666
754
|
this.invalidateScalingCache();
|
|
667
755
|
const settings = newVideoTrack.getSettings();
|
|
@@ -725,7 +813,7 @@ Please check encoder server logs and DATABASE_URL configuration.`
|
|
|
725
813
|
let streamToRecord = this.mediaStream;
|
|
726
814
|
if (this.isVideo) {
|
|
727
815
|
this.cleanupCanvasRendering();
|
|
728
|
-
streamToRecord = this.setupCanvasRendering();
|
|
816
|
+
streamToRecord = await this.setupCanvasRendering();
|
|
729
817
|
console.log("\u{1F3A8} Canvas rendering recreated for new stream");
|
|
730
818
|
}
|
|
731
819
|
const recorderOptions = getMediaRecorderOptions(this.isVideo);
|
|
@@ -2065,7 +2153,7 @@ function DialtribeStreamer({
|
|
|
2065
2153
|
console.log("\u{1F4F7} Got new camera stream:", newFacingMode);
|
|
2066
2154
|
const newVideoTrack = newStream.getVideoTracks()[0];
|
|
2067
2155
|
if (newVideoTrack) {
|
|
2068
|
-
streamer.replaceVideoTrack(newVideoTrack);
|
|
2156
|
+
await streamer.replaceVideoTrack(newVideoTrack);
|
|
2069
2157
|
}
|
|
2070
2158
|
const updatedStream = streamer.getMediaStream();
|
|
2071
2159
|
setMediaStream(updatedStream);
|