stream-chat-react-native 9.0.0-beta.9 → 9.0.1-beta.1
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
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import Photos
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
private final class StreamPhotoLibraryAssetRequestState: @unchecked Sendable {
|
|
6
|
+
let lock = NSLock()
|
|
7
|
+
var didResume = false
|
|
8
|
+
var requestID: PHImageRequestID = PHInvalidImageRequestID
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@objcMembers
|
|
12
|
+
public final class StreamVideoThumbnailResult: NSObject {
|
|
13
|
+
public let error: String?
|
|
14
|
+
public let uri: String?
|
|
15
|
+
|
|
16
|
+
public init(error: String? = nil, uri: String? = nil) {
|
|
17
|
+
self.error = error
|
|
18
|
+
self.uri = uri
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@objcMembers
|
|
23
|
+
public final class StreamVideoThumbnailGenerator: NSObject {
|
|
24
|
+
private static let compressionQuality: CGFloat = 0.8
|
|
25
|
+
private static let maxDimension: CGFloat = 512
|
|
26
|
+
private static let cacheVersion = "v1"
|
|
27
|
+
private static let cacheDirectoryName = "@stream-io-stream-video-thumbnails"
|
|
28
|
+
private static let maxConcurrentGenerations = 5
|
|
29
|
+
private static let photoLibraryAssetResolutionTimeout: TimeInterval = 3
|
|
30
|
+
|
|
31
|
+
@objc(generateThumbnailsWithUrls:completion:)
|
|
32
|
+
public static func generateThumbnails(
|
|
33
|
+
urls: [String],
|
|
34
|
+
completion: @escaping ([StreamVideoThumbnailResult]) -> Void
|
|
35
|
+
) {
|
|
36
|
+
Task(priority: .userInitiated) {
|
|
37
|
+
completion(await generateThumbnailsAsync(urls: urls))
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private static func generateThumbnailsAsync(urls: [String]) async -> [StreamVideoThumbnailResult] {
|
|
42
|
+
guard !urls.isEmpty else {
|
|
43
|
+
return []
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if urls.count == 1 {
|
|
47
|
+
return [await generateThumbnailResult(url: urls[0])]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let parallelism = min(maxConcurrentGenerations, urls.count)
|
|
51
|
+
|
|
52
|
+
return await withTaskGroup(
|
|
53
|
+
of: (Int, StreamVideoThumbnailResult).self,
|
|
54
|
+
returning: [StreamVideoThumbnailResult].self
|
|
55
|
+
) { group in
|
|
56
|
+
var thumbnails = Array<StreamVideoThumbnailResult?>(repeating: nil, count: urls.count)
|
|
57
|
+
var nextIndexToSchedule = 0
|
|
58
|
+
|
|
59
|
+
while nextIndexToSchedule < parallelism {
|
|
60
|
+
let index = nextIndexToSchedule
|
|
61
|
+
let url = urls[index]
|
|
62
|
+
group.addTask {
|
|
63
|
+
(index, await generateThumbnailResult(url: url))
|
|
64
|
+
}
|
|
65
|
+
nextIndexToSchedule += 1
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
while let (index, thumbnail) = await group.next() {
|
|
69
|
+
thumbnails[index] = thumbnail
|
|
70
|
+
|
|
71
|
+
if nextIndexToSchedule < urls.count {
|
|
72
|
+
let nextIndex = nextIndexToSchedule
|
|
73
|
+
let nextURL = urls[nextIndex]
|
|
74
|
+
group.addTask {
|
|
75
|
+
(nextIndex, await generateThumbnailResult(url: nextURL))
|
|
76
|
+
}
|
|
77
|
+
nextIndexToSchedule += 1
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return thumbnails.enumerated().map { index, thumbnail in
|
|
82
|
+
thumbnail ?? StreamVideoThumbnailResult(
|
|
83
|
+
error: "Thumbnail generation produced no output for index \(index)",
|
|
84
|
+
uri: nil
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private static func generateThumbnailResult(url: String) async -> StreamVideoThumbnailResult {
|
|
91
|
+
do {
|
|
92
|
+
return StreamVideoThumbnailResult(uri: try await generateThumbnail(url: url))
|
|
93
|
+
} catch {
|
|
94
|
+
return StreamVideoThumbnailResult(
|
|
95
|
+
error: error.localizedDescription,
|
|
96
|
+
uri: nil
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private static func generateThumbnail(url: String) async throws -> String {
|
|
102
|
+
let outputDirectory = try thumbnailCacheDirectory()
|
|
103
|
+
let outputURL = outputDirectory
|
|
104
|
+
.appendingPathComponent(buildCacheFileName(url: url))
|
|
105
|
+
.appendingPathExtension("jpg")
|
|
106
|
+
|
|
107
|
+
if
|
|
108
|
+
FileManager.default.fileExists(atPath: outputURL.path),
|
|
109
|
+
let attributes = try? FileManager.default.attributesOfItem(atPath: outputURL.path),
|
|
110
|
+
let fileSize = attributes[.size] as? NSNumber,
|
|
111
|
+
fileSize.intValue > 0
|
|
112
|
+
{
|
|
113
|
+
return outputURL.absoluteString
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let asset = try await resolveAsset(url: url)
|
|
117
|
+
let generator = AVAssetImageGenerator(asset: asset)
|
|
118
|
+
generator.appliesPreferredTrackTransform = true
|
|
119
|
+
generator.maximumSize = CGSize(width: maxDimension, height: maxDimension)
|
|
120
|
+
|
|
121
|
+
let requestedTime = thumbnailTime(for: asset)
|
|
122
|
+
|
|
123
|
+
do {
|
|
124
|
+
let cgImage = try generator.copyCGImage(at: requestedTime, actualTime: nil)
|
|
125
|
+
let image = UIImage(cgImage: cgImage)
|
|
126
|
+
guard let data = image.jpegData(compressionQuality: compressionQuality) else {
|
|
127
|
+
throw thumbnailError(code: 2, message: "Failed to encode JPEG thumbnail for \(url)")
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try data.write(to: outputURL, options: .atomic)
|
|
131
|
+
return outputURL.absoluteString
|
|
132
|
+
} catch {
|
|
133
|
+
throw thumbnailError(error, code: 3, message: "Thumbnail generation failed for \(url)")
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private static func thumbnailCacheDirectory() throws -> URL {
|
|
138
|
+
let outputDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
|
139
|
+
.appendingPathComponent(cacheDirectoryName, isDirectory: true)
|
|
140
|
+
try FileManager.default.createDirectory(
|
|
141
|
+
at: outputDirectory,
|
|
142
|
+
withIntermediateDirectories: true
|
|
143
|
+
)
|
|
144
|
+
return outputDirectory
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private static func buildCacheFileName(url: String) -> String {
|
|
148
|
+
let cacheKey = fnv1a64("\(cacheVersion)|\(Int(maxDimension))|\(compressionQuality)|\(url)")
|
|
149
|
+
return "stream-video-thumbnail-\(cacheKey)"
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private static func fnv1a64(_ value: String) -> String {
|
|
153
|
+
var hash: UInt64 = 0xcbf29ce484222325
|
|
154
|
+
let prime: UInt64 = 0x100000001b3
|
|
155
|
+
|
|
156
|
+
for byte in value.utf8 {
|
|
157
|
+
hash ^= UInt64(byte)
|
|
158
|
+
hash &*= prime
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return String(hash, radix: 16)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private static func thumbnailTime(for asset: AVAsset) -> CMTime {
|
|
165
|
+
let durationSeconds = asset.duration.seconds
|
|
166
|
+
if durationSeconds.isFinite, durationSeconds > 0.1 {
|
|
167
|
+
return CMTime(seconds: 0.1, preferredTimescale: 600)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return .zero
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private static func resolveAsset(url: String) async throws -> AVAsset {
|
|
174
|
+
if isPhotoLibraryURL(url) {
|
|
175
|
+
return try await resolvePhotoLibraryAsset(url: url)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if let normalizedURL = normalizeLocalURL(url) {
|
|
179
|
+
return AVURLAsset(url: normalizedURL)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
throw thumbnailError(code: 5, message: "Unsupported video URL for thumbnail generation: \(url)")
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private static func isPhotoLibraryURL(_ url: String) -> Bool {
|
|
186
|
+
url.lowercased().hasPrefix("ph://")
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private static func resolvePhotoLibraryAsset(url: String) async throws -> AVAsset {
|
|
190
|
+
let identifier = photoLibraryIdentifier(from: url)
|
|
191
|
+
|
|
192
|
+
guard !identifier.isEmpty else {
|
|
193
|
+
throw thumbnailError(code: 6, message: "Missing photo library identifier for \(url)")
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil)
|
|
197
|
+
guard let asset = fetchResult.firstObject else {
|
|
198
|
+
throw thumbnailError(code: 7, message: "Failed to find photo library asset for \(url)")
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let options = PHVideoRequestOptions()
|
|
202
|
+
options.deliveryMode = .highQualityFormat
|
|
203
|
+
options.isNetworkAccessAllowed = true
|
|
204
|
+
options.version = .current
|
|
205
|
+
|
|
206
|
+
return try await withThrowingTaskGroup(of: AVAsset.self) { group in
|
|
207
|
+
group.addTask {
|
|
208
|
+
try await requestPhotoLibraryAsset(url: url, asset: asset, options: options)
|
|
209
|
+
}
|
|
210
|
+
group.addTask {
|
|
211
|
+
try await Task.sleep(nanoseconds: UInt64(photoLibraryAssetResolutionTimeout * 1_000_000_000))
|
|
212
|
+
throw thumbnailError(
|
|
213
|
+
code: 11,
|
|
214
|
+
message: "Timed out resolving photo library asset for \(url)"
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
guard let resolvedAsset = try await group.next() else {
|
|
219
|
+
throw thumbnailError(
|
|
220
|
+
code: 10,
|
|
221
|
+
message: "Failed to resolve photo library asset for \(url)"
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
group.cancelAll()
|
|
226
|
+
return resolvedAsset
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private static func requestPhotoLibraryAsset(
|
|
231
|
+
url: String,
|
|
232
|
+
asset: PHAsset,
|
|
233
|
+
options: PHVideoRequestOptions
|
|
234
|
+
) async throws -> AVAsset {
|
|
235
|
+
let imageManager = PHImageManager.default()
|
|
236
|
+
let state = StreamPhotoLibraryAssetRequestState()
|
|
237
|
+
|
|
238
|
+
return try await withTaskCancellationHandler(operation: {
|
|
239
|
+
try await withCheckedThrowingContinuation { continuation in
|
|
240
|
+
let requestID = imageManager.requestAVAsset(forVideo: asset, options: options) {
|
|
241
|
+
avAsset, _, info in
|
|
242
|
+
state.lock.lock()
|
|
243
|
+
if state.didResume {
|
|
244
|
+
state.lock.unlock()
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
state.didResume = true
|
|
248
|
+
state.lock.unlock()
|
|
249
|
+
|
|
250
|
+
if let isCancelled = (info?[PHImageCancelledKey] as? NSNumber)?.boolValue, isCancelled {
|
|
251
|
+
continuation.resume(
|
|
252
|
+
throwing: thumbnailError(
|
|
253
|
+
code: 8,
|
|
254
|
+
message: "Photo library asset request was cancelled for \(url)"
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if let error = info?[PHImageErrorKey] as? Error {
|
|
261
|
+
continuation.resume(
|
|
262
|
+
throwing: thumbnailError(
|
|
263
|
+
error,
|
|
264
|
+
code: 9,
|
|
265
|
+
message: "Photo library asset request failed for \(url)"
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
guard let avAsset else {
|
|
272
|
+
continuation.resume(
|
|
273
|
+
throwing: thumbnailError(
|
|
274
|
+
code: 10,
|
|
275
|
+
message: "Failed to resolve photo library asset for \(url)"
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
continuation.resume(returning: avAsset)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
state.lock.lock()
|
|
285
|
+
state.requestID = requestID
|
|
286
|
+
state.lock.unlock()
|
|
287
|
+
}
|
|
288
|
+
}, onCancel: {
|
|
289
|
+
state.lock.lock()
|
|
290
|
+
let requestID = state.requestID
|
|
291
|
+
state.lock.unlock()
|
|
292
|
+
|
|
293
|
+
if requestID != PHInvalidImageRequestID {
|
|
294
|
+
imageManager.cancelImageRequest(requestID)
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private static func photoLibraryIdentifier(from url: String) -> String {
|
|
300
|
+
guard let parsedURL = URL(string: url), parsedURL.scheme?.lowercased() == "ph" else {
|
|
301
|
+
return url
|
|
302
|
+
.replacingOccurrences(of: "ph://", with: "", options: [.caseInsensitive])
|
|
303
|
+
.removingPercentEncoding?
|
|
304
|
+
.trimmingCharacters(in: CharacterSet(charactersIn: "/")) ?? ""
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let host = parsedURL.host ?? ""
|
|
308
|
+
let path = parsedURL.path
|
|
309
|
+
let combined = host.isEmpty ? path : "\(host)\(path)"
|
|
310
|
+
return combined.removingPercentEncoding?
|
|
311
|
+
.trimmingCharacters(in: CharacterSet(charactersIn: "/")) ?? ""
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private static func normalizeLocalURL(_ url: String) -> URL? {
|
|
315
|
+
if let parsedURL = URL(string: url), let scheme = parsedURL.scheme?.lowercased() {
|
|
316
|
+
if scheme == "file" {
|
|
317
|
+
return parsedURL
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return nil
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return URL(fileURLWithPath: url)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private static func thumbnailError(
|
|
327
|
+
_ error: Error? = nil,
|
|
328
|
+
code: Int,
|
|
329
|
+
message: String
|
|
330
|
+
) -> Error {
|
|
331
|
+
let description = error.map { "\(message): \($0.localizedDescription)" } ?? message
|
|
332
|
+
return NSError(
|
|
333
|
+
domain: "StreamVideoThumbnail",
|
|
334
|
+
code: code,
|
|
335
|
+
userInfo: [NSLocalizedDescriptionKey: description]
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
}
|
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.
|
|
4
|
+
"version": "9.0.1-beta.1",
|
|
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.
|
|
33
|
+
"stream-chat-react-native-core": "9.0.1-beta.1"
|
|
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/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
|
});
|