@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.
@@ -224,6 +224,9 @@ declare class WebSocketStreamer {
224
224
  * Set up canvas-based rendering pipeline for video streams.
225
225
  * This allows seamless camera flips by changing the video source
226
226
  * without affecting MediaRecorder (which records from the canvas).
227
+ *
228
+ * This is async to ensure the video is producing frames before returning,
229
+ * which prevents black initial thumbnails.
227
230
  */
228
231
  private setupCanvasRendering;
229
232
  /**
@@ -258,12 +261,14 @@ declare class WebSocketStreamer {
258
261
  /**
259
262
  * Replace the video track for camera flips.
260
263
  *
261
- * When using canvas-based rendering (video streams), this updates the video
262
- * element source. The canvas continues drawing, and MediaRecorder is unaffected.
264
+ * When using canvas-based rendering (video streams), this preloads the new
265
+ * camera in a temporary video element, waits for it to be ready, then swaps
266
+ * it in. This ensures continuous frame output with no gaps.
263
267
  *
264
268
  * @param newVideoTrack - The new video track from the flipped camera
269
+ * @returns Promise that resolves when the swap is complete
265
270
  */
266
- replaceVideoTrack(newVideoTrack: MediaStreamTrack): void;
271
+ replaceVideoTrack(newVideoTrack: MediaStreamTrack): Promise<void>;
267
272
  /**
268
273
  * Replace the audio track in the current MediaStream without stopping MediaRecorder.
269
274
  *
@@ -224,6 +224,9 @@ declare class WebSocketStreamer {
224
224
  * Set up canvas-based rendering pipeline for video streams.
225
225
  * This allows seamless camera flips by changing the video source
226
226
  * without affecting MediaRecorder (which records from the canvas).
227
+ *
228
+ * This is async to ensure the video is producing frames before returning,
229
+ * which prevents black initial thumbnails.
227
230
  */
228
231
  private setupCanvasRendering;
229
232
  /**
@@ -258,12 +261,14 @@ declare class WebSocketStreamer {
258
261
  /**
259
262
  * Replace the video track for camera flips.
260
263
  *
261
- * When using canvas-based rendering (video streams), this updates the video
262
- * element source. The canvas continues drawing, and MediaRecorder is unaffected.
264
+ * When using canvas-based rendering (video streams), this preloads the new
265
+ * camera in a temporary video element, waits for it to be ready, then swaps
266
+ * it in. This ensures continuous frame output with no gaps.
263
267
  *
264
268
  * @param newVideoTrack - The new video track from the flipped camera
269
+ * @returns Promise that resolves when the swap is complete
265
270
  */
266
- replaceVideoTrack(newVideoTrack: MediaStreamTrack): void;
271
+ replaceVideoTrack(newVideoTrack: MediaStreamTrack): Promise<void>;
267
272
  /**
268
273
  * Replace the audio track in the current MediaStream without stopping MediaRecorder.
269
274
  *
@@ -1,4 +1,4 @@
1
1
  export { A as ApiClientConfig, h as AudioWaveform, B as Broadcast, C as CDN_DOMAIN, d as DIALTRIBE_API_BASE, c as DialtribeClient, a as DialtribeContextValue, i as DialtribeOverlay, k as DialtribeOverlayMode, j as DialtribeOverlayProps, e as DialtribePlayer, g as DialtribePlayerErrorBoundary, f as DialtribePlayerProps, D as DialtribeProvider, b as DialtribeProviderProps, E as ENDPOINTS, H as HTTP_STATUS, q as HttpStatusCode, L as LoadingSpinner, m as TranscriptData, l as TranscriptSegment, T as TranscriptWord, o as buildBroadcastCdnUrl, p as buildBroadcastS3KeyPrefix, n as formatTime, u as useDialtribe, r as useDialtribeOptional } from './dialtribe-player-CNriUtNi.mjs';
2
- export { j as DEFAULT_ENCODER_SERVER_URL, D as DialtribeStreamer, a as DialtribeStreamerProps, M as MediaConstraintsOptions, O as OpenDialtribeStreamerPopupOptions, P as PopupDimensions, w as PopupFallbackMode, f as StreamKeyDisplay, g as StreamKeyDisplayProps, h as StreamKeyInput, i as StreamKeyInputProps, e as StreamingControlState, c as StreamingControls, d as StreamingControlsProps, S as StreamingPreview, b as StreamingPreviewProps, t as UseDialtribeStreamerLauncherOptions, v as UseDialtribeStreamerLauncherReturn, U as UseDialtribeStreamerPopupReturn, W as WebSocketStreamer, k as WebSocketStreamerOptions, r as calculatePopupDimensions, o as checkBrowserCompatibility, m as getMediaConstraints, n as getMediaRecorderOptions, q as openBroadcastPopup, p as openDialtribeStreamerPopup, s as useDialtribeStreamerLauncher, u as useDialtribeStreamerPopup } from './dialtribe-streamer-DH23BseY.mjs';
2
+ export { j as DEFAULT_ENCODER_SERVER_URL, D as DialtribeStreamer, a as DialtribeStreamerProps, M as MediaConstraintsOptions, O as OpenDialtribeStreamerPopupOptions, P as PopupDimensions, w as PopupFallbackMode, f as StreamKeyDisplay, g as StreamKeyDisplayProps, h as StreamKeyInput, i as StreamKeyInputProps, e as StreamingControlState, c as StreamingControls, d as StreamingControlsProps, S as StreamingPreview, b as StreamingPreviewProps, t as UseDialtribeStreamerLauncherOptions, v as UseDialtribeStreamerLauncherReturn, U as UseDialtribeStreamerPopupReturn, W as WebSocketStreamer, k as WebSocketStreamerOptions, r as calculatePopupDimensions, o as checkBrowserCompatibility, m as getMediaConstraints, n as getMediaRecorderOptions, q as openBroadcastPopup, p as openDialtribeStreamerPopup, s as useDialtribeStreamerLauncher, u as useDialtribeStreamerPopup } from './dialtribe-streamer-Do-8Oavc.mjs';
3
3
  import 'react/jsx-runtime';
4
4
  import 'react';
@@ -1,4 +1,4 @@
1
1
  export { A as ApiClientConfig, h as AudioWaveform, B as Broadcast, C as CDN_DOMAIN, d as DIALTRIBE_API_BASE, c as DialtribeClient, a as DialtribeContextValue, i as DialtribeOverlay, k as DialtribeOverlayMode, j as DialtribeOverlayProps, e as DialtribePlayer, g as DialtribePlayerErrorBoundary, f as DialtribePlayerProps, D as DialtribeProvider, b as DialtribeProviderProps, E as ENDPOINTS, H as HTTP_STATUS, q as HttpStatusCode, L as LoadingSpinner, m as TranscriptData, l as TranscriptSegment, T as TranscriptWord, o as buildBroadcastCdnUrl, p as buildBroadcastS3KeyPrefix, n as formatTime, u as useDialtribe, r as useDialtribeOptional } from './dialtribe-player-CNriUtNi.js';
2
- export { j as DEFAULT_ENCODER_SERVER_URL, D as DialtribeStreamer, a as DialtribeStreamerProps, M as MediaConstraintsOptions, O as OpenDialtribeStreamerPopupOptions, P as PopupDimensions, w as PopupFallbackMode, f as StreamKeyDisplay, g as StreamKeyDisplayProps, h as StreamKeyInput, i as StreamKeyInputProps, e as StreamingControlState, c as StreamingControls, d as StreamingControlsProps, S as StreamingPreview, b as StreamingPreviewProps, t as UseDialtribeStreamerLauncherOptions, v as UseDialtribeStreamerLauncherReturn, U as UseDialtribeStreamerPopupReturn, W as WebSocketStreamer, k as WebSocketStreamerOptions, r as calculatePopupDimensions, o as checkBrowserCompatibility, m as getMediaConstraints, n as getMediaRecorderOptions, q as openBroadcastPopup, p as openDialtribeStreamerPopup, s as useDialtribeStreamerLauncher, u as useDialtribeStreamerPopup } from './dialtribe-streamer-D9ulVBVb.js';
2
+ export { j as DEFAULT_ENCODER_SERVER_URL, D as DialtribeStreamer, a as DialtribeStreamerProps, M as MediaConstraintsOptions, O as OpenDialtribeStreamerPopupOptions, P as PopupDimensions, w as PopupFallbackMode, f as StreamKeyDisplay, g as StreamKeyDisplayProps, h as StreamKeyInput, i as StreamKeyInputProps, e as StreamingControlState, c as StreamingControls, d as StreamingControlsProps, S as StreamingPreview, b as StreamingPreviewProps, t as UseDialtribeStreamerLauncherOptions, v as UseDialtribeStreamerLauncherReturn, U as UseDialtribeStreamerPopupReturn, W as WebSocketStreamer, k as WebSocketStreamerOptions, r as calculatePopupDimensions, o as checkBrowserCompatibility, m as getMediaConstraints, n as getMediaRecorderOptions, q as openBroadcastPopup, p as openDialtribeStreamerPopup, s as useDialtribeStreamerLauncher, u as useDialtribeStreamerPopup } from './dialtribe-streamer-DOhD-r_F.js';
3
3
  import 'react/jsx-runtime';
4
4
  import 'react';
@@ -394,8 +394,11 @@ var WebSocketStreamer = class {
394
394
  * Set up canvas-based rendering pipeline for video streams.
395
395
  * This allows seamless camera flips by changing the video source
396
396
  * without affecting MediaRecorder (which records from the canvas).
397
+ *
398
+ * This is async to ensure the video is producing frames before returning,
399
+ * which prevents black initial thumbnails.
397
400
  */
398
- setupCanvasRendering() {
401
+ async setupCanvasRendering() {
399
402
  console.log("\u{1F3A8} Setting up canvas-based rendering for seamless camera flips");
400
403
  const videoTrack = this.mediaStream.getVideoTracks()[0];
401
404
  const settings = videoTrack?.getSettings() || {};
@@ -413,7 +416,27 @@ var WebSocketStreamer = class {
413
416
  videoElement.srcObject = this.mediaStream;
414
417
  videoElement.muted = true;
415
418
  videoElement.playsInline = true;
416
- videoElement.play().catch((e) => console.warn("Video autoplay warning:", e));
419
+ await new Promise((resolve) => {
420
+ const checkReady = () => {
421
+ if (videoElement.videoWidth > 0 && videoElement.videoHeight > 0) {
422
+ console.log(`\u{1F4F9} Video ready: ${videoElement.videoWidth}x${videoElement.videoHeight}`);
423
+ resolve();
424
+ } else {
425
+ requestAnimationFrame(checkReady);
426
+ }
427
+ };
428
+ videoElement.addEventListener("loadeddata", () => {
429
+ checkReady();
430
+ }, { once: true });
431
+ videoElement.play().catch((e) => {
432
+ console.warn("Video autoplay warning:", e);
433
+ resolve();
434
+ });
435
+ setTimeout(() => {
436
+ console.warn("\u26A0\uFE0F Video ready timeout - continuing anyway");
437
+ resolve();
438
+ }, 2e3);
439
+ });
417
440
  const frameRate = settings.frameRate || 30;
418
441
  const stream = canvas.captureStream(frameRate);
419
442
  const audioTracks = this.mediaStream.getAudioTracks();
@@ -577,7 +600,7 @@ Please check encoder server logs and DATABASE_URL configuration.`
577
600
  });
578
601
  console.log("\u2705 WebSocket connected");
579
602
  this.setupWebSocketHandlers();
580
- const streamToRecord = this.isVideo ? this.setupCanvasRendering() : this.mediaStream;
603
+ const streamToRecord = this.isVideo ? await this.setupCanvasRendering() : this.mediaStream;
581
604
  const recorderOptions = getMediaRecorderOptions(this.isVideo);
582
605
  this.mimeType = recorderOptions.mimeType;
583
606
  this.mediaRecorder = new MediaRecorder(streamToRecord, recorderOptions);
@@ -655,20 +678,50 @@ Please check encoder server logs and DATABASE_URL configuration.`
655
678
  /**
656
679
  * Replace the video track for camera flips.
657
680
  *
658
- * When using canvas-based rendering (video streams), this updates the video
659
- * element source. The canvas continues drawing, and MediaRecorder is unaffected.
681
+ * When using canvas-based rendering (video streams), this preloads the new
682
+ * camera in a temporary video element, waits for it to be ready, then swaps
683
+ * it in. This ensures continuous frame output with no gaps.
660
684
  *
661
685
  * @param newVideoTrack - The new video track from the flipped camera
686
+ * @returns Promise that resolves when the swap is complete
662
687
  */
663
- replaceVideoTrack(newVideoTrack) {
688
+ async replaceVideoTrack(newVideoTrack) {
664
689
  console.log("\u{1F504} Replacing video track");
665
690
  if (this.canvasState) {
666
- console.log("\u{1F3A8} Using canvas-based swap (MediaRecorder unaffected)");
691
+ console.log("\u{1F3A8} Using canvas-based swap with preloading (no frame gaps)");
667
692
  const audioTracks = this.mediaStream.getAudioTracks();
668
693
  const newStream = new MediaStream([newVideoTrack, ...audioTracks]);
694
+ const preloadVideo = document.createElement("video");
695
+ preloadVideo.srcObject = newStream;
696
+ preloadVideo.muted = true;
697
+ preloadVideo.playsInline = true;
698
+ await new Promise((resolve, reject) => {
699
+ const timeout = setTimeout(() => {
700
+ console.warn("\u26A0\uFE0F Video preload timeout - switching anyway");
701
+ resolve();
702
+ }, 3e3);
703
+ preloadVideo.addEventListener("loadeddata", () => {
704
+ if (preloadVideo.videoWidth > 0 && preloadVideo.videoHeight > 0) {
705
+ clearTimeout(timeout);
706
+ console.log(`\u{1F4F9} New camera ready: ${preloadVideo.videoWidth}x${preloadVideo.videoHeight}`);
707
+ resolve();
708
+ }
709
+ }, { once: true });
710
+ preloadVideo.addEventListener("error", (e) => {
711
+ clearTimeout(timeout);
712
+ reject(new Error(`Video preload failed: ${e}`));
713
+ }, { once: true });
714
+ preloadVideo.play().catch((e) => {
715
+ clearTimeout(timeout);
716
+ console.warn("Video preload play warning:", e);
717
+ resolve();
718
+ });
719
+ });
669
720
  this.mediaStream.getVideoTracks().forEach((track) => track.stop());
670
- this.canvasState.videoElement.srcObject = newStream;
671
- this.canvasState.videoElement.play().catch((e) => console.warn("Video play warning:", e));
721
+ const oldVideoElement = this.canvasState.videoElement;
722
+ this.canvasState.videoElement = preloadVideo;
723
+ oldVideoElement.pause();
724
+ oldVideoElement.srcObject = null;
672
725
  this.mediaStream = newStream;
673
726
  this.invalidateScalingCache();
674
727
  const settings = newVideoTrack.getSettings();
@@ -732,7 +785,7 @@ Please check encoder server logs and DATABASE_URL configuration.`
732
785
  let streamToRecord = this.mediaStream;
733
786
  if (this.isVideo) {
734
787
  this.cleanupCanvasRendering();
735
- streamToRecord = this.setupCanvasRendering();
788
+ streamToRecord = await this.setupCanvasRendering();
736
789
  console.log("\u{1F3A8} Canvas rendering recreated for new stream");
737
790
  }
738
791
  const recorderOptions = getMediaRecorderOptions(this.isVideo);
@@ -2072,7 +2125,7 @@ function DialtribeStreamer({
2072
2125
  console.log("\u{1F4F7} Got new camera stream:", newFacingMode);
2073
2126
  const newVideoTrack = newStream.getVideoTracks()[0];
2074
2127
  if (newVideoTrack) {
2075
- streamer.replaceVideoTrack(newVideoTrack);
2128
+ await streamer.replaceVideoTrack(newVideoTrack);
2076
2129
  }
2077
2130
  const updatedStream = streamer.getMediaStream();
2078
2131
  setMediaStream(updatedStream);