kasunk99-livestream-core 0.3.12 → 0.3.14

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.
@@ -5,9 +5,6 @@ type LiveStreamViewerItemProps = {
5
5
  isActive: boolean;
6
6
  index: number;
7
7
  };
8
- /**
9
- * Single full-screen viewer cell. When isActive, joins the stream room and consumes video/audio.
10
- */
11
8
  export declare const LiveStreamViewerItem: React.NamedExoticComponent<LiveStreamViewerItemProps>;
12
9
  export {};
13
10
  //# sourceMappingURL=LiveStreamViewerItem.d.ts.map
@@ -1 +1 @@
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;AAO/C,KAAK,yBAAyB,GAAG;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AA8BF;;GAEG;AACH,eAAO,MAAM,oBAAoB,uDAqY/B,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;AAQ/C,KAAK,yBAAyB,GAAG;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAiCF,eAAO,MAAM,oBAAoB,uDA8Z/B,CAAC"}
@@ -4,28 +4,25 @@ import { ActivityIndicator, Animated, Dimensions, Keyboard, NativeModules, Platf
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
- // iOS needs bottom safe-area padding; Android auto-adjusts via adjustResize
8
7
  const BOTTOM_SAFE = Platform.OS === 'ios' ? 28 : 10;
8
+ const BOTTOM_BAR_H = 58;
9
+ const CHAT_BOTTOM_DEFAULT = BOTTOM_SAFE + BOTTOM_BAR_H + 8;
9
10
  let RTCViewComponent = null;
10
11
  try {
11
12
  const webrtc = require('react-native-webrtc');
12
13
  RTCViewComponent = webrtc.RTCView;
13
14
  }
14
- catch {
15
- // react-native-webrtc not available in Expo Go
16
- }
17
- // expo-linear-gradient — lazy require so the package stays safe in non-Expo environments.
18
- // The consuming app (social-casino-mobile) has expo-linear-gradient as a dependency.
15
+ catch { }
19
16
  let LinearGradient = null;
20
17
  try {
21
18
  LinearGradient = require('expo-linear-gradient').LinearGradient;
22
19
  }
23
- catch {
24
- // expo-linear-gradient not available
20
+ catch { }
21
+ let RNKCEvents = null;
22
+ try {
23
+ RNKCEvents = require('react-native-keyboard-controller').KeyboardEvents;
25
24
  }
26
- /**
27
- * Single full-screen viewer cell. When isActive, joins the stream room and consumes video/audio.
28
- */
25
+ catch { }
29
26
  export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream, isActive, index: _index, }) {
30
27
  const roomId = isActive ? stream.roomId : null;
31
28
  const { joined, joining, error, producerList, roomState, remoteStream, remoteVideoStream, webrtcUnavailable, consumeError, socket, viewerCount: liveViewerCount, streamEnded, } = useViewerSocket(roomId);
@@ -44,29 +41,31 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
44
41
  const showVideo = RTCViewComponent && remoteStream && !!streamURL && hasVideoTrack;
45
42
  const [chatInput, setChatInput] = useState('');
46
43
  const [chatMessages, setChatMessages] = useState([]);
47
- // iOS keyboard animation — Android handled automatically via windowSoftInputMode=adjustResize
48
- const kbOffset = useRef(new Animated.Value(0)).current;
49
- useEffect(() => {
50
- if (Platform.OS !== 'ios')
51
- return;
52
- const show = Keyboard.addListener('keyboardWillShow', (e) => {
53
- Animated.timing(kbOffset, {
54
- toValue: e.endCoordinates.height,
55
- duration: e.duration ?? 260,
56
- useNativeDriver: false,
57
- }).start();
58
- });
59
- const hide = Keyboard.addListener('keyboardWillHide', (e) => {
60
- Animated.timing(kbOffset, {
61
- toValue: 0,
62
- duration: e.duration ?? 220,
63
- useNativeDriver: false,
64
- }).start();
65
- });
66
- return () => { show.remove(); hide.remove(); };
67
- }, [kbOffset]);
44
+ const [isTyping, setIsTyping] = useState(false);
68
45
  const seededRoomRef = useRef(null);
69
46
  const chatListRef = useRef(null);
47
+ const textInputRef = useRef(null);
48
+ // Keyboard-driven animated positions
49
+ const inputBottom = useRef(new Animated.Value(0)).current;
50
+ const chatAnim = useRef(new Animated.Value(CHAT_BOTTOM_DEFAULT)).current;
51
+ // Animate floating input and chat column with keyboard
52
+ useEffect(() => {
53
+ const animate = (toInput, toChat, dur, onDone) => {
54
+ Animated.parallel([
55
+ Animated.timing(inputBottom, { toValue: toInput, duration: dur, useNativeDriver: false }),
56
+ Animated.timing(chatAnim, { toValue: toChat, duration: dur, useNativeDriver: false }),
57
+ ]).start(() => onDone?.());
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(); };
63
+ }
64
+ // Standard Keyboard API fallback
65
+ const sub1 = Keyboard.addListener((Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow'), (e) => animate(e.endCoordinates.height, e.endCoordinates.height + 60, e.duration || 250));
66
+ const sub2 = Keyboard.addListener((Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide'), (e) => animate(0, CHAT_BOTTOM_DEFAULT, e.duration || 200, () => setIsTyping(false)));
67
+ return () => { sub1.remove(); sub2.remove(); };
68
+ }, [inputBottom, chatAnim]);
70
69
  const seededFromRoomState = useMemo(() => {
71
70
  const chat = roomState && typeof roomState === 'object'
72
71
  ? roomState.chat
@@ -78,6 +77,10 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
78
77
  seededRoomRef.current = null;
79
78
  setChatMessages([]);
80
79
  setChatInput('');
80
+ setIsTyping(false);
81
+ Keyboard.dismiss();
82
+ inputBottom.setValue(0);
83
+ chatAnim.setValue(CHAT_BOTTOM_DEFAULT);
81
84
  return;
82
85
  }
83
86
  if (seededRoomRef.current !== roomId) {
@@ -85,7 +88,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
85
88
  setChatMessages([]);
86
89
  setChatInput('');
87
90
  }
88
- }, [roomId]);
91
+ }, [roomId, inputBottom, chatAnim]);
89
92
  useEffect(() => {
90
93
  if (!roomId)
91
94
  return;
@@ -157,7 +160,6 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
157
160
  }
158
161
  catch { /* ignore */ }
159
162
  }, [chatData.length]);
