kasunk99-livestream-core 0.3.31 → 0.3.33
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/LiveStreamFeed.d.ts +7 -1
- package/dist/components/LiveStreamFeed.d.ts.map +1 -1
- package/dist/components/LiveStreamFeed.js +2 -2
- package/dist/components/LiveStreamViewerItem.d.ts +2 -0
- package/dist/components/LiveStreamViewerItem.d.ts.map +1 -1
- package/dist/components/LiveStreamViewerItem.js +22 -2
- package/package.json +1 -1
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
type LiveStreamFeedProps = {
|
|
3
|
+
isScreenFocused?: boolean;
|
|
4
|
+
onLeave?: () => void;
|
|
5
|
+
onJoinStatusChange?: (joined: boolean) => void;
|
|
6
|
+
};
|
|
2
7
|
/**
|
|
3
8
|
* TikTok-style vertical feed: one stream per full-screen item.
|
|
4
9
|
* Height is measured from the actual container so it fills exactly to the
|
|
5
10
|
* navigation bar on every device — no hardcoded offsets.
|
|
6
11
|
* Scrolling snaps per-item (next/previous stream only).
|
|
7
12
|
*/
|
|
8
|
-
export declare function LiveStreamFeed(): React.ReactElement;
|
|
13
|
+
export declare function LiveStreamFeed({ isScreenFocused, onLeave, onJoinStatusChange, }?: LiveStreamFeedProps): React.ReactElement;
|
|
14
|
+
export {};
|
|
9
15
|
//# sourceMappingURL=LiveStreamFeed.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LiveStreamFeed.d.ts","sourceRoot":"","sources":["../../src/components/LiveStreamFeed.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAwC,MAAM,OAAO,CAAC;AAiB7D;;;;;GAKG;AACH,wBAAgB,cAAc,
|
|
1
|
+
{"version":3,"file":"LiveStreamFeed.d.ts","sourceRoot":"","sources":["../../src/components/LiveStreamFeed.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAwC,MAAM,OAAO,CAAC;AAiB7D,KAAK,mBAAmB,GAAG;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CAChD,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAC7B,eAAe,EACf,OAAO,EACP,kBAAkB,GACnB,GAAE,mBAAwB,GAAG,KAAK,CAAC,YAAY,CAsI/C"}
|
|
@@ -9,7 +9,7 @@ import { useLiveStreams } from '../hooks/useLiveStreams';
|
|
|
9
9
|
* navigation bar on every device — no hardcoded offsets.
|
|
10
10
|
* Scrolling snaps per-item (next/previous stream only).
|
|
11
11
|
*/
|
|
12
|
-
export function LiveStreamFeed() {
|
|
12
|
+
export function LiveStreamFeed({ isScreenFocused, onLeave, onJoinStatusChange, } = {}) {
|
|
13
13
|
const { streams, loading, error, refetch } = useLiveStreams();
|
|
14
14
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
15
15
|
const [refreshing, setRefreshing] = useState(false);
|
|
@@ -38,7 +38,7 @@ export function LiveStreamFeed() {
|
|
|
38
38
|
const index = Math.round(e.nativeEvent.contentOffset.y / viewportHeight);
|
|
39
39
|
setActiveIndex(Math.max(0, Math.min(index, streams.length - 1)));
|
|
40
40
|
}, [streams.length, viewportHeight]);
|
|
41
|
-
const renderItem = useCallback(({ item, index }) => (_jsx(View, { style: { height: viewportHeight, width: '100%' }, children: _jsx(LiveStreamViewerItem, { stream: item, isActive: index === activeIndex, index: index }) })), [activeIndex, viewportHeight]);
|
|
41
|
+
const renderItem = useCallback(({ item, index }) => (_jsx(View, { style: { height: viewportHeight, width: '100%' }, children: _jsx(LiveStreamViewerItem, { stream: item, isActive: index === activeIndex && (isScreenFocused ?? true), index: index, onLeave: onLeave, onJoinStatusChange: index === activeIndex ? onJoinStatusChange : undefined }) })), [activeIndex, viewportHeight, isScreenFocused, onLeave, onJoinStatusChange]);
|
|
42
42
|
const keyExtractor = useCallback((item) => item.roomId, []);
|
|
43
43
|
const getItemLayout = useCallback((_, index) => ({
|
|
44
44
|
length: viewportHeight,
|
|
@@ -4,6 +4,8 @@ type LiveStreamViewerItemProps = {
|
|
|
4
4
|
stream: LiveStreamInfo;
|
|
5
5
|
isActive: boolean;
|
|
6
6
|
index: number;
|
|
7
|
+
onLeave?: () => void;
|
|
8
|
+
onJoinStatusChange?: (joined: boolean) => void;
|
|
7
9
|
};
|
|
8
10
|
export declare const LiveStreamViewerItem: React.NamedExoticComponent<LiveStreamViewerItemProps>;
|
|
9
11
|
export {};
|
|
@@ -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;
|
|
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"}
|
|
@@ -41,7 +41,7 @@ try {
|
|
|
41
41
|
KeyboardAvoidingView = rnkc.KeyboardAvoidingView;
|
|
42
42
|
}
|
|
43
43
|
catch { }
|
|
44
|
-
export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream, isActive, index: _index, }) {
|
|
44
|
+
export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream, isActive, index: _index, onLeave, onJoinStatusChange, }) {
|
|
45
45
|
const roomId = isActive ? stream.roomId : null;
|
|
46
46
|
const { joined, joining, error, producerList, roomState, remoteStream, remoteVideoStream, webrtcUnavailable, consumeError, socket, viewerCount: liveViewerCount, streamEnded, } = useViewerSocket(roomId);
|
|
47
47
|
const viewerCount = liveViewerCount > 0 ? liveViewerCount : stream.viewerCount;
|
|
@@ -71,6 +71,9 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
71
71
|
const sub = Keyboard.addListener('keyboardDidHide', () => setIsTyping(false));
|
|
72
72
|
return () => sub.remove();
|
|
73
73
|
}, []);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
onJoinStatusChange?.(joined);
|
|
76
|
+
}, [joined, onJoinStatusChange]);
|
|
74
77
|
// Dismiss input when stream becomes inactive (e.g. user swipes to next stream)
|
|
75
78
|
useEffect(() => {
|
|
76
79
|
if (!isActive) {
|
|
@@ -222,7 +225,9 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
222
225
|
const avatarLetter = hostLabel[0]?.toUpperCase() ?? 'L';
|
|
223
226
|
const hasUnsent = chatInput.trim().length > 0;
|
|
224
227
|
const canType = joined && !joining && !error;
|
|
225
|
-
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.
|
|
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
|
+
? _jsx(IoniconsComponent, { name: "close", size: 20, color: "#fff" })
|
|
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
|
|
226
231
|
? _jsx(IoniconsComponent, { name: "send", size: 18, color: "#fff" })
|
|
227
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
|
|
228
233
|
? _jsx(IoniconsComponent, { name: "send", size: 18, color: "#fff" })
|
|
@@ -272,6 +277,21 @@ const styles = StyleSheet.create({
|
|
|
272
277
|
marginRight: 8,
|
|
273
278
|
overflow: 'hidden',
|
|
274
279
|
},
|
|
280
|
+
topRight: {
|
|
281
|
+
flexDirection: 'row',
|
|
282
|
+
alignItems: 'center',
|
|
283
|
+
gap: 6,
|
|
284
|
+
flexShrink: 0,
|
|
285
|
+
},
|
|
286
|
+
closeBtn: {
|
|
287
|
+
width: 32,
|
|
288
|
+
height: 32,
|
|
289
|
+
borderRadius: 16,
|
|
290
|
+
backgroundColor: 'rgba(0,0,0,0.45)',
|
|
291
|
+
alignItems: 'center',
|
|
292
|
+
justifyContent: 'center',
|
|
293
|
+
flexShrink: 0,
|
|
294
|
+
},
|
|
275
295
|
avatar: {
|
|
276
296
|
width: 36,
|
|
277
297
|
height: 36,
|
package/package.json
CHANGED