@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
|
@@ -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
|
|
262
|
-
*
|
|
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
|
|
262
|
-
*
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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();
|
|
@@ -436,13 +459,28 @@ var WebSocketStreamer = class {
|
|
|
436
459
|
lastVideoWidth: 0,
|
|
437
460
|
lastVideoHeight: 0
|
|
438
461
|
};
|
|
462
|
+
if (videoElement.videoWidth > 0 && videoElement.videoHeight > 0) {
|
|
463
|
+
const vw = videoElement.videoWidth;
|
|
464
|
+
const vh = videoElement.videoHeight;
|
|
465
|
+
const cw = canvas.width;
|
|
466
|
+
const ch = canvas.height;
|
|
467
|
+
const scale = Math.min(cw / vw, ch / vh);
|
|
468
|
+
const sw = vw * scale;
|
|
469
|
+
const sh = vh * scale;
|
|
470
|
+
const sx = (cw - sw) / 2;
|
|
471
|
+
const sy = (ch - sh) / 2;
|
|
472
|
+
ctx.fillStyle = "#000";
|
|
473
|
+
ctx.fillRect(0, 0, cw, ch);
|
|
474
|
+
ctx.drawImage(videoElement, sx, sy, sw, sh);
|
|
475
|
+
console.log("\u{1F5BC}\uFE0F Drew first frame synchronously to prevent black thumbnail");
|
|
476
|
+
}
|
|
439
477
|
const state = this.canvasState;
|
|
440
478
|
const renderFrame = () => {
|
|
441
479
|
if (!this.canvasState || state !== this.canvasState) return;
|
|
442
480
|
const { ctx: ctx2, canvas: canvas2, videoElement: videoElement2 } = state;
|
|
443
481
|
if (videoElement2.paused) {
|
|
444
|
-
|
|
445
|
-
|
|
482
|
+
videoElement2.play().catch(() => {
|
|
483
|
+
});
|
|
446
484
|
}
|
|
447
485
|
const canvasWidth = canvas2.width;
|
|
448
486
|
const canvasHeight = canvas2.height;
|
|
@@ -577,7 +615,7 @@ Please check encoder server logs and DATABASE_URL configuration.`
|
|
|
577
615
|
});
|
|
578
616
|
console.log("\u2705 WebSocket connected");
|
|
579
617
|
this.setupWebSocketHandlers();
|
|
580
|
-
const streamToRecord = this.isVideo ? this.setupCanvasRendering() : this.mediaStream;
|
|
618
|
+
const streamToRecord = this.isVideo ? await this.setupCanvasRendering() : this.mediaStream;
|
|
581
619
|
const recorderOptions = getMediaRecorderOptions(this.isVideo);
|
|
582
620
|
this.mimeType = recorderOptions.mimeType;
|
|
583
621
|
this.mediaRecorder = new MediaRecorder(streamToRecord, recorderOptions);
|
|
@@ -655,20 +693,70 @@ Please check encoder server logs and DATABASE_URL configuration.`
|
|
|
655
693
|
/**
|
|
656
694
|
* Replace the video track for camera flips.
|
|
657
695
|
*
|
|
658
|
-
* When using canvas-based rendering (video streams), this
|
|
659
|
-
*
|
|
696
|
+
* When using canvas-based rendering (video streams), this preloads the new
|
|
697
|
+
* camera in a temporary video element, waits for it to be ready, then swaps
|
|
698
|
+
* it in. This ensures continuous frame output with no gaps.
|
|
660
699
|
*
|
|
661
700
|
* @param newVideoTrack - The new video track from the flipped camera
|
|
701
|
+
* @returns Promise that resolves when the swap is complete
|
|
662
702
|
*/
|
|
663
|
-
replaceVideoTrack(newVideoTrack) {
|
|
703
|
+
async replaceVideoTrack(newVideoTrack) {
|
|
664
704
|
console.log("\u{1F504} Replacing video track");
|
|
665
705
|
if (this.canvasState) {
|
|
666
|
-
console.log("\u{1F3A8} Using canvas-based swap (
|
|
706
|
+
console.log("\u{1F3A8} Using canvas-based swap with preloading (no frame gaps)");
|
|
667
707
|
const audioTracks = this.mediaStream.getAudioTracks();
|
|
668
708
|
const newStream = new MediaStream([newVideoTrack, ...audioTracks]);
|
|
709
|
+
const preloadVideo = document.createElement("video");
|
|
710
|
+
preloadVideo.srcObject = newStream;
|
|
711
|
+
preloadVideo.muted = true;
|
|
712
|
+
preloadVideo.playsInline = true;
|
|
713
|
+
await new Promise((resolve, reject) => {
|
|
714
|
+
const timeout = setTimeout(() => {
|
|
715
|
+
console.warn("\u26A0\uFE0F Video preload timeout - switching anyway");
|
|
716
|
+
if (preloadVideo.paused) {
|
|
717
|
+
preloadVideo.play().catch(() => {
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
resolve();
|
|
721
|
+
}, 3e3);
|
|
722
|
+
const checkFullyReady = () => {
|
|
723
|
+
if (preloadVideo.videoWidth > 0 && preloadVideo.videoHeight > 0 && !preloadVideo.paused) {
|
|
724
|
+
clearTimeout(timeout);
|
|
725
|
+
console.log(`\u{1F4F9} New camera ready and playing: ${preloadVideo.videoWidth}x${preloadVideo.videoHeight}`);
|
|
726
|
+
resolve();
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
return false;
|
|
730
|
+
};
|
|
731
|
+
preloadVideo.addEventListener("loadeddata", () => {
|
|
732
|
+
preloadVideo.play().then(() => {
|
|
733
|
+
requestAnimationFrame(() => {
|
|
734
|
+
if (!checkFullyReady()) {
|
|
735
|
+
const pollPlaying = setInterval(() => {
|
|
736
|
+
if (checkFullyReady()) {
|
|
737
|
+
clearInterval(pollPlaying);
|
|
738
|
+
}
|
|
739
|
+
}, 50);
|
|
740
|
+
setTimeout(() => clearInterval(pollPlaying), 2e3);
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
}).catch((e) => {
|
|
744
|
+
console.warn("Video preload play warning:", e);
|
|
745
|
+
checkFullyReady();
|
|
746
|
+
});
|
|
747
|
+
}, { once: true });
|
|
748
|
+
preloadVideo.addEventListener("error", (e) => {
|
|
749
|
+
clearTimeout(timeout);
|
|
750
|
+
reject(new Error(`Video preload failed: ${e}`));
|
|
751
|
+
}, { once: true });
|
|
752
|
+
preloadVideo.play().catch(() => {
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
const oldVideoElement = this.canvasState.videoElement;
|
|
756
|
+
this.canvasState.videoElement = preloadVideo;
|
|
669
757
|
this.mediaStream.getVideoTracks().forEach((track) => track.stop());
|
|
670
|
-
|
|
671
|
-
|
|
758
|
+
oldVideoElement.pause();
|
|
759
|
+
oldVideoElement.srcObject = null;
|
|
672
760
|
this.mediaStream = newStream;
|
|
673
761
|
this.invalidateScalingCache();
|
|
674
762
|
const settings = newVideoTrack.getSettings();
|
|
@@ -732,7 +820,7 @@ Please check encoder server logs and DATABASE_URL configuration.`
|
|
|
732
820
|
let streamToRecord = this.mediaStream;
|
|
733
821
|
if (this.isVideo) {
|
|
734
822
|
this.cleanupCanvasRendering();
|
|
735
|
-
streamToRecord = this.setupCanvasRendering();
|
|
823
|
+
streamToRecord = await this.setupCanvasRendering();
|
|
736
824
|
console.log("\u{1F3A8} Canvas rendering recreated for new stream");
|
|
737
825
|
}
|
|
738
826
|
const recorderOptions = getMediaRecorderOptions(this.isVideo);
|
|
@@ -2072,7 +2160,7 @@ function DialtribeStreamer({
|
|
|
2072
2160
|
console.log("\u{1F4F7} Got new camera stream:", newFacingMode);
|
|
2073
2161
|
const newVideoTrack = newStream.getVideoTracks()[0];
|
|
2074
2162
|
if (newVideoTrack) {
|
|
2075
|
-
streamer.replaceVideoTrack(newVideoTrack);
|
|
2163
|
+
await streamer.replaceVideoTrack(newVideoTrack);
|
|
2076
2164
|
}
|
|
2077
2165
|
const updatedStream = streamer.getMediaStream();
|
|
2078
2166
|
setMediaStream(updatedStream);
|