160
- // System audio playback (Android only)
161
163
  useEffect(() => {
162
164
  if (!socket || !joined || !isActive || Platform.OS !== 'android')
163
165
  return;
@@ -209,12 +211,8 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
209
211
  };
210
212
  const hostLabel = stream.hostDisplayName || stream.title || 'Live';
211
213
  const avatarLetter = hostLabel[0]?.toUpperCase() ?? 'L';
212
- 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: [
213
- 'rgba(0,0,0,0.70)', // top edge opaque for host info readability
214
- 'rgba(0,0,0,0.0)', // clears out ~30% down
215
- 'rgba(0,0,0,0.0)', // stays clear through mid-screen
216
- 'rgba(0,0,0,0.78)', // darkens toward bottom for chat / input
217
- ], 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() : '—' })] })] }), _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" })] })] }), _jsxs(Animated.View, { style: [styles.bottomPanel, { bottom: kbOffset }], pointerEvents: "box-none", children: [_jsx(View, { style: styles.chatColumn, pointerEvents: "box-none", children: _jsx(ScrollView, { ref: chatListRef, contentContainerStyle: styles.chatContent, showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "always", keyboardDismissMode: "none", 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))) }) }), _jsxs(View, { style: styles.inputBar, children: [_jsx(TouchableOpacity, { style: styles.inputIconBtn, activeOpacity: 0.7, children: _jsx(Text, { style: styles.inputIconGlyph, children: "\u263A" }) }), _jsx(TextInput, { style: styles.textInput, value: chatInput, onChangeText: setChatInput, placeholder: joined ? 'Say something...' : 'Connecting...', placeholderTextColor: "rgba(255,255,255,0.35)", editable: joined && !joining && !error, onSubmitEditing: sendChat, returnKeyType: "send", blurOnSubmit: false }), _jsx(TouchableOpacity, { style: styles.inputIconBtn, activeOpacity: 0.7, children: _jsx(Text, { style: styles.inputIconGlyph, children: "\uD83C\uDF39" }) }), _jsx(TouchableOpacity, { style: [styles.sendBtn, (!chatInput.trim() || !joined) && styles.sendBtnOff], onPress: sendChat, activeOpacity: 0.75, disabled: !joined || !chatInput.trim(), 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" })] }) }))] }));
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", keyboardDismissMode: "on-drag", 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
+ 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" })] }) }))] }));
218
216
  });
219
217
  // ─────────────────────────────────────────────────────────────────────────────
220
218
  const styles = StyleSheet.create({
@@ -222,7 +220,6 @@ const styles = StyleSheet.create({
222
220
  flex: 1,
223
221
  backgroundColor: '#000',
224
222
  },
225
- // ── Video ─────────────────────────────────────────────────────────────────
226
223
  rtcView: {
227
224
  position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
228
225
  backgroundColor: '#000',
@@ -246,7 +243,9 @@ const styles = StyleSheet.create({
246
243
  textAlign: 'center',
247
244
  paddingHorizontal: 28,
248
245
  },
249
- // ── Top bar ───────────────────────────────────────────────────────────────
246
+ gradientOverlay: {
247
+ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
248
+ },
250
249
  topBar: {
251
250
  position: 'absolute',
252
251
  top: 10,
@@ -275,17 +274,8 @@ const styles = StyleSheet.create({
275
274
  justifyContent: 'center',
276
275
  flexShrink: 0,
277
276
  },
278
- avatarLetter: {
279
- color: '#fff',
280
- fontSize: 15,
281
- fontWeight: '700',
282
- },
283
- hostName: {
284
- color: '#fff',
285
- fontSize: 14,
286
- fontWeight: '600',
287
- flex: 1,
288
- },
277
+ avatarLetter: { color: '#fff', fontSize: 15, fontWeight: '700' },
278
+ hostName: { color: '#fff', fontSize: 14, fontWeight: '600', flex: 1 },
289
279
  livePill: {
290
280
  backgroundColor: '#ef4444',
291
281
  borderRadius: 6,
@@ -293,12 +283,7 @@ const styles = StyleSheet.create({
293
283
  paddingVertical: 3,
294
284
  flexShrink: 0,
295
285
  },
296
- livePillText: {
297
- color: '#fff',
298
- fontSize: 11,
299
- fontWeight: '700',
300
- letterSpacing: 0.8,
301
- },
286
+ livePillText: { color: '#fff', fontSize: 11, fontWeight: '700', letterSpacing: 0.8 },
302
287
  viewerChip: {
303
288
  flexDirection: 'row',
304
289
  alignItems: 'center',
@@ -310,23 +295,15 @@ const styles = StyleSheet.create({
310
295
  flexShrink: 0,
311
296
  },
312
297
  viewerEye: { fontSize: 11 },
313
- viewerCount: {
314
- color: 'rgba(255,255,255,0.88)',
315
- fontSize: 12,
316
- fontWeight: '500',
317
- },
318
- // ── Right action column ───────────────────────────────────────────────────
298
+ viewerCount: { color: 'rgba(255,255,255,0.88)', fontSize: 12, fontWeight: '500' },
319
299
  rightColumn: {
320
300
  position: 'absolute',
321
301
  right: 12,
322
- bottom: 92,
302
+ bottom: BOTTOM_SAFE + BOTTOM_BAR_H + 8,
323
303
  alignItems: 'center',
324
304
  gap: 14,
325
305
  },
326
- actionBtn: {
327
- alignItems: 'center',
328
- gap: 5,
329
- },
306
+ actionBtn: { alignItems: 'center', gap: 5 },
330
307
  actionCircle: {
331
308
  width: 48,
332
309
  height: 48,
@@ -337,32 +314,16 @@ const styles = StyleSheet.create({
337
314
  alignItems: 'center',
338
315
  justifyContent: 'center',
339
316
  },
340
- actionIcon: {
341
- fontSize: 22,
342
- color: '#fff',
343
- },
344
- actionLabel: {
345
- color: 'rgba(255,255,255,0.7)',
346
- fontSize: 11,
347
- fontWeight: '500',
348
- },
349
- // ── Bottom panel ──────────────────────────────────────────────────────────
350
- bottomPanel: {
351
- position: 'absolute',
352
- left: 0,
353
- right: 0,
354
- paddingHorizontal: 12,
355
- paddingBottom: BOTTOM_SAFE,
356
- gap: 8,
357
- },
358
- // Chat messages
317
+ actionIcon: { fontSize: 22, color: '#fff' },
318
+ actionLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 11, fontWeight: '500' },
319
+ // Chat column — absolute, bottom driven by animation
359
320
  chatColumn: {
321
+ position: 'absolute',
322
+ left: 12,
360
323
  width: '66%',
361
324
  maxHeight: Math.round(SCREEN_HEIGHT * 0.30),
362
325
  },
363
- chatContent: {
364
- paddingBottom: 2,
365
- },
326
+ chatContent: { paddingBottom: 2 },
366
327
  chatBubble: {
367
328
  alignSelf: 'flex-start',
368
329
  marginBottom: 5,
@@ -381,53 +342,57 @@ const styles = StyleSheet.create({
381
342
  paddingVertical: 4,
382
343
  maxWidth: '100%',
383
344
  },
384
- chatLine: {
385
- fontSize: 13,
386
- lineHeight: 18,
387
- },
388
- chatUsername: {
389
- fontWeight: '700',
390
- },
391
- chatMsg: {
392
- color: '#e5e5e5',
393
- fontWeight: '400',
394
- },
395
- joinText: {
396
- color: 'rgba(255,255,255,0.48)',
397
- fontSize: 12,
398
- fontStyle: 'italic',
399
- },
400
- // Input bar
401
- inputBar: {
345
+ chatLine: { fontSize: 13, lineHeight: 18 },
346
+ chatUsername: { fontWeight: '700' },
347
+ chatMsg: { color: '#e5e5e5', fontWeight: '400' },
348
+ joinText: { color: 'rgba(255,255,255,0.48)', fontSize: 12, fontStyle: 'italic' },
349
+ // Bottom action bar
350
+ bottomBar: {
351
+ position: 'absolute',
352
+ bottom: BOTTOM_SAFE,
353
+ left: 12,
354
+ right: 12,
402
355
  flexDirection: 'row',
403
356
  alignItems: 'center',
404
- backgroundColor: 'rgba(18,18,18,0.75)',
405
- borderRadius: 999,
406
- borderWidth: StyleSheet.hairlineWidth,
407
- borderColor: 'rgba(255,255,255,0.12)',
408
- paddingHorizontal: 4,
409
- paddingVertical: 4,
410
- gap: 2,
357
+ gap: 6,
411
358
  },
412
- inputIconBtn: {
413
- width: 40,
414
- height: 40,
415
- alignItems: 'center',
416
- justifyContent: 'center',
359
+ typeTouchable: {
360
+ flex: 1,
361
+ backgroundColor: 'rgba(255,255,255,0.12)',
417
362
  borderRadius: 20,
363
+ paddingHorizontal: 14,
364
+ paddingVertical: 10,
365
+ borderWidth: StyleSheet.hairlineWidth,
366
+ borderColor: 'rgba(255,255,255,0.18)',
418
367
  },
419
- inputIconGlyph: {
420
- fontSize: 21,
368
+ typePlaceholder: { color: 'rgba(255,255,255,0.50)', fontSize: 14 },
369
+ bottomIconBtn: { width: 40, height: 40, alignItems: 'center', justifyContent: 'center' },
370
+ bottomIconGlyph: { fontSize: 22 },
371
+ // Floating input bar — sits directly above keyboard, animated
372
+ floatingInputWrap: {
373
+ position: 'absolute',
374
+ left: 0,
375
+ right: 0,
376
+ flexDirection: 'row',
377
+ alignItems: 'center',
378
+ backgroundColor: 'rgba(20,20,20,0.96)',
379
+ paddingHorizontal: 12,
380
+ paddingVertical: 8,
381
+ gap: 8,
382
+ borderTopWidth: StyleSheet.hairlineWidth,
383
+ borderTopColor: 'rgba(255,255,255,0.10)',
421
384
  },
422
- textInput: {
385
+ floatingTextInput: {
423
386
  flex: 1,
387
+ backgroundColor: 'rgba(255,255,255,0.10)',
388
+ borderRadius: 20,
389
+ paddingHorizontal: 14,
390
+ paddingVertical: Platform.OS === 'ios' ? 10 : 8,
424
391
  color: '#fff',
425
392
  fontSize: 14,
426
- paddingHorizontal: 6,
427
- paddingVertical: Platform.OS === 'ios' ? 10 : 6,
428
393
  minHeight: 40,
429
394
  },
430
- sendBtn: {
395
+ floatingSendBtn: {
431
396
  width: 40,
432
397
  height: 40,
433
398
  borderRadius: 20,
@@ -435,42 +400,15 @@ const styles = StyleSheet.create({
435
400
  alignItems: 'center',
436
401
  justifyContent: 'center',
437
402
  },
438
- sendBtnOff: {
439
- backgroundColor: 'rgba(255,255,255,0.1)',
440
- },
441
- sendIcon: {
442
- color: '#fff',
443
- fontSize: 15,
444
- fontWeight: '700',
445
- },
446
- // ── Stream ended ──────────────────────────────────────────────────────────
403
+ sendBtnOff: { backgroundColor: 'rgba(255,255,255,0.12)' },
404
+ sendIcon: { color: '#fff', fontSize: 15, fontWeight: '700' },
447
405
  endedOverlay: {
448
406
  position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
449
407
  backgroundColor: 'rgba(0,0,0,0.80)',
450
408
  alignItems: 'center',
451
409
  justifyContent: 'center',
452
410
  },
453
- endedCard: {
454
- alignItems: 'center',
455
- gap: 10,
456
- paddingHorizontal: 36,
457
- },
458
- endedTitle: {
459
- color: '#fff',
460
- fontSize: 22,
461
- fontWeight: '700',
462
- },
463
- endedSub: {
464
- color: 'rgba(255,255,255,0.5)',
465
- fontSize: 14,
466
- textAlign: 'center',
467
- lineHeight: 20,
468
- },
469
- gradientOverlay: {
470
- position: 'absolute',
471
- top: 0,
472
- left: 0,
473
- right: 0,
474
- bottom: 0,
475
- },
411
+ endedCard: { alignItems: 'center', gap: 10, paddingHorizontal: 36 },
412
+ endedTitle: { color: '#fff', fontSize: 22, fontWeight: '700' },
413
+ endedSub: { color: 'rgba(255,255,255,0.5)', fontSize: 14, textAlign: 'center', lineHeight: 20 },
476
414
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasunk99-livestream-core",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "description": "Reusable livestream viewer/host module for React Native (Expo) — mediasoup + Socket.IO",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",