kasunk99-livestream-core 0.3.37 โ†’ 0.3.39

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,KAAkE,MAAM,OAAO,CAAC;AAmBvF,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;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CAChD,CAAC;AAmEF,eAAO,MAAM,oBAAoB,uDAme/B,CAAC"}
1
+ {"version":3,"file":"LiveStreamViewerItem.d.ts","sourceRoot":"","sources":["../../src/components/LiveStreamViewerItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkE,MAAM,OAAO,CAAC;AAmBvF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAmB/C,KAAK,yBAAyB,GAAG;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CAChD,CAAC;AAmEF,eAAO,MAAM,oBAAoB,uDA0gB/B,CAAC"}
@@ -5,6 +5,17 @@ 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
7
  const BRAND_GREEN = '#009F68';
8
+ const EMOJIS = [
9
+ '๐Ÿ˜€', '๐Ÿ˜', '๐Ÿ˜‚', '๐Ÿคฃ', '๐Ÿ˜ƒ', '๐Ÿ˜„', '๐Ÿ˜…', '๐Ÿ˜†', '๐Ÿ˜‰', '๐Ÿ˜Š',
10
+ '๐Ÿ˜‹', '๐Ÿ˜Ž', '๐Ÿ˜', '๐Ÿฅฐ', '๐Ÿ˜˜', '๐Ÿคฉ', '๐Ÿฅณ', '๐Ÿ˜', '๐Ÿ˜’', '๐Ÿ™„',
11
+ '๐Ÿ˜ค', '๐Ÿ˜ก', '๐Ÿคฌ', '๐Ÿ˜ญ', '๐Ÿ˜ข', '๐Ÿ˜ฑ', '๐Ÿ˜จ', '๐Ÿค”', '๐Ÿคญ', '๐Ÿ˜ด',
12
+ 'โค๏ธ', '๐Ÿงก', '๐Ÿ’›', '๐Ÿ’š', '๐Ÿ’™', '๐Ÿ’œ', '๐Ÿ–ค', '๐Ÿค', '๐Ÿ’”', '๐Ÿ’•',
13
+ '๐Ÿ’ฏ', '๐Ÿ”ฅ', 'โœจ', 'โญ', '๐ŸŒŸ', '๐Ÿ’ฅ', '๐ŸŽ‰', '๐ŸŽŠ', '๐Ÿ†', '๐Ÿ‘‘',
14
+ '๐Ÿ‘', '๐Ÿ‘Ž', '๐Ÿ‘', '๐Ÿ™Œ', '๐Ÿ‘Š', 'โœŒ๏ธ', '๐Ÿคž', '๐Ÿค™', '๐Ÿ’ช', '๐Ÿ™',
15
+ '๐Ÿ˜ˆ', '๐Ÿ‘ป', '๐Ÿ’€', '๐Ÿถ', '๐Ÿฑ', '๐Ÿผ', '๐ŸฆŠ', '๐Ÿธ', '๐Ÿฆ‹', '๐ŸŒธ',
16
+ '๐Ÿ•', '๐Ÿ”', '๐ŸŸ', '๐ŸŒฎ', '๐Ÿœ', '๐Ÿฃ', '๐Ÿฐ', '๐ŸŽ‚', '๐Ÿซ', '๐Ÿญ',
17
+ 'โšฝ', '๐Ÿ€', '๐ŸŽฎ', '๐ŸŽต', '๐ŸŽถ', '๐Ÿ“ฑ', '๐Ÿ’ป', '๐Ÿ“ธ', '๐Ÿš€', '๐ŸŒˆ',
18
+ ];
8
19
  let RTCViewComponent = null;
9
20
  try {
10
21
  const webrtc = require('react-native-webrtc');
@@ -62,6 +73,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
62
73
  const [chatMessages, setChatMessages] = useState([]);
63
74
  // isTyping controls whether we show the TextInput bar or the pill bar
64
75
  const [isTyping, setIsTyping] = useState(false);
76
+ const [showEmoji, setShowEmoji] = useState(false);
65
77
  const [floatingHearts, setFloatingHearts] = useState([]);
66
78
  const heartIdRef = useRef(0);
67
79
  const handleLike = useCallback(() => {
@@ -76,6 +88,16 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
76
88
  setFloatingHearts((prev) => prev.filter((h) => h.id !== id));
77
89
  });
78
90
  }, [joined, emitLike]);
