@cinecrew/cinecrew-player 0.1.1 โ 0.1.2
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.
- package/README.md +20 -16
- package/package.json +1 -2
- package/src/data/README.md +5 -0
- package/src/data/emoji-data.json +1 -0
- package/src/data/emoji.js +30 -0
- package/src/native/MediaPlayerView.js +28 -12
- package/src/native/media/EmojiPickerModal.js +217 -21
- package/src/native/media/LiveChatDrawer.js +261 -103
- package/src/native/media/YouTubeVideoPlayer.web.js +64 -10
- package/src/native/media/player/PlayerBottomBar.js +4 -2
- package/src/native/media/player/PlayerTopBar.js +2 -4
- package/src/web/index.js +386 -99
- package/src/web/styles.css +341 -17
- package/types/index.d.ts +22 -7
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import dataset from './emoji-data.json';
|
|
2
|
+
|
|
3
|
+
export const EMOJI_GROUPS = [
|
|
4
|
+
{ name: 'Smileys & Emotion', icon: '๐' },
|
|
5
|
+
{ name: 'People & Body', icon: '๐' },
|
|
6
|
+
{ name: 'Animals & Nature', icon: '๐ป' },
|
|
7
|
+
{ name: 'Food & Drink', icon: '๐' },
|
|
8
|
+
{ name: 'Travel & Places', icon: '๐' },
|
|
9
|
+
{ name: 'Activities', icon: 'โฝ' },
|
|
10
|
+
{ name: 'Objects', icon: '๐ก' },
|
|
11
|
+
{ name: 'Symbols', icon: 'โค๏ธ' },
|
|
12
|
+
{ name: 'Flags', icon: '๐ณ๏ธ' },
|
|
13
|
+
].map((group) => ({
|
|
14
|
+
...group,
|
|
15
|
+
items: dataset.filter((item) => item.status === 'fully-qualified' && item.group === group.name),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
export const EMOJI_ITEMS = EMOJI_GROUPS.flatMap((group) => group.items);
|
|
19
|
+
|
|
20
|
+
export function searchEmojis(query) {
|
|
21
|
+
const normalizedQuery = String(query || '').trim().toLocaleLowerCase();
|
|
22
|
+
if (!normalizedQuery) return EMOJI_ITEMS;
|
|
23
|
+
|
|
24
|
+
return EMOJI_ITEMS.filter((item) => [
|
|
25
|
+
item.short_name,
|
|
26
|
+
item.description,
|
|
27
|
+
...(item.aliases || []),
|
|
28
|
+
...(item.keywords || []),
|
|
29
|
+
].some((value) => String(value || '').toLocaleLowerCase().includes(normalizedQuery)));
|
|
30
|
+
}
|
|
@@ -476,7 +476,9 @@ function InlinePreviewFrame({
|
|
|
476
476
|
onPress={onPromotePreview}
|
|
477
477
|
/>
|
|
478
478
|
<InlinePreviewTopActions controls={controls} muted={muted} videoOnlyMode={videoOnlyMode} handleMuteAction={handleMuteAction} />
|
|
479
|
-
|
|
479
|
+
{!isLoading ? (
|
|
480
|
+
<InlinePreviewCenterAction controls={controls} colors={colors} isPlaying={isPlaying} handlePlayPauseAction={handlePlayPauseAction} />
|
|
481
|
+
) : null}
|
|
480
482
|
<InlinePreviewBottomActions
|
|
481
483
|
controls={controls}
|
|
482
484
|
title={title}
|
|
@@ -570,10 +572,9 @@ function FullscreenControlsPanel(props) {
|
|
|
570
572
|
onRestart={props.handleRestartAction}
|
|
571
573
|
onToggleMute={props.handleMuteAction}
|
|
572
574
|
onToggleLock={props.handleLockAction}
|
|
573
|
-
onMinimize={props.handleMinimizeAction}
|
|
574
575
|
/>
|
|
575
576
|
<CenterControls
|
|
576
|
-
visible={!props.isAudioOnly && props.controls.playPause !== false}
|
|
577
|
+
visible={!props.isAudioOnly && !props.isLoading && props.controls.playPause !== false}
|
|
577
578
|
isLive={props.isLive}
|
|
578
579
|
isPlaying={props.isPlaying}
|
|
579
580
|
onSeekBy={props.handleSeekByAction}
|
|
@@ -687,6 +688,8 @@ function FullscreenChatLayer(props) {
|
|
|
687
688
|
streamId={props.mediaId}
|
|
688
689
|
integrations={props.integrations}
|
|
689
690
|
colors={props.colors}
|
|
691
|
+
messagePageSize={props.messagePageSize}
|
|
692
|
+
drawerStyle={props.drawerStyle}
|
|
690
693
|
/>
|
|
691
694
|
);
|
|
692
695
|
}
|
|
@@ -773,7 +776,6 @@ export const MediaPlayerView = ({
|
|
|
773
776
|
inlinePreview = false,
|
|
774
777
|
inlinePreviewRect = null,
|
|
775
778
|
onPromotePreview,
|
|
776
|
-
onMinimize,
|
|
777
779
|
mediaId,
|
|
778
780
|
posterUrl,
|
|
779
781
|
episodeLabel,
|
|
@@ -786,6 +788,8 @@ export const MediaPlayerView = ({
|
|
|
786
788
|
shuffle,
|
|
787
789
|
onNextEpisode,
|
|
788
790
|
liveChatNonce = 0,
|
|
791
|
+
messagePageSize = 50,
|
|
792
|
+
drawerStyle,
|
|
789
793
|
genre,
|
|
790
794
|
categoryName,
|
|
791
795
|
controls = {},
|
|
@@ -1904,9 +1908,6 @@ export const MediaPlayerView = ({
|
|
|
1904
1908
|
const handleFullscreenAction = useCallback(() => invokeAction(
|
|
1905
1909
|
'onFullscreen', toggleFullscreen, { isFullscreen: !isFullscreen },
|
|
1906
1910
|
), [invokeAction, toggleFullscreen, isFullscreen]);
|
|
1907
|
-
const handleMinimizeAction = useCallback(() => invokeAction(
|
|
1908
|
-
'onMinimize', onMinimize, { title, mediaId },
|
|
1909
|
-
), [invokeAction, onMinimize, title, mediaId]);
|
|
1910
1911
|
const handleRecordingAction = useCallback((name, fallback, event) => {
|
|
1911
1912
|
event?.stopPropagation?.();
|
|
1912
1913
|
return invokeAction(name, fallback, { title, streamUrl, mediaId });
|
|
@@ -1937,7 +1938,6 @@ export const MediaPlayerView = ({
|
|
|
1937
1938
|
seekBy: handleSeekBy,
|
|
1938
1939
|
setPlaybackRate: (rate) => setPlaybackRate(clampNumber(rate, 0.25, 4, 1)),
|
|
1939
1940
|
back: handleClose,
|
|
1940
|
-
minimize: handleMinimizeAction,
|
|
1941
1941
|
getVideoElement: () => vlcRef.current?.getVideoElement?.() || null,
|
|
1942
1942
|
getAudioTracks: () => audioTracks,
|
|
1943
1943
|
};
|
|
@@ -1945,7 +1945,7 @@ export const MediaPlayerView = ({
|
|
|
1945
1945
|
return () => {
|
|
1946
1946
|
for (const key of Object.keys(api)) delete playerApiRef.current[key];
|
|
1947
1947
|
};
|
|
1948
|
-
}, [playerApiRef, togglePlayPause, handleRestart, toggleMute, handleSelectAspectRatio, handleAudioSelect, handleSeekTo, handleSeekBy, handleClose,
|
|
1948
|
+
}, [playerApiRef, togglePlayPause, handleRestart, toggleMute, handleSelectAspectRatio, handleAudioSelect, handleSeekTo, handleSeekBy, handleClose, audioTracks]);
|
|
1949
1949
|
|
|
1950
1950
|
const handleSpeedSelect = useCallback((speed) => invokeAction(
|
|
1951
1951
|
'onPlaybackRateChange', () => {
|
|
@@ -2004,6 +2004,8 @@ export const MediaPlayerView = ({
|
|
|
2004
2004
|
windowWidthRef.current = windowWidth;
|
|
2005
2005
|
const windowHeightRef = useRef(windowHeight);
|
|
2006
2006
|
windowHeightRef.current = windowHeight;
|
|
2007
|
+
const showControlsRef = useRef(showControls);
|
|
2008
|
+
showControlsRef.current = showControls;
|
|
2007
2009
|
const handleSeekByRef = useRef(handleSeekByAction);
|
|
2008
2010
|
handleSeekByRef.current = handleSeekByAction;
|
|
2009
2011
|
const triggerSeekRippleRef = useRef(triggerSeekRipple);
|
|
@@ -2019,10 +2021,21 @@ export const MediaPlayerView = ({
|
|
|
2019
2021
|
const panResponder = useMemo(() => {
|
|
2020
2022
|
if (isWeb()) return null;
|
|
2021
2023
|
|
|
2024
|
+
const isSeekScrubGesture = (evt) => {
|
|
2025
|
+
if (!showControlsRef.current) return false;
|
|
2026
|
+
const height = windowHeightRef.current || 0;
|
|
2027
|
+
const y = Number(evt?.nativeEvent?.locationY);
|
|
2028
|
+
if (height <= 0 || !Number.isFinite(y)) return false;
|
|
2029
|
+
// Keep the bottom timeline region available to the native Slider instead
|
|
2030
|
+
// of letting the full-screen tap/gesture layer claim horizontal scrubs.
|
|
2031
|
+
return y >= height - Math.max(180, height * 0.14);
|
|
2032
|
+
};
|
|
2033
|
+
|
|
2022
2034
|
return PanResponder.create({
|
|
2023
|
-
onStartShouldSetPanResponder: () =>
|
|
2035
|
+
onStartShouldSetPanResponder: (evt) => !isSeekScrubGesture(evt),
|
|
2024
2036
|
onStartShouldSetPanResponderCapture: () => false,
|
|
2025
|
-
onMoveShouldSetPanResponder: (
|
|
2037
|
+
onMoveShouldSetPanResponder: (evt, gestureState) => {
|
|
2038
|
+
if (isSeekScrubGesture(evt)) return false;
|
|
2026
2039
|
return Math.abs(gestureState.dx) > 3 || Math.abs(gestureState.dy) > 3;
|
|
2027
2040
|
},
|
|
2028
2041
|
onMoveShouldSetPanResponderCapture: () => false,
|
|
@@ -2506,6 +2519,8 @@ export const MediaPlayerView = ({
|
|
|
2506
2519
|
popupMode
|
|
2507
2520
|
integrations={integrations}
|
|
2508
2521
|
colors={colors}
|
|
2522
|
+
messagePageSize={messagePageSize}
|
|
2523
|
+
drawerStyle={drawerStyle}
|
|
2509
2524
|
/>
|
|
2510
2525
|
) : null;
|
|
2511
2526
|
return (
|
|
@@ -2564,7 +2579,6 @@ export const MediaPlayerView = ({
|
|
|
2564
2579
|
handleRestartAction,
|
|
2565
2580
|
handleMuteAction,
|
|
2566
2581
|
handleLockAction,
|
|
2567
|
-
handleMinimizeAction,
|
|
2568
2582
|
isAudioOnly,
|
|
2569
2583
|
isPlaying,
|
|
2570
2584
|
handleSeekByAction,
|
|
@@ -2686,6 +2700,8 @@ export const MediaPlayerView = ({
|
|
|
2686
2700
|
diagnosticsEnabled={diagnosticsOverlayEnabled}
|
|
2687
2701
|
integrations={integrations}
|
|
2688
2702
|
colors={colors}
|
|
2703
|
+
messagePageSize={messagePageSize}
|
|
2704
|
+
drawerStyle={drawerStyle}
|
|
2689
2705
|
/>
|
|
2690
2706
|
<FullscreenRecordingLayer
|
|
2691
2707
|
isAudioOnly={isAudioOnly}
|
|
@@ -1,30 +1,226 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { useColorScheme } from 'react-native';
|
|
1
|
+
import React, { useMemo, useState } from 'react';
|
|
3
2
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
FlatList,
|
|
4
|
+
Modal,
|
|
5
|
+
Pressable,
|
|
6
|
+
ScrollView,
|
|
7
|
+
StyleSheet,
|
|
8
|
+
Text,
|
|
9
|
+
TextInput,
|
|
10
|
+
useColorScheme,
|
|
11
|
+
View,
|
|
12
|
+
useWindowDimensions,
|
|
13
|
+
} from 'react-native';
|
|
14
|
+
import { EMOJI_GROUPS, searchEmojis } from '../../data/emoji';
|
|
7
15
|
|
|
8
16
|
export const EmojiPickerModal = ({ visible, onClose, onSelectEmoji, colors }) => {
|
|
9
17
|
const systemScheme = useColorScheme();
|
|
10
|
-
const
|
|
18
|
+
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
|
|
19
|
+
const [activeGroup, setActiveGroup] = useState(EMOJI_GROUPS[0].name);
|
|
20
|
+
const [query, setQuery] = useState('');
|
|
21
|
+
const isDark = colors?.mode ? colors.mode === 'dark' : systemScheme !== 'light';
|
|
22
|
+
const sheetWidth = Math.min(windowWidth - 32, 420);
|
|
23
|
+
const numColumns = Math.max(5, Math.min(9, Math.floor((sheetWidth - 32) / 42)));
|
|
24
|
+
const activeGroupData = EMOJI_GROUPS.find((group) => group.name === activeGroup) || EMOJI_GROUPS[0];
|
|
25
|
+
const emojis = useMemo(
|
|
26
|
+
() => query.trim() ? searchEmojis(query) : activeGroupData.items,
|
|
27
|
+
[activeGroupData, query],
|
|
28
|
+
);
|
|
29
|
+
const palette = {
|
|
30
|
+
surface: colors?.surfaceColor || (isDark ? '#17212B' : '#FFFFFF'),
|
|
31
|
+
text: colors?.controlColor || (isDark ? '#F5F7FA' : '#202124'),
|
|
32
|
+
muted: colors?.mutedColor || (isDark ? '#AAB4C0' : '#5F6368'),
|
|
33
|
+
accent: colors?.accentColor || '#00B8D4',
|
|
34
|
+
outline: colors?.borderColor || (isDark ? 'rgba(255,255,255,0.16)' : 'rgba(32,33,36,0.18)'),
|
|
35
|
+
backdrop: 'rgba(0,0,0,0.58)',
|
|
36
|
+
};
|
|
37
|
+
const close = () => {
|
|
38
|
+
onClose?.();
|
|
39
|
+
};
|
|
11
40
|
|
|
12
41
|
return (
|
|
13
|
-
<
|
|
42
|
+
<Modal
|
|
14
43
|
visible={visible}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
44
|
+
transparent
|
|
45
|
+
animationType="fade"
|
|
46
|
+
onRequestClose={close}
|
|
47
|
+
statusBarTranslucent
|
|
48
|
+
>
|
|
49
|
+
<Pressable style={[styles.backdrop, { backgroundColor: palette.backdrop }]} onPress={close}>
|
|
50
|
+
<Pressable
|
|
51
|
+
style={[styles.sheet, {
|
|
52
|
+
width: sheetWidth,
|
|
53
|
+
height: Math.min(Math.max(300, windowHeight * 0.72), 560),
|
|
54
|
+
backgroundColor: palette.surface,
|
|
55
|
+
borderColor: palette.outline,
|
|
56
|
+
}]}
|
|
57
|
+
onPress={(event) => event.stopPropagation()}
|
|
58
|
+
>
|
|
59
|
+
<View style={styles.header}>
|
|
60
|
+
<Text style={[styles.title, { color: palette.text }]}>Choose an emoji</Text>
|
|
61
|
+
<Pressable
|
|
62
|
+
accessibilityRole="button"
|
|
63
|
+
accessibilityLabel="Close emoji picker"
|
|
64
|
+
hitSlop={10}
|
|
65
|
+
onPress={close}
|
|
66
|
+
style={[styles.closeButton, { borderColor: palette.outline }]}
|
|
67
|
+
>
|
|
68
|
+
<Text style={[styles.closeLabel, { color: palette.muted }]}>ร</Text>
|
|
69
|
+
</Pressable>
|
|
70
|
+
</View>
|
|
71
|
+
<TextInput
|
|
72
|
+
accessibilityLabel="Search emojis"
|
|
73
|
+
value={query}
|
|
74
|
+
onChangeText={setQuery}
|
|
75
|
+
placeholder="Search all emojis"
|
|
76
|
+
placeholderTextColor={palette.muted}
|
|
77
|
+
returnKeyType="search"
|
|
78
|
+
style={[styles.search, { color: palette.text, borderColor: palette.outline }]}
|
|
79
|
+
/>
|
|
80
|
+
<ScrollView
|
|
81
|
+
horizontal
|
|
82
|
+
showsHorizontalScrollIndicator={false}
|
|
83
|
+
keyboardShouldPersistTaps="handled"
|
|
84
|
+
contentContainerStyle={styles.categoryList}
|
|
85
|
+
accessibilityLabel="Emoji categories"
|
|
86
|
+
>
|
|
87
|
+
{EMOJI_GROUPS.map((group) => {
|
|
88
|
+
const selected = !query.trim() && group.name === activeGroup;
|
|
89
|
+
return (
|
|
90
|
+
<Pressable
|
|
91
|
+
key={group.name}
|
|
92
|
+
accessibilityRole="button"
|
|
93
|
+
accessibilityLabel={`${group.name} emojis`}
|
|
94
|
+
accessibilityState={{ selected }}
|
|
95
|
+
onPress={() => setActiveGroup(group.name)}
|
|
96
|
+
style={[styles.categoryButton, { borderColor: selected ? palette.accent : 'transparent' }]}
|
|
97
|
+
>
|
|
98
|
+
<Text style={styles.categoryIcon}>{group.icon}</Text>
|
|
99
|
+
</Pressable>
|
|
100
|
+
);
|
|
101
|
+
})}
|
|
102
|
+
</ScrollView>
|
|
103
|
+
<Text style={[styles.groupTitle, { color: palette.muted }]}>
|
|
104
|
+
{query.trim() ? `Search results ยท ${emojis.length}` : activeGroupData.name}
|
|
105
|
+
</Text>
|
|
106
|
+
<FlatList
|
|
107
|
+
key={`${numColumns}-${query.trim() ? 'search' : activeGroup}`}
|
|
108
|
+
data={emojis}
|
|
109
|
+
keyExtractor={(item) => item.codepoints}
|
|
110
|
+
numColumns={numColumns}
|
|
111
|
+
keyboardShouldPersistTaps="handled"
|
|
112
|
+
contentContainerStyle={styles.grid}
|
|
113
|
+
initialNumToRender={numColumns * 6}
|
|
114
|
+
maxToRenderPerBatch={numColumns * 8}
|
|
115
|
+
windowSize={7}
|
|
116
|
+
renderItem={({ item }) => (
|
|
117
|
+
<Pressable
|
|
118
|
+
accessibilityRole="button"
|
|
119
|
+
accessibilityLabel={`Insert ${item.short_name}`}
|
|
120
|
+
onPress={() => {
|
|
121
|
+
onSelectEmoji?.(item.emoji);
|
|
122
|
+
close();
|
|
123
|
+
}}
|
|
124
|
+
style={({ pressed }) => [styles.emojiButton, pressed && { backgroundColor: `${palette.accent}24` }]}
|
|
125
|
+
>
|
|
126
|
+
<Text style={styles.emoji}>{item.emoji}</Text>
|
|
127
|
+
</Pressable>
|
|
128
|
+
)}
|
|
129
|
+
ListEmptyComponent={<Text style={[styles.empty, { color: palette.muted }]}>No emojis found.</Text>}
|
|
130
|
+
/>
|
|
131
|
+
</Pressable>
|
|
132
|
+
</Pressable>
|
|
133
|
+
</Modal>
|
|
29
134
|
);
|
|
30
135
|
};
|
|
136
|
+
|
|
137
|
+
const styles = StyleSheet.create({
|
|
138
|
+
backdrop: {
|
|
139
|
+
flex: 1,
|
|
140
|
+
justifyContent: 'center',
|
|
141
|
+
alignItems: 'center',
|
|
142
|
+
padding: 16,
|
|
143
|
+
},
|
|
144
|
+
sheet: {
|
|
145
|
+
maxHeight: '82%',
|
|
146
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
147
|
+
borderRadius: 20,
|
|
148
|
+
padding: 16,
|
|
149
|
+
overflow: 'hidden',
|
|
150
|
+
elevation: 12,
|
|
151
|
+
shadowColor: '#000',
|
|
152
|
+
shadowOffset: { width: 0, height: 8 },
|
|
153
|
+
shadowOpacity: 0.28,
|
|
154
|
+
shadowRadius: 20,
|
|
155
|
+
},
|
|
156
|
+
header: {
|
|
157
|
+
minHeight: 44,
|
|
158
|
+
flexDirection: 'row',
|
|
159
|
+
alignItems: 'center',
|
|
160
|
+
justifyContent: 'space-between',
|
|
161
|
+
marginBottom: 10,
|
|
162
|
+
},
|
|
163
|
+
title: {
|
|
164
|
+
fontSize: 17,
|
|
165
|
+
fontWeight: '700',
|
|
166
|
+
},
|
|
167
|
+
closeButton: {
|
|
168
|
+
width: 34,
|
|
169
|
+
height: 34,
|
|
170
|
+
alignItems: 'center',
|
|
171
|
+
justifyContent: 'center',
|
|
172
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
173
|
+
borderRadius: 17,
|
|
174
|
+
},
|
|
175
|
+
closeLabel: {
|
|
176
|
+
fontSize: 25,
|
|
177
|
+
lineHeight: 29,
|
|
178
|
+
},
|
|
179
|
+
search: {
|
|
180
|
+
minHeight: 42,
|
|
181
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
182
|
+
borderRadius: 12,
|
|
183
|
+
paddingHorizontal: 12,
|
|
184
|
+
marginBottom: 10,
|
|
185
|
+
fontSize: 15,
|
|
186
|
+
},
|
|
187
|
+
categoryList: {
|
|
188
|
+
alignItems: 'center',
|
|
189
|
+
gap: 4,
|
|
190
|
+
paddingBottom: 8,
|
|
191
|
+
},
|
|
192
|
+
categoryButton: {
|
|
193
|
+
width: 38,
|
|
194
|
+
height: 38,
|
|
195
|
+
alignItems: 'center',
|
|
196
|
+
justifyContent: 'center',
|
|
197
|
+
borderBottomWidth: 2,
|
|
198
|
+
borderRadius: 8,
|
|
199
|
+
},
|
|
200
|
+
categoryIcon: {
|
|
201
|
+
fontSize: 20,
|
|
202
|
+
},
|
|
203
|
+
groupTitle: {
|
|
204
|
+
fontSize: 13,
|
|
205
|
+
fontWeight: '600',
|
|
206
|
+
marginBottom: 6,
|
|
207
|
+
},
|
|
208
|
+
grid: {
|
|
209
|
+
alignItems: 'center',
|
|
210
|
+
paddingBottom: 8,
|
|
211
|
+
},
|
|
212
|
+
emojiButton: {
|
|
213
|
+
width: 42,
|
|
214
|
+
height: 42,
|
|
215
|
+
alignItems: 'center',
|
|
216
|
+
justifyContent: 'center',
|
|
217
|
+
borderRadius: 12,
|
|
218
|
+
},
|
|
219
|
+
emoji: {
|
|
220
|
+
fontSize: 24,
|
|
221
|
+
},
|
|
222
|
+
empty: {
|
|
223
|
+
paddingVertical: 20,
|
|
224
|
+
textAlign: 'center',
|
|
225
|
+
},
|
|
226
|
+
});
|