@dialtribe/react-sdk 0.1.0-alpha.21 → 0.1.0-alpha.22

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.
@@ -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
- videoElement.play().catch((e) => console.warn("Video autoplay warning:", e));
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();
@@ -570,7 +593,7 @@ Please check encoder server logs and DATABASE_URL configuration.`
570
593
  });
571
594
  console.log("\u2705 WebSocket connected");
572
595
  this.setupWebSocketHandlers();
573
- const streamToRecord = this.isVideo ? this.setupCanvasRendering() : this.mediaStream;
596
+ const streamToRecord = this.isVideo ? await this.setupCanvasRendering() : this.mediaStream;
574
597
  const recorderOptions = getMediaRecorderOptions(this.isVideo);
575
598
  this.mimeType = recorderOptions.mimeType;
576
599
  this.mediaRecorder = new MediaRecorder(streamToRecord, recorderOptions);
@@ -648,20 +671,50 @@ Please check encoder server logs and DATABASE_URL configuration.`
648
671
  /**
649
672
  * Replace the video track for camera flips.
650
673
  *
651
- * When using canvas-based rendering (video streams), this updates the video
652
- * element source. The canvas continues drawing, and MediaRecorder is unaffected.
674
+ * When using canvas-based rendering (video streams), this preloads the new
675
+ * camera in a temporary video element, waits for it to be ready, then swaps
676
+ * it in. This ensures continuous frame output with no gaps.
653
677
  *
654
678
  * @param newVideoTrack - The new video track from the flipped camera
679
+ * @returns Promise that resolves when the swap is complete
655
680
  */
656
- replaceVideoTrack(newVideoTrack) {
681
+ async replaceVideoTrack(newVideoTrack) {
657
682
  console.log("\u{1F504} Replacing video track");
658
683
  if (this.canvasState) {
659
- console.log("\u{1F3A8} Using canvas-based swap (MediaRecorder unaffected)");
684
+ console.log("\u{1F3A8} Using canvas-based swap with preloading (no frame gaps)");
660
685
  const audioTracks = this.mediaStream.getAudioTracks();
661
686
  const newStream = new MediaStream([newVideoTrack, ...audioTracks]);
687
+ const preloadVideo = document.createElement("video");
688
+ preloadVideo.srcObject = newStream;
689
+ preloadVideo.muted = true;
690
+ preloadVideo.playsInline = true;
691
+ await new Promise((resolve, reject) => {
692
+ const timeout = setTimeout(() => {
693
+ console.warn("\u26A0\uFE0F Video preload timeout - switching anyway");
694
+ resolve();
695
+ }, 3e3);
696
+ preloadVideo.addEventListener("loadeddata", () => {
697
+ if (preloadVideo.videoWidth > 0 && preloadVideo.videoHeight > 0) {
698
+ clearTimeout(timeout);
699
+ console.log(`\u{1F4F9} New camera ready: ${preloadVideo.videoWidth}x${preloadVideo.videoHeight}`);
700
+ resolve();
701
+ }
702
+ }, { once: true });
703
+ preloadVideo.addEventListener("error", (e) => {
704
+ clearTimeout(timeout);
705
+ reject(new Error(`Video preload failed: ${e}`));
706
+ }, { once: true });
707
+ preloadVideo.play().catch((e) => {
708
+ clearTimeout(timeout);
709
+ console.warn("Video preload play warning:", e);
710
+ resolve();
711
+ });
712
+ });
662
713
  this.mediaStream.getVideoTracks().forEach((track) => track.stop());
663
- this.canvasState.videoElement.srcObject = newStream;
664
- this.canvasState.videoElement.play().catch((e) => console.warn("Video play warning:", e));
714
+ const oldVideoElement = this.canvasState.videoElement;
715
+ this.canvasState.videoElement = preloadVideo;
716
+ oldVideoElement.pause();
717
+ oldVideoElement.srcObject = null;
665
718
  this.mediaStream = newStream;
666
719
  this.invalidateScalingCache();
667
720
  const settings = newVideoTrack.getSettings();
@@ -725,7 +778,7 @@ Please check encoder server logs and DATABASE_URL configuration.`
725
778
  let streamToRecord = this.mediaStream;
726
779
  if (this.isVideo) {
727
780
  this.cleanupCanvasRendering();
728
- streamToRecord = this.setupCanvasRendering();
781
+ streamToRecord = await this.setupCanvasRendering();
729
782
  console.log("\u{1F3A8} Canvas rendering recreated for new stream");
730
783
  }
731
784
  const recorderOptions = getMediaRecorderOptions(this.isVideo);
@@ -2065,7 +2118,7 @@ function DialtribeStreamer({
2065
2118
  console.log("\u{1F4F7} Got new camera stream:", newFacingMode);
2066
2119
  const newVideoTrack = newStream.getVideoTracks()[0];
2067
2120
  if (newVideoTrack) {
2068
- streamer.replaceVideoTrack(newVideoTrack);
2121
+ await streamer.replaceVideoTrack(newVideoTrack);
2069
2122
  }
2070
2123
  const updatedStream = streamer.getMediaStream();
2071
2124
  setMediaStream(updatedStream);