kasunk99-livestream-core 0.3.32 → 0.3.34
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LiveStreamViewerItem.d.ts","sourceRoot":"","sources":["../../src/components/LiveStreamViewerItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqD,MAAM,OAAO,CAAC;AAiB1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAM/C,KAAK,yBAAyB,GAAG;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CAChD,CAAC;AAmEF,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"LiveStreamViewerItem.d.ts","sourceRoot":"","sources":["../../src/components/LiveStreamViewerItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqD,MAAM,OAAO,CAAC;AAiB1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAM/C,KAAK,yBAAyB,GAAG;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CAChD,CAAC;AAmEF,eAAO,MAAM,oBAAoB,uDA2Z/B,CAAC"}
|
|
@@ -225,9 +225,9 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
225
225
|
const avatarLetter = hostLabel[0]?.toUpperCase() ?? 'L';
|
|
226
226
|
const hasUnsent = chatInput.trim().length > 0;
|
|
227
227
|
const canType = joined && !joining && !error;
|
|
228
|
-
return (_jsxs(KeyboardAvoidingView, { style: styles.container, behavior: "padding", automaticOffset: true, children: [showVideo && RTCViewComponent && streamURL ? (_jsx(RTCViewComponent, { streamURL: streamURL, stream: displayStream, style: StyleSheet.absoluteFillObject, objectFit: "cover", mirror: false, pointerEvents: "none" }, `rtc-${trackCount}-${streamURL}`)) : (_jsxs(View, { style: [StyleSheet.absoluteFillObject, styles.videoPlaceholder], children: [joining && _jsx(ActivityIndicator, { size: "large", color: "rgba(255,255,255,0.6)" }), !joining && error && _jsx(Text, { style: styles.placeholderError, children: error }), joined && !joining && !error && hasVideo && !remoteStream && (_jsx(ActivityIndicator, { size: "large", color: "rgba(255,255,255,0.45)" })), joined && !joining && !error && hasVideo && webrtcUnavailable && (_jsx(Text, { style: styles.placeholderHint, children: "Video needs a development build" })), joined && !joining && !error && hasVideo && consumeError && (_jsx(Text, { style: styles.placeholderError, children: consumeError }))] })), LinearGradient ? (_jsx(LinearGradient, { colors: ['rgba(0,0,0,0.70)', 'rgba(0,0,0,0.0)', 'rgba(0,0,0,0.0)', 'rgba(0,0,0,0.78)'], locations: [0, 0.28, 0.48, 1], style: StyleSheet.absoluteFillObject, pointerEvents: "none" })) : null, _jsxs(View, { style: styles.topBar, pointerEvents: "box-none", children: [_jsxs(View, { style: styles.hostRow, pointerEvents: "box-none", children: [onLeave ? (_jsx(TouchableOpacity, { style: styles.closeBtn, onPress: onLeave, hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, activeOpacity: 0.7, children: IoniconsComponent
|
|
228
|
+
return (_jsxs(KeyboardAvoidingView, { style: styles.container, behavior: "padding", automaticOffset: true, children: [showVideo && RTCViewComponent && streamURL ? (_jsx(RTCViewComponent, { streamURL: streamURL, stream: displayStream, style: StyleSheet.absoluteFillObject, objectFit: "cover", mirror: false, pointerEvents: "none" }, `rtc-${trackCount}-${streamURL}`)) : (_jsxs(View, { style: [StyleSheet.absoluteFillObject, styles.videoPlaceholder], children: [joining && _jsx(ActivityIndicator, { size: "large", color: "rgba(255,255,255,0.6)" }), !joining && error && _jsx(Text, { style: styles.placeholderError, children: error }), joined && !joining && !error && hasVideo && !remoteStream && (_jsx(ActivityIndicator, { size: "large", color: "rgba(255,255,255,0.45)" })), joined && !joining && !error && hasVideo && webrtcUnavailable && (_jsx(Text, { style: styles.placeholderHint, children: "Video needs a development build" })), joined && !joining && !error && hasVideo && consumeError && (_jsx(Text, { style: styles.placeholderError, children: consumeError }))] })), LinearGradient ? (_jsx(LinearGradient, { colors: ['rgba(0,0,0,0.70)', 'rgba(0,0,0,0.0)', 'rgba(0,0,0,0.0)', 'rgba(0,0,0,0.78)'], locations: [0, 0.28, 0.48, 1], style: StyleSheet.absoluteFillObject, pointerEvents: "none" })) : null, _jsxs(View, { style: styles.topBar, pointerEvents: "box-none", children: [_jsxs(View, { style: styles.hostRow, pointerEvents: "none", children: [_jsx(View, { style: styles.avatar, children: _jsx(Text, { style: styles.avatarLetter, children: avatarLetter }) }), _jsx(Text, { style: styles.hostName, numberOfLines: 1, children: hostLabel }), _jsx(View, { style: styles.livePill, children: _jsx(Text, { style: styles.livePillText, children: "LIVE" }) })] }), _jsxs(View, { style: styles.topRight, pointerEvents: "box-none", children: [_jsxs(View, { style: styles.viewerChip, pointerEvents: "none", children: [_jsx(Text, { style: styles.viewerEye, children: "\uD83D\uDC41" }), _jsx(Text, { style: styles.viewerCount, children: viewerCount > 0 ? viewerCount.toLocaleString() : '—' })] }), onLeave ? (_jsx(TouchableOpacity, { style: styles.closeBtn, onPress: onLeave, hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, activeOpacity: 0.7, children: IoniconsComponent
|
|
229
229
|
? _jsx(IoniconsComponent, { name: "close", size: 20, color: "#fff" })
|
|
230
|
-
: _jsx(Text, { style: { color: '#fff', fontSize: 18, fontWeight: '700' }, children: "\u00D7" }) })) : null
|
|
230
|
+
: _jsx(Text, { style: { color: '#fff', fontSize: 18, fontWeight: '700' }, children: "\u00D7" }) })) : null] })] }), _jsx(View, { style: styles.spacer, pointerEvents: "none" }), _jsx(ScrollView, { ref: chatListRef, style: styles.chatList, contentContainerStyle: styles.chatContent, showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "always", children: chatData.map((item) => item.displayName ? (_jsx(View, { style: styles.chatBubble, children: _jsxs(Text, { style: styles.chatLine, numberOfLines: 3, children: [_jsxs(Text, { style: [styles.chatUsername, { color: getNameColor(item.displayName) }], children: [item.displayName, ' '] }), _jsx(Text, { style: styles.chatMsg, children: item.text })] }) }, item.id)) : (_jsx(View, { style: styles.joinBubble, children: _jsx(Text, { style: styles.joinText, children: item.text }) }, item.id))) }), isTyping ? (_jsxs(View, { style: styles.inputBar, children: [_jsx(TextInput, { ref: textInputRef, style: styles.textInput, value: chatInput, onChangeText: (text) => { chatInputRef.current = text; setChatInput(text); }, placeholder: "Say something...", placeholderTextColor: "rgba(255,255,255,0.40)", editable: canType, onSubmitEditing: sendChat, returnKeyType: "send", submitBehavior: "submit", autoFocus: true }), RNGHTouchable ? (_jsx(RNGHTouchable, { onPress: sendChat, activeOpacity: 0.75, style: [styles.sendBtn, (!hasUnsent || !joined) && styles.sendBtnOff], hitSlop: { top: 8, right: 8, bottom: 8, left: 8 }, children: IoniconsComponent
|
|
231
231
|
? _jsx(IoniconsComponent, { name: "send", size: 18, color: "#fff" })
|
|
232
232
|
: _jsx(Text, { style: styles.sendIcon, children: "\u27A4" }) })) : (_jsx(Pressable, { style: [styles.sendBtn, (!hasUnsent || !joined) && styles.sendBtnOff], onPressIn: sendChat, hitSlop: { top: 8, right: 8, bottom: 8, left: 8 }, children: IoniconsComponent
|
|
233
233
|
? _jsx(IoniconsComponent, { name: "send", size: 18, color: "#fff" })
|
|
@@ -277,6 +277,12 @@ const styles = StyleSheet.create({
|
|
|
277
277
|
marginRight: 8,
|
|
278
278
|
overflow: 'hidden',
|
|
279
279
|
},
|
|
280
|
+
topRight: {
|
|
281
|
+
flexDirection: 'row',
|
|
282
|
+
alignItems: 'center',
|
|
283
|
+
gap: 6,
|
|
284
|
+
flexShrink: 0,
|
|
285
|
+
},
|
|
280
286
|
closeBtn: {
|
|
281
287
|
width: 32,
|
|
282
288
|
height: 32,
|
|
@@ -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,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG;IAC5C,YAAY,EAAE,eAAe,EAAE,CAAC;IAChC,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,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,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG;IAC5C,YAAY,EAAE,eAAe,EAAE,CAAC;IAChC,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,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,CAirBrF"}
|
|
@@ -310,20 +310,19 @@ export function useHostSocket(options = {}) {
|
|
|
310
310
|
patchHostState({ isMicMuted: !newEnabled });
|
|
311
311
|
}, []);
|
|
312
312
|
// ── Screen share ───────────────────────────────────────────────────────────
|
|
313
|
-
//
|
|
314
|
-
// replaceTrack() is unreliable when switching between different capture types
|
|
315
|
-
// (camera → screen). Close the existing video producer and publish a new one
|
|
316
|
-
// so the server emits new-producer to viewers, who then re-consume the correct
|
|
317
|
-
// feed automatically.
|
|
318
313
|
const replaceVideoProducer = useCallback(async (newTrack) => {
|
|
319
314
|
const transport = hostSession.sendTransport;
|
|
320
315
|
if (!transport || transport.closed)
|
|
321
316
|
return; // preview-only, no transport yet
|
|
322
|
-
|
|
323
|
-
|
|
317
|
+
// If a producer already exists, swap its track in-place.
|
|
318
|
+
// replaceTrack() keeps the same server-side producer ID so viewers
|
|
319
|
+
// automatically receive the new content without needing to re-consume.
|
|
320
|
+
const existingProducer = hostSession.videoProducer;
|
|
321
|
+
if (existingProducer && !existingProducer.closed) {
|
|
322
|
+
await existingProducer.replaceTrack({ track: newTrack });
|
|
323
|
+
return;
|
|
324
324
|
}
|
|
325
|
-
|
|
326
|
-
hostSession.videoProducer = null;
|
|
325
|
+
// No active producer — publish a fresh one (first publish or post-cleanup).
|
|
327
326
|
const producer = await transport.produce({
|
|
328
327
|
track: newTrack,
|
|
329
328
|
encodings: [{ maxBitrate: 2500000 }],
|
|
@@ -420,28 +419,27 @@ export function useHostSocket(options = {}) {
|
|
|
420
419
|
hostSession.screenStream = screenStream;
|
|
421
420
|
const screenAudioTrack = screenStream.getAudioTracks()[0] ?? null;
|
|
422
421
|
if (Platform.OS === 'android') {
|
|
423
|
-
|
|
424
|
-
|
|
422
|
+
patchHostState({ streamURL: getStreamURL(screenStream), captureMode: 'screen' });
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
// iOS: keep camera hardware running — RPScreenRecorder captures the app's
|
|
426
|
+
// own screen, so the camera preview must stay alive to provide content.
|
|
427
|
+
patchHostState({ captureMode: 'screen', streamURL: getStreamURL(hostSession.localStream) });
|
|
428
|
+
}
|
|
429
|
+
// Replace the video producer track BEFORE stopping camera hardware.
|
|
430
|
+
// replaceTrack() must locate the existing RTP sender — stopping the camera
|
|
431
|
+
// track first can invalidate the sender reference on some Android devices,
|
|
432
|
+
// causing the replacement to silently fail and viewers to see a black screen.
|
|
433
|
+
await replaceVideoProducer(screenVideoTrack);
|
|
434
|
+
// Android only: safe to stop the camera now that replaceTrack() has
|
|
435
|
+
// redirected the sender to the screen capture track.
|
|
436
|
+
if (Platform.OS === 'android') {
|
|
425
437
|
hostSession.localStream?.getVideoTracks()
|
|
426
438
|
?.forEach((t) => { try {
|
|
427
439
|
t.stop?.();
|
|
428
440
|
}
|
|
429
441
|
catch { /* ignore */ } });
|
|
430
|
-
// Point the RTCView preview at the screen capture stream.
|
|
431
|
-
patchHostState({ streamURL: getStreamURL(screenStream), captureMode: 'screen' });
|
|
432
442
|
}
|
|
433
|
-
else {
|
|
434
|
-
// iOS: keep camera hardware running.
|
|
435
|
-
// RPScreenRecorder captures the app's own screen; if we stopped the camera
|
|
436
|
-
// first, the app would display black and that black frame would be captured
|
|
437
|
-
// → host and viewers both see a black screen. Keeping the camera alive
|
|
438
|
-
// means the host's RTCView still shows the camera preview while the
|
|
439
|
-
// WebRTC producer (replaced below) delivers screen content to viewers.
|
|
440
|
-
patchHostState({ captureMode: 'screen' });
|
|
441
|
-
// Update the preview URL so the RTCView keeps showing the local camera stream.
|
|
442
|
-
patchHostState({ streamURL: getStreamURL(hostSession.localStream) });
|
|
443
|
-
}
|
|
444
|
-
await replaceVideoProducer(screenVideoTrack);
|
|
445
443
|
if (screenAudioTrack && hostSession.sendTransport && !hostSession.sendTransport.closed) {
|
|
446
444
|
try {
|
|
447
445
|
const screenAudioProducer = await hostSession.sendTransport.produce({ track: screenAudioTrack });
|
package/package.json
CHANGED