kasunk99-livestream-core 0.2.4 → 0.2.6
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/components/LiveStreamViewerItem.js +5 -5
- package/dist/hooks/useHostSocket.d.ts.map +1 -1
- package/dist/hooks/useHostSocket.js +38 -13
- package/dist/hooks/useViewerSocket.d.ts +2 -0
- package/dist/hooks/useViewerSocket.d.ts.map +1 -1
- package/dist/hooks/useViewerSocket.js +11 -1
- package/dist/services/livestream.service.d.ts.map +1 -1
- package/dist/services/livestream.service.js +6 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -17,10 +17,10 @@ catch {
|
|
|
17
17
|
*/
|
|
18
18
|
export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream, isActive, index, }) {
|
|
19
19
|
const roomId = isActive ? stream.roomId : null;
|
|
20
|
-
const { joined, joining, error, producerList, roomState, remoteStream, remoteVideoStream, webrtcUnavailable, consumeError, socket, } = useViewerSocket(roomId);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
const { joined, joining, error, producerList, roomState, remoteStream, remoteVideoStream, webrtcUnavailable, consumeError, socket, viewerCount: liveViewerCount, } = useViewerSocket(roomId);
|
|
21
|
+
// Prefer real-time socket count (updated on every join/leave event);
|
|
22
|
+
// fall back to the HTTP-polled value from the feed for the initial render.
|
|
23
|
+
const viewerCount = liveViewerCount > 0 ? liveViewerCount : stream.viewerCount;
|
|
24
24
|
const hasVideo = producerList.some((p) => p.kind === 'video');
|
|
25
25
|
const streamObj = remoteStream;
|
|
26
26
|
const hasVideoTrack = streamObj &&
|
|
@@ -147,7 +147,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
147
147
|
}
|
|
148
148
|
});
|
|
149
149
|
};
|
|
150
|
-
return (_jsxs(View, { style: styles.container, children: [showVideo && RTCViewComponent && streamURL ? (_jsx(RTCViewComponent, { streamURL: streamURL, stream: displayStream, style: styles.rtcView, objectFit: "contain", mirror: false }, `rtc-${trackCount}-${streamURL}`)) : (_jsxs(View, { style: styles.videoPlaceholder, children: [joining && _jsx(Text, { style: styles.placeholderText, children: "Joining..." }), error && _jsx(Text, { style: [styles.placeholderText, styles.errorText], children: error }), joined && !joining && !error && (_jsxs(_Fragment, { children: [_jsx(Text, { style: styles.liveBadge, children: "LIVE" }), _jsx(Text, { style: styles.placeholderText, children: stream.roomId }), hasVideo && webrtcUnavailable && (_jsxs(Text, { style: [styles.hintText, styles.warnText], children: ["Video needs a development build.", '\n', "Run: npx expo run:android"] })), hasVideo && consumeError && (_jsx(Text, { style: [styles.hintText, styles.errorText], children: consumeError })), hasVideo && !remoteStream && !webrtcUnavailable && !consumeError && (_jsx(Text, { style: styles.hintText, children: "Loading video..." })), !hasVideo && joined && producerList.length === 0 && (_jsxs(Text, { style: [styles.hintText, styles.warnText], children: ["Host has not started camera yet.", '\n', "Use a web client to stream, or wait for host to produce."] })), hasVideo && remoteStream && !RTCViewComponent && (_jsx(Text, { style: styles.hintText, children: "Video received (use dev build to see it)" })), hasVideo && remoteStream && RTCViewComponent && !hasVideoTrack && (_jsx(Text, { style: [styles.hintText, styles.warnText], children: "Audio only (no video track yet)" })), hasVideo && remoteStream && hasVideoTrack && !streamURL && (_jsx(Text, { style: [styles.hintText, styles.warnText], children: "Video track received but cannot display (need dev build with react-native-webrtc)" }))] }))] })), _jsxs(View, { style: styles.overlay, children: [_jsxs(View, { style: styles.topMeta, children: [_jsx(View, { style: styles.row, children: _jsxs(Text, { style: styles.roomId, numberOfLines: 1, children: ["LIVE \u00B7 ", stream.roomId] }) }), _jsxs(View, { style: styles.stats, children: [_jsxs(Text, { style: styles.statsText, children: ["\uD83D\uDC41 ", viewerCount] }), _jsxs(Text, { style: [styles.statsText, styles.statsTextSpacer], children: ["\u00B7 ", stream.producerCount, " track(s)"] })] })] }), _jsx(View, { style: styles.chatPanel, pointerEvents: "box-none", children: _jsx(KeyboardAvoidingView, { style: styles.chatKeyboardAvoider, behavior: Platform.OS === 'ios' ? 'padding' : 'height', keyboardVerticalOffset: 0, children: _jsxs(View, { style: styles.chatStack, children: [_jsx(View, { style: styles.chatListWrapper, children: _jsx(ScrollView, { ref: chatListRef, contentContainerStyle: styles.chatListContent, showsVerticalScrollIndicator: false, keyboardDismissMode: "on-drag", keyboardShouldPersistTaps: "always", nestedScrollEnabled: true, children: chatData.map((item) => (_jsxs(Text, { style: styles.chatLine, numberOfLines: 2, children: [_jsx(Text, { style: [styles.chatName, { color: getNameColor(item.displayName) }], children: item.displayName }), _jsxs(Text, { style: styles.chatText, children: [" ", item.text] })] }, item.id))) }) }), _jsxs(View, { style: styles.chatInputRow, children: [_jsx(TextInput, { style: styles.chatInput, value: chatInput, onChangeText: setChatInput, placeholder: joined ? 'Say something...' : 'Connecting...', placeholderTextColor: "#9ca3af", editable: joined && !joining && !error, onSubmitEditing: sendChat, returnKeyType: "send" }), _jsx(TouchableOpacity, { onPress: sendChat, disabled: !joined || joining || !!error || !chatInput.trim(), style: [
|
|
150
|
+
return (_jsxs(View, { style: styles.container, children: [showVideo && RTCViewComponent && streamURL ? (_jsx(RTCViewComponent, { streamURL: streamURL, stream: displayStream, style: styles.rtcView, objectFit: "contain", mirror: false }, `rtc-${trackCount}-${streamURL}`)) : (_jsxs(View, { style: styles.videoPlaceholder, children: [joining && _jsx(Text, { style: styles.placeholderText, children: "Joining..." }), error && _jsx(Text, { style: [styles.placeholderText, styles.errorText], children: error }), joined && !joining && !error && (_jsxs(_Fragment, { children: [_jsx(Text, { style: styles.liveBadge, children: "LIVE" }), _jsx(Text, { style: styles.placeholderText, children: stream.roomId }), hasVideo && webrtcUnavailable && (_jsxs(Text, { style: [styles.hintText, styles.warnText], children: ["Video needs a development build.", '\n', "Run: npx expo run:android"] })), hasVideo && consumeError && (_jsx(Text, { style: [styles.hintText, styles.errorText], children: consumeError })), hasVideo && !remoteStream && !webrtcUnavailable && !consumeError && (_jsx(Text, { style: styles.hintText, children: "Loading video..." })), !hasVideo && joined && producerList.length === 0 && (_jsxs(Text, { style: [styles.hintText, styles.warnText], children: ["Host has not started camera yet.", '\n', "Use a web client to stream, or wait for host to produce."] })), hasVideo && remoteStream && !RTCViewComponent && (_jsx(Text, { style: styles.hintText, children: "Video received (use dev build to see it)" })), hasVideo && remoteStream && RTCViewComponent && !hasVideoTrack && (_jsx(Text, { style: [styles.hintText, styles.warnText], children: "Audio only (no video track yet)" })), hasVideo && remoteStream && hasVideoTrack && !streamURL && (_jsx(Text, { style: [styles.hintText, styles.warnText], children: "Video track received but cannot display (need dev build with react-native-webrtc)" }))] }))] })), _jsxs(View, { style: styles.overlay, children: [_jsxs(View, { style: styles.topMeta, children: [_jsx(View, { style: styles.row, children: _jsxs(Text, { style: styles.roomId, numberOfLines: 1, children: ["LIVE \u00B7 ", stream.hostDisplayName || stream.title || stream.roomId] }) }), _jsxs(View, { style: styles.stats, children: [_jsxs(Text, { style: styles.statsText, children: ["\uD83D\uDC41 ", viewerCount] }), _jsxs(Text, { style: [styles.statsText, styles.statsTextSpacer], children: ["\u00B7 ", stream.producerCount, " track(s)"] })] })] }), _jsx(View, { style: styles.chatPanel, pointerEvents: "box-none", children: _jsx(KeyboardAvoidingView, { style: styles.chatKeyboardAvoider, behavior: Platform.OS === 'ios' ? 'padding' : 'height', keyboardVerticalOffset: 0, children: _jsxs(View, { style: styles.chatStack, children: [_jsx(View, { style: styles.chatListWrapper, children: _jsx(ScrollView, { ref: chatListRef, contentContainerStyle: styles.chatListContent, showsVerticalScrollIndicator: false, keyboardDismissMode: "on-drag", keyboardShouldPersistTaps: "always", nestedScrollEnabled: true, children: chatData.map((item) => (_jsxs(Text, { style: styles.chatLine, numberOfLines: 2, children: [_jsx(Text, { style: [styles.chatName, { color: getNameColor(item.displayName) }], children: item.displayName }), _jsxs(Text, { style: styles.chatText, children: [" ", item.text] })] }, item.id))) }) }), _jsxs(View, { style: styles.chatInputRow, children: [_jsx(TextInput, { style: styles.chatInput, value: chatInput, onChangeText: setChatInput, placeholder: joined ? 'Say something...' : 'Connecting...', placeholderTextColor: "#9ca3af", editable: joined && !joining && !error, onSubmitEditing: sendChat, returnKeyType: "send" }), _jsx(TouchableOpacity, { onPress: sendChat, disabled: !joined || joining || !!error || !chatInput.trim(), style: [
|
|
151
151
|
styles.chatSendButton,
|
|
152
152
|
(!joined || joining || !!error || !chatInput.trim()) && styles.chatSendButtonDisabled,
|
|
153
153
|
], children: _jsx(Text, { style: styles.chatSendText, children: "\u27A4" }) })] })] }) }) })] })] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useHostSocket.d.ts","sourceRoot":"","sources":["../../src/hooks/useHostSocket.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAYH,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,mBAAmB,CAAC;AAoB3B,MAAM,MAAM,oBAAoB,GAAG;IACjC,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG;IAC5C,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC,CAAC;AAMF,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,mBAAmB,
|
|
1
|
+
{"version":3,"file":"useHostSocket.d.ts","sourceRoot":"","sources":["../../src/hooks/useHostSocket.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAYH,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,mBAAmB,CAAC;AAoB3B,MAAM,MAAM,oBAAoB,GAAG;IACjC,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG;IAC5C,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC,CAAC;AAMF,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,mBAAmB,CAmlBrF"}
|
|
@@ -70,6 +70,12 @@ export function useHostSocket(options = {}) {
|
|
|
70
70
|
// FIX: stop screen tracks synchronously in stopLive() BEFORE calling
|
|
71
71
|
// fullCleanup(). By the time we reach here, hostSession.screenStream
|
|
72
72
|
// is already null.
|
|
73
|
+
// Step 0 — null streamURL immediately so the RTCView renderer detaches from the
|
|
74
|
+
// track on the UI thread *before* we start disposing EGL/capture resources below.
|
|
75
|
+
// Without this, the UI-thread surfaceViewRenderer.release() races the executor-thread
|
|
76
|
+
// TrackPrivate.dispose() whenever onDisconnect triggers fullCleanup mid-stream
|
|
77
|
+
// (e.g. unexpected socket drop while screen sharing), wedging the main thread.
|
|
78
|
+
patchHostState({ streamURL: null });
|
|
73
79
|
// Step 1 — transport first (calls transportClosed on producers, sets _closed=true)
|
|
74
80
|
const transportToClose = hostSession.sendTransport;
|
|
75
81
|
hostSession.sendTransport = null;
|
|
@@ -277,13 +283,14 @@ export function useHostSocket(options = {}) {
|
|
|
277
283
|
const stopScreenShare = useCallback(async () => {
|
|
278
284
|
if (!hostSession.screenStream)
|
|
279
285
|
return; // fullCleanup already ran, bail
|
|
286
|
+
// Detach the RTCView renderer FIRST (same logic as stopLive):
|
|
287
|
+
// Null streamURL so the UI-thread surfaceViewRenderer.release() fires against
|
|
288
|
+
// the still-live screen track. Only after a short yield do we release() the
|
|
289
|
+
// native capture. This prevents the EGL deadlock on the toggle/auto-revert path.
|
|
290
|
+
patchHostState({ streamURL: null });
|
|
291
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
280
292
|
const screenStreamToStop = hostSession.screenStream;
|
|
281
293
|
hostSession.screenStream = null;
|
|
282
|
-
// release() → track.release() → mediaStreamTrackRelease → TrackPrivate.dispose()
|
|
283
|
-
// → stopCapture() (returns true) + dispose() → videoCapturer.dispose()
|
|
284
|
-
// → ScreenCapturerAndroid.dispose() → mediaProjection.stop(). This is the only
|
|
285
|
-
// path that properly stops the MediaProjection; t.stop() only calls stopCapture()
|
|
286
|
-
// which is now a no-op to avoid blocking the executor.
|
|
287
294
|
try {
|
|
288
295
|
screenStreamToStop.release?.();
|
|
289
296
|
}
|
|
@@ -498,16 +505,34 @@ export function useHostSocket(options = {}) {
|
|
|
498
505
|
}, [fullCleanup, hostUserId]);
|
|
499
506
|
const stopLive = useCallback(async () => {
|
|
500
507
|
const { captureMode } = getHostState();
|
|
501
|
-
|
|
502
|
-
|
|
508
|
+
const wasScreen = captureMode === 'screen' && !!hostSession.screenStream;
|
|
509
|
+
if (wasScreen) {
|
|
510
|
+
// DETACH THE RENDERER FIRST, before disposing the native screen track/capturer.
|
|
511
|
+
//
|
|
512
|
+
// Root cause of the freeze (Flow B — navigate away then back, then Stop):
|
|
513
|
+
// When the Go-Live screen unmounts mid-stream, WebRTCView.onDetachedFromWindow()
|
|
514
|
+
// releases + reinitialises its EGL renderer. On Stop, two teardowns run in
|
|
515
|
+
// parallel on different threads:
|
|
516
|
+
// 1. UI thread: RTCView sees streamURL → null, calls surfaceViewRenderer.release()
|
|
517
|
+
// which blocks an EGL latch until the render thread drains.
|
|
518
|
+
// 2. Executor thread: screenStream.release() → TrackPrivate.dispose() tears down
|
|
519
|
+
// the VideoTrack, MediaSource, SurfaceTextureHelper, and EGL frame buffers.
|
|
520
|
+
// When they overlap the EGL latch never fires → main thread wedged → app freeze.
|
|
521
|
+
// The foreground service (MediaProjection) also never reaches abort() → lingering
|
|
522
|
+
// background process that requires Force Stop.
|
|
523
|
+
//
|
|
524
|
+
// Fix: null streamURL here (step A) so the UI thread releases the renderer against
|
|
525
|
+
// a still-live track — nothing to contend, latch resolves immediately. Only after a
|
|
526
|
+
// short yield (step B) do we dispose the native track/capturer (step C). No overlap,
|
|
527
|
+
// no deadlock, dispose() reaches MediaProjectionService.abort() and exits cleanly.
|
|
528
|
+
// Step A — detach RTCView renderer while the screen track is still alive.
|
|
529
|
+
patchHostState({ status: 'Stopping screen share...', streamURL: null });
|
|
530
|
+
// Step B — let React flush the streamURL change to native and the UI-thread
|
|
531
|
+
// renderer release() complete before we dispose EGL resources from the executor.
|
|
532
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
533
|
+
// Step C — dispose the native screen capture (executor thread, non-blocking).
|
|
503
534
|
const screenStream = hostSession.screenStream;
|
|
504
535
|
hostSession.screenStream = null; // guard: stopScreenShare() bails on re-entry
|
|
505
|
-
// release() calls TrackPrivate.dispose() → ScreenCaptureController.dispose()
|
|
506
|
-
// → MediaProjectionService.abort() + videoCapturer.dispose() which unblocks
|
|
507
|
-
// captureStopped.await() immediately. t.stop() alone does not call dispose(),
|
|
508
|
-
// so the background thread in stopCapture() blocks the latch for up to 60 s
|
|
509
|
-
// when peerConnectionDispose() already freed SurfaceTextureHelper (e.g. after
|
|
510
|
-
// the user navigates away and comes back to a fresh component mount).
|
|
511
536
|
try {
|
|
512
537
|
screenStream.release?.();
|
|
513
538
|
}
|
|
@@ -13,6 +13,8 @@ type ViewerSocketState = {
|
|
|
13
13
|
consumeError: string | null;
|
|
14
14
|
/** Incremented to trigger one auto-retry when recv transport fails (ICE). */
|
|
15
15
|
consumeRetryKey: number;
|
|
16
|
+
/** Real-time viewer count, updated via viewer-count-updated socket event. */
|
|
17
|
+
viewerCount: number;
|
|
16
18
|
};
|
|
17
19
|
/**
|
|
18
20
|
* Connects to the livestream signaling server and joins a room as viewer.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useViewerSocket.d.ts","sourceRoot":"","sources":["../../src/hooks/useViewerSocket.ts"],"names":[],"mappings":"AACA,OAAO,EAAM,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7D,KAAK,iBAAiB,GAAG;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC1C,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,iBAAiB,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,6EAA6E;IAC7E,eAAe,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"useViewerSocket.d.ts","sourceRoot":"","sources":["../../src/hooks/useViewerSocket.ts"],"names":[],"mappings":"AACA,OAAO,EAAM,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7D,KAAK,iBAAiB,GAAG;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC1C,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,iBAAiB,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,6EAA6E;IAC7E,eAAe,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,GAAG;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CA2dpG"}
|
|
@@ -20,6 +20,7 @@ export function useViewerSocket(roomId) {
|
|
|
20
20
|
webrtcUnavailable: false,
|
|
21
21
|
consumeError: null,
|
|
22
22
|
consumeRetryKey: 0,
|
|
23
|
+
viewerCount: 0,
|
|
23
24
|
});
|
|
24
25
|
const socketRef = useRef(null);
|
|
25
26
|
const roomIdRef = useRef(roomId);
|
|
@@ -61,6 +62,7 @@ export function useViewerSocket(roomId) {
|
|
|
61
62
|
webrtcUnavailable: false,
|
|
62
63
|
consumeError: null,
|
|
63
64
|
consumeRetryKey: 0,
|
|
65
|
+
viewerCount: 0,
|
|
64
66
|
});
|
|
65
67
|
consumeRetryCountRef.current = 0;
|
|
66
68
|
}, []);
|
|
@@ -156,14 +158,17 @@ export function useViewerSocket(roomId) {
|
|
|
156
158
|
if (Array.isArray(iceFromJoin) && iceFromJoin.length > 0) {
|
|
157
159
|
iceServersRef.current = iceFromJoin;
|
|
158
160
|
}
|
|
161
|
+
const roomState = res.roomState ?? null;
|
|
162
|
+
const initialViewerCount = typeof roomState?.viewerCount === 'number' ? roomState.viewerCount : 0;
|
|
159
163
|
setState((prev) => ({
|
|
160
164
|
...prev,
|
|
161
165
|
joining: false,
|
|
162
166
|
joined: true,
|
|
163
167
|
producerList: res.producerList ?? [],
|
|
164
|
-
roomState
|
|
168
|
+
roomState,
|
|
165
169
|
rtpCapabilities: res.rtpCapabilities ?? null,
|
|
166
170
|
error: null,
|
|
171
|
+
viewerCount: initialViewerCount,
|
|
167
172
|
}));
|
|
168
173
|
});
|
|
169
174
|
});
|
|
@@ -191,6 +196,11 @@ export function useViewerSocket(roomId) {
|
|
|
191
196
|
}
|
|
192
197
|
});
|
|
193
198
|
});
|
|
199
|
+
socket.on('viewer-count-updated', (payload) => {
|
|
200
|
+
if (typeof payload?.viewerCount === 'number') {
|
|
201
|
+
setState((prev) => ({ ...prev, viewerCount: payload.viewerCount }));
|
|
202
|
+
}
|
|
203
|
+
});
|
|
194
204
|
})();
|
|
195
205
|
return () => {
|
|
196
206
|
clearTimeout(joinTimeout);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"livestream.service.d.ts","sourceRoot":"","sources":["../../src/services/livestream.service.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,MAAM,MAAM,kBAAkB,GAC1B;IAAE,OAAO,EAAE,cAAc,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAC5C;IAAE,OAAO,EAAE,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"livestream.service.d.ts","sourceRoot":"","sources":["../../src/services/livestream.service.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,MAAM,MAAM,kBAAkB,GAC1B;IAAE,OAAO,EAAE,cAAc,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAC5C;IAAE,OAAO,EAAE,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAiBnC,MAAM,MAAM,qBAAqB,GAC7B;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAChC;IAAE,KAAK,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAErC,MAAM,MAAM,kBAAkB,GAC1B;IAAE,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAC/F;IAAE,MAAM,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CA+BtE;AAED,wBAAsB,YAAY,CAAC,MAAM,CAAC,EAAE;IAC1C,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAsC9B;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAyBjC;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAGD,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
|
|
@@ -8,7 +8,12 @@ function normalizeStreamRow(row) {
|
|
|
8
8
|
return null;
|
|
9
9
|
const viewerCount = typeof row?.viewerCount === 'number' ? row.viewerCount : typeof row?.viewer_count === 'number' ? row.viewer_count : 0;
|
|
10
10
|
const producerCount = typeof row?.producerCount === 'number' ? row.producerCount : typeof row?.producer_count === 'number' ? row.producer_count : 0;
|
|
11
|
-
|
|
11
|
+
const title = typeof row?.title === 'string' ? row.title : null;
|
|
12
|
+
const hostDisplayName = typeof row?.hostDisplayName === 'string' ? row.hostDisplayName :
|
|
13
|
+
typeof row?.host_display_name === 'string' ? row.host_display_name : null;
|
|
14
|
+
const hostUserId = typeof row?.hostUserId === 'string' ? row.hostUserId :
|
|
15
|
+
typeof row?.host_user_id === 'string' ? row.host_user_id : null;
|
|
16
|
+
return { roomId, viewerCount, producerCount, title, hostDisplayName, hostUserId };
|
|
12
17
|
}
|
|
13
18
|
export async function fetchActiveStreams() {
|
|
14
19
|
const base = getBaseUrl();
|
package/dist/types.d.ts
CHANGED
|
@@ -6,6 +6,9 @@ export type LiveStreamInfo = {
|
|
|
6
6
|
roomId: string;
|
|
7
7
|
viewerCount: number;
|
|
8
8
|
producerCount: number;
|
|
9
|
+
title?: string | null;
|
|
10
|
+
hostDisplayName?: string | null;
|
|
11
|
+
hostUserId?: string | null;
|
|
9
12
|
};
|
|
10
13
|
export type ProducerInfo = {
|
|
11
14
|
id: string;
|
|
@@ -27,6 +30,8 @@ export type RoomState = {
|
|
|
27
30
|
chat?: unknown[];
|
|
28
31
|
likes?: number;
|
|
29
32
|
viewerCount?: number;
|
|
33
|
+
cards?: unknown[];
|
|
34
|
+
shares?: number;
|
|
30
35
|
[key: string]: unknown;
|
|
31
36
|
};
|
|
32
37
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,YAAY,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC"}
|
package/package.json
CHANGED