kasunk99-livestream-core 0.3.15 → 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;
|
|
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"}
|
|
@@ -1,14 +1,14 @@
|
|
|
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, Animated, Dimensions, Keyboard,
|
|
3
|
+
import { ActivityIndicator, Animated, Dimensions, Keyboard, NativeModules, Platform, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native';
|
|
4
4
|
import { useViewerSocket } from '../hooks/useViewerSocket';
|
|
5
5
|
const SCREEN_HEIGHT = Dimensions.get('window').height;
|
|
6
6
|
const CHAT_NAME_COLORS = ['#f97316', '#22c55e', '#3b82f6', '#eab308', '#ec4899', '#a855f7'];
|
|
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
|
|
11
|
-
const CHAT_BOTTOM_TYPING = BOTTOM_SAFE +
|
|
10
|
+
// Chat shifts above the floating input bar when typing
|
|
11
|
+
const CHAT_BOTTOM_TYPING = BOTTOM_SAFE + 68;
|
|
12
12
|
let RTCViewComponent = null;
|
|
13
13
|
try {
|
|
14
14
|
const webrtc = require('react-native-webrtc');
|
|
@@ -42,21 +42,60 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
42
42
|
const seededRoomRef = useRef(null);
|
|
43
43
|
const chatListRef = useRef(null);
|
|
44
44
|
const textInputRef = useRef(null);
|
|
45
|
-
//
|
|
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;
|
|
46
48
|
const chatAnim = useRef(new Animated.Value(CHAT_BOTTOM_DEFAULT)).current;
|
|
47
|
-
//
|
|
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.
|
|
48
56
|
useEffect(() => {
|
|
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
|
+
}
|
|
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(); };
|
|
81
|
+
}, [inputBottom, chatAnim]);
|
|
82
|
+
// Animate chat up immediately when isTyping flips (Android: before keyboard appears)
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (Platform.OS !== 'android')
|
|
85
|
+
return;
|
|
49
86
|
Animated.timing(chatAnim, {
|
|
50
87
|
toValue: isTyping ? CHAT_BOTTOM_TYPING : CHAT_BOTTOM_DEFAULT,
|
|
51
|
-
duration:
|
|
88
|
+
duration: 200,
|
|
52
89
|
useNativeDriver: false,
|
|
53
90
|
}).start();
|
|
54
91
|
}, [isTyping, chatAnim]);
|
|
55
|
-
//
|
|
92
|
+
// Dismiss keyboard and reset when stream becomes inactive
|
|
56
93
|
useEffect(() => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
94
|
+
if (!isActive) {
|
|
95
|
+
Keyboard.dismiss();
|
|
96
|
+
setIsTyping(false);
|
|
97
|
+
}
|
|
98
|
+
}, [isActive]);
|
|
60
99
|
const seededFromRoomState = useMemo(() => {
|
|
61
100
|
const chat = roomState && typeof roomState === 'object'
|
|
62
101
|
? roomState.chat
|
|
@@ -70,6 +109,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
70
109
|
setChatInput('');
|
|
71
110
|
setIsTyping(false);
|
|
72
111
|
Keyboard.dismiss();
|
|
112
|
+
inputBottom.setValue(BOTTOM_SAFE);
|
|
73
113
|
chatAnim.setValue(CHAT_BOTTOM_DEFAULT);
|
|
74
114
|
return;
|
|
75
115
|
}
|
|
@@ -78,7 +118,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
78
118
|
setChatMessages([]);
|
|
79
119
|
setChatInput('');
|
|
80
120
|
}
|
|
81
|
-
}, [roomId, chatAnim]);
|
|
121
|
+
}, [roomId, inputBottom, chatAnim]);
|
|
82
122
|
useEffect(() => {
|
|
83
123
|
if (!roomId)
|
|
84
124
|
return;
|
|
@@ -202,7 +242,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
202
242
|
const hostLabel = stream.hostDisplayName || stream.title || 'Live';
|
|
203
243
|
const avatarLetter = hostLabel[0]?.toUpperCase() ?? 'L';
|
|
204
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)
|
|
205
|
-
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" }) })] })),
|
|
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" })] }) }))] }));
|
|
206
246
|
});
|
|
207
247
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
208
248
|
const styles = StyleSheet.create({
|
|
@@ -306,7 +346,6 @@ const styles = StyleSheet.create({
|
|
|
306
346
|
},
|
|
307
347
|
actionIcon: { fontSize: 22, color: '#fff' },
|
|
308
348
|
actionLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 11, fontWeight: '500' },
|
|
309
|
-
// Chat column — absolute, bottom animated between default and typing positions
|
|
310
349
|
chatColumn: {
|
|
311
350
|
position: 'absolute',
|
|
312
351
|
left: 12,
|
|
@@ -336,7 +375,6 @@ const styles = StyleSheet.create({
|
|
|
336
375
|
chatUsername: { fontWeight: '700' },
|
|
337
376
|
chatMsg: { color: '#e5e5e5', fontWeight: '400' },
|
|
338
377
|
joinText: { color: 'rgba(255,255,255,0.48)', fontSize: 12, fontStyle: 'italic' },
|
|
339
|
-
// Bottom action bar
|
|
340
378
|
bottomBar: {
|
|
341
379
|
position: 'absolute',
|
|
342
380
|
bottom: BOTTOM_SAFE,
|
|
@@ -358,20 +396,15 @@ const styles = StyleSheet.create({
|
|
|
358
396
|
typePlaceholder: { color: 'rgba(255,255,255,0.50)', fontSize: 14 },
|
|
359
397
|
bottomIconBtn: { width: 40, height: 40, alignItems: 'center', justifyContent: 'center' },
|
|
360
398
|
bottomIconGlyph: { fontSize: 22 },
|
|
361
|
-
// Modal input overlay
|
|
362
|
-
inputModalKAV: {
|
|
363
|
-
flex: 1,
|
|
364
|
-
},
|
|
365
|
-
inputModalBackdrop: {
|
|
366
|
-
flex: 1,
|
|
367
|
-
},
|
|
368
399
|
floatingInputWrap: {
|
|
400
|
+
position: 'absolute',
|
|
401
|
+
left: 0,
|
|
402
|
+
right: 0,
|
|
369
403
|
flexDirection: 'row',
|
|
370
404
|
alignItems: 'center',
|
|
371
405
|
backgroundColor: 'rgba(20,20,20,0.97)',
|
|
372
406
|
paddingHorizontal: 12,
|
|
373
407
|
paddingVertical: 8,
|
|
374
|
-
paddingBottom: BOTTOM_SAFE,
|
|
375
408
|
gap: 8,
|
|
376
409
|
borderTopWidth: StyleSheet.hairlineWidth,
|
|
377
410
|
borderTopColor: 'rgba(255,255,255,0.10)',
|
package/package.json
CHANGED