91
+ const openEmoji = useCallback(() => {
92
+ Keyboard.dismiss();
93
+ setIsTyping(false);
94
+ setShowEmoji(true);
95
+ }, []);
96
+ const handleEmojiSelect = useCallback((emoji) => {
97
+ const next = chatInputRef.current + emoji;
98
+ chatInputRef.current = next;
99
+ setChatInput(next);
100
+ }, []);
79
101
  // Incremented every time the app returns to foreground โ€” forces RTCView to remount,
80
102
  // which restarts the Android SurfaceView renderer that freezes during background.
81
103
  const [videoKey, setVideoKey] = useState(0);
@@ -268,7 +290,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
268
290
  const avatarLetter = hostLabel[0]?.toUpperCase() ?? 'L';
269
291
  const hasUnsent = chatInput.trim().length > 0;
270
292
  const canType = joined && !joining && !error;
271
- return (_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}-${videoKey}`)) : (_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.topRight, pointerEvents: "box-none", children: [_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() : 'โ€”' })] }), onLeave ? (_jsx(TouchableOpacity, { style: styles.closeBtn, onPress: onLeave, hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, activeOpacity: 0.7, children: IoniconsComponent
293
+ return (_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}-${videoKey}`)) : (_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.topRight, pointerEvents: "box-none", children: [_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.viewerChip, pointerEvents: "none", children: [_jsx(Text, { style: styles.viewerEye, children: "\u2665" }), _jsx(Text, { style: styles.viewerCount, children: likeCount >= 1000 ? `${(likeCount / 1000).toFixed(1)}K` : String(likeCount) })] }), onLeave ? (_jsx(TouchableOpacity, { style: styles.closeBtn, onPress: onLeave, hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, activeOpacity: 0.7, children: IoniconsComponent
272
294
  ? _jsx(IoniconsComponent, { name: "close", size: 20, color: "#fff" })
273
295
  : _jsx(Text, { style: { color: '#fff', fontSize: 18, fontWeight: '700' }, children: "\u00D7" }) })) : null] })] }), _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))) }), _jsx(View, { style: styles.heartsContainer, pointerEvents: "none", children: floatingHearts.map(({ id, anim, dx }) => (_jsx(Animated.Text, { style: [
274
296
  styles.floatingHeart,
@@ -279,14 +301,16 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
279
301
  ],
280
302
  opacity: anim.interpolate({ inputRange: [0, 0.65, 1], outputRange: [1, 0.85, 0] }),
281
303
  },
282
- ], children: "\u2665" }, 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 }), RNGHTouchable ? (_jsx(RNGHTouchable, { onPress: sendChat, activeOpacity: 0.75, style: [styles.sendBtn, (!hasUnsent || !joined) && styles.sendBtnOff], hitSlop: { top: 8, right: 8, bottom: 8, left: 8 }, children: IoniconsComponent
304
+ ], children: "\u2665" }, 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, onFocus: () => setShowEmoji(false), onSubmitEditing: sendChat, returnKeyType: "send", submitBehavior: "submit", autoFocus: true }), RNGHTouchable ? (_jsx(RNGHTouchable, { onPress: sendChat, activeOpacity: 0.75, style: [styles.sendBtn, (!hasUnsent || !joined) && styles.sendBtnOff], hitSlop: { top: 8, right: 8, bottom: 8, left: 8 }, children: IoniconsComponent
283
305
  ? _jsx(IoniconsComponent, { name: "send", size: 18, color: "#fff" })
284
306
  : _jsx(Text, { style: styles.sendIcon, children: "\u27A4" }) })) : (_jsx(Pressable, { style: [styles.sendBtn, (!hasUnsent || !joined) && styles.sendBtnOff], onPressIn: sendChat, hitSlop: { top: 8, right: 8, bottom: 8, left: 8 }, children: IoniconsComponent
285
307
  ? _jsx(IoniconsComponent, { name: "send", size: 18, color: "#fff" })
286
308
  : _jsx(Text, { style: styles.sendIcon, children: "\u27A4" }) }))] })) : (
287
309
  // Pill bar โ€” tapping opens the input bar.
288
- _jsxs(View, { style: styles.bottomBar, children: [_jsx(TouchableOpacity, { style: [styles.typeTouchable, hasUnsent && styles.typeTouchableFilled], onPress: () => { if (canType)
289
- 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" }) }), _jsxs(TouchableOpacity, { style: styles.likeBtn, onPress: handleLike, activeOpacity: 0.75, children: [_jsx(Text, { style: [styles.bottomIconGlyph, styles.heartIcon], children: "\u2665" }), likeCount > 0 && (_jsx(Text, { style: styles.likeCountText, children: likeCount >= 1000 ? `${(likeCount / 1000).toFixed(1)}K` : String(likeCount) }))] })] })), 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" })] }) }))] }));
310
+ _jsxs(View, { style: styles.bottomBar, children: [_jsx(TouchableOpacity, { style: [styles.typeTouchable, hasUnsent && styles.typeTouchableFilled], onPress: () => { if (canType) {
311
+ setShowEmoji(false);
312
+ setIsTyping(true);
313
+ } }, 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, onPress: openEmoji, children: _jsx(Text, { style: [styles.bottomIconGlyph, showEmoji && styles.emojiIconActive], children: "\u263A" }) }), _jsx(TouchableOpacity, { style: styles.likeBtn, onPress: handleLike, activeOpacity: 0.75, children: _jsx(Text, { style: [styles.bottomIconGlyph, styles.heartIcon], children: "\u2665" }) })] })), showEmoji && !isTyping ? (_jsx(View, { style: styles.emojiContainer, children: _jsx(ScrollView, { style: styles.emojiScroll, showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "always", children: _jsx(View, { style: styles.emojiGrid, children: EMOJIS.map((emoji, idx) => (_jsx(TouchableOpacity, { style: styles.emojiCell, onPress: () => handleEmojiSelect(emoji), activeOpacity: 0.6, children: _jsx(Text, { style: styles.emojiText, children: emoji }) }, idx))) }) }) })) : null, 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" })] }) }))] }));
290
314
  });
