kasunk99-livestream-core 0.3.30 → 0.3.32
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 +30 -3
- 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,uDAyZ/B,CAAC"}
|
|
@@ -23,6 +23,17 @@ try {
|
|
|
23
23
|
IoniconsComponent = vi.Ionicons;
|
|
24
24
|
}
|
|
25
25
|
catch { }
|
|
26
|
+
// RNGH TouchableOpacity — fires at the native gesture layer (GestureHandlerRootView is
|
|
27
|
+
// outermost in _layout.tsx, above KeyboardProvider), so it claims the touch before
|
|
28
|
+
// RNKC's InputMethodManager intercept drops it. This is why onPressIn/Pressable fail
|
|
29
|
+
// but this component works for the send button when the keyboard is open.
|
|
30
|
+
let RNGHTouchable = null;
|
|
31
|
+
try {
|
|
32
|
+
const rgh = require('react-native-gesture-handler');
|
|
33
|
+
if (rgh.TouchableOpacity)
|
|
34
|
+
RNGHTouchable = rgh.TouchableOpacity;
|
|
35
|
+
}
|
|
36
|
+
catch { }
|
|
26
37
|
let KeyboardAvoidingView = RNKeyboardAvoidingView;
|
|
27
38
|
try {
|
|
28
39
|
const rnkc = require('react-native-keyboard-controller');
|
|
@@ -30,7 +41,7 @@ try {
|
|
|
30
41
|
KeyboardAvoidingView = rnkc.KeyboardAvoidingView;
|
|
31
42
|
}
|
|
32
43
|
catch { }
|
|
33
|
-
export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream, isActive, index: _index, }) {
|
|
44
|
+
export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream, isActive, index: _index, onLeave, onJoinStatusChange, }) {
|
|
34
45
|
const roomId = isActive ? stream.roomId : null;
|
|
35
46
|
const { joined, joining, error, producerList, roomState, remoteStream, remoteVideoStream, webrtcUnavailable, consumeError, socket, viewerCount: liveViewerCount, streamEnded, } = useViewerSocket(roomId);
|
|
36
47
|
const viewerCount = liveViewerCount > 0 ? liveViewerCount : stream.viewerCount;
|
|
@@ -60,6 +71,9 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
60
71
|
const sub = Keyboard.addListener('keyboardDidHide', () => setIsTyping(false));
|
|
61
72
|
return () => sub.remove();
|
|
62
73
|
}, []);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
onJoinStatusChange?.(joined);
|
|
76
|
+
}, [joined, onJoinStatusChange]);
|
|
63
77
|
// Dismiss input when stream becomes inactive (e.g. user swipes to next stream)
|
|
64
78
|
useEffect(() => {
|
|
65
79
|
if (!isActive) {
|
|
@@ -211,9 +225,13 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
211
225
|
const avatarLetter = hostLabel[0]?.toUpperCase() ?? 'L';
|
|
212
226
|
const hasUnsent = chatInput.trim().length > 0;
|
|
213
227
|
const canType = joined && !joining && !error;
|
|
214
|
-
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: [
|
|
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
|
|
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.avatar, pointerEvents: "none", children: _jsx(Text, { style: styles.avatarLetter, children: avatarLetter }) }), _jsx(Text, { style: styles.hostName, numberOfLines: 1, pointerEvents: "none", children: hostLabel }), _jsx(View, { style: styles.livePill, pointerEvents: "none", children: _jsx(Text, { style: styles.livePillText, children: "LIVE" }) })] }), _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() : '—' })] })] }), _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
|
+
? _jsx(IoniconsComponent, { name: "send", size: 18, color: "#fff" })
|
|
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
|
|
215
233
|
? _jsx(IoniconsComponent, { name: "send", size: 18, color: "#fff" })
|
|
216
|
-
: _jsx(Text, { style: styles.sendIcon, children: "\u27A4" }) })] })) : (
|
|
234
|
+
: _jsx(Text, { style: styles.sendIcon, children: "\u27A4" }) }))] })) : (
|
|
217
235
|
// Pill bar — tapping opens the input bar.
|
|
218
236
|
_jsxs(View, { style: styles.bottomBar, children: [_jsx(TouchableOpacity, { style: [styles.typeTouchable, hasUnsent && styles.typeTouchableFilled], onPress: () => { if (canType)
|
|
219
237
|
setIsTyping(true); }, activeOpacity: 0.7, children: _jsx(Text, { style: [styles.typePlaceholder, hasUnsent && styles.typePlaceholderFilled], numberOfLines: 1, children: hasUnsent ? chatInput : (joined && !joining ? 'Say something...' : 'Connecting...') }) }), _jsx(TouchableOpacity, { style: styles.bottomIconBtn, activeOpacity: 0.7, children: _jsx(Text, { style: styles.bottomIconGlyph, children: "\u263A" }) }), _jsx(TouchableOpacity, { style: styles.bottomIconBtn, activeOpacity: 0.7, children: _jsx(Text, { style: [styles.bottomIconGlyph, styles.heartIcon], children: "\u2665" }) })] })), streamEnded && (_jsx(View, { style: styles.endedOverlay, children: _jsxs(View, { style: styles.endedCard, children: [_jsx(Text, { style: styles.endedTitle, children: "Stream ended" }), _jsx(Text, { style: styles.endedSub, children: "The host has ended this live stream" })] }) }))] }));
|
|
@@ -259,6 +277,15 @@ const styles = StyleSheet.create({
|
|
|
259
277
|
marginRight: 8,
|
|
260
278
|
overflow: 'hidden',
|
|
261
279
|
},
|
|
280
|
+
closeBtn: {
|
|
281
|
+
width: 32,
|
|
282
|
+
height: 32,
|
|
283
|
+
borderRadius: 16,
|
|
284
|
+
backgroundColor: 'rgba(0,0,0,0.45)',
|
|
285
|
+
alignItems: 'center',
|
|
286
|
+
justifyContent: 'center',
|
|
287
|
+
flexShrink: 0,
|
|
288
|
+
},
|
|
262
289
|
avatar: {
|
|
263
290
|
width: 36,
|
|
264
291
|
height: 36,
|
package/package.json
CHANGED