kasunk99-livestream-core 0.3.14 → 0.3.16
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;
|
|
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;AAU/C,KAAK,yBAAyB,GAAG;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAwBF,eAAO,MAAM,oBAAoB,uDAub/B,CAAC"}
|
|
@@ -7,6 +7,8 @@ const CHAT_NAME_COLORS = ['#f97316', '#22c55e', '#3b82f6', '#eab308', '#ec4899',
|
|
|
7
7
|
const BOTTOM_SAFE = Platform.OS === 'ios' ? 28 : 10;
|
|
8
8
|
const BOTTOM_BAR_H = 58;
|
|
9
9
|
const CHAT_BOTTOM_DEFAULT = BOTTOM_SAFE + BOTTOM_BAR_H + 8;
|
|
10
|
+
// Chat shifts above the floating input bar when typing
|
|
11
|
+
const CHAT_BOTTOM_TYPING = BOTTOM_SAFE + 68;
|
|
10
12
|
let RTCViewComponent = null;
|
|
11
13
|
try {
|
|
12
14
|
const webrtc = require('react-native-webrtc');
|
|
@@ -18,11 +20,6 @@ try {
|
|
|
18
20
|
LinearGradient = require('expo-linear-gradient').LinearGradient;
|
|
19
21
|
}
|
|
20
22
|
catch { }
|
|
21
|
-
let RNKCEvents = null;
|
|
22
|
-
try {
|
|
23
|
-
RNKCEvents = require('react-native-keyboard-controller').KeyboardEvents;
|
|
24
|
-
}
|
|
25
|
-
catch { }
|
|
26
23
|
export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream, isActive, index: _index, }) {
|
|
27
24
|
const roomId = isActive ? stream.roomId : null;
|
|
28
25
|
const { joined, joining, error, producerList, roomState, remoteStream, remoteVideoStream, webrtcUnavailable, consumeError, socket, viewerCount: liveViewerCount, streamEnded, } = useViewerSocket(roomId);
|
|
@@ -45,27 +42,60 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
45
42
|
const seededRoomRef = useRef(null);
|
|
46
43
|
const chatListRef = useRef(null);
|
|
47
44
|
const textInputRef = useRef(null);
|
|
48
|
-
//
|
|
49
|
-
|
|
45
|
+
// inputBottom: Android stays fixed at BOTTOM_SAFE (adjustResize handles the rest).
|
|
46
|
+
// iOS animates to keyboard height.
|
|
47
|
+
const inputBottom = useRef(new Animated.Value(BOTTOM_SAFE)).current;
|
|
50
48
|
const chatAnim = useRef(new Animated.Value(CHAT_BOTTOM_DEFAULT)).current;
|
|
51
|
-
//
|
|
49
|
+
// Android: adjustResize shrinks the container when the keyboard appears, so the
|
|
50
|
+
// input at bottom:BOTTOM_SAFE naturally ends up above the keyboard — no height math.
|
|
51
|
+
// Chat column animates up when typing starts (before keyboard appears) so it clears
|
|
52
|
+
// the floating input bar. keyboardDidHide resets everything.
|
|
53
|
+
//
|
|
54
|
+
// iOS: adjustResize does NOT apply. Keyboard covers content. We must use
|
|
55
|
+
// keyboardWillShow to push both the input bar and chat above the keyboard.
|
|
52
56
|
useEffect(() => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (RNKCEvents) {
|
|
60
|
-
const s1 = RNKCEvents.addListener('keyboardWillShow', (e) => animate(e.height, e.height + 60, e.duration || 250));
|
|
61
|
-
const s2 = RNKCEvents.addListener('keyboardWillHide', (e) => animate(0, CHAT_BOTTOM_DEFAULT, e.duration || 250, () => setIsTyping(false)));
|
|
62
|
-
return () => { s1.remove(); s2.remove(); };
|
|
57
|
+
if (Platform.OS === 'android') {
|
|
58
|
+
const sub = Keyboard.addListener('keyboardDidHide', () => {
|
|
59
|
+
setIsTyping(false);
|
|
60
|
+
chatAnim.setValue(CHAT_BOTTOM_DEFAULT);
|
|
61
|
+
});
|
|
62
|
+
return () => sub.remove();
|
|
63
63
|
}
|
|
64
|
-
//
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
// iOS — animate with keyboard
|
|
65
|
+
const s1 = Keyboard.addListener('keyboardWillShow', (e) => {
|
|
66
|
+
const h = e.endCoordinates.height;
|
|
67
|
+
const dur = e.duration || 250;
|
|
68
|
+
Animated.parallel([
|
|
69
|
+
Animated.timing(inputBottom, { toValue: h, duration: dur, useNativeDriver: false }),
|
|
70
|
+
Animated.timing(chatAnim, { toValue: h + 60, duration: dur, useNativeDriver: false }),
|
|
71
|
+
]).start();
|
|
72
|
+
});
|
|
73
|
+
const s2 = Keyboard.addListener('keyboardWillHide', (e) => {
|
|
74
|
+
const dur = e.duration || 250;
|
|
75
|
+
Animated.parallel([
|
|
76
|
+
Animated.timing(inputBottom, { toValue: BOTTOM_SAFE, duration: dur, useNativeDriver: false }),
|
|
77
|
+
Animated.timing(chatAnim, { toValue: CHAT_BOTTOM_DEFAULT, duration: dur, useNativeDriver: false }),
|
|
78
|
+
]).start(() => setIsTyping(false));
|
|
79
|
+
});
|
|
80
|
+
return () => { s1.remove(); s2.remove(); };
|
|
68
81
|
}, [inputBottom, chatAnim]);
|
|
82
|
+
// Animate chat up immediately when isTyping flips (Android: before keyboard appears)
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (Platform.OS !== 'android')
|
|
85
|
+
return;
|
|
86
|
+
Animated.timing(chatAnim, {
|
|
87
|
+
toValue: isTyping ? CHAT_BOTTOM_TYPING : CHAT_BOTTOM_DEFAULT,
|
|
88
|
+
duration: 200,
|
|
89
|
+
useNativeDriver: false,
|
|
90
|
+
}).start();
|
|
91
|
+
}, [isTyping, chatAnim]);
|
|
92
|
+
// Dismiss keyboard and reset when stream becomes inactive
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (!isActive) {
|
|
95
|
+
Keyboard.dismiss();
|
|
96
|
+
setIsTyping(false);
|
|
97
|
+
}
|
|
98
|
+
}, [isActive]);
|
|
69
99
|
const seededFromRoomState = useMemo(() => {
|
|
70
100
|
const chat = roomState && typeof roomState === 'object'
|
|
71
101
|
? roomState.chat
|
|
@@ -79,7 +109,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
79
109
|
setChatInput('');
|
|
80
110
|
setIsTyping(false);
|
|
81
111
|
Keyboard.dismiss();
|
|
82
|
-
inputBottom.setValue(
|
|
112
|
+
inputBottom.setValue(BOTTOM_SAFE);
|
|
83
113
|
chatAnim.setValue(CHAT_BOTTOM_DEFAULT);
|
|
84
114
|
return;
|
|
85
115
|
}
|
|
@@ -211,7 +241,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
211
241
|
};
|
|
212
242
|
const hostLabel = stream.hostDisplayName || stream.title || 'Live';
|
|
213
243
|
const avatarLetter = hostLabel[0]?.toUpperCase() ?? 'L';
|
|
214
|
-
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(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: styles.gradientOverlay, 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.viewerChip, pointerEvents: "none", children: [_jsx(Text, { style: styles.viewerEye, children: "\uD83D\uDC41" }), _jsx(Text, { style: styles.viewerCount, children: viewerCount > 0 ? viewerCount.toLocaleString() : '—' })] })] }), !isTyping && (_jsxs(View, { style: styles.rightColumn, pointerEvents: "box-none", children: [_jsxs(TouchableOpacity, { style: styles.actionBtn, activeOpacity: 0.75, children: [_jsx(View, { style: styles.actionCircle, children: _jsx(Text, { style: styles.actionIcon, children: "\u2665" }) }), _jsx(Text, { style: styles.actionLabel, children: "Like" })] }), _jsxs(TouchableOpacity, { style: styles.actionBtn, activeOpacity: 0.75, children: [_jsx(View, { style: styles.actionCircle, children: _jsx(Text, { style: styles.actionIcon, children: "\uD83C\uDF81" }) }), _jsx(Text, { style: styles.actionLabel, children: "Gift" })] }), _jsxs(TouchableOpacity, { style: styles.actionBtn, activeOpacity: 0.75, children: [_jsx(View, { style: styles.actionCircle, children: _jsx(Text, { style: styles.actionIcon, children: "\u2197" }) }), _jsx(Text, { style: styles.actionLabel, children: "Share" })] })] })), _jsx(Animated.View, { style: [styles.chatColumn, { bottom: chatAnim }], pointerEvents: "box-none", children: _jsx(ScrollView, { ref: chatListRef, contentContainerStyle: styles.chatContent, showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "always",
|
|
244
|
+
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(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: styles.gradientOverlay, 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.viewerChip, pointerEvents: "none", children: [_jsx(Text, { style: styles.viewerEye, children: "\uD83D\uDC41" }), _jsx(Text, { style: styles.viewerCount, children: viewerCount > 0 ? viewerCount.toLocaleString() : '—' })] })] }), !isTyping && (_jsxs(View, { style: styles.rightColumn, pointerEvents: "box-none", children: [_jsxs(TouchableOpacity, { style: styles.actionBtn, activeOpacity: 0.75, children: [_jsx(View, { style: styles.actionCircle, children: _jsx(Text, { style: styles.actionIcon, children: "\u2665" }) }), _jsx(Text, { style: styles.actionLabel, children: "Like" })] }), _jsxs(TouchableOpacity, { style: styles.actionBtn, activeOpacity: 0.75, children: [_jsx(View, { style: styles.actionCircle, children: _jsx(Text, { style: styles.actionIcon, children: "\uD83C\uDF81" }) }), _jsx(Text, { style: styles.actionLabel, children: "Gift" })] }), _jsxs(TouchableOpacity, { style: styles.actionBtn, activeOpacity: 0.75, children: [_jsx(View, { style: styles.actionCircle, children: _jsx(Text, { style: styles.actionIcon, children: "\u2197" }) }), _jsx(Text, { style: styles.actionLabel, children: "Share" })] })] })), _jsx(Animated.View, { style: [styles.chatColumn, { bottom: chatAnim }], pointerEvents: "box-none", children: _jsx(ScrollView, { ref: chatListRef, 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.bottomBar, children: [_jsx(TouchableOpacity, { style: styles.typeTouchable, onPress: () => { if (joined && !joining && !error)
|
|
215
245
|
setIsTyping(true); }, activeOpacity: 0.7, children: _jsx(Text, { style: styles.typePlaceholder, children: 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, children: "\uD83C\uDF39" }) }), _jsx(TouchableOpacity, { style: styles.bottomIconBtn, activeOpacity: 0.7, children: _jsx(Text, { style: styles.bottomIconGlyph, children: "\uD83C\uDF81" }) }), _jsx(TouchableOpacity, { style: styles.bottomIconBtn, activeOpacity: 0.7, children: _jsx(Text, { style: styles.bottomIconGlyph, children: "\u2197" }) })] })), isTyping && (_jsxs(Animated.View, { style: [styles.floatingInputWrap, { bottom: inputBottom }], children: [_jsx(TextInput, { ref: textInputRef, style: styles.floatingTextInput, value: chatInput, onChangeText: setChatInput, placeholder: "Say something...", placeholderTextColor: "rgba(255,255,255,0.40)", editable: joined && !joining && !error, onSubmitEditing: sendChat, returnKeyType: "send", submitBehavior: "submit", autoFocus: true }), _jsx(TouchableOpacity, { style: [styles.floatingSendBtn, (!chatInput.trim() || !joined) && styles.sendBtnOff], onPress: sendChat, disabled: !joined || !chatInput.trim(), activeOpacity: 0.75, children: _jsx(Text, { style: styles.sendIcon, children: "\u25B6" }) })] })), 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" })] }) }))] }));
|
|
216
246
|
});
|
|
217
247
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -316,7 +346,6 @@ const styles = StyleSheet.create({
|
|
|
316
346
|
},
|
|
317
347
|
actionIcon: { fontSize: 22, color: '#fff' },
|
|
318
348
|
actionLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 11, fontWeight: '500' },
|
|
319
|
-
// Chat column — absolute, bottom driven by animation
|
|
320
349
|
chatColumn: {
|
|
321
350
|
position: 'absolute',
|
|
322
351
|
left: 12,
|
|
@@ -346,7 +375,6 @@ const styles = StyleSheet.create({
|
|
|
346
375
|
chatUsername: { fontWeight: '700' },
|
|
347
376
|
chatMsg: { color: '#e5e5e5', fontWeight: '400' },
|
|
348
377
|
joinText: { color: 'rgba(255,255,255,0.48)', fontSize: 12, fontStyle: 'italic' },
|
|
349
|
-
// Bottom action bar
|
|
350
378
|
bottomBar: {
|
|
351
379
|
position: 'absolute',
|
|
352
380
|
bottom: BOTTOM_SAFE,
|
|
@@ -368,14 +396,13 @@ const styles = StyleSheet.create({
|
|
|
368
396
|
typePlaceholder: { color: 'rgba(255,255,255,0.50)', fontSize: 14 },
|
|
369
397
|
bottomIconBtn: { width: 40, height: 40, alignItems: 'center', justifyContent: 'center' },
|
|
370
398
|
bottomIconGlyph: { fontSize: 22 },
|
|
371
|
-
// Floating input bar — sits directly above keyboard, animated
|
|
372
399
|
floatingInputWrap: {
|
|
373
400
|
position: 'absolute',
|
|
374
401
|
left: 0,
|
|
375
402
|
right: 0,
|
|
376
403
|
flexDirection: 'row',
|
|
377
404
|
alignItems: 'center',
|
|
378
|
-
backgroundColor: 'rgba(20,20,20,0.
|
|
405
|
+
backgroundColor: 'rgba(20,20,20,0.97)',
|
|
379
406
|
paddingHorizontal: 12,
|
|
380
407
|
paddingVertical: 8,
|
|
381
408
|
gap: 8,
|
package/package.json
CHANGED