291
315
  // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
292
316
  const styles = StyleSheet.create({
@@ -488,4 +512,26 @@ const styles = StyleSheet.create({
488
512
  endedCard: { alignItems: 'center', gap: 10, paddingHorizontal: 36 },
489
513
  endedTitle: { color: '#fff', fontSize: 22, fontWeight: '700' },
490
514
  endedSub: { color: 'rgba(255,255,255,0.5)', fontSize: 14, textAlign: 'center', lineHeight: 20 },
515
+ // โ”€โ”€ Emoji picker โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
516
+ emojiIconActive: { color: BRAND_GREEN },
517
+ emojiContainer: {
518
+ height: 260,
519
+ backgroundColor: 'rgba(18,18,18,0.97)',
520
+ borderTopWidth: StyleSheet.hairlineWidth,
521
+ borderTopColor: 'rgba(255,255,255,0.10)',
522
+ },
523
+ emojiScroll: { flex: 1 },
524
+ emojiGrid: {
525
+ flexDirection: 'row',
526
+ flexWrap: 'wrap',
527
+ paddingHorizontal: 4,
528
+ paddingVertical: 8,
529
+ },
530
+ emojiCell: {
531
+ width: '12.5%',
532
+ aspectRatio: 1,
533
+ alignItems: 'center',
534
+ justifyContent: 'center',
535
+ },
536
+ emojiText: { fontSize: 26 },
491
537
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasunk99-livestream-core",
3
- "version": "0.3.37",
3
+ "version": "0.3.39",
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",