kasunk99-livestream-core 0.3.26 → 0.3.28
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;
|
|
1
|
+
{"version":3,"file":"LiveStreamViewerItem.d.ts","sourceRoot":"","sources":["../../src/components/LiveStreamViewerItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqD,MAAM,OAAO,CAAC;AAgB1E,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;CACf,CAAC;AA8CF,eAAO,MAAM,oBAAoB,uDAgZ/B,CAAC"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { memo, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { ActivityIndicator, Keyboard,
|
|
3
|
+
import { ActivityIndicator, Keyboard, NativeModules, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native';
|
|
4
4
|
import { useViewerSocket } from '../hooks/useViewerSocket';
|
|
5
5
|
const CHAT_NAME_COLORS = ['#f97316', '#22c55e', '#3b82f6', '#eab308', '#ec4899', '#a855f7'];
|
|
6
6
|
const BOTTOM_SAFE = Platform.OS === 'ios' ? 28 : 10;
|
|
7
|
-
const
|
|
7
|
+
const BRAND_GREEN = '#009F68';
|
|
8
8
|
let RTCViewComponent = null;
|
|
9
9
|
try {
|
|
10
10
|
const webrtc = require('react-native-webrtc');
|
|
@@ -16,14 +16,18 @@ try {
|
|
|
16
16
|
LinearGradient = require('expo-linear-gradient').LinearGradient;
|
|
17
17
|
}
|
|
18
18
|
catch { }
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
let IoniconsComponent = null;
|
|
20
|
+
try {
|
|
21
|
+
const vi = require('@expo/vector-icons');
|
|
22
|
+
if (vi.Ionicons)
|
|
23
|
+
IoniconsComponent = vi.Ionicons;
|
|
24
|
+
}
|
|
25
|
+
catch { }
|
|
26
|
+
let RNKCKeyboardEvents = null;
|
|
23
27
|
try {
|
|
24
28
|
const rnkc = require('react-native-keyboard-controller');
|
|
25
|
-
if (rnkc.
|
|
26
|
-
|
|
29
|
+
if (rnkc.KeyboardEvents)
|
|
30
|
+
RNKCKeyboardEvents = rnkc.KeyboardEvents;
|
|
27
31
|
}
|
|
28
32
|
catch { }
|
|
29
33
|
export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream, isActive, index: _index, }) {
|
|
@@ -52,10 +56,29 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
52
56
|
const textInputRef = useRef(null);
|
|
53
57
|
// Ref so sendChat always reads the latest typed text regardless of React's render cycle.
|
|
54
58
|
const chatInputRef = useRef('');
|
|
55
|
-
//
|
|
59
|
+
// Keyboard height used to pad the container above the keyboard.
|
|
60
|
+
// We track it manually instead of using KeyboardAvoidingView because
|
|
61
|
+
// KAV's internal measure() returns wrong coordinates inside a FlatList on Android.
|
|
62
|
+
const [kbPadding, setKbPadding] = useState(0);
|
|
56
63
|
useEffect(() => {
|
|
57
|
-
const
|
|
58
|
-
|
|
64
|
+
const onHide = () => { setKbPadding(0); setIsTyping(false); };
|
|
65
|
+
if (RNKCKeyboardEvents) {
|
|
66
|
+
// RNKC's KeyboardEvents fire with the accurate WindowInsetsCompat height.
|
|
67
|
+
// keyboardWillShow fires at the START of the animation so the bar is
|
|
68
|
+
// already above the keyboard before it finishes rising.
|
|
69
|
+
const show = RNKCKeyboardEvents.addListener('keyboardWillShow', (e) => {
|
|
70
|
+
if (e.height > 0)
|
|
71
|
+
setKbPadding(e.height);
|
|
72
|
+
});
|
|
73
|
+
const hide = RNKCKeyboardEvents.addListener('keyboardDidHide', onHide);
|
|
74
|
+
return () => { show.remove(); hide.remove(); };
|
|
75
|
+
}
|
|
76
|
+
// Fallback: standard RN events (fires after animation completes).
|
|
77
|
+
const show = Keyboard.addListener('keyboardDidShow', (e) => {
|
|
78
|
+
setKbPadding(e.endCoordinates.height);
|
|
79
|
+
});
|
|
80
|
+
const hide = Keyboard.addListener('keyboardDidHide', onHide);
|
|
81
|
+
return () => { show.remove(); hide.remove(); };
|
|
59
82
|
}, []);
|
|
60
83
|
// Dismiss input when stream becomes inactive (e.g. user swipes to next stream)
|
|
61
84
|
useEffect(() => {
|
|
@@ -208,14 +231,12 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
208
231
|
const avatarLetter = hostLabel[0]?.toUpperCase() ?? 'L';
|
|
209
232
|
const hasUnsent = chatInput.trim().length > 0;
|
|
210
233
|
const canType = joined && !joining && !error;
|
|
211
|
-
return (
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// No manual offset formulas or layout listeners are needed.
|
|
215
|
-
_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.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(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 }), _jsx(Pressable, { style: [styles.sendBtn, (!hasUnsent || !joined) && styles.sendBtnOff], onPressIn: sendChat, hitSlop: { top: 8, right: 8, bottom: 8, left: 8 }, children: _jsx(Text, { style: styles.sendIcon, children: "\u25B6" }) })] })) : (
|
|
234
|
+
return (_jsxs(View, { style: [styles.container, kbPadding > 0 && { paddingBottom: kbPadding }], 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.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 }), _jsx(Pressable, { style: [styles.sendBtn, (!hasUnsent || !joined) && styles.sendBtnOff], onPressIn: sendChat, hitSlop: { top: 8, right: 8, bottom: 8, left: 8 }, children: IoniconsComponent
|
|
235
|
+
? _jsx(IoniconsComponent, { name: "send", size: 18, color: "#fff" })
|
|
236
|
+
: _jsx(Text, { style: styles.sendIcon, children: "\u27A4" }) })] })) : (
|
|
216
237
|
// Pill bar — tapping opens the input bar.
|
|
217
238
|
_jsxs(View, { style: styles.bottomBar, children: [_jsx(TouchableOpacity, { style: [styles.typeTouchable, hasUnsent && styles.typeTouchableFilled], onPress: () => { if (canType)
|
|
218
|
-
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,
|
|
239
|
+
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" })] }) }))] }));
|
|
219
240
|
});
|
|
220
241
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
221
242
|
const styles = StyleSheet.create({
|
|
@@ -291,26 +312,6 @@ const styles = StyleSheet.create({
|
|
|
291
312
|
},
|
|
292
313
|
viewerEye: { fontSize: 11 },
|
|
293
314
|
viewerCount: { color: 'rgba(255,255,255,0.88)', fontSize: 12, fontWeight: '500' },
|
|
294
|
-
rightColumn: {
|
|
295
|
-
position: 'absolute',
|
|
296
|
-
right: 12,
|
|
297
|
-
bottom: BOTTOM_SAFE + BOTTOM_BAR_H + 8,
|
|
298
|
-
alignItems: 'center',
|
|
299
|
-
gap: 14,
|
|
300
|
-
},
|
|
301
|
-
actionBtn: { alignItems: 'center', gap: 5 },
|
|
302
|
-
actionCircle: {
|
|
303
|
-
width: 48,
|
|
304
|
-
height: 48,
|
|
305
|
-
borderRadius: 24,
|
|
306
|
-
backgroundColor: 'rgba(255,255,255,0.14)',
|
|
307
|
-
borderWidth: StyleSheet.hairlineWidth,
|
|
308
|
-
borderColor: 'rgba(255,255,255,0.2)',
|
|
309
|
-
alignItems: 'center',
|
|
310
|
-
justifyContent: 'center',
|
|
311
|
-
},
|
|
312
|
-
actionIcon: { fontSize: 22, color: '#fff' },
|
|
313
|
-
actionLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 11, fontWeight: '500' },
|
|
314
315
|
// Flex spacer — fills remaining vertical space, pushes chat + bars to bottom
|
|
315
316
|
spacer: { flex: 1 },
|
|
316
317
|
// Chat list — normal flow block, grows upward from input bar
|
|
@@ -368,12 +369,13 @@ const styles = StyleSheet.create({
|
|
|
368
369
|
width: 40,
|
|
369
370
|
height: 40,
|
|
370
371
|
borderRadius: 20,
|
|
371
|
-
backgroundColor:
|
|
372
|
+
backgroundColor: BRAND_GREEN,
|
|
372
373
|
alignItems: 'center',
|
|
373
374
|
justifyContent: 'center',
|
|
374
375
|
},
|
|
375
376
|
sendBtnOff: { backgroundColor: 'rgba(255,255,255,0.12)' },
|
|
376
377
|
sendIcon: { color: '#fff', fontSize: 15, fontWeight: '700' },
|
|
378
|
+
heartIcon: { color: BRAND_GREEN, fontSize: 24 },
|
|
377
379
|
// Pill bar — normal flow, visible when keyboard is closed
|
|
378
380
|
bottomBar: {
|
|
379
381
|
flexDirection: 'row',
|
package/package.json
CHANGED