kasunk99-livestream-core 0.3.6 → 0.3.8
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.d.ts.map +1 -1
- package/dist/components/LiveStreamViewerItem.js +25 -2
- package/dist/hooks/useHostSocket.d.ts.map +1 -1
- package/dist/hooks/useHostSocket.js +15 -1
- package/dist/hooks/useViewerSocket.d.ts +2 -0
- package/dist/hooks/useViewerSocket.d.ts.map +1 -1
- package/dist/hooks/useViewerSocket.js +8 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LiveStreamViewerItem.d.ts","sourceRoot":"","sources":["../../src/components/LiveStreamViewerItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqD,MAAM,OAAO,CAAC;AAe1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAK/C,KAAK,yBAAyB,GAAG;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAgBF;;GAEG;AACH,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"LiveStreamViewerItem.d.ts","sourceRoot":"","sources":["../../src/components/LiveStreamViewerItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqD,MAAM,OAAO,CAAC;AAe1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAK/C,KAAK,yBAAyB,GAAG;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAgBF;;GAEG;AACH,eAAO,MAAM,oBAAoB,uDAgX/B,CAAC"}
|
|
@@ -17,7 +17,7 @@ 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, viewerCount: liveViewerCount, } = useViewerSocket(roomId);
|
|
20
|
+
const { joined, joining, error, producerList, roomState, remoteStream, remoteVideoStream, webrtcUnavailable, consumeError, socket, viewerCount: liveViewerCount, streamEnded, } = useViewerSocket(roomId);
|
|
21
21
|
// Prefer real-time socket count (updated on every join/leave event);
|
|
22
22
|
// fall back to the HTTP-polled value from the feed for the initial render.
|
|
23
23
|
const viewerCount = liveViewerCount > 0 ? liveViewerCount : stream.viewerCount;
|
|
@@ -133,9 +133,21 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
133
133
|
return next;
|
|
134
134
|
});
|
|
135
135
|
};
|
|
136
|
+
const onViewerJoined = (msg) => {
|
|
137
|
+
const m = msg;
|
|
138
|
+
const name = typeof m.displayName === 'string' ? m.displayName : 'Someone';
|
|
139
|
+
const timestamp = Date.now();
|
|
140
|
+
const id = `${roomId}-join-${String(m.peerId ?? timestamp)}-${timestamp}`;
|
|
141
|
+
setChatMessages((prev) => [
|
|
142
|
+
...prev,
|
|
143
|
+
{ id, displayName: '', text: `${name} joined`, timestamp },
|
|
144
|
+
]);
|
|
145
|
+
};
|
|
136
146
|
socket.on('chat-message', onChatMessage);
|
|
147
|
+
socket.on('viewer-joined', onViewerJoined);
|
|
137
148
|
return () => {
|
|
138
149
|
socket.off('chat-message', onChatMessage);
|
|
150
|
+
socket.off('viewer-joined', onViewerJoined);
|
|
139
151
|
};
|
|
140
152
|
}, [isActive, roomId, socket]);
|
|
141
153
|
// Render messages in natural order (oldest -> newest).
|
|
@@ -208,13 +220,24 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
208
220
|
return (_jsxs(View, { style: styles.container, children: [showVideo && RTCViewComponent && streamURL ? (_jsx(RTCViewComponent, { streamURL: streamURL, stream: displayStream, style: styles.rtcView, objectFit: "cover", 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] }) }), _jsx(View, { style: styles.stats, children: _jsxs(Text, { style: styles.statsText, children: ["\uD83D\uDC41 ", viewerCount] }) })] }), _jsx(Animated.View, { style: [styles.chatPanel, { bottom: chatBottomAnim }], pointerEvents: "box-none", children: _jsxs(ScrollView, { scrollEnabled: false, keyboardShouldPersistTaps: "always", contentContainerStyle: 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: [
|
|
209
221
|
styles.chatSendButton,
|
|
210
222
|
(!joined || joining || !!error || !chatInput.trim()) && styles.chatSendButtonDisabled,
|
|
211
|
-
], children: _jsx(Text, { style: styles.chatSendText, children: "\u27A4" }) })] })] }) })] })] }));
|
|
223
|
+
], children: _jsx(Text, { style: styles.chatSendText, children: "\u27A4" }) })] })] }) })] }), streamEnded && (_jsx(View, { style: styles.streamEndedOverlay, children: _jsx(Text, { style: styles.streamEndedText, children: "Live stream ended" }) }))] }));
|
|
212
224
|
});
|
|
213
225
|
const styles = StyleSheet.create({
|
|
214
226
|
container: {
|
|
215
227
|
flex: 1,
|
|
216
228
|
backgroundColor: '#0a0a0a',
|
|
217
229
|
},
|
|
230
|
+
streamEndedOverlay: {
|
|
231
|
+
...StyleSheet.absoluteFillObject,
|
|
232
|
+
backgroundColor: 'rgba(0,0,0,0.82)',
|
|
233
|
+
alignItems: 'center',
|
|
234
|
+
justifyContent: 'center',
|
|
235
|
+
},
|
|
236
|
+
streamEndedText: {
|
|
237
|
+
color: '#ffffff',
|
|
238
|
+
fontSize: 20,
|
|
239
|
+
fontWeight: '600',
|
|
240
|
+
},
|
|
218
241
|
rtcView: {
|
|
219
242
|
...StyleSheet.absoluteFillObject,
|
|
220
243
|
backgroundColor: '#0a0a0a',
|
|
@@ -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,CAgrBrF"}
|
|
@@ -214,8 +214,22 @@ export function useHostSocket(options = {}) {
|
|
|
214
214
|
const id = `${String(m.peerId ?? 'p')}-${timestamp}-${text.slice(0, 6)}`;
|
|
215
215
|
setChatMessages((prev) => [...prev.slice(-99), { id, displayName, text, timestamp }]);
|
|
216
216
|
};
|
|
217
|
+
const onViewerJoined = (msg) => {
|
|
218
|
+
const m = msg;
|
|
219
|
+
const name = typeof m.displayName === 'string' ? m.displayName : 'Someone';
|
|
220
|
+
const timestamp = Date.now();
|
|
221
|
+
const id = `join-${String(m.peerId ?? timestamp)}-${timestamp}`;
|
|
222
|
+
setChatMessages((prev) => [
|
|
223
|
+
...prev.slice(-99),
|
|
224
|
+
{ id, displayName: '', text: `${name} joined`, timestamp },
|
|
225
|
+
]);
|
|
226
|
+
};
|
|
217
227
|
socket.on('chat-message', onChatMessage);
|
|
218
|
-
|
|
228
|
+
socket.on('viewer-joined', onViewerJoined);
|
|
229
|
+
return () => {
|
|
230
|
+
socket.off('chat-message', onChatMessage);
|
|
231
|
+
socket.off('viewer-joined', onViewerJoined);
|
|
232
|
+
};
|
|
219
233
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
220
234
|
}, [state.live]);
|
|
221
235
|
// ── Permissions ────────────────────────────────────────────────────────────
|
|
@@ -15,6 +15,8 @@ type ViewerSocketState = {
|
|
|
15
15
|
consumeRetryKey: number;
|
|
16
16
|
/** Real-time viewer count, updated via viewer-count-updated socket event. */
|
|
17
17
|
viewerCount: number;
|
|
18
|
+
/** True once the host disconnects — triggers the "stream ended" overlay. */
|
|
19
|
+
streamEnded: boolean;
|
|
18
20
|
};
|
|
19
21
|
/**
|
|
20
22
|
* 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;IACxB,6EAA6E;IAC7E,WAAW,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;IACpB,4EAA4E;IAC5E,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,GAAG;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CA2gBpG"}
|
|
@@ -21,6 +21,7 @@ export function useViewerSocket(roomId) {
|
|
|
21
21
|
consumeError: null,
|
|
22
22
|
consumeRetryKey: 0,
|
|
23
23
|
viewerCount: 0,
|
|
24
|
+
streamEnded: false,
|
|
24
25
|
});
|
|
25
26
|
const socketRef = useRef(null);
|
|
26
27
|
const roomIdRef = useRef(roomId);
|
|
@@ -62,6 +63,7 @@ export function useViewerSocket(roomId) {
|
|
|
62
63
|
consumeError: null,
|
|
63
64
|
consumeRetryKey: 0,
|
|
64
65
|
viewerCount: 0,
|
|
66
|
+
streamEnded: false,
|
|
65
67
|
});
|
|
66
68
|
consumeRetryCountRef.current = 0;
|
|
67
69
|
}, []);
|
|
@@ -87,7 +89,7 @@ export function useViewerSocket(roomId) {
|
|
|
87
89
|
setState((prev) => ({ ...prev, error: 'Livestream server not configured', joining: false }));
|
|
88
90
|
return;
|
|
89
91
|
}
|
|
90
|
-
setState((prev) => ({ ...prev, joining: true, error: null }));
|
|
92
|
+
setState((prev) => ({ ...prev, joining: true, error: null, streamEnded: false }));
|
|
91
93
|
const JOIN_TIMEOUT_MS = 12000;
|
|
92
94
|
const joinTimeout = setTimeout(() => {
|
|
93
95
|
if (roomIdRef.current !== roomId)
|
|
@@ -201,6 +203,11 @@ export function useViewerSocket(roomId) {
|
|
|
201
203
|
setState((prev) => ({ ...prev, viewerCount: payload.viewerCount }));
|
|
202
204
|
}
|
|
203
205
|
});
|
|
206
|
+
socket.on('peer-left', (payload) => {
|
|
207
|
+
if (payload?.isHost) {
|
|
208
|
+
setState((prev) => ({ ...prev, streamEnded: true, producerList: [] }));
|
|
209
|
+
}
|
|
210
|
+
});
|
|
204
211
|
socket.on('system-audio-chunk', (payload) => {
|
|
205
212
|
const b64 = typeof payload === 'string' ? payload
|
|
206
213
|
: typeof payload?.data === 'string'
|
package/package.json
CHANGED