stream-chat-react-native 9.0.0-beta.9 → 9.0.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/android/build.gradle +30 -0
- package/android/src/main/java/com/streamchatreactnative/StreamChatReactNativePackage.java +32 -0
- package/android/src/main/java/com/streamchatreactnative/shared/StreamVideoThumbnailGenerator.kt +159 -0
- package/android/src/newarch/com/streamchatreactnative/StreamVideoThumbnailModule.kt +50 -0
- package/ios/shared/StreamVideoThumbnail.h +14 -0
- package/ios/shared/StreamVideoThumbnail.mm +56 -0
- package/ios/shared/StreamVideoThumbnailGenerator.swift +338 -0
- package/package.json +15 -12
- package/react-native.config.js +13 -0
- package/src/native/NativeStreamVideoThumbnail.ts +14 -0
- package/src/native/types.ts +2 -0
- package/src/native/videoThumbnail.ts +8 -0
- package/src/optionalDependencies/Audio.ts +1 -1
- package/src/optionalDependencies/Sound.tsx +349 -43
- package/src/optionalDependencies/generateThumbnail.ts +8 -0
- package/src/optionalDependencies/getPhotos.ts +28 -12
- package/src/optionalDependencies/pickImage.ts +34 -9
- package/src/optionalDependencies/takePhoto.ts +7 -0
|
@@ -1,47 +1,353 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { PlaybackStatus, SoundReturnType } from 'stream-chat-react-native-core';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type InitialPlaybackStatus = {
|
|
4
|
+
positionMillis?: number;
|
|
5
|
+
progressUpdateIntervalMillis?: number;
|
|
6
|
+
rate?: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type LegacyAudioRecorderPlayerConstructor = new () => NativePlaybackInstance;
|
|
10
|
+
|
|
11
|
+
let LegacyAudioRecorderPlayer: LegacyAudioRecorderPlayerConstructor | undefined;
|
|
12
|
+
let createNitroSound: (() => NativePlaybackInstance) | undefined;
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
({ createSound: createNitroSound } = require('react-native-nitro-sound'));
|
|
16
|
+
} catch (e) {
|
|
17
|
+
// do nothing
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
LegacyAudioRecorderPlayer = require('react-native-audio-recorder-player').default;
|
|
22
|
+
} catch (e) {
|
|
23
|
+
// do nothing
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const PROGRESS_UPDATE_INTERVAL_MILLIS = 100;
|
|
27
|
+
|
|
28
|
+
type NativePlaybackMeta = {
|
|
29
|
+
currentPosition?: number;
|
|
30
|
+
duration?: number;
|
|
31
|
+
isFinished?: boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type NativePlaybackEndMeta = {
|
|
35
|
+
currentPosition: number;
|
|
36
|
+
duration: number;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type NativePlaybackInstance = {
|
|
40
|
+
addPlayBackListener: (callback: (meta: NativePlaybackMeta) => void) => void;
|
|
41
|
+
pausePlayer: () => Promise<string>;
|
|
42
|
+
removePlayBackListener: () => void;
|
|
43
|
+
resumePlayer: () => Promise<string>;
|
|
44
|
+
seekToPlayer: (time: number) => Promise<string>;
|
|
45
|
+
setPlaybackSpeed?: (playbackSpeed: number) => Promise<string>;
|
|
46
|
+
setSubscriptionDuration?: (seconds: number) => void | Promise<string>;
|
|
47
|
+
startPlayer: (uri?: string, httpHeaders?: Record<string, string>) => Promise<string>;
|
|
48
|
+
stopPlayer: () => Promise<string>;
|
|
49
|
+
addPlaybackEndListener?: (callback: (meta: NativePlaybackEndMeta) => void) => void;
|
|
50
|
+
dispose?: () => void;
|
|
51
|
+
removePlaybackEndListener?: () => void;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const createPlaybackInstance = (): NativePlaybackInstance | null => {
|
|
55
|
+
if (createNitroSound) {
|
|
56
|
+
return createNitroSound();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (LegacyAudioRecorderPlayer) {
|
|
60
|
+
return new LegacyAudioRecorderPlayer();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return null;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const createPlaybackStatus = ({
|
|
67
|
+
didJustFinish = false,
|
|
68
|
+
durationMillis,
|
|
69
|
+
error = '',
|
|
70
|
+
isLoaded,
|
|
71
|
+
isPlaying,
|
|
72
|
+
positionMillis,
|
|
73
|
+
}: {
|
|
74
|
+
didJustFinish?: boolean;
|
|
75
|
+
durationMillis: number;
|
|
76
|
+
error?: string;
|
|
77
|
+
isLoaded: boolean;
|
|
78
|
+
isPlaying: boolean;
|
|
79
|
+
positionMillis: number;
|
|
80
|
+
}): PlaybackStatus => ({
|
|
81
|
+
currentPosition: positionMillis,
|
|
82
|
+
didJustFinish,
|
|
83
|
+
duration: durationMillis,
|
|
84
|
+
durationMillis,
|
|
85
|
+
error,
|
|
86
|
+
isBuffering: false,
|
|
87
|
+
isLoaded,
|
|
88
|
+
isLooping: false,
|
|
89
|
+
isMuted: false,
|
|
90
|
+
isPlaying,
|
|
91
|
+
isSeeking: false,
|
|
92
|
+
positionMillis,
|
|
93
|
+
shouldPlay: isPlaying,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
class NativeAudioSoundAdapter implements SoundReturnType {
|
|
97
|
+
testID = 'native-audio-sound';
|
|
98
|
+
onPlaybackStatusUpdate?: (playbackStatus: PlaybackStatus) => void;
|
|
99
|
+
private playbackInstance: NativePlaybackInstance | null;
|
|
100
|
+
private sourceUri?: string;
|
|
101
|
+
private isDisposed = false;
|
|
102
|
+
private isLoaded = false;
|
|
103
|
+
private playing = false;
|
|
104
|
+
private durationMillis = 0;
|
|
105
|
+
private positionMillis = 0;
|
|
106
|
+
private playbackRate = 1;
|
|
107
|
+
private hasProgressListener = false;
|
|
108
|
+
private hasPlaybackEndListener = false;
|
|
109
|
+
|
|
110
|
+
constructor({
|
|
111
|
+
source,
|
|
112
|
+
initialStatus,
|
|
113
|
+
onPlaybackStatusUpdate,
|
|
114
|
+
}: {
|
|
115
|
+
source?: { uri: string };
|
|
116
|
+
initialStatus?: InitialPlaybackStatus;
|
|
117
|
+
onPlaybackStatusUpdate?: (playbackStatus: PlaybackStatus) => void;
|
|
118
|
+
}) {
|
|
119
|
+
this.playbackInstance = createPlaybackInstance();
|
|
120
|
+
this.sourceUri = source?.uri;
|
|
121
|
+
this.onPlaybackStatusUpdate = onPlaybackStatusUpdate;
|
|
122
|
+
this.playbackRate = initialStatus?.rate ?? 1;
|
|
123
|
+
this.positionMillis = initialStatus?.positionMillis ?? 0;
|
|
124
|
+
const progressUpdateIntervalMillis =
|
|
125
|
+
initialStatus?.progressUpdateIntervalMillis ?? PROGRESS_UPDATE_INTERVAL_MILLIS;
|
|
126
|
+
|
|
127
|
+
this.playbackInstance?.setSubscriptionDuration?.(progressUpdateIntervalMillis / 1000);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private emitPlaybackStatus({
|
|
131
|
+
didJustFinish = false,
|
|
132
|
+
error = '',
|
|
133
|
+
}: {
|
|
134
|
+
didJustFinish?: boolean;
|
|
135
|
+
error?: string;
|
|
136
|
+
} = {}) {
|
|
137
|
+
this.onPlaybackStatusUpdate?.(
|
|
138
|
+
createPlaybackStatus({
|
|
139
|
+
didJustFinish,
|
|
140
|
+
durationMillis: this.durationMillis,
|
|
141
|
+
error,
|
|
142
|
+
isLoaded: this.isLoaded,
|
|
143
|
+
isPlaying: this.playing,
|
|
144
|
+
positionMillis: this.positionMillis,
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private attachListeners() {
|
|
150
|
+
if (!this.playbackInstance || this.hasProgressListener) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.playbackInstance.addPlayBackListener(this.handlePlaybackProgress);
|
|
155
|
+
this.hasProgressListener = true;
|
|
156
|
+
|
|
157
|
+
if (this.playbackInstance.addPlaybackEndListener) {
|
|
158
|
+
this.playbackInstance.addPlaybackEndListener(this.handlePlaybackEnd);
|
|
159
|
+
this.hasPlaybackEndListener = true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private detachListeners() {
|
|
164
|
+
if (!this.playbackInstance) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (this.hasProgressListener) {
|
|
169
|
+
this.playbackInstance.removePlayBackListener();
|
|
170
|
+
this.hasProgressListener = false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (this.hasPlaybackEndListener && this.playbackInstance.removePlaybackEndListener) {
|
|
174
|
+
this.playbackInstance.removePlaybackEndListener();
|
|
175
|
+
this.hasPlaybackEndListener = false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private handlePlaybackProgress = ({
|
|
180
|
+
currentPosition,
|
|
181
|
+
duration,
|
|
182
|
+
isFinished,
|
|
183
|
+
}: NativePlaybackMeta) => {
|
|
184
|
+
this.positionMillis = currentPosition ?? this.positionMillis;
|
|
185
|
+
this.durationMillis = duration ?? this.durationMillis;
|
|
186
|
+
|
|
187
|
+
const didJustFinish =
|
|
188
|
+
isFinished === true && this.durationMillis > 0 && this.positionMillis >= this.durationMillis;
|
|
189
|
+
|
|
190
|
+
if (didJustFinish) {
|
|
191
|
+
this.playing = false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.emitPlaybackStatus({ didJustFinish });
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
private handlePlaybackEnd = ({ currentPosition, duration }: NativePlaybackEndMeta) => {
|
|
198
|
+
this.positionMillis = currentPosition ?? this.positionMillis;
|
|
199
|
+
this.durationMillis = duration ?? this.durationMillis;
|
|
200
|
+
this.playing = false;
|
|
201
|
+
this.emitPlaybackStatus({ didJustFinish: true });
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
private async ensureLoaded({ shouldPlay }: { shouldPlay: boolean }) {
|
|
205
|
+
if (!this.playbackInstance || this.isDisposed || !this.sourceUri) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!this.isLoaded) {
|
|
210
|
+
this.attachListeners();
|
|
211
|
+
await this.playbackInstance.startPlayer(this.sourceUri);
|
|
212
|
+
this.isLoaded = true;
|
|
213
|
+
|
|
214
|
+
if (this.playbackRate !== 1 && this.playbackInstance.setPlaybackSpeed) {
|
|
215
|
+
await this.playbackInstance.setPlaybackSpeed(this.playbackRate);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (this.positionMillis > 0) {
|
|
219
|
+
await this.playbackInstance.seekToPlayer(this.positionMillis);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (!shouldPlay) {
|
|
223
|
+
await this.playbackInstance.pausePlayer();
|
|
224
|
+
}
|
|
225
|
+
} else if (shouldPlay) {
|
|
226
|
+
await this.playbackInstance.resumePlayer();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
playAsync: SoundReturnType['playAsync'] = async () => {
|
|
233
|
+
const loaded = await this.ensureLoaded({ shouldPlay: true });
|
|
234
|
+
if (!loaded) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
this.playing = true;
|
|
239
|
+
this.emitPlaybackStatus();
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
resume: SoundReturnType['resume'] = () => {
|
|
243
|
+
void this.playAsync?.();
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
pauseAsync: SoundReturnType['pauseAsync'] = async () => {
|
|
247
|
+
if (!this.playbackInstance || !this.isLoaded || this.isDisposed) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
await this.playbackInstance.pausePlayer();
|
|
252
|
+
this.playing = false;
|
|
253
|
+
this.emitPlaybackStatus();
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
pause: SoundReturnType['pause'] = () => {
|
|
257
|
+
void this.pauseAsync?.();
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
seek: SoundReturnType['seek'] = async (progress) => {
|
|
261
|
+
const loaded = await this.ensureLoaded({ shouldPlay: false });
|
|
262
|
+
if (!loaded || !this.playbackInstance) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
this.positionMillis = progress * 1000;
|
|
267
|
+
await this.playbackInstance.seekToPlayer(this.positionMillis);
|
|
268
|
+
this.emitPlaybackStatus();
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
setPositionAsync: SoundReturnType['setPositionAsync'] = async (millis) => {
|
|
272
|
+
await this.seek?.(millis / 1000);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
setRateAsync: SoundReturnType['setRateAsync'] = async (rate) => {
|
|
276
|
+
this.playbackRate = rate;
|
|
277
|
+
|
|
278
|
+
if (this.playbackInstance?.setPlaybackSpeed && this.isLoaded) {
|
|
279
|
+
await this.playbackInstance.setPlaybackSpeed(rate);
|
|
280
|
+
|
|
281
|
+
// Some Android backends resume playback as a side effect of changing speed.
|
|
282
|
+
// Preserve the previous paused state explicitly so rate changes stay silent.
|
|
283
|
+
if (!this.playing) {
|
|
284
|
+
await this.playbackInstance.pausePlayer();
|
|
285
|
+
this.emitPlaybackStatus();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
replayAsync: SoundReturnType['replayAsync'] = async () => {
|
|
291
|
+
await this.stopAsync?.();
|
|
292
|
+
this.positionMillis = 0;
|
|
293
|
+
await this.playAsync?.();
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
stopAsync: SoundReturnType['stopAsync'] = async () => {
|
|
297
|
+
if (!this.playbackInstance || !this.isLoaded || this.isDisposed) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
await this.playbackInstance.stopPlayer();
|
|
302
|
+
this.isLoaded = false;
|
|
303
|
+
this.playing = false;
|
|
304
|
+
this.positionMillis = 0;
|
|
305
|
+
this.emitPlaybackStatus();
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
unloadAsync: SoundReturnType['unloadAsync'] = async () => {
|
|
309
|
+
if (this.isDisposed) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
if (this.isLoaded && this.playbackInstance) {
|
|
315
|
+
await this.playbackInstance.stopPlayer();
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
// Best effort cleanup.
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
this.detachListeners();
|
|
322
|
+
this.playbackInstance?.dispose?.();
|
|
323
|
+
this.isLoaded = false;
|
|
324
|
+
this.playing = false;
|
|
325
|
+
this.isDisposed = true;
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const initializeSound =
|
|
330
|
+
createNitroSound || LegacyAudioRecorderPlayer
|
|
331
|
+
? (
|
|
332
|
+
source?: { uri: string },
|
|
333
|
+
initialStatus?: InitialPlaybackStatus,
|
|
334
|
+
onPlaybackStatusUpdate?: (playbackStatus: PlaybackStatus) => void,
|
|
335
|
+
) => {
|
|
336
|
+
if (!source?.uri) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const sound = new NativeAudioSoundAdapter({
|
|
341
|
+
initialStatus,
|
|
342
|
+
onPlaybackStatusUpdate,
|
|
343
|
+
source,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
return sound;
|
|
347
|
+
}
|
|
348
|
+
: null;
|
|
4
349
|
|
|
5
350
|
export const Sound = {
|
|
6
|
-
initializeSound
|
|
7
|
-
|
|
8
|
-
Player: AudioVideoPlayer
|
|
9
|
-
? ({
|
|
10
|
-
onBuffer,
|
|
11
|
-
onEnd,
|
|
12
|
-
onLoad,
|
|
13
|
-
onLoadStart,
|
|
14
|
-
onPlaybackStateChanged,
|
|
15
|
-
onProgress,
|
|
16
|
-
onSeek,
|
|
17
|
-
paused,
|
|
18
|
-
rate,
|
|
19
|
-
soundRef,
|
|
20
|
-
style,
|
|
21
|
-
uri,
|
|
22
|
-
}) => (
|
|
23
|
-
<AudioVideoPlayer
|
|
24
|
-
audioOnly={true}
|
|
25
|
-
ignoreSilentSwitch={'ignore'}
|
|
26
|
-
onBuffer={onBuffer}
|
|
27
|
-
onEnd={onEnd}
|
|
28
|
-
onError={(error: Error) => {
|
|
29
|
-
console.log(error);
|
|
30
|
-
}}
|
|
31
|
-
onLoad={onLoad}
|
|
32
|
-
onLoadStart={onLoadStart}
|
|
33
|
-
onPlaybackStateChanged={onPlaybackStateChanged}
|
|
34
|
-
onProgress={onProgress}
|
|
35
|
-
onSeek={onSeek}
|
|
36
|
-
paused={paused}
|
|
37
|
-
rate={rate}
|
|
38
|
-
ref={soundRef}
|
|
39
|
-
progressUpdateInterval={100}
|
|
40
|
-
source={{
|
|
41
|
-
uri,
|
|
42
|
-
}}
|
|
43
|
-
style={style}
|
|
44
|
-
/>
|
|
45
|
-
)
|
|
46
|
-
: null,
|
|
351
|
+
initializeSound,
|
|
352
|
+
Player: null,
|
|
47
353
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createGenerateVideoThumbnails } from 'stream-chat-react-native-core';
|
|
2
|
+
|
|
3
|
+
import { createVideoThumbnails, type VideoThumbnailResult } from '../native/videoThumbnail';
|
|
4
|
+
|
|
5
|
+
export const generateThumbnails: (uris: string[]) => Promise<Record<string, VideoThumbnailResult>> =
|
|
6
|
+
createGenerateVideoThumbnails({
|
|
7
|
+
createVideoThumbnails,
|
|
8
|
+
});
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { PermissionsAndroid, Platform } from 'react-native';
|
|
2
|
+
|
|
2
3
|
import mime from 'mime';
|
|
3
4
|
|
|
4
5
|
import type { File } from 'stream-chat-react-native-core';
|
|
5
6
|
|
|
7
|
+
import { generateThumbnails } from './generateThumbnail';
|
|
8
|
+
import { getLocalAssetUri } from './getLocalAssetUri';
|
|
9
|
+
|
|
6
10
|
let CameraRollDependency;
|
|
7
11
|
|
|
8
12
|
try {
|
|
@@ -14,8 +18,6 @@ try {
|
|
|
14
18
|
);
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
import { getLocalAssetUri } from './getLocalAssetUri';
|
|
18
|
-
|
|
19
21
|
type ReturnType = {
|
|
20
22
|
assets: File[];
|
|
21
23
|
endCursor: string | undefined;
|
|
@@ -88,7 +90,7 @@ export const getPhotos = CameraRollDependency
|
|
|
88
90
|
first,
|
|
89
91
|
include: ['fileSize', 'filename', 'imageSize', 'playableDuration'],
|
|
90
92
|
});
|
|
91
|
-
const
|
|
93
|
+
const assetEntries = await Promise.all(
|
|
92
94
|
results.edges.map(async (edge) => {
|
|
93
95
|
const originalUri = edge.node?.image?.uri;
|
|
94
96
|
const type =
|
|
@@ -100,20 +102,34 @@ export const getPhotos = CameraRollDependency
|
|
|
100
102
|
(edge.node.image.playableDuration ? 'video/*' : 'image/*');
|
|
101
103
|
const isImage = type.includes('image');
|
|
102
104
|
|
|
103
|
-
const uri =
|
|
104
|
-
isImage && getLocalAssetUri ? await getLocalAssetUri(originalUri) : originalUri;
|
|
105
|
-
|
|
106
105
|
return {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
thumb_url: isImage ? undefined : originalUri,
|
|
111
|
-
size: edge.node.image.fileSize as number,
|
|
106
|
+
edge,
|
|
107
|
+
isImage,
|
|
108
|
+
originalUri,
|
|
112
109
|
type,
|
|
113
|
-
uri,
|
|
110
|
+
uri: isImage && getLocalAssetUri ? await getLocalAssetUri(originalUri) : originalUri,
|
|
114
111
|
};
|
|
115
112
|
}),
|
|
116
113
|
);
|
|
114
|
+
const videoUris = assetEntries
|
|
115
|
+
.filter(({ isImage, originalUri }) => !isImage && !!originalUri)
|
|
116
|
+
.map(({ originalUri }) => originalUri);
|
|
117
|
+
const videoThumbnailResults = await generateThumbnails(videoUris);
|
|
118
|
+
|
|
119
|
+
const assets = assetEntries.map(({ edge, isImage, originalUri, type, uri }) => {
|
|
120
|
+
const thumbnailResult =
|
|
121
|
+
!isImage && originalUri ? videoThumbnailResults[originalUri] : undefined;
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
...edge.node.image,
|
|
125
|
+
name: edge.node.image.filename as string,
|
|
126
|
+
duration: edge.node.image.playableDuration * 1000,
|
|
127
|
+
thumb_url: thumbnailResult?.uri || undefined,
|
|
128
|
+
size: edge.node.image.fileSize as number,
|
|
129
|
+
type,
|
|
130
|
+
uri,
|
|
131
|
+
};
|
|
132
|
+
});
|
|
117
133
|
const hasNextPage = results.page_info.has_next_page;
|
|
118
134
|
const endCursor = results.page_info.end_cursor;
|
|
119
135
|
return { assets, endCursor, hasNextPage, iOSLimited: !!results.limited };
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { Platform } from 'react-native';
|
|
2
|
+
|
|
2
3
|
import mime from 'mime';
|
|
4
|
+
|
|
3
5
|
import { PickImageOptions } from 'stream-chat-react-native-core';
|
|
6
|
+
|
|
7
|
+
import { generateThumbnails } from './generateThumbnail';
|
|
8
|
+
|
|
4
9
|
let ImagePicker;
|
|
5
10
|
|
|
6
11
|
try {
|
|
@@ -24,17 +29,37 @@ export const pickImage = ImagePicker
|
|
|
24
29
|
return { askToOpenSettings: true, cancelled: true };
|
|
25
30
|
}
|
|
26
31
|
if (!canceled) {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
duration: asset.duration ? asset.duration * 1000 : undefined, // in milliseconds
|
|
30
|
-
name: asset.fileName,
|
|
31
|
-
size: asset.fileSize,
|
|
32
|
-
type:
|
|
32
|
+
const assetsWithType = result.assets.map((asset) => {
|
|
33
|
+
const type =
|
|
33
34
|
asset.type ||
|
|
34
35
|
mime.getType(asset.fileName || asset.uri) ||
|
|
35
|
-
(asset.duration ? 'video/*' : 'image/*')
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
(asset.duration ? 'video/*' : 'image/*');
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
asset,
|
|
40
|
+
isVideo: type.includes('video'),
|
|
41
|
+
type,
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
const videoUris = assetsWithType
|
|
45
|
+
.filter(({ asset, isVideo }) => isVideo && !!asset.uri)
|
|
46
|
+
.map(({ asset }) => asset.uri);
|
|
47
|
+
const videoThumbnailResults = await generateThumbnails(videoUris);
|
|
48
|
+
|
|
49
|
+
const assets = assetsWithType.map(({ asset, isVideo, type }) => {
|
|
50
|
+
const thumbnailResult =
|
|
51
|
+
isVideo && asset.uri ? videoThumbnailResults[asset.uri] : undefined;
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
...asset,
|
|
55
|
+
duration: asset.duration ? asset.duration * 1000 : undefined, // in milliseconds
|
|
56
|
+
name: asset.fileName,
|
|
57
|
+
size: asset.fileSize,
|
|
58
|
+
thumb_url: thumbnailResult?.uri || undefined,
|
|
59
|
+
type,
|
|
60
|
+
uri: asset.uri,
|
|
61
|
+
};
|
|
62
|
+
});
|
|
38
63
|
return { assets, cancelled: false };
|
|
39
64
|
} else {
|
|
40
65
|
return { cancelled: true };
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { AppState, Image, PermissionsAndroid, Platform } from 'react-native';
|
|
2
|
+
|
|
2
3
|
import mime from 'mime';
|
|
3
4
|
|
|
5
|
+
import { generateThumbnails } from './generateThumbnail';
|
|
6
|
+
|
|
4
7
|
let ImagePicker;
|
|
5
8
|
|
|
6
9
|
try {
|
|
@@ -54,12 +57,16 @@ export const takePhoto = ImagePicker
|
|
|
54
57
|
if (assetType.includes('video')) {
|
|
55
58
|
const clearFilter = new RegExp('[.:]', 'g');
|
|
56
59
|
const date = new Date().toISOString().replace(clearFilter, '_');
|
|
60
|
+
const thumbnailResults = await generateThumbnails([asset.uri]);
|
|
61
|
+
const thumbnailResult = thumbnailResults[asset.uri];
|
|
62
|
+
|
|
57
63
|
return {
|
|
58
64
|
...asset,
|
|
59
65
|
cancelled: false,
|
|
60
66
|
duration: asset.duration * 1000,
|
|
61
67
|
name: 'video_recording_' + date + '.' + asset.fileName.split('.').pop(),
|
|
62
68
|
size: asset.fileSize,
|
|
69
|
+
thumb_url: thumbnailResult?.uri || undefined,
|
|
63
70
|
type: assetType,
|
|
64
71
|
uri: asset.uri,
|
|
65
72
|
};
|