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;AAmB1E,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,uDAka/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;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, KeyboardAvoidingView, Modal, NativeModules, Platform, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native';
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 up when keyboard is open fixed offset works with both adjustResize + adjustNothing
11
- const CHAT_BOTTOM_TYPING = BOTTOM_SAFE + 8;
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
- // Animates chat column up/down when keyboard opens/closes
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
- // Slide chat column based on typing state
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: 220,
88
+ duration: 200,
52
89
  useNativeDriver: false,
53
90
  }).start();
54
91
  }, [isTyping, chatAnim]);
55
- // When keyboard fully hides, close the floating input modal
92
+ // Dismiss keyboard and reset when stream becomes inactive
56
93
  useEffect(() => {
57
- const sub = Keyboard.addListener('keyboardDidHide', () => setIsTyping(false));
58
- return () => sub.remove();
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" }) })] })), 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" })] }) })), _jsx(Modal, { visible: isTyping, transparent: true, animationType: "none", statusBarTranslucent: true, onRequestClose: () => Keyboard.dismiss(), children: _jsxs(KeyboardAvoidingView, { style: styles.inputModalKAV, behavior: Platform.OS === 'ios' ? 'padding' : 'height', children: [_jsx(TouchableOpacity, { style: styles.inputModalBackdrop, activeOpacity: 1, onPress: () => Keyboard.dismiss() }), _jsxs(View, { style: styles.floatingInputWrap, 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" }) })] })] }) })] }));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasunk99-livestream-core",
3
- "version": "0.3.15",
3
+ "version": "0.3.16",
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",