stream-chat-react-native 9.0.0-beta.8 → 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/StreamChatReactNative.java +0 -153
- package/android/src/main/java/com/streamchatreactnative/StreamChatReactNativeModule.java +2 -15
- 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/android/src/oldarch/com/streamchatreactnative/StreamChatReactNative.java +1 -1
- package/ios/StreamChatReactNative.mm +16 -148
- 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/handlers/compressImage.ts +0 -1
- package/src/native/NativeStreamChatReactNative.ts +0 -1
- package/src/native/NativeStreamVideoThumbnail.ts +14 -0
- package/src/native/index.tsx +0 -2
- 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
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stream-chat-react-native",
|
|
3
3
|
"description": "The official React Native SDK for Stream Chat, a service for building chat applications",
|
|
4
|
-
"version": "9.0.0
|
|
4
|
+
"version": "9.0.0",
|
|
5
5
|
"homepage": "https://www.npmjs.com/package/stream-chat-react-native",
|
|
6
6
|
"author": {
|
|
7
7
|
"company": "Stream.io Inc",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"android/gradle",
|
|
22
22
|
"ios",
|
|
23
23
|
"*.podspec",
|
|
24
|
+
"react-native.config.js",
|
|
24
25
|
"package.json"
|
|
25
26
|
],
|
|
26
27
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -29,21 +30,20 @@
|
|
|
29
30
|
"dependencies": {
|
|
30
31
|
"es6-symbol": "^3.1.3",
|
|
31
32
|
"mime": "^4.0.7",
|
|
32
|
-
"stream-chat-react-native-core": "9.0.0
|
|
33
|
+
"stream-chat-react-native-core": "9.0.0"
|
|
33
34
|
},
|
|
34
35
|
"peerDependencies": {
|
|
35
|
-
"@react-native-camera-roll/camera-roll": ">=7.
|
|
36
|
-
"@react-native-clipboard/clipboard": ">=1.
|
|
36
|
+
"@react-native-camera-roll/camera-roll": ">=7.9.0",
|
|
37
|
+
"@react-native-clipboard/clipboard": ">=1.16.0",
|
|
37
38
|
"@react-native-documents/picker": ">=10.1.1",
|
|
38
|
-
"
|
|
39
|
-
"react-native": ">=0.73.0",
|
|
39
|
+
"react-native": ">=0.76.0",
|
|
40
40
|
"react-native-audio-recorder-player": ">=3.6.13",
|
|
41
41
|
"react-native-nitro-sound": ">=0.2.9",
|
|
42
|
-
"react-native-blob-util": ">=0.
|
|
42
|
+
"react-native-blob-util": ">=0.22.0",
|
|
43
43
|
"react-native-haptic-feedback": ">=2.3.0",
|
|
44
44
|
"react-native-image-picker": ">=7.1.2",
|
|
45
45
|
"react-native-share": ">=11.0.0",
|
|
46
|
-
"react-native-video": ">=6.
|
|
46
|
+
"react-native-video": ">=6.18.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependenciesMeta": {
|
|
49
49
|
"@react-native-camera-roll/camera-roll": {
|
|
@@ -52,9 +52,6 @@
|
|
|
52
52
|
"@react-native-clipboard/clipboard": {
|
|
53
53
|
"optional": true
|
|
54
54
|
},
|
|
55
|
-
"@stream-io/flat-list-mvcp": {
|
|
56
|
-
"optional": true
|
|
57
|
-
},
|
|
58
55
|
"react-native-share": {
|
|
59
56
|
"optional": true
|
|
60
57
|
},
|
|
@@ -81,7 +78,6 @@
|
|
|
81
78
|
}
|
|
82
79
|
},
|
|
83
80
|
"scripts": {
|
|
84
|
-
"postinstall": "if [ -f ../scripts/sync-shared-native.sh ] && [ -d ../shared-native/ios ]; then bash ../scripts/sync-shared-native.sh native-package; fi",
|
|
85
81
|
"prepack": "bash ../scripts/sync-shared-native.sh native-package && cp ../../README.md .",
|
|
86
82
|
"postpack": "rm README.md && bash ../scripts/clean-shared-native-copies.sh native-package"
|
|
87
83
|
},
|
|
@@ -92,7 +88,14 @@
|
|
|
92
88
|
"name": "StreamChatReactNativeSpec",
|
|
93
89
|
"type": "all",
|
|
94
90
|
"jsSrcsDir": "src/native",
|
|
91
|
+
"android": {
|
|
92
|
+
"javaPackageName": "com.streamchatreactnative"
|
|
93
|
+
},
|
|
95
94
|
"ios": {
|
|
95
|
+
"modulesProvider": {
|
|
96
|
+
"StreamChatReactNative": "StreamChatReactNative",
|
|
97
|
+
"StreamVideoThumbnail": "StreamVideoThumbnail"
|
|
98
|
+
},
|
|
96
99
|
"componentProvider": {
|
|
97
100
|
"StreamShimmerView": "StreamShimmerViewComponentView"
|
|
98
101
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
dependency: {
|
|
3
|
+
platforms: {
|
|
4
|
+
android: {
|
|
5
|
+
packageImportPath: 'import com.streamchatreactnative.StreamChatReactNativePackage;',
|
|
6
|
+
packageInstance: 'new StreamChatReactNativePackage()',
|
|
7
|
+
},
|
|
8
|
+
ios: {
|
|
9
|
+
podspecPath: 'stream-chat-react-native.podspec',
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
4
|
+
|
|
5
|
+
export type VideoThumbnailResult = {
|
|
6
|
+
error?: string | null;
|
|
7
|
+
uri?: string | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export interface Spec extends TurboModule {
|
|
11
|
+
createVideoThumbnails(urls: ReadonlyArray<string>): Promise<ReadonlyArray<VideoThumbnailResult>>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default TurboModuleRegistry.getEnforcing<Spec>('StreamVideoThumbnail');
|
package/src/native/index.tsx
CHANGED
|
@@ -24,7 +24,6 @@ async function createResizedImage(
|
|
|
24
24
|
quality: number,
|
|
25
25
|
rotation: number = 0,
|
|
26
26
|
outputPath?: string | null,
|
|
27
|
-
keepMeta = false,
|
|
28
27
|
options: Options = defaultOptions,
|
|
29
28
|
): Promise<Response> {
|
|
30
29
|
const { mode, onlyScaleDown } = { ...defaultOptions, ...options };
|
|
@@ -39,7 +38,6 @@ async function createResizedImage(
|
|
|
39
38
|
onlyScaleDown,
|
|
40
39
|
rotation,
|
|
41
40
|
outputPath,
|
|
42
|
-
keepMeta,
|
|
43
41
|
);
|
|
44
42
|
}
|
|
45
43
|
|
package/src/native/types.ts
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import NativeStreamVideoThumbnail, { type VideoThumbnailResult } from './NativeStreamVideoThumbnail';
|
|
2
|
+
|
|
3
|
+
export type { VideoThumbnailResult } from './NativeStreamVideoThumbnail';
|
|
4
|
+
|
|
5
|
+
export const createVideoThumbnails = async (urls: string[]): Promise<VideoThumbnailResult[]> => {
|
|
6
|
+
const results = await NativeStreamVideoThumbnail.createVideoThumbnails(urls);
|
|
7
|
+
return Array.from(results);
|
|
8
|
+
};
|
|
@@ -136,7 +136,7 @@ class _Audio {
|
|
|
136
136
|
};
|
|
137
137
|
startPlayer = async (uri, _, onPlaybackStatusUpdate) => {
|
|
138
138
|
try {
|
|
139
|
-
|
|
139
|
+
await audioRecorderPlayer.startPlayer(uri);
|
|
140
140
|
audioRecorderPlayer.addPlayBackListener((status) => {
|
|
141
141
|
onPlaybackStatusUpdate(status);
|
|
142
142
|
});
|
|
@@ -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 };
|