@movementinfra/expo-twostep-video 0.1.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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +420 -0
  3. package/build/ExpoTwostepVideo.types.d.ts +65 -0
  4. package/build/ExpoTwostepVideo.types.d.ts.map +1 -0
  5. package/build/ExpoTwostepVideo.types.js +2 -0
  6. package/build/ExpoTwostepVideo.types.js.map +1 -0
  7. package/build/ExpoTwostepVideoModule.d.ts +70 -0
  8. package/build/ExpoTwostepVideoModule.d.ts.map +1 -0
  9. package/build/ExpoTwostepVideoModule.js +5 -0
  10. package/build/ExpoTwostepVideoModule.js.map +1 -0
  11. package/build/ExpoTwostepVideoModule.web.d.ts +14 -0
  12. package/build/ExpoTwostepVideoModule.web.d.ts.map +1 -0
  13. package/build/ExpoTwostepVideoModule.web.js +12 -0
  14. package/build/ExpoTwostepVideoModule.web.js.map +1 -0
  15. package/build/ExpoTwostepVideoView.d.ts +27 -0
  16. package/build/ExpoTwostepVideoView.d.ts.map +1 -0
  17. package/build/ExpoTwostepVideoView.js +47 -0
  18. package/build/ExpoTwostepVideoView.js.map +1 -0
  19. package/build/ExpoTwostepVideoView.web.d.ts +4 -0
  20. package/build/ExpoTwostepVideoView.web.d.ts.map +1 -0
  21. package/build/ExpoTwostepVideoView.web.js +8 -0
  22. package/build/ExpoTwostepVideoView.web.js.map +1 -0
  23. package/build/index.d.ts +569 -0
  24. package/build/index.d.ts.map +1 -0
  25. package/build/index.js +430 -0
  26. package/build/index.js.map +1 -0
  27. package/expo-module.config.json +10 -0
  28. package/ios/ExpoTwostepVideo.podspec +30 -0
  29. package/ios/ExpoTwostepVideoModule.swift +739 -0
  30. package/ios/ExpoTwostepVideoView.swift +223 -0
  31. package/ios/Package.swift +32 -0
  32. package/ios/TwoStepVideo/Core/AssetLoader.swift +175 -0
  33. package/ios/TwoStepVideo/Core/VideoExporter.swift +353 -0
  34. package/ios/TwoStepVideo/Core/VideoTransformer.swift +365 -0
  35. package/ios/TwoStepVideo/Core/VideoTrimmer.swift +300 -0
  36. package/ios/TwoStepVideo/Models/ExportConfiguration.swift +104 -0
  37. package/ios/TwoStepVideo/Models/LoopConfiguration.swift +101 -0
  38. package/ios/TwoStepVideo/Models/TimeRange.swift +98 -0
  39. package/ios/TwoStepVideo/Models/VideoAsset.swift +126 -0
  40. package/ios/TwoStepVideo/Models/VideoEditingError.swift +82 -0
  41. package/ios/TwoStepVideo/TwoStepVideo.swift +30 -0
  42. package/package.json +57 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Richard Guo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,420 @@
1
+ # expo-twostep-video
2
+
3
+ Professional video editing for React Native, powered by native AVFoundation.
4
+
5
+ [![npm version](https://badge.fury.io/js/expo-twostep-video.svg)](https://www.npmjs.com/package/expo-twostep-video)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Features
9
+
10
+ - ๐ŸŽฌ **Load videos** from file system or Photos library
11
+ - โœ‚๏ธ **Trim videos** with frame-accurate precision
12
+ - ๐ŸŽž๏ธ **Create multi-segment compositions** (highlight reels)
13
+ - ๐Ÿ“ธ **Generate thumbnails** at any timestamp
14
+ - ๐Ÿ’พ **Export** with customizable quality settings
15
+ - ๐Ÿ“Š **Real-time progress tracking** during exports
16
+ - ๐Ÿงน **Automatic cleanup** of partial/temp files
17
+ - ๐Ÿ”’ **Type-safe TypeScript API** with full IntelliSense
18
+ - ๐Ÿ“ฑ **iOS 15+** support
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npx expo install expo-twostep-video
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ```typescript
29
+ import * as TwoStepVideo from 'expo-twostep-video';
30
+
31
+ // Load a video
32
+ const asset = await TwoStepVideo.loadAsset({
33
+ uri: 'file:///path/to/video.mp4'
34
+ });
35
+
36
+ // Trim it (5 seconds to 15 seconds)
37
+ const composition = await TwoStepVideo.trimVideo({
38
+ assetId: asset.id,
39
+ startTime: 5.0,
40
+ endTime: 15.0
41
+ });
42
+
43
+ // Export with progress tracking
44
+ const result = await TwoStepVideo.exportVideo({
45
+ compositionId: composition.id,
46
+ quality: TwoStepVideo.Quality.HIGH
47
+ });
48
+
49
+ console.log('Exported to:', result.uri);
50
+ ```
51
+
52
+ ## Complete Example
53
+
54
+ ```typescript
55
+ import React, { useState, useEffect } from 'react';
56
+ import { View, Button, Text } from 'react-native';
57
+ import * as TwoStepVideo from 'expo-twostep-video';
58
+
59
+ function VideoEditor() {
60
+ const [progress, setProgress] = useState(0);
61
+
62
+ useEffect(() => {
63
+ const subscription = TwoStepVideo.addExportProgressListener((event) => {
64
+ setProgress(event.progress);
65
+ });
66
+ return () => subscription.remove();
67
+ }, []);
68
+
69
+ const trimAndExport = async () => {
70
+ try {
71
+ // Load
72
+ const asset = await TwoStepVideo.loadAsset({
73
+ uri: 'file:///path/to/video.mp4'
74
+ });
75
+
76
+ // Trim
77
+ const composition = await TwoStepVideo.trimVideo({
78
+ assetId: asset.id,
79
+ startTime: 0,
80
+ endTime: 10
81
+ });
82
+
83
+ // Export
84
+ const result = await TwoStepVideo.exportVideo({
85
+ compositionId: composition.id,
86
+ quality: TwoStepVideo.Quality.HIGH
87
+ });
88
+
89
+ alert(`Saved to: ${result.uri}`);
90
+
91
+ // Cleanup
92
+ TwoStepVideo.releaseAsset(asset.id);
93
+ TwoStepVideo.releaseComposition(composition.id);
94
+
95
+ } catch (error) {
96
+ console.error('Error:', error);
97
+ }
98
+ };
99
+
100
+ return (
101
+ <View>
102
+ <Text>Progress: {Math.round(progress * 100)}%</Text>
103
+ <Button title="Trim & Export" onPress={trimAndExport} />
104
+ </View>
105
+ );
106
+ }
107
+ ```
108
+
109
+ ## API Reference
110
+
111
+ ### Loading Videos
112
+
113
+ #### `loadAsset(options)`
114
+ Load a video from a file URI.
115
+
116
+ ```typescript
117
+ const asset = await TwoStepVideo.loadAsset({
118
+ uri: 'file:///path/to/video.mp4'
119
+ });
120
+ // Returns: { id, duration, width, height, frameRate, hasAudio }
121
+ ```
122
+
123
+ #### `loadAssetFromPhotos(localIdentifier)`
124
+ Load a video from the Photos library.
125
+
126
+ ```typescript
127
+ const asset = await TwoStepVideo.loadAssetFromPhotos(photoAsset.id);
128
+ ```
129
+
130
+ #### `validateVideoUri(uri)`
131
+ Quickly validate a video URI without loading.
132
+
133
+ ```typescript
134
+ const isValid = await TwoStepVideo.validateVideoUri(uri);
135
+ ```
136
+
137
+ ### Trimming
138
+
139
+ #### `trimVideo(options)`
140
+ Trim to a single time range.
141
+
142
+ ```typescript
143
+ const composition = await TwoStepVideo.trimVideo({
144
+ assetId: asset.id,
145
+ startTime: 5.0, // seconds
146
+ endTime: 15.0
147
+ });
148
+ ```
149
+
150
+ #### `trimVideoMultiple(options)`
151
+ Create a composition from multiple segments.
152
+
153
+ ```typescript
154
+ const composition = await TwoStepVideo.trimVideoMultiple({
155
+ assetId: asset.id,
156
+ segments: [
157
+ { start: 0, end: 5 },
158
+ { start: 10, end: 15 },
159
+ { start: 20, end: 25 }
160
+ ]
161
+ });
162
+ ```
163
+
164
+ ### Thumbnails
165
+
166
+ #### `generateThumbnails(options)`
167
+ Extract thumbnail images at specific times.
168
+
169
+ ```typescript
170
+ const thumbnails = await TwoStepVideo.generateThumbnails({
171
+ assetId: asset.id,
172
+ times: [0, 5, 10, 15],
173
+ size: { width: 300, height: 300 }
174
+ });
175
+
176
+ // Use in Image component
177
+ <Image source={{ uri: `data:image/png;base64,${thumbnails[0]}` }} />
178
+ ```
179
+
180
+ ### Exporting
181
+
182
+ #### `exportVideo(options)`
183
+ Export a composition to a file.
184
+
185
+ ```typescript
186
+ const result = await TwoStepVideo.exportVideo({
187
+ compositionId: composition.id,
188
+ quality: TwoStepVideo.Quality.HIGH, // or LOW, MEDIUM, HIGHEST
189
+ outputUri: 'file:///path/to/output.mp4' // optional
190
+ });
191
+ ```
192
+
193
+ #### `exportAsset(options)`
194
+ Export an asset directly (without trimming).
195
+
196
+ ```typescript
197
+ const result = await TwoStepVideo.exportAsset({
198
+ assetId: asset.id,
199
+ quality: TwoStepVideo.Quality.MEDIUM
200
+ });
201
+ ```
202
+
203
+ ### Events
204
+
205
+ #### `addExportProgressListener(callback)`
206
+ Listen for export progress updates.
207
+
208
+ ```typescript
209
+ const subscription = TwoStepVideo.addExportProgressListener((event) => {
210
+ console.log(`${Math.round(event.progress * 100)}%`);
211
+ });
212
+
213
+ // Remove listener
214
+ subscription.remove();
215
+ ```
216
+
217
+ ### Memory Management
218
+
219
+ ```typescript
220
+ // Release assets when done
221
+ TwoStepVideo.releaseAsset(asset.id);
222
+ TwoStepVideo.releaseComposition(composition.id);
223
+
224
+ // Release all at once
225
+ TwoStepVideo.releaseAll();
226
+
227
+ // Clean up temp files
228
+ TwoStepVideo.cleanupFile(tempFileUri);
229
+ ```
230
+
231
+ ## Quality Presets
232
+
233
+ ```typescript
234
+ TwoStepVideo.Quality.LOW // ~0.1 bits per pixel
235
+ TwoStepVideo.Quality.MEDIUM // ~0.2 bits per pixel
236
+ TwoStepVideo.Quality.HIGH // ~0.4 bits per pixel (recommended)
237
+ TwoStepVideo.Quality.HIGHEST // ~0.8 bits per pixel
238
+ ```
239
+
240
+ ## TypeScript Support
241
+
242
+ Full TypeScript definitions included:
243
+
244
+ ```typescript
245
+ interface VideoAsset {
246
+ id: string;
247
+ duration: number;
248
+ width: number;
249
+ height: number;
250
+ frameRate: number;
251
+ hasAudio: boolean;
252
+ }
253
+
254
+ interface VideoComposition {
255
+ id: string;
256
+ duration: number;
257
+ }
258
+
259
+ interface ExportResult {
260
+ uri: string;
261
+ path: string;
262
+ }
263
+ ```
264
+
265
+ ## Advanced Usage
266
+
267
+ ### Create a Highlight Reel
268
+
269
+ ```typescript
270
+ async function createHighlightReel(videoUri: string) {
271
+ const asset = await TwoStepVideo.loadAsset({ uri: videoUri });
272
+
273
+ // Extract 3-second clips evenly throughout video
274
+ const numClips = 5;
275
+ const interval = asset.duration / (numClips + 1);
276
+
277
+ const segments = Array.from({ length: numClips }, (_, i) => ({
278
+ start: interval * (i + 1),
279
+ end: Math.min(interval * (i + 1) + 3, asset.duration)
280
+ }));
281
+
282
+ const composition = await TwoStepVideo.trimVideoMultiple({
283
+ assetId: asset.id,
284
+ segments
285
+ });
286
+
287
+ return await TwoStepVideo.exportVideo({
288
+ compositionId: composition.id,
289
+ quality: TwoStepVideo.Quality.HIGH
290
+ });
291
+ }
292
+ ```
293
+
294
+ ### With Progress Bar
295
+
296
+ ```typescript
297
+ function VideoExporter({ videoUri }) {
298
+ const progress = TwoStepVideo.useExportProgress();
299
+
300
+ return (
301
+ <View>
302
+ <ProgressBar progress={progress} />
303
+ <Text>{Math.round(progress * 100)}%</Text>
304
+ </View>
305
+ );
306
+ }
307
+ ```
308
+
309
+ ## Development
310
+
311
+ This module contains both the Swift library and Expo bridge in one package.
312
+
313
+ ### Running Tests
314
+
315
+ ```bash
316
+ # Run Swift unit tests
317
+ npm run test:swift
318
+
319
+ # All tests should pass:
320
+ # โœ… 69 tests executed, 20 skipped (integration tests)
321
+ ```
322
+
323
+ ### Building
324
+
325
+ ```bash
326
+ # Build TypeScript
327
+ npm run build
328
+
329
+ # Clean build artifacts
330
+ npm run clean
331
+ ```
332
+
333
+ ### Running Example App
334
+
335
+ ```bash
336
+ cd example
337
+ npm install
338
+ npm run ios
339
+ ```
340
+
341
+ ## Architecture
342
+
343
+ ```
344
+ expo-twostep-video/
345
+ โ”œโ”€โ”€ ios/
346
+ โ”‚ โ”œโ”€โ”€ TwoStepVideo/ # Swift library
347
+ โ”‚ โ”‚ โ”œโ”€โ”€ Models/ # Data models
348
+ โ”‚ โ”‚ โ”œโ”€โ”€ Core/ # Core functionality
349
+ โ”‚ โ”‚ โ””โ”€โ”€ TwoStepVideo.swift # Main facade
350
+ โ”‚ โ”œโ”€โ”€ Tests/ # Swift unit tests
351
+ โ”‚ โ”œโ”€โ”€ ExpoTwoStepVideoModule.swift # Expo bridge
352
+ โ”‚ โ””โ”€โ”€ Package.swift # Swift package for testing
353
+ โ”œโ”€โ”€ src/
354
+ โ”‚ โ””โ”€โ”€ index.ts # TypeScript API
355
+ โ”œโ”€โ”€ example/ # Example React Native app
356
+ โ””โ”€โ”€ docs/ # Documentation
357
+ ```
358
+
359
+ ## Documentation
360
+
361
+ - [Architecture](./docs/ARCHITECTURE.md) - Design and implementation details
362
+ - [Developer Guide](./docs/CLAUDE.md) - Best practices and patterns
363
+ - [Temp File Management](./docs/TEMP_FILE_MANAGEMENT.md) - File cleanup strategies
364
+
365
+ ## Requirements
366
+
367
+ - iOS 15.0+
368
+ - Expo SDK 50+
369
+ - React Native 0.72+
370
+
371
+ ## Platform Support
372
+
373
+ - โœ… iOS (fully supported)
374
+ - โณ Android (coming soon)
375
+
376
+ ## Error Handling
377
+
378
+ All errors include descriptive codes and messages:
379
+
380
+ ```typescript
381
+ try {
382
+ await TwoStepVideo.exportVideo(options);
383
+ } catch (error: any) {
384
+ switch (error.code) {
385
+ case 'EXPORT_FAILED':
386
+ // Handle export failure
387
+ break;
388
+ case 'ASSET_NOT_FOUND':
389
+ // Asset was released too early
390
+ break;
391
+ default:
392
+ console.error('Unknown error:', error);
393
+ }
394
+ }
395
+ ```
396
+
397
+ ## Performance Tips
398
+
399
+ 1. **Release resources promptly** after use
400
+ 2. **Use `releaseAll()`** when unmounting components
401
+ 3. **Validate URIs** before loading with `validateVideoUri()`
402
+ 4. **Clean up temp files** after moving/uploading
403
+
404
+ ## Contributing
405
+
406
+ Contributions welcome! Please see [CONTRIBUTING](./docs/CONTRIBUTING.md).
407
+
408
+ ## License
409
+
410
+ MIT ยฉ Richard Guo
411
+
412
+ ## Support
413
+
414
+ - ๐Ÿ“ง Email: richardg7890@gmail.com
415
+ - ๐Ÿ› Issues: [GitHub Issues](https://github.com/rguo123/twostep-video/issues)
416
+ - ๐Ÿ“š Docs: [Full Documentation](./docs/)
417
+
418
+ ## Acknowledgments
419
+
420
+ Built with AVFoundation and Expo Modules.
@@ -0,0 +1,65 @@
1
+ import type { StyleProp, ViewStyle } from 'react-native';
2
+ /**
3
+ * Playback status event payload
4
+ */
5
+ export type PlaybackStatusEvent = {
6
+ status: 'ready' | 'playing' | 'paused' | 'ended' | 'seeked';
7
+ time?: number;
8
+ };
9
+ /**
10
+ * Progress event payload
11
+ */
12
+ export type ProgressEvent = {
13
+ currentTime: number;
14
+ duration: number;
15
+ progress: number;
16
+ };
17
+ /**
18
+ * Error event payload
19
+ */
20
+ export type ErrorEvent = {
21
+ error: string;
22
+ };
23
+ /**
24
+ * Video player view props
25
+ */
26
+ export type TwoStepVideoViewProps = {
27
+ /** ID of a composition to play (from trimVideo, mirrorVideo, etc.) */
28
+ compositionId?: string;
29
+ /** ID of an asset to play (from loadAsset) */
30
+ assetId?: string;
31
+ /** Enable continuous looping - video will restart automatically when it ends */
32
+ loop?: boolean;
33
+ /** Called when playback status changes */
34
+ onPlaybackStatusChange?: (event: {
35
+ nativeEvent: PlaybackStatusEvent;
36
+ }) => void;
37
+ /** Called periodically with playback progress */
38
+ onProgress?: (event: {
39
+ nativeEvent: ProgressEvent;
40
+ }) => void;
41
+ /** Called when playback reaches the end (not called if loop is true) */
42
+ onEnd?: (event: {
43
+ nativeEvent: Record<string, never>;
44
+ }) => void;
45
+ /** Called when an error occurs */
46
+ onError?: (event: {
47
+ nativeEvent: ErrorEvent;
48
+ }) => void;
49
+ /** View style */
50
+ style?: StyleProp<ViewStyle>;
51
+ };
52
+ /**
53
+ * Ref handle for video player view
54
+ */
55
+ export type TwoStepVideoViewRef = {
56
+ /** Start playback */
57
+ play: () => Promise<void>;
58
+ /** Pause playback */
59
+ pause: () => Promise<void>;
60
+ /** Seek to a specific time in seconds */
61
+ seek: (time: number) => Promise<void>;
62
+ /** Restart playback from the beginning */
63
+ replay: () => Promise<void>;
64
+ };
65
+ //# sourceMappingURL=ExpoTwostepVideo.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoTwostepVideo.types.d.ts","sourceRoot":"","sources":["../src/ExpoTwostepVideo.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzD;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0CAA0C;IAC1C,sBAAsB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,WAAW,EAAE,mBAAmB,CAAA;KAAE,KAAK,IAAI,CAAC;IAC/E,iDAAiD;IACjD,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,WAAW,EAAE,aAAa,CAAA;KAAE,KAAK,IAAI,CAAC;IAC7D,wEAAwE;IACxE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;KAAE,KAAK,IAAI,CAAC;IAChE,kCAAkC;IAClC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,WAAW,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,CAAC;IACvD,iBAAiB;IACjB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,qBAAqB;IACrB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,qBAAqB;IACrB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,yCAAyC;IACzC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,0CAA0C;IAC1C,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ExpoTwostepVideo.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoTwostepVideo.types.js","sourceRoot":"","sources":["../src/ExpoTwostepVideo.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { StyleProp, ViewStyle } from 'react-native';\n\n/**\n * Playback status event payload\n */\nexport type PlaybackStatusEvent = {\n status: 'ready' | 'playing' | 'paused' | 'ended' | 'seeked';\n time?: number;\n};\n\n/**\n * Progress event payload\n */\nexport type ProgressEvent = {\n currentTime: number;\n duration: number;\n progress: number;\n};\n\n/**\n * Error event payload\n */\nexport type ErrorEvent = {\n error: string;\n};\n\n/**\n * Video player view props\n */\nexport type TwoStepVideoViewProps = {\n /** ID of a composition to play (from trimVideo, mirrorVideo, etc.) */\n compositionId?: string;\n /** ID of an asset to play (from loadAsset) */\n assetId?: string;\n /** Enable continuous looping - video will restart automatically when it ends */\n loop?: boolean;\n /** Called when playback status changes */\n onPlaybackStatusChange?: (event: { nativeEvent: PlaybackStatusEvent }) => void;\n /** Called periodically with playback progress */\n onProgress?: (event: { nativeEvent: ProgressEvent }) => void;\n /** Called when playback reaches the end (not called if loop is true) */\n onEnd?: (event: { nativeEvent: Record<string, never> }) => void;\n /** Called when an error occurs */\n onError?: (event: { nativeEvent: ErrorEvent }) => void;\n /** View style */\n style?: StyleProp<ViewStyle>;\n};\n\n/**\n * Ref handle for video player view\n */\nexport type TwoStepVideoViewRef = {\n /** Start playback */\n play: () => Promise<void>;\n /** Pause playback */\n pause: () => Promise<void>;\n /** Seek to a specific time in seconds */\n seek: (time: number) => Promise<void>;\n /** Restart playback from the beginning */\n replay: () => Promise<void>;\n};\n"]}
@@ -0,0 +1,70 @@
1
+ import { NativeModule } from 'expo-modules-core';
2
+ interface TimeSegment {
3
+ start: number;
4
+ end: number;
5
+ }
6
+ interface ThumbnailSize {
7
+ width: number;
8
+ height: number;
9
+ }
10
+ interface AssetResult {
11
+ id: string;
12
+ duration: number;
13
+ width: number;
14
+ height: number;
15
+ frameRate: number;
16
+ hasAudio: boolean;
17
+ }
18
+ interface CompositionResult {
19
+ id: string;
20
+ duration: number;
21
+ }
22
+ interface CompositionResultWithDuration {
23
+ id: string;
24
+ duration: number;
25
+ }
26
+ interface LoopResult {
27
+ id: string;
28
+ duration: number;
29
+ loopCount: number;
30
+ totalPlays: number;
31
+ }
32
+ interface ExportResult {
33
+ uri: string;
34
+ path: string;
35
+ }
36
+ interface ExportProgressEvent {
37
+ progress: number;
38
+ assetId?: string;
39
+ compositionId?: string;
40
+ }
41
+ declare class ExpoTwostepVideoModule extends NativeModule<{
42
+ onExportProgress: (event: ExportProgressEvent) => void;
43
+ }> {
44
+ QUALITY_LOW: string;
45
+ QUALITY_MEDIUM: string;
46
+ QUALITY_HIGH: string;
47
+ QUALITY_HIGHEST: string;
48
+ MIRROR_HORIZONTAL: string;
49
+ MIRROR_VERTICAL: string;
50
+ MIRROR_BOTH: string;
51
+ loadAsset(uri: string): Promise<AssetResult>;
52
+ loadAssetFromPhotos(localIdentifier: string): Promise<AssetResult>;
53
+ validateVideoUri(uri: string): Promise<boolean>;
54
+ trimVideo(assetId: string, startTime: number, endTime: number): Promise<CompositionResult>;
55
+ trimVideoMultiple(assetId: string, segments: TimeSegment[]): Promise<CompositionResult>;
56
+ mirrorVideo(assetId: string, axis: string, startTime?: number, endTime?: number): Promise<CompositionResult>;
57
+ adjustSpeed(assetId: string, speed: number, startTime?: number, endTime?: number): Promise<CompositionResult>;
58
+ transformVideo(assetId: string, speed?: number, mirrorAxis?: string, startTime?: number, endTime?: number): Promise<CompositionResultWithDuration>;
59
+ loopSegment(assetId: string, startTime: number, endTime: number, loopCount: number): Promise<LoopResult>;
60
+ generateThumbnails(assetId: string, times: number[], size?: ThumbnailSize): Promise<string[]>;
61
+ exportVideo(compositionId: string, outputUri?: string, quality?: string): Promise<ExportResult>;
62
+ exportAsset(assetId: string, outputUri?: string, quality?: string): Promise<ExportResult>;
63
+ cleanupFile(uri: string): void;
64
+ releaseAsset(assetId: string): void;
65
+ releaseComposition(compositionId: string): void;
66
+ releaseAll(): void;
67
+ }
68
+ declare const _default: ExpoTwostepVideoModule;
69
+ export default _default;
70
+ //# sourceMappingURL=ExpoTwostepVideoModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoTwostepVideoModule.d.ts","sourceRoot":"","sources":["../src/ExpoTwostepVideoModule.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ // This call loads the native module object from the JSI.
3
+ // The module name must match Name("ExpoTwoStepVideo") in the Swift module.
4
+ export default requireNativeModule('ExpoTwoStepVideo');
5
+ //# sourceMappingURL=ExpoTwostepVideoModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoTwostepVideoModule.js","sourceRoot":"","sources":["../src/ExpoTwostepVideoModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAwFtE,yDAAyD;AACzD,2EAA2E;AAC3E,eAAe,mBAAmB,CAAyB,kBAAkB,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from 'expo-modules-core';\n\ninterface TimeSegment {\n start: number;\n end: number;\n}\n\ninterface ThumbnailSize {\n width: number;\n height: number;\n}\n\ninterface AssetResult {\n id: string;\n duration: number;\n width: number;\n height: number;\n frameRate: number;\n hasAudio: boolean;\n}\n\ninterface CompositionResult {\n id: string;\n duration: number;\n}\n\ninterface CompositionResultWithDuration {\n id: string;\n duration: number;\n}\n\ninterface LoopResult {\n id: string;\n duration: number;\n loopCount: number;\n totalPlays: number;\n}\n\ninterface ExportResult {\n uri: string;\n path: string;\n}\n\ninterface ExportProgressEvent {\n progress: number;\n assetId?: string;\n compositionId?: string;\n}\n\ndeclare class ExpoTwostepVideoModule extends NativeModule<{\n onExportProgress: (event: ExportProgressEvent) => void;\n}> {\n // Constants\n QUALITY_LOW: string;\n QUALITY_MEDIUM: string;\n QUALITY_HIGH: string;\n QUALITY_HIGHEST: string;\n MIRROR_HORIZONTAL: string;\n MIRROR_VERTICAL: string;\n MIRROR_BOTH: string;\n\n // Asset loading\n loadAsset(uri: string): Promise<AssetResult>;\n loadAssetFromPhotos(localIdentifier: string): Promise<AssetResult>;\n validateVideoUri(uri: string): Promise<boolean>;\n\n // Editing operations\n trimVideo(assetId: string, startTime: number, endTime: number): Promise<CompositionResult>;\n trimVideoMultiple(assetId: string, segments: TimeSegment[]): Promise<CompositionResult>;\n mirrorVideo(assetId: string, axis: string, startTime?: number, endTime?: number): Promise<CompositionResult>;\n adjustSpeed(assetId: string, speed: number, startTime?: number, endTime?: number): Promise<CompositionResult>;\n transformVideo(assetId: string, speed?: number, mirrorAxis?: string, startTime?: number, endTime?: number): Promise<CompositionResultWithDuration>;\n loopSegment(assetId: string, startTime: number, endTime: number, loopCount: number): Promise<LoopResult>;\n\n // Thumbnails\n generateThumbnails(assetId: string, times: number[], size?: ThumbnailSize): Promise<string[]>;\n\n // Export\n exportVideo(compositionId: string, outputUri?: string, quality?: string): Promise<ExportResult>;\n exportAsset(assetId: string, outputUri?: string, quality?: string): Promise<ExportResult>;\n\n // Cleanup\n cleanupFile(uri: string): void;\n releaseAsset(assetId: string): void;\n releaseComposition(compositionId: string): void;\n releaseAll(): void;\n}\n\n// This call loads the native module object from the JSI.\n// The module name must match Name(\"ExpoTwoStepVideo\") in the Swift module.\nexport default requireNativeModule<ExpoTwostepVideoModule>('ExpoTwoStepVideo');\n"]}
@@ -0,0 +1,14 @@
1
+ declare const _default: {
2
+ QUALITY_LOW: string;
3
+ QUALITY_MEDIUM: string;
4
+ QUALITY_HIGH: string;
5
+ QUALITY_HIGHEST: string;
6
+ MIRROR_HORIZONTAL: string;
7
+ MIRROR_VERTICAL: string;
8
+ MIRROR_BOTH: string;
9
+ addListener: () => {
10
+ remove: () => void;
11
+ };
12
+ };
13
+ export default _default;
14
+ //# sourceMappingURL=ExpoTwostepVideoModule.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoTwostepVideoModule.web.d.ts","sourceRoot":"","sources":["../src/ExpoTwostepVideoModule.web.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,wBASE"}
@@ -0,0 +1,12 @@
1
+ // Web is not supported - this is a stub
2
+ export default {
3
+ QUALITY_LOW: 'low',
4
+ QUALITY_MEDIUM: 'medium',
5
+ QUALITY_HIGH: 'high',
6
+ QUALITY_HIGHEST: 'highest',
7
+ MIRROR_HORIZONTAL: 'horizontal',
8
+ MIRROR_VERTICAL: 'vertical',
9
+ MIRROR_BOTH: 'both',
10
+ addListener: () => ({ remove: () => { } }),
11
+ };
12
+ //# sourceMappingURL=ExpoTwostepVideoModule.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoTwostepVideoModule.web.js","sourceRoot":"","sources":["../src/ExpoTwostepVideoModule.web.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,eAAe;IACb,WAAW,EAAE,KAAK;IAClB,cAAc,EAAE,QAAQ;IACxB,YAAY,EAAE,MAAM;IACpB,eAAe,EAAE,SAAS;IAC1B,iBAAiB,EAAE,YAAY;IAC/B,eAAe,EAAE,UAAU;IAC3B,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC;CAC1C,CAAC","sourcesContent":["// Web is not supported - this is a stub\nexport default {\n QUALITY_LOW: 'low',\n QUALITY_MEDIUM: 'medium',\n QUALITY_HIGH: 'high',\n QUALITY_HIGHEST: 'highest',\n MIRROR_HORIZONTAL: 'horizontal',\n MIRROR_VERTICAL: 'vertical',\n MIRROR_BOTH: 'both',\n addListener: () => ({ remove: () => {} }),\n};\n"]}
@@ -0,0 +1,27 @@
1
+ import * as React from 'react';
2
+ import { TwoStepVideoViewProps, TwoStepVideoViewRef } from './ExpoTwostepVideo.types';
3
+ /**
4
+ * Video player view that can play compositions and assets directly
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * const playerRef = useRef<TwoStepVideoViewRef>(null);
9
+ *
10
+ * // Play a composition (from mirrorVideo, trimVideo, etc.)
11
+ * <TwoStepVideoView
12
+ * ref={playerRef}
13
+ * compositionId={composition.id}
14
+ * onPlaybackStatusChange={(e) => console.log(e.nativeEvent.status)}
15
+ * onProgress={(e) => setProgress(e.nativeEvent.progress)}
16
+ * style={{ width: '100%', height: 300 }}
17
+ * />
18
+ *
19
+ * // Control playback
20
+ * playerRef.current?.play();
21
+ * playerRef.current?.pause();
22
+ * playerRef.current?.seek(5.0);
23
+ * ```
24
+ */
25
+ declare const TwoStepVideoView: React.ForwardRefExoticComponent<TwoStepVideoViewProps & React.RefAttributes<TwoStepVideoViewRef>>;
26
+ export default TwoStepVideoView;
27
+ //# sourceMappingURL=ExpoTwostepVideoView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoTwostepVideoView.d.ts","sourceRoot":"","sources":["../src/ExpoTwostepVideoView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAiBtF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,QAAA,MAAM,gBAAgB,mGAqBrB,CAAC;AAIF,eAAe,gBAAgB,CAAC"}