movius-chats 1.3.13 → 1.4.0
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/lib/commonjs/index.js +5 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +5 -3
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/assets/Icons/ChevronUpIcon.d.ts +5 -0
- package/lib/typescript/assets/Icons/LockIcon.d.ts +5 -0
- package/lib/typescript/assets/Icons/TrashIcon.d.ts +5 -0
- package/lib/typescript/components/AudioPlayer/types.d.ts +1 -0
- package/lib/typescript/components/ChatInput/types.d.ts +2 -2
- package/lib/typescript/components/VoiceRecorder/LongPressRecording.d.ts +13 -0
- package/lib/typescript/components/VoiceRecorder/NormalRecording.d.ts +20 -0
- package/lib/typescript/components/VoiceRecorder/WaveformAnimation.d.ts +10 -0
- package/lib/typescript/hooks/useVoiceRecorder.d.ts +19 -0
- package/lib/typescript/types/index.d.ts +74 -1
- package/package.json +12 -2
- package/scripts/patchSound.js +48 -23
- package/src/assets/Icons/ChevronUpIcon.tsx +20 -0
- package/src/assets/Icons/LockIcon.tsx +28 -0
- package/src/assets/Icons/TrashIcon.tsx +26 -0
- package/src/components/AudioPlayer/AudioPlayer.tsx +147 -163
- package/src/components/AudioPlayer/types.ts +1 -0
- package/src/components/ChatBubble/MediaGrid.tsx +4 -1
- package/src/components/ChatBubble/MessageContent.tsx +1 -0
- package/src/components/ChatInput/ChatInput.tsx +296 -62
- package/src/components/ChatInput/FilePreview.tsx +3 -0
- package/src/components/ChatInput/types.ts +2 -2
- package/src/components/MediaViewer/MediaViewer.tsx +45 -10
- package/src/components/VoiceRecorder/LongPressRecording.tsx +195 -0
- package/src/components/VoiceRecorder/NormalRecording.tsx +156 -0
- package/src/components/VoiceRecorder/WaveformAnimation.tsx +56 -0
- package/src/hooks/useVoiceRecorder.ts +206 -0
- package/src/types/index.ts +80 -1
|
@@ -1,34 +1,22 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useState } from 'react';
|
|
1
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { Image, PanResponder, Pressable, Text, View } from 'react-native';
|
|
3
|
-
import Animated, {
|
|
4
|
-
useAnimatedStyle,
|
|
5
|
-
useSharedValue,
|
|
6
|
-
withSpring,
|
|
7
|
-
} from 'react-native-reanimated';
|
|
8
3
|
|
|
9
4
|
// ─── New-Architecture compatibility shim ─────────────────────────────────────
|
|
10
|
-
// react-native-sound imports resolveAssetSource
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// so that the Sound constructor doesn't throw.
|
|
15
|
-
// The movius-chats postinstall script permanently patches Sound.js on `npm install`;
|
|
16
|
-
// this runtime shim is a second safety net for apps that haven't reinstalled yet.
|
|
5
|
+
// react-native-sound imports resolveAssetSource the old way; on New Architecture
|
|
6
|
+
// that path returns an Object not a function → "resolveAssetSource is not a function".
|
|
7
|
+
// We patch the cached module here as a runtime safety net.
|
|
8
|
+
// The movius-chats postinstall script applies the permanent file-level fix.
|
|
17
9
|
try {
|
|
18
10
|
const ras = require('react-native/Libraries/Image/resolveAssetSource');
|
|
19
11
|
if (typeof ras !== 'function') {
|
|
20
12
|
const fn = Image.resolveAssetSource.bind(Image);
|
|
21
|
-
// Overwrite every exported key so any destructure or default-import also works.
|
|
22
13
|
Object.keys(ras).forEach((k) => {
|
|
23
14
|
try { (ras as any)[k] = (fn as any)[k]; } catch {}
|
|
24
15
|
});
|
|
25
|
-
// Copy the function's own properties onto the object so calling it works too.
|
|
26
16
|
Object.defineProperty(ras, '__esModule', { value: false, configurable: true });
|
|
27
|
-
// Make the object itself callable — not possible in JS, but we expose a helper
|
|
28
|
-
// via the global that react-native-sound can fall back to if it checks typeof.
|
|
29
17
|
(global as any).__moviusRAS = fn;
|
|
30
18
|
}
|
|
31
|
-
} catch { /*
|
|
19
|
+
} catch { /* safe to skip */ }
|
|
32
20
|
|
|
33
21
|
import Sound from 'react-native-sound';
|
|
34
22
|
import tw from 'twrnc';
|
|
@@ -40,25 +28,67 @@ import { formatDuration } from '../../utils/datefunc';
|
|
|
40
28
|
import { withFontFamily } from '../../utils/theme';
|
|
41
29
|
import { AudioPlayerProps } from './types';
|
|
42
30
|
|
|
31
|
+
// ─── Waveform generator ───────────────────────────────────────────────────────
|
|
32
|
+
// Generates a deterministic pseudo-random bar-height array from the audio URL
|
|
33
|
+
// so the same message always shows the same waveform shape.
|
|
34
|
+
const WAVEFORM_BARS = 34;
|
|
35
|
+
const WAVEFORM_H = 34; // total container height in px
|
|
36
|
+
|
|
37
|
+
function generateWaveform(url: string, count: number): number[] {
|
|
38
|
+
// djb2 hash seeded from URL
|
|
39
|
+
let h = 5381;
|
|
40
|
+
for (let i = 0; i < url.length; i++) {
|
|
41
|
+
h = ((h << 5) + h + url.charCodeAt(i)) | 0;
|
|
42
|
+
}
|
|
43
|
+
return Array.from({ length: count }, (_, i) => {
|
|
44
|
+
// Mix the index in so adjacent bars differ
|
|
45
|
+
h = (Math.imul(h ^ (h >>> 16), 0x45d9f3b + i * 31337)) | 0;
|
|
46
|
+
h = h ^ (h >>> 13);
|
|
47
|
+
const raw = Math.abs(h) % 100;
|
|
48
|
+
// Shape: bias toward mid heights (more natural look)
|
|
49
|
+
// minimum 18 % so very short bars still show
|
|
50
|
+
return 0.18 + (raw / 100) * 0.82;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── Component ───────────────────────────────────────────────────────────────
|
|
43
55
|
const AudioPlayer: React.FC<AudioPlayerProps> = ({
|
|
44
56
|
audioUrl,
|
|
45
57
|
audioId,
|
|
46
58
|
isVideoPlaying,
|
|
59
|
+
isCurrentUser,
|
|
47
60
|
}) => {
|
|
48
61
|
const { theme, CustomPlayIcon, CustomPauseIcon } = useChatContext();
|
|
49
62
|
const { currentlyPlayingId, setCurrentlyPlayingId } = useAudio();
|
|
63
|
+
|
|
50
64
|
const [sound, setSound] = useState<Sound | null>(null);
|
|
51
65
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
52
66
|
const [currentTime, setCurrentTime] = useState(0);
|
|
53
67
|
const [duration, setDuration] = useState(0);
|
|
54
68
|
const [isDragging, setIsDragging] = useState(false);
|
|
55
|
-
const progressRef = useRef<View>(null);
|
|
56
|
-
const progressWidth = useRef(0);
|
|
57
|
-
const progressX = useRef(0);
|
|
58
|
-
const startX = useRef(0);
|
|
59
|
-
const knobPosition = useSharedValue(0);
|
|
60
69
|
|
|
61
|
-
|
|
70
|
+
const waveformWidth = useRef(0);
|
|
71
|
+
const soundRef = useRef<Sound | null>(null);
|
|
72
|
+
|
|
73
|
+
// Pre-compute waveform shape once per URL
|
|
74
|
+
const waveform = useMemo(() => generateWaveform(audioUrl, WAVEFORM_BARS), [audioUrl]);
|
|
75
|
+
|
|
76
|
+
// ── Resolved colors (sent vs received defaults) ─────────────────────────
|
|
77
|
+
const inactiveBarColor =
|
|
78
|
+
theme?.colors?.audioWaveformColor ??
|
|
79
|
+
(isCurrentUser ? 'rgba(255,255,255,0.35)' : 'rgba(0,0,0,0.20)');
|
|
80
|
+
|
|
81
|
+
const activeBarColor =
|
|
82
|
+
theme?.colors?.audioWaveformActiveColor ??
|
|
83
|
+
(isCurrentUser ? 'rgba(255,255,255,0.95)' : 'rgba(0,0,0,0.60)');
|
|
84
|
+
|
|
85
|
+
const timestampColor =
|
|
86
|
+
(isCurrentUser
|
|
87
|
+
? theme?.colors?.sentAudioTimestampColor
|
|
88
|
+
: theme?.colors?.receivedAudioTimestampColor) ??
|
|
89
|
+
(isCurrentUser ? 'rgba(255,255,255,0.75)' : 'rgba(0,0,0,0.45)');
|
|
90
|
+
|
|
91
|
+
// ── Initialize sound ────────────────────────────────────────────────────
|
|
62
92
|
useEffect(() => {
|
|
63
93
|
let mounted = true;
|
|
64
94
|
let newSound: Sound | null = null;
|
|
@@ -70,111 +100,83 @@ const AudioPlayer: React.FC<AudioPlayerProps> = ({
|
|
|
70
100
|
}
|
|
71
101
|
});
|
|
72
102
|
setSound(newSound);
|
|
73
|
-
|
|
103
|
+
soundRef.current = newSound;
|
|
104
|
+
} catch {
|
|
74
105
|
console.warn(
|
|
75
|
-
'[movius-chats] AudioPlayer: Could not initialize react-native-sound
|
|
76
|
-
'
|
|
77
|
-
'the resolveAssetSource compatibility patch automatically.'
|
|
106
|
+
'[movius-chats] AudioPlayer: Could not initialize react-native-sound. ' +
|
|
107
|
+
'Reinstall the package to apply the resolveAssetSource patch automatically.'
|
|
78
108
|
);
|
|
79
109
|
}
|
|
80
110
|
|
|
81
111
|
return () => {
|
|
82
112
|
mounted = false;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
113
|
+
newSound?.pause();
|
|
114
|
+
newSound?.release();
|
|
115
|
+
soundRef.current = null;
|
|
87
116
|
};
|
|
88
117
|
}, [audioUrl]);
|
|
89
118
|
|
|
90
|
-
//
|
|
119
|
+
// ── Stop when another audio/video starts ────────────────────────────────
|
|
91
120
|
useEffect(() => {
|
|
92
|
-
if (
|
|
93
|
-
currentlyPlayingId &&
|
|
94
|
-
currentlyPlayingId !== audioId &&
|
|
95
|
-
isPlaying &&
|
|
96
|
-
sound
|
|
97
|
-
) {
|
|
121
|
+
if (currentlyPlayingId && currentlyPlayingId !== audioId && isPlaying && sound) {
|
|
98
122
|
sound.pause();
|
|
99
123
|
setIsPlaying(false);
|
|
100
124
|
setCurrentTime(0);
|
|
101
|
-
knobPosition.value = 0;
|
|
102
125
|
}
|
|
103
126
|
}, [currentlyPlayingId, audioId, isPlaying, sound]);
|
|
104
127
|
|
|
105
|
-
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (isVideoPlaying && isPlaying && sound) {
|
|
130
|
+
sound.pause(() => {
|
|
131
|
+
setIsPlaying(false);
|
|
132
|
+
setCurrentlyPlayingId(null);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}, [isVideoPlaying]);
|
|
136
|
+
|
|
137
|
+
// ── Progress polling ─────────────────────────────────────────────────────
|
|
106
138
|
useEffect(() => {
|
|
107
139
|
let interval: ReturnType<typeof setInterval>;
|
|
108
140
|
if (isPlaying && sound && !isDragging) {
|
|
109
141
|
interval = setInterval(() => {
|
|
110
|
-
sound.getCurrentTime((
|
|
111
|
-
if (typeof
|
|
112
|
-
setCurrentTime(
|
|
113
|
-
if (progressWidth.current > 0 && duration > 0) {
|
|
114
|
-
const progress = (seconds / duration) * progressWidth.current;
|
|
115
|
-
if (!isNaN(progress)) {
|
|
116
|
-
knobPosition.value = withSpring(progress, {
|
|
117
|
-
damping: 15,
|
|
118
|
-
stiffness: 100,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
}
|
|
142
|
+
sound.getCurrentTime((sec) => {
|
|
143
|
+
if (typeof sec === 'number' && !isNaN(sec)) {
|
|
144
|
+
setCurrentTime(sec);
|
|
122
145
|
}
|
|
123
146
|
});
|
|
124
|
-
},
|
|
147
|
+
}, 80);
|
|
125
148
|
}
|
|
126
|
-
return () => {
|
|
127
|
-
if (interval) clearInterval(interval);
|
|
128
|
-
};
|
|
149
|
+
return () => { if (interval) clearInterval(interval); };
|
|
129
150
|
}, [isPlaying, sound, isDragging, duration]);
|
|
130
151
|
|
|
152
|
+
// ── Seek helper ──────────────────────────────────────────────────────────
|
|
153
|
+
const seekTo = (x: number) => {
|
|
154
|
+
const w = waveformWidth.current;
|
|
155
|
+
if (w <= 0 || duration <= 0) return;
|
|
156
|
+
const ratio = Math.max(0, Math.min(x / w, 1));
|
|
157
|
+
const t = ratio * duration;
|
|
158
|
+
setCurrentTime(t);
|
|
159
|
+
soundRef.current?.setCurrentTime(t);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// ── PanResponder on waveform ─────────────────────────────────────────────
|
|
131
163
|
const panResponder = PanResponder.create({
|
|
132
164
|
onStartShouldSetPanResponder: () => true,
|
|
133
165
|
onMoveShouldSetPanResponder: () => true,
|
|
134
166
|
onPanResponderGrant: (evt) => {
|
|
135
167
|
setIsDragging(true);
|
|
136
|
-
|
|
168
|
+
seekTo(evt.nativeEvent.locationX);
|
|
137
169
|
},
|
|
138
170
|
onPanResponderMove: (evt) => {
|
|
139
|
-
|
|
140
|
-
const newPosition = evt.nativeEvent.pageX - startX.current;
|
|
141
|
-
const boundedPosition = Math.max(
|
|
142
|
-
0,
|
|
143
|
-
Math.min(newPosition, progressWidth.current)
|
|
144
|
-
);
|
|
145
|
-
knobPosition.value = boundedPosition;
|
|
146
|
-
|
|
147
|
-
const percentage = boundedPosition / progressWidth.current;
|
|
148
|
-
const newTime = percentage * duration;
|
|
149
|
-
if (!isNaN(newTime)) {
|
|
150
|
-
setCurrentTime(newTime);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
},
|
|
154
|
-
onPanResponderRelease: () => {
|
|
155
|
-
setIsDragging(false);
|
|
156
|
-
if (sound && progressWidth.current > 0) {
|
|
157
|
-
const percentage = knobPosition.value / progressWidth.current;
|
|
158
|
-
const newTime = percentage * duration;
|
|
159
|
-
if (!isNaN(newTime)) {
|
|
160
|
-
sound.setCurrentTime(newTime);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
onPanResponderTerminate: () => {
|
|
165
|
-
setIsDragging(false);
|
|
171
|
+
seekTo(evt.nativeEvent.locationX);
|
|
166
172
|
},
|
|
173
|
+
onPanResponderRelease: () => { setIsDragging(false); },
|
|
174
|
+
onPanResponderTerminate: () => { setIsDragging(false); },
|
|
167
175
|
});
|
|
168
176
|
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
transform: [{ translateX: knobPosition.value }],
|
|
172
|
-
};
|
|
173
|
-
});
|
|
174
|
-
|
|
177
|
+
// ── Play / pause ─────────────────────────────────────────────────────────
|
|
175
178
|
const togglePlay = () => {
|
|
176
179
|
if (!sound) return;
|
|
177
|
-
|
|
178
180
|
if (isPlaying) {
|
|
179
181
|
sound.pause(() => {
|
|
180
182
|
setIsPlaying(false);
|
|
@@ -186,7 +188,6 @@ const AudioPlayer: React.FC<AudioPlayerProps> = ({
|
|
|
186
188
|
if (success) {
|
|
187
189
|
setIsPlaying(false);
|
|
188
190
|
setCurrentTime(0);
|
|
189
|
-
knobPosition.value = withSpring(0);
|
|
190
191
|
setCurrentlyPlayingId(null);
|
|
191
192
|
}
|
|
192
193
|
});
|
|
@@ -194,103 +195,86 @@ const AudioPlayer: React.FC<AudioPlayerProps> = ({
|
|
|
194
195
|
}
|
|
195
196
|
};
|
|
196
197
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
if (isVideoPlaying && isPlaying && sound) {
|
|
200
|
-
sound.pause(() => {
|
|
201
|
-
setIsPlaying(false);
|
|
202
|
-
setCurrentlyPlayingId(null);
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
}, [isVideoPlaying]);
|
|
198
|
+
// ── Progress ratio ───────────────────────────────────────────────────────
|
|
199
|
+
const progress = duration > 0 ? currentTime / duration : 0;
|
|
206
200
|
|
|
207
201
|
return (
|
|
208
|
-
<View style={tw`rounded-lg w-
|
|
209
|
-
<View style={tw`flex-row items-center gap-2 px-2 pt-2`}>
|
|
202
|
+
<View style={tw`rounded-lg w-60`}>
|
|
203
|
+
<View style={tw`flex-row items-center gap-2 px-2 pt-2 pb-1`}>
|
|
204
|
+
{/* Play / Pause button */}
|
|
210
205
|
<Pressable
|
|
211
206
|
onPress={togglePlay}
|
|
212
207
|
style={[
|
|
213
|
-
tw`bg-black/
|
|
208
|
+
tw`bg-black/35 rounded-full p-2 shrink-0`,
|
|
214
209
|
theme?.messageStyle?.audioPlayButtonStyle,
|
|
215
210
|
]}
|
|
216
211
|
>
|
|
217
212
|
{isPlaying ? (
|
|
218
|
-
CustomPauseIcon ? (
|
|
219
|
-
<CustomPauseIcon />
|
|
220
|
-
) : (
|
|
213
|
+
CustomPauseIcon ? <CustomPauseIcon /> : (
|
|
221
214
|
<PauseIcon
|
|
222
|
-
style={tw.style('h-
|
|
215
|
+
style={tw.style('h-5 w-5')}
|
|
223
216
|
color={theme?.colors?.audioPauseIconColor || 'white'}
|
|
224
217
|
/>
|
|
225
218
|
)
|
|
226
|
-
) : CustomPlayIcon ? (
|
|
227
|
-
<CustomPlayIcon />
|
|
228
|
-
) : (
|
|
219
|
+
) : CustomPlayIcon ? <CustomPlayIcon /> : (
|
|
229
220
|
<PlayIcon
|
|
230
|
-
style={tw.style('h-
|
|
221
|
+
style={tw.style('h-5 w-5')}
|
|
231
222
|
color={theme?.colors?.audioPlayIconColor || 'white'}
|
|
232
223
|
/>
|
|
233
224
|
)}
|
|
234
225
|
</Pressable>
|
|
235
226
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const { width } = e.nativeEvent.layout;
|
|
240
|
-
progressWidth.current = width;
|
|
241
|
-
progressRef.current?.measure((_, __, ___, ____, pageX) => {
|
|
242
|
-
progressX.current = pageX;
|
|
243
|
-
});
|
|
244
|
-
}}
|
|
245
|
-
style={[
|
|
246
|
-
tw`relative h-1 bg-zinc-400 rounded overflow-visible w-[75%]`,
|
|
247
|
-
theme?.messageStyle?.progressBarStyle,
|
|
248
|
-
]}
|
|
249
|
-
>
|
|
227
|
+
{/* Waveform + timestamp column */}
|
|
228
|
+
<View style={{ flex: 1 }}>
|
|
229
|
+
{/* Waveform bars */}
|
|
250
230
|
<View
|
|
251
231
|
style={[
|
|
252
|
-
tw`absolute h-full bg-slate-200`,
|
|
253
232
|
{
|
|
254
|
-
|
|
233
|
+
height: WAVEFORM_H,
|
|
234
|
+
flexDirection: 'row',
|
|
235
|
+
alignItems: 'flex-end',
|
|
255
236
|
},
|
|
256
|
-
theme?.messageStyle?.
|
|
237
|
+
theme?.messageStyle?.progressBarStyle,
|
|
257
238
|
]}
|
|
258
|
-
|
|
259
|
-
<Animated.View
|
|
239
|
+
onLayout={(e) => { waveformWidth.current = e.nativeEvent.layout.width; }}
|
|
260
240
|
{...panResponder.panHandlers}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
241
|
+
>
|
|
242
|
+
{waveform.map((amp, i) => {
|
|
243
|
+
const barProgress = (i + 0.5) / WAVEFORM_BARS;
|
|
244
|
+
const active = barProgress <= progress;
|
|
245
|
+
return (
|
|
246
|
+
<View
|
|
247
|
+
key={i}
|
|
248
|
+
style={[
|
|
249
|
+
{
|
|
250
|
+
flex: 1,
|
|
251
|
+
marginHorizontal: 1,
|
|
252
|
+
height: Math.max(3, Math.round(amp * WAVEFORM_H)),
|
|
253
|
+
borderRadius: 2,
|
|
254
|
+
backgroundColor: active ? activeBarColor : inactiveBarColor,
|
|
255
|
+
},
|
|
256
|
+
active ? theme?.messageStyle?.activeProgressBarStyle : undefined,
|
|
257
|
+
]}
|
|
258
|
+
/>
|
|
259
|
+
);
|
|
260
|
+
})}
|
|
261
|
+
</View>
|
|
262
|
+
|
|
263
|
+
{/* Duration */}
|
|
264
|
+
<Text
|
|
265
|
+
style={withFontFamily(
|
|
266
|
+
[
|
|
267
|
+
tw`text-[10px] mt-0.5`,
|
|
268
|
+
{ color: timestampColor },
|
|
269
|
+
theme?.messageStyle?.audioDurationStyle,
|
|
270
|
+
],
|
|
271
|
+
theme?.fontFamily
|
|
272
|
+
)}
|
|
273
|
+
>
|
|
274
|
+
{!isNaN(currentTime) ? formatDuration(currentTime) : '0:00'}
|
|
275
|
+
</Text>
|
|
279
276
|
</View>
|
|
280
277
|
</View>
|
|
281
|
-
<View style={tw`px-4 py-1`}>
|
|
282
|
-
<Text
|
|
283
|
-
style={withFontFamily(
|
|
284
|
-
[
|
|
285
|
-
tw`text-xs text-gray-500`,
|
|
286
|
-
theme?.messageStyle?.audioDurationStyle,
|
|
287
|
-
],
|
|
288
|
-
theme?.fontFamily
|
|
289
|
-
)}
|
|
290
|
-
>
|
|
291
|
-
{!isNaN(currentTime) ? formatDuration(currentTime) : '0:00'}
|
|
292
|
-
</Text>
|
|
293
|
-
</View>
|
|
294
278
|
</View>
|
|
295
279
|
);
|
|
296
280
|
};
|
|
@@ -42,12 +42,15 @@ const VideoThumbCell: React.FC<{
|
|
|
42
42
|
const [error, setError] = React.useState(false);
|
|
43
43
|
|
|
44
44
|
return (
|
|
45
|
-
<View style={[cellStyle, roundedStyle]}>
|
|
45
|
+
<View style={[cellStyle, roundedStyle]} pointerEvents="none">
|
|
46
46
|
<Video
|
|
47
47
|
source={{ uri }}
|
|
48
48
|
ref={videoRef}
|
|
49
49
|
paused
|
|
50
50
|
muted
|
|
51
|
+
playInBackground={false}
|
|
52
|
+
playWhenInactive={false}
|
|
53
|
+
pointerEvents="none"
|
|
51
54
|
style={[roundedStyle, { width: '100%', height: '100%' }]}
|
|
52
55
|
resizeMode="cover"
|
|
53
56
|
onLoadStart={() => {
|