@movementinfra/expo-twostep-video 0.1.15 → 0.1.16

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/README.md CHANGED
@@ -7,6 +7,7 @@ Professional video editing for React Native, powered by native AVFoundation.
7
7
 
8
8
  ## Features
9
9
 
10
+ - **Native video picker** with album support, iCloud downloading, and rich metadata
10
11
  - **Trim videos** with frame-accurate precision
11
12
  - **Mirror videos** horizontally, vertically, or both
12
13
  - **Speed adjustment** - slow motion (0.25x) to fast forward (4x)
@@ -36,10 +37,15 @@ npx expo install expo-twostep-video
36
37
  ```typescript
37
38
  import * as TwoStepVideo from 'expo-twostep-video';
38
39
 
39
- // Load a video
40
- const asset = await TwoStepVideo.loadAsset({
41
- uri: 'file:///path/to/video.mp4'
42
- });
40
+ // Pick a video from the library
41
+ const videos = await TwoStepVideo.pickVideo();
42
+ if (videos.length === 0) return; // User cancelled
43
+
44
+ const video = videos[0];
45
+ console.log(`Selected: ${video.fileName} (${video.duration}s)`);
46
+
47
+ // Load the video for editing
48
+ const asset = await TwoStepVideo.loadAsset({ uri: video.uri });
43
49
 
44
50
  // Trim to 10 seconds
45
51
  const composition = await TwoStepVideo.trimVideo({
@@ -57,11 +63,66 @@ const result = await TwoStepVideo.exportVideo({
57
63
  console.log('Exported to:', result.uri);
58
64
 
59
65
  // Cleanup
66
+ TwoStepVideo.cleanupPickedVideo(video.path);
60
67
  TwoStepVideo.releaseAll();
61
68
  ```
62
69
 
63
70
  ## API Reference
64
71
 
72
+ ### Picking Videos from Library
73
+
74
+ Native photo library picker with full album support, iCloud downloading, and rich metadata:
75
+
76
+ ```typescript
77
+ // Pick a single video
78
+ const videos = await TwoStepVideo.pickVideo();
79
+
80
+ if (videos.length > 0) {
81
+ const video = videos[0];
82
+ console.log('File:', video.fileName);
83
+ console.log('Size:', video.width, 'x', video.height);
84
+ console.log('Duration:', video.duration, 'seconds');
85
+ console.log('File size:', video.fileSize, 'bytes');
86
+ console.log('Created:', video.creationDate); // ISO 8601
87
+
88
+ // Load for editing
89
+ const asset = await TwoStepVideo.loadAsset({ uri: video.uri });
90
+
91
+ // ... edit video ...
92
+
93
+ // Clean up temp file when done
94
+ TwoStepVideo.cleanupPickedVideo(video.path);
95
+ }
96
+
97
+ // Pick multiple videos
98
+ const videos = await TwoStepVideo.pickVideo({ selectionLimit: 5 });
99
+
100
+ // Clean up all picked videos
101
+ TwoStepVideo.cleanupAllPickedVideos();
102
+ ```
103
+
104
+ **Returned metadata (`PickedVideo`):**
105
+
106
+ | Field | Type | Description |
107
+ |-------|------|-------------|
108
+ | `uri` | `string` | File URI to temp copy |
109
+ | `path` | `string` | Absolute file path (for cleanup) |
110
+ | `fileName` | `string` | Original filename |
111
+ | `width` | `number` | Video width in pixels |
112
+ | `height` | `number` | Video height in pixels |
113
+ | `duration` | `number` | Duration in seconds |
114
+ | `fileSize` | `number` | File size in bytes |
115
+ | `creationDate` | `string?` | Original capture date (ISO 8601) |
116
+ | `modificationDate` | `string?` | Last modified date (ISO 8601) |
117
+ | `assetIdentifier` | `string?` | PHAsset ID for `loadAssetFromPhotos` |
118
+ | `type` | `string` | Always `"video"` |
119
+
120
+ **Features:**
121
+ - Native iOS picker with albums, favorites, and search
122
+ - Automatic iCloud video downloading with native progress UI
123
+ - Natural permission flow (prompts when needed)
124
+ - Comprehensive metadata including original creation date
125
+
65
126
  ### Loading Videos
66
127
 
67
128
  ```typescript
@@ -218,10 +279,15 @@ const subscription = TwoStepVideo.addExportProgressListener((event) => {
218
279
  ### Memory Management
219
280
 
220
281
  ```typescript
282
+ // Release loaded assets and compositions
221
283
  TwoStepVideo.releaseAsset(asset.id);
222
284
  TwoStepVideo.releaseComposition(composition.id);
223
285
  TwoStepVideo.releaseAll(); // Release everything
224
- TwoStepVideo.cleanupFile(result.uri); // Delete temp file
286
+
287
+ // Clean up files
288
+ TwoStepVideo.cleanupFile(result.uri); // Delete exported temp file
289
+ TwoStepVideo.cleanupPickedVideo(video.path); // Delete picked video temp file
290
+ TwoStepVideo.cleanupAllPickedVideos(); // Delete all picked video temp files
225
291
  ```
226
292
 
227
293
  ## Video Scrubber Component
@@ -388,18 +454,25 @@ TwoStepVideo.Mirror.BOTH // Flip both
388
454
 
389
455
  ```typescript
390
456
  import type {
457
+ // Picker types
458
+ PickedVideo,
459
+ PickVideoOptions,
460
+ // Asset types
391
461
  VideoAsset,
392
462
  VideoComposition,
393
463
  ExportResult,
394
464
  LoopResult,
465
+ // View types
395
466
  TwoStepVideoViewRef,
396
467
  TwoStepVideoViewProps,
397
468
  PanZoomState,
398
- MirrorAxis,
399
- VideoQuality,
469
+ // Scrubber types
400
470
  VideoScrubberProps,
401
471
  VideoScrubberRef,
402
472
  VideoScrubberTheme,
473
+ // Constants
474
+ MirrorAxis,
475
+ VideoQuality,
403
476
  } from 'expo-twostep-video';
404
477
  ```
405
478
 
@@ -1,4 +1,38 @@
1
1
  import type { StyleProp, ViewStyle } from 'react-native';
2
+ /**
3
+ * Options for the video picker
4
+ */
5
+ export type PickVideoOptions = {
6
+ /** Maximum number of videos to select (default 1) */
7
+ selectionLimit?: number;
8
+ };
9
+ /**
10
+ * Result from picking a video from the photo library
11
+ */
12
+ export type PickedVideo = {
13
+ /** File URI (file:// path to temp copy) */
14
+ uri: string;
15
+ /** Absolute file path */
16
+ path: string;
17
+ /** Original filename */
18
+ fileName: string;
19
+ /** Video width in pixels */
20
+ width: number;
21
+ /** Video height in pixels */
22
+ height: number;
23
+ /** Duration in seconds */
24
+ duration: number;
25
+ /** File size in bytes */
26
+ fileSize: number;
27
+ /** Date the video was originally created/captured (ISO 8601 string) */
28
+ creationDate?: string;
29
+ /** Date the video was last modified (ISO 8601 string) */
30
+ modificationDate?: string;
31
+ /** PHAsset local identifier (for use with loadAssetFromPhotos) */
32
+ assetIdentifier?: string;
33
+ /** Media type (always "video" for this picker) */
34
+ type: 'video';
35
+ };
2
36
  /**
3
37
  * Playback status event payload
4
38
  */
@@ -35,6 +69,17 @@ export type PanZoomState = {
35
69
  * Pan/Zoom change event payload
36
70
  */
37
71
  export type PanZoomChangeEvent = PanZoomState;
72
+ /**
73
+ * Double-tap skip event payload
74
+ */
75
+ export type DoubleTapSkipEvent = {
76
+ /** Direction of skip: 'forward' or 'backward' */
77
+ direction: 'forward' | 'backward';
78
+ /** Seconds skipped */
79
+ skipInterval: number;
80
+ /** New playback time after skip */
81
+ newTime: number;
82
+ };
38
83
  /**
39
84
  * Options for creating a pan/zoom composition
40
85
  */
@@ -86,6 +131,10 @@ export type TwoStepVideoViewProps = {
86
131
  onPanZoomChange?: (event: {
87
132
  nativeEvent: PanZoomChangeEvent;
88
133
  }) => void;
134
+ /** Called when user double-taps to skip (native gesture) */
135
+ onDoubleTapSkip?: (event: {
136
+ nativeEvent: DoubleTapSkipEvent;
137
+ }) => void;
89
138
  /** Show playhead bar overlay at bottom of video (default: true) */
90
139
  showPlayheadBar?: boolean;
91
140
  /** Enable double-tap to skip forward/backward (default: true) */
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoTwoStepVideo.types.d.ts","sourceRoot":"","sources":["../src/ExpoTwoStepVideo.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ExpoTwoStepVideo.types.d.ts","sourceRoot":"","sources":["../src/ExpoTwoStepVideo.types.ts"],"names":[],"mappings":"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"}
@@ -1 +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 * Pan/Zoom state representing the current view transform\n */\nexport type PanZoomState = {\n /** Horizontal pan position (-1.0 to 1.0, 0 = center) */\n panX: number;\n /** Vertical pan position (-1.0 to 1.0, 0 = center) */\n panY: number;\n /** Zoom level (1.0 = no zoom, 2.0 = 2x zoom, etc.) */\n zoomLevel: number;\n};\n\n/**\n * Pan/Zoom change event payload\n */\nexport type PanZoomChangeEvent = PanZoomState;\n\n/**\n * Options for creating a pan/zoom composition\n */\nexport type PanZoomVideoOptions = {\n /** ID of the loaded asset */\n assetId: string;\n /** Horizontal pan position (-1.0 to 1.0, 0 = center) */\n panX?: number;\n /** Vertical pan position (-1.0 to 1.0, 0 = center) */\n panY?: number;\n /** Zoom level (1.0 = no zoom, 2.0 = 2x zoom, etc.) */\n zoomLevel?: number;\n /** Optional start time in seconds */\n startTime?: number;\n /** Optional end time in seconds */\n endTime?: number;\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 /** Minimum zoom level (default: 1.0) */\n minZoom?: number;\n /** Maximum zoom level (default: 5.0) */\n maxZoom?: number;\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 /** Called when pan/zoom gesture changes */\n onPanZoomChange?: (event: { nativeEvent: PanZoomChangeEvent }) => void;\n /** Show playhead bar overlay at bottom of video (default: true) */\n showPlayheadBar?: boolean;\n /** Enable double-tap to skip forward/backward (default: true) */\n enableDoubleTapSkip?: boolean;\n /** Seconds to skip on double-tap (default: 5) */\n doubleTapSkipInterval?: number;\n /** View style */\n style?: StyleProp<ViewStyle>;\n};\n\n/**\n * Base ref handle for video player views (common playback controls)\n */\nexport type BaseVideoViewRef = {\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\n/**\n * Ref handle for video player view (with pan/zoom support)\n */\nexport type TwoStepVideoViewRef = BaseVideoViewRef & {\n /** Get the current pan/zoom state */\n getPanZoomState: () => Promise<PanZoomState>;\n /** Set the pan/zoom state programmatically */\n setPanZoomState: (state: Partial<PanZoomState>) => Promise<void>;\n /** Reset pan/zoom to default state (zoom=1, pan=0,0) */\n resetPanZoom: () => Promise<void>;\n};\n\n/**\n * Video player controller view props (with native controls)\n */\nexport type TwoStepPlayerControllerViewProps = TwoStepVideoViewProps & {\n /** Show native playback controls (default: true) */\n showsPlaybackControls?: boolean;\n};\n\n/**\n * Ref handle for video player controller view (with fullscreen support)\n * Note: Pan/zoom gestures are not available on this view as it uses AVPlayerViewController\n */\nexport type TwoStepPlayerControllerViewRef = BaseVideoViewRef & {\n /** Enter fullscreen mode */\n enterFullscreen: () => Promise<void>;\n /** Exit fullscreen mode */\n exitFullscreen: () => Promise<void>;\n};\n"]}
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// MARK: - Media Picker Types\n\n/**\n * Options for the video picker\n */\nexport type PickVideoOptions = {\n /** Maximum number of videos to select (default 1) */\n selectionLimit?: number;\n};\n\n/**\n * Result from picking a video from the photo library\n */\nexport type PickedVideo = {\n /** File URI (file:// path to temp copy) */\n uri: string;\n /** Absolute file path */\n path: string;\n /** Original filename */\n fileName: string;\n /** Video width in pixels */\n width: number;\n /** Video height in pixels */\n height: number;\n /** Duration in seconds */\n duration: number;\n /** File size in bytes */\n fileSize: number;\n /** Date the video was originally created/captured (ISO 8601 string) */\n creationDate?: string;\n /** Date the video was last modified (ISO 8601 string) */\n modificationDate?: string;\n /** PHAsset local identifier (for use with loadAssetFromPhotos) */\n assetIdentifier?: string;\n /** Media type (always \"video\" for this picker) */\n type: 'video';\n};\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 * Pan/Zoom state representing the current view transform\n */\nexport type PanZoomState = {\n /** Horizontal pan position (-1.0 to 1.0, 0 = center) */\n panX: number;\n /** Vertical pan position (-1.0 to 1.0, 0 = center) */\n panY: number;\n /** Zoom level (1.0 = no zoom, 2.0 = 2x zoom, etc.) */\n zoomLevel: number;\n};\n\n/**\n * Pan/Zoom change event payload\n */\nexport type PanZoomChangeEvent = PanZoomState;\n\n/**\n * Double-tap skip event payload\n */\nexport type DoubleTapSkipEvent = {\n /** Direction of skip: 'forward' or 'backward' */\n direction: 'forward' | 'backward';\n /** Seconds skipped */\n skipInterval: number;\n /** New playback time after skip */\n newTime: number;\n};\n\n/**\n * Options for creating a pan/zoom composition\n */\nexport type PanZoomVideoOptions = {\n /** ID of the loaded asset */\n assetId: string;\n /** Horizontal pan position (-1.0 to 1.0, 0 = center) */\n panX?: number;\n /** Vertical pan position (-1.0 to 1.0, 0 = center) */\n panY?: number;\n /** Zoom level (1.0 = no zoom, 2.0 = 2x zoom, etc.) */\n zoomLevel?: number;\n /** Optional start time in seconds */\n startTime?: number;\n /** Optional end time in seconds */\n endTime?: number;\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 /** Minimum zoom level (default: 1.0) */\n minZoom?: number;\n /** Maximum zoom level (default: 5.0) */\n maxZoom?: number;\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 /** Called when pan/zoom gesture changes */\n onPanZoomChange?: (event: { nativeEvent: PanZoomChangeEvent }) => void;\n /** Called when user double-taps to skip (native gesture) */\n onDoubleTapSkip?: (event: { nativeEvent: DoubleTapSkipEvent }) => void;\n /** Show playhead bar overlay at bottom of video (default: true) */\n showPlayheadBar?: boolean;\n /** Enable double-tap to skip forward/backward (default: true) */\n enableDoubleTapSkip?: boolean;\n /** Seconds to skip on double-tap (default: 5) */\n doubleTapSkipInterval?: number;\n /** View style */\n style?: StyleProp<ViewStyle>;\n};\n\n/**\n * Base ref handle for video player views (common playback controls)\n */\nexport type BaseVideoViewRef = {\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\n/**\n * Ref handle for video player view (with pan/zoom support)\n */\nexport type TwoStepVideoViewRef = BaseVideoViewRef & {\n /** Get the current pan/zoom state */\n getPanZoomState: () => Promise<PanZoomState>;\n /** Set the pan/zoom state programmatically */\n setPanZoomState: (state: Partial<PanZoomState>) => Promise<void>;\n /** Reset pan/zoom to default state (zoom=1, pan=0,0) */\n resetPanZoom: () => Promise<void>;\n};\n\n/**\n * Video player controller view props (with native controls)\n */\nexport type TwoStepPlayerControllerViewProps = TwoStepVideoViewProps & {\n /** Show native playback controls (default: true) */\n showsPlaybackControls?: boolean;\n};\n\n/**\n * Ref handle for video player controller view (with fullscreen support)\n * Note: Pan/zoom gestures are not available on this view as it uses AVPlayerViewController\n */\nexport type TwoStepPlayerControllerViewRef = BaseVideoViewRef & {\n /** Enter fullscreen mode */\n enterFullscreen: () => Promise<void>;\n /** Exit fullscreen mode */\n exitFullscreen: () => Promise<void>;\n};\n"]}
@@ -34,6 +34,19 @@ interface ExportProgressEvent {
34
34
  assetId?: string;
35
35
  compositionId?: string;
36
36
  }
37
+ interface PickedVideoResult {
38
+ uri: string;
39
+ path: string;
40
+ fileName: string;
41
+ width: number;
42
+ height: number;
43
+ duration: number;
44
+ fileSize: number;
45
+ creationDate?: string;
46
+ modificationDate?: string;
47
+ assetIdentifier?: string;
48
+ type: 'video';
49
+ }
37
50
  declare class ExpoTwoStepVideoModule extends NativeModule<{
38
51
  onExportProgress: (event: ExportProgressEvent) => void;
39
52
  }> {
@@ -61,6 +74,10 @@ declare class ExpoTwoStepVideoModule extends NativeModule<{
61
74
  releaseAsset(assetId: string): void;
62
75
  releaseComposition(compositionId: string): void;
63
76
  releaseAll(): void;
77
+ pickVideo(selectionLimit?: number): Promise<PickedVideoResult[]>;
78
+ cleanupPickedVideo(path: string): void;
79
+ cleanupAllPickedVideos(): void;
80
+ getPickedVideoPaths(): string[];
64
81
  }
65
82
  declare const _default: ExpoTwoStepVideoModule;
66
83
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoTwoStepVideoModule.d.ts","sourceRoot":"","sources":["../src/ExpoTwoStepVideoModule.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ExpoTwoStepVideoModule.d.ts","sourceRoot":"","sources":["../src/ExpoTwoStepVideoModule.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoTwoStepVideoModule.js","sourceRoot":"","sources":["../src/ExpoTwoStepVideoModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAoFtE,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 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<CompositionResult>;\n loopSegment(assetId: string, startTime: number, endTime: number, loopCount: number): Promise<LoopResult>;\n panZoomVideo(assetId: string, panX: number, panY: number, zoomLevel: number, startTime?: number, endTime?: number): Promise<CompositionResult>;\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"]}
1
+ {"version":3,"file":"ExpoTwoStepVideoModule.js","sourceRoot":"","sources":["../src/ExpoTwoStepVideoModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAwGtE,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 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\ninterface PickedVideoResult {\n uri: string;\n path: string;\n fileName: string;\n width: number;\n height: number;\n duration: number;\n fileSize: number;\n creationDate?: string;\n modificationDate?: string;\n assetIdentifier?: string;\n type: 'video';\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<CompositionResult>;\n loopSegment(assetId: string, startTime: number, endTime: number, loopCount: number): Promise<LoopResult>;\n panZoomVideo(assetId: string, panX: number, panY: number, zoomLevel: number, startTime?: number, endTime?: number): Promise<CompositionResult>;\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 // Media picker\n pickVideo(selectionLimit?: number): Promise<PickedVideoResult[]>;\n cleanupPickedVideo(path: string): void;\n cleanupAllPickedVideos(): void;\n getPickedVideoPaths(): string[];\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"]}
@@ -9,6 +9,10 @@ declare const _default: {
9
9
  addListener: () => {
10
10
  remove: () => void;
11
11
  };
12
+ pickVideo: () => Promise<never[]>;
13
+ cleanupPickedVideo: () => void;
14
+ cleanupAllPickedVideos: () => void;
15
+ getPickedVideoPaths: () => never[];
12
16
  };
13
17
  export default _default;
14
18
  //# sourceMappingURL=ExpoTwoStepVideoModule.web.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoTwoStepVideoModule.web.d.ts","sourceRoot":"","sources":["../src/ExpoTwoStepVideoModule.web.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,wBASE"}
1
+ {"version":3,"file":"ExpoTwoStepVideoModule.web.d.ts","sourceRoot":"","sources":["../src/ExpoTwoStepVideoModule.web.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AACA,wBAwBE"}
@@ -8,5 +8,20 @@ export default {
8
8
  MIRROR_VERTICAL: 'vertical',
9
9
  MIRROR_BOTH: 'both',
10
10
  addListener: () => ({ remove: () => { } }),
11
+ // Media picker stubs
12
+ pickVideo: async () => {
13
+ console.warn('TwoStepVideo.pickVideo is not supported on web');
14
+ return [];
15
+ },
16
+ cleanupPickedVideo: () => {
17
+ console.warn('TwoStepVideo.cleanupPickedVideo is not supported on web');
18
+ },
19
+ cleanupAllPickedVideos: () => {
20
+ console.warn('TwoStepVideo.cleanupAllPickedVideos is not supported on web');
21
+ },
22
+ getPickedVideoPaths: () => {
23
+ console.warn('TwoStepVideo.getPickedVideoPaths is not supported on web');
24
+ return [];
25
+ },
11
26
  };
12
27
  //# sourceMappingURL=ExpoTwoStepVideoModule.web.js.map
@@ -1 +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"]}
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;IACzC,qBAAqB;IACrB,SAAS,EAAE,KAAK,IAAI,EAAE;QACpB,OAAO,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;QAC/D,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,kBAAkB,EAAE,GAAG,EAAE;QACvB,OAAO,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;IAC1E,CAAC;IACD,sBAAsB,EAAE,GAAG,EAAE;QAC3B,OAAO,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;IAC9E,CAAC;IACD,mBAAmB,EAAE,GAAG,EAAE;QACxB,OAAO,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;QACzE,OAAO,EAAE,CAAC;IACZ,CAAC;CACF,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 // Media picker stubs\n pickVideo: async () => {\n console.warn('TwoStepVideo.pickVideo is not supported on web');\n return [];\n },\n cleanupPickedVideo: () => {\n console.warn('TwoStepVideo.cleanupPickedVideo is not supported on web');\n },\n cleanupAllPickedVideos: () => {\n console.warn('TwoStepVideo.cleanupAllPickedVideos is not supported on web');\n },\n getPickedVideoPaths: () => {\n console.warn('TwoStepVideo.getPickedVideoPaths is not supported on web');\n return [];\n },\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoTwoStepVideoView.d.ts","sourceRoot":"","sources":["../src/ExpoTwoStepVideoView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,EAAgB,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAsBpG;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,QAAA,MAAM,gBAAgB,mGA0HrB,CAAC;AAIF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"ExpoTwoStepVideoView.d.ts","sourceRoot":"","sources":["../src/ExpoTwoStepVideoView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,EAAoC,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAqBxH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,QAAA,MAAM,gBAAgB,mGAmKrB,CAAC;AAoCF,eAAe,gBAAgB,CAAC"}
@@ -1,9 +1,8 @@
1
1
  import { requireNativeView } from 'expo';
2
2
  import * as React from 'react';
3
- import { forwardRef, useCallback, useImperativeHandle, useRef } from 'react';
4
- import { View } from 'react-native';
3
+ import { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react';
4
+ import { View, Animated, Text, StyleSheet } from 'react-native';
5
5
  import PlayheadBar from './components/PlayheadBar';
6
- import DoubleTapSkip from './components/DoubleTapSkip';
7
6
  const NativeView = requireNativeView('ExpoTwoStepVideo');
8
7
  /**
9
8
  * Video player view that can play compositions and assets directly
@@ -28,7 +27,7 @@ const NativeView = requireNativeView('ExpoTwoStepVideo');
28
27
  * ```
29
28
  */
30
29
  const TwoStepVideoView = forwardRef((props, ref) => {
31
- const { showPlayheadBar = true, enableDoubleTapSkip = true, doubleTapSkipInterval = 5, onProgress, onPlaybackStatusChange, style, ...nativeProps } = props;
30
+ const { showPlayheadBar = true, enableDoubleTapSkip = true, doubleTapSkipInterval = 5, onProgress, onPlaybackStatusChange, onDoubleTapSkip, style, ...nativeProps } = props;
32
31
  const nativeRef = useRef(null);
33
32
  const currentTimeRef = useRef(0);
34
33
  const durationRef = useRef(0);
@@ -37,6 +36,9 @@ const TwoStepVideoView = forwardRef((props, ref) => {
37
36
  // Force re-render for overlay state
38
37
  const [currentTime, setCurrentTime] = React.useState(0);
39
38
  const [duration, setDuration] = React.useState(0);
39
+ // Double-tap feedback animation state
40
+ const [skipFeedback, setSkipFeedback] = useState(null);
41
+ const feedbackOpacity = useRef(new Animated.Value(0)).current;
40
42
  useImperativeHandle(ref, () => ({
41
43
  play: async () => {
42
44
  await nativeRef.current?.play();
@@ -72,6 +74,26 @@ const TwoStepVideoView = forwardRef((props, ref) => {
72
74
  isPlayingRef.current = event.nativeEvent.status === 'playing';
73
75
  onPlaybackStatusChange?.(event);
74
76
  }, [onPlaybackStatusChange]);
77
+ // Handle native double-tap skip event - show visual feedback
78
+ const handleDoubleTapSkip = useCallback((event) => {
79
+ const { direction, skipInterval, newTime } = event.nativeEvent;
80
+ // Update local time state
81
+ currentTimeRef.current = newTime;
82
+ setCurrentTime(newTime);
83
+ // Show visual feedback
84
+ setSkipFeedback({ direction, interval: skipInterval });
85
+ feedbackOpacity.setValue(1);
86
+ Animated.timing(feedbackOpacity, {
87
+ toValue: 0,
88
+ duration: 600,
89
+ delay: 200,
90
+ useNativeDriver: true,
91
+ }).start(() => {
92
+ setSkipFeedback(null);
93
+ });
94
+ // Forward the event to parent if provided
95
+ onDoubleTapSkip?.(event);
96
+ }, [feedbackOpacity, onDoubleTapSkip]);
75
97
  // PlayheadBar callbacks
76
98
  const handleSeekStart = useCallback(() => {
77
99
  wasPlayingRef.current = isPlayingRef.current;
@@ -89,18 +111,57 @@ const TwoStepVideoView = forwardRef((props, ref) => {
89
111
  nativeRef.current?.play();
90
112
  }
91
113
  }, []);
92
- // DoubleTapSkip callback
93
- const handleDoubleTapSeek = useCallback((time) => {
94
- nativeRef.current?.seek(time);
95
- currentTimeRef.current = time;
96
- setCurrentTime(time);
97
- }, []);
98
- return (<View style={[style, { overflow: 'hidden' }]}>
99
- <NativeView ref={nativeRef} {...nativeProps} onProgress={handleProgress} onPlaybackStatusChange={handlePlaybackStatusChange} style={{ flex: 1 }}/>
100
- {enableDoubleTapSkip && (<DoubleTapSkip currentTime={currentTime} duration={duration} skipInterval={doubleTapSkipInterval} onSeek={handleDoubleTapSeek}/>)}
114
+ return (<View style={[style, { overflow: 'hidden' }]} pointerEvents="box-none">
115
+ <NativeView ref={nativeRef} {...nativeProps} enableDoubleTapSkip={enableDoubleTapSkip} doubleTapSkipInterval={doubleTapSkipInterval} onProgress={handleProgress} onPlaybackStatusChange={handlePlaybackStatusChange} onDoubleTapSkip={handleDoubleTapSkip} style={{ flex: 1 }}/>
116
+ {/* Double-tap skip visual feedback (shows on native event) */}
117
+ {skipFeedback && (<View style={styles.feedbackContainer} pointerEvents="none">
118
+ <View style={[styles.feedbackHalf, skipFeedback.direction === 'backward' && styles.feedbackActive]}>
119
+ {skipFeedback.direction === 'backward' && (<Animated.View style={[styles.feedbackCircle, { opacity: feedbackOpacity }]}>
120
+ <Text style={styles.arrowText}>{'<<'}</Text>
121
+ <Text style={styles.skipText}>{skipFeedback.interval}s</Text>
122
+ </Animated.View>)}
123
+ </View>
124
+ <View style={[styles.feedbackHalf, skipFeedback.direction === 'forward' && styles.feedbackActive]}>
125
+ {skipFeedback.direction === 'forward' && (<Animated.View style={[styles.feedbackCircle, { opacity: feedbackOpacity }]}>
126
+ <Text style={styles.arrowText}>{'>>'}</Text>
127
+ <Text style={styles.skipText}>{skipFeedback.interval}s</Text>
128
+ </Animated.View>)}
129
+ </View>
130
+ </View>)}
101
131
  {showPlayheadBar && (<PlayheadBar currentTime={currentTime} duration={duration} onSeekStart={handleSeekStart} onSeek={handlePlayheadSeek} onSeekEnd={handleSeekEnd}/>)}
102
132
  </View>);
103
133
  });
134
+ const styles = StyleSheet.create({
135
+ feedbackContainer: {
136
+ ...StyleSheet.absoluteFillObject,
137
+ flexDirection: 'row',
138
+ },
139
+ feedbackHalf: {
140
+ flex: 1,
141
+ justifyContent: 'center',
142
+ alignItems: 'center',
143
+ },
144
+ feedbackActive: {},
145
+ feedbackCircle: {
146
+ width: 64,
147
+ height: 64,
148
+ borderRadius: 32,
149
+ backgroundColor: 'rgba(0,0,0,0.4)',
150
+ justifyContent: 'center',
151
+ alignItems: 'center',
152
+ },
153
+ arrowText: {
154
+ color: '#FFFFFF',
155
+ fontSize: 20,
156
+ fontWeight: '700',
157
+ },
158
+ skipText: {
159
+ color: '#FFFFFF',
160
+ fontSize: 12,
161
+ fontWeight: '600',
162
+ marginTop: 2,
163
+ },
164
+ });
104
165
  TwoStepVideoView.displayName = 'TwoStepVideoView';
105
166
  export default TwoStepVideoView;
106
167
  //# sourceMappingURL=ExpoTwoStepVideoView.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoTwoStepVideoView.js","sourceRoot":"","sources":["../src/ExpoTwoStepVideoView.tsx"],"names":[],"mappings":"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","sourcesContent":["import { requireNativeView } from 'expo';\nimport * as React from 'react';\nimport { forwardRef, useCallback, useImperativeHandle, useRef } from 'react';\nimport { View } from 'react-native';\n\nimport { PanZoomState, TwoStepVideoViewProps, TwoStepVideoViewRef } from './ExpoTwoStepVideo.types';\nimport PlayheadBar from './components/PlayheadBar';\nimport DoubleTapSkip from './components/DoubleTapSkip';\n\n// Internal props type that includes the native ref\ntype NativeViewProps = TwoStepVideoViewProps & {\n ref?: React.Ref<NativeViewRef>;\n};\n\n// Native view ref with native methods\ntype NativeViewRef = {\n play: () => Promise<void>;\n pause: () => Promise<void>;\n seek: (time: number) => Promise<void>;\n replay: () => Promise<void>;\n getPanZoomState: () => Promise<PanZoomState>;\n setPanZoomState: (panX?: number, panY?: number, zoomLevel?: number) => Promise<void>;\n resetPanZoom: () => Promise<void>;\n};\n\nconst NativeView: React.ComponentType<NativeViewProps> = requireNativeView('ExpoTwoStepVideo');\n\n/**\n * Video player view that can play compositions and assets directly\n *\n * @example\n * ```tsx\n * const playerRef = useRef<TwoStepVideoViewRef>(null);\n *\n * // Play a composition (from mirrorVideo, trimVideo, etc.)\n * <TwoStepVideoView\n * ref={playerRef}\n * compositionId={composition.id}\n * onPlaybackStatusChange={(e) => console.log(e.nativeEvent.status)}\n * onProgress={(e) => setProgress(e.nativeEvent.progress)}\n * style={{ width: '100%', height: 300 }}\n * />\n *\n * // Control playback\n * playerRef.current?.play();\n * playerRef.current?.pause();\n * playerRef.current?.seek(5.0);\n * ```\n */\nconst TwoStepVideoView = forwardRef<TwoStepVideoViewRef, TwoStepVideoViewProps>(\n (props, ref) => {\n const {\n showPlayheadBar = true,\n enableDoubleTapSkip = true,\n doubleTapSkipInterval = 5,\n onProgress,\n onPlaybackStatusChange,\n style,\n ...nativeProps\n } = props;\n\n const nativeRef = useRef<NativeViewRef>(null);\n const currentTimeRef = useRef(0);\n const durationRef = useRef(0);\n const isPlayingRef = useRef(false);\n const wasPlayingRef = useRef(false);\n\n // Force re-render for overlay state\n const [currentTime, setCurrentTime] = React.useState(0);\n const [duration, setDuration] = React.useState(0);\n\n useImperativeHandle(ref, () => ({\n play: async () => {\n await nativeRef.current?.play();\n },\n pause: async () => {\n await nativeRef.current?.pause();\n },\n seek: async (time: number) => {\n await nativeRef.current?.seek(time);\n },\n replay: async () => {\n await nativeRef.current?.replay();\n },\n getPanZoomState: async () => {\n const state = await nativeRef.current?.getPanZoomState();\n return state ?? { panX: 0, panY: 0, zoomLevel: 1 };\n },\n setPanZoomState: async (state: Partial<PanZoomState>) => {\n await nativeRef.current?.setPanZoomState(state.panX, state.panY, state.zoomLevel);\n },\n resetPanZoom: async () => {\n await nativeRef.current?.resetPanZoom();\n },\n }));\n\n const handleProgress = useCallback(\n (event: { nativeEvent: { currentTime: number; duration: number; progress: number } }) => {\n currentTimeRef.current = event.nativeEvent.currentTime;\n durationRef.current = event.nativeEvent.duration;\n setCurrentTime(event.nativeEvent.currentTime);\n setDuration(event.nativeEvent.duration);\n onProgress?.(event);\n },\n [onProgress]\n );\n\n const handlePlaybackStatusChange = useCallback(\n (event: { nativeEvent: { status: string; time?: number } }) => {\n isPlayingRef.current = event.nativeEvent.status === 'playing';\n onPlaybackStatusChange?.(event as any);\n },\n [onPlaybackStatusChange]\n );\n\n // PlayheadBar callbacks\n const handleSeekStart = useCallback(() => {\n wasPlayingRef.current = isPlayingRef.current;\n if (isPlayingRef.current) {\n nativeRef.current?.pause();\n }\n }, []);\n\n const handlePlayheadSeek = useCallback((time: number) => {\n nativeRef.current?.seek(time);\n currentTimeRef.current = time;\n setCurrentTime(time);\n }, []);\n\n const handleSeekEnd = useCallback(() => {\n if (wasPlayingRef.current) {\n nativeRef.current?.play();\n }\n }, []);\n\n // DoubleTapSkip callback\n const handleDoubleTapSeek = useCallback((time: number) => {\n nativeRef.current?.seek(time);\n currentTimeRef.current = time;\n setCurrentTime(time);\n }, []);\n\n return (\n <View style={[style, { overflow: 'hidden' }]}>\n <NativeView\n ref={nativeRef}\n {...nativeProps}\n onProgress={handleProgress}\n onPlaybackStatusChange={handlePlaybackStatusChange}\n style={{ flex: 1 }}\n />\n {enableDoubleTapSkip && (\n <DoubleTapSkip\n currentTime={currentTime}\n duration={duration}\n skipInterval={doubleTapSkipInterval}\n onSeek={handleDoubleTapSeek}\n />\n )}\n {showPlayheadBar && (\n <PlayheadBar\n currentTime={currentTime}\n duration={duration}\n onSeekStart={handleSeekStart}\n onSeek={handlePlayheadSeek}\n onSeekEnd={handleSeekEnd}\n />\n )}\n </View>\n );\n }\n);\n\nTwoStepVideoView.displayName = 'TwoStepVideoView';\n\nexport default TwoStepVideoView;\n"]}
1
+ {"version":3,"file":"ExpoTwoStepVideoView.js","sourceRoot":"","sources":["../src/ExpoTwoStepVideoView.tsx"],"names":[],"mappings":"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","sourcesContent":["import { requireNativeView } from 'expo';\nimport * as React from 'react';\nimport { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react';\nimport { View, Animated, Text, StyleSheet } from 'react-native';\n\nimport { DoubleTapSkipEvent, PanZoomState, TwoStepVideoViewProps, TwoStepVideoViewRef } from './ExpoTwoStepVideo.types';\nimport PlayheadBar from './components/PlayheadBar';\n\n// Internal props type that includes the native ref\ntype NativeViewProps = TwoStepVideoViewProps & {\n ref?: React.Ref<NativeViewRef>;\n};\n\n// Native view ref with native methods\ntype NativeViewRef = {\n play: () => Promise<void>;\n pause: () => Promise<void>;\n seek: (time: number) => Promise<void>;\n replay: () => Promise<void>;\n getPanZoomState: () => Promise<PanZoomState>;\n setPanZoomState: (panX?: number, panY?: number, zoomLevel?: number) => Promise<void>;\n resetPanZoom: () => Promise<void>;\n};\n\nconst NativeView: React.ComponentType<NativeViewProps> = requireNativeView('ExpoTwoStepVideo');\n\n/**\n * Video player view that can play compositions and assets directly\n *\n * @example\n * ```tsx\n * const playerRef = useRef<TwoStepVideoViewRef>(null);\n *\n * // Play a composition (from mirrorVideo, trimVideo, etc.)\n * <TwoStepVideoView\n * ref={playerRef}\n * compositionId={composition.id}\n * onPlaybackStatusChange={(e) => console.log(e.nativeEvent.status)}\n * onProgress={(e) => setProgress(e.nativeEvent.progress)}\n * style={{ width: '100%', height: 300 }}\n * />\n *\n * // Control playback\n * playerRef.current?.play();\n * playerRef.current?.pause();\n * playerRef.current?.seek(5.0);\n * ```\n */\nconst TwoStepVideoView = forwardRef<TwoStepVideoViewRef, TwoStepVideoViewProps>(\n (props, ref) => {\n const {\n showPlayheadBar = true,\n enableDoubleTapSkip = true,\n doubleTapSkipInterval = 5,\n onProgress,\n onPlaybackStatusChange,\n onDoubleTapSkip,\n style,\n ...nativeProps\n } = props;\n\n const nativeRef = useRef<NativeViewRef>(null);\n const currentTimeRef = useRef(0);\n const durationRef = useRef(0);\n const isPlayingRef = useRef(false);\n const wasPlayingRef = useRef(false);\n\n // Force re-render for overlay state\n const [currentTime, setCurrentTime] = React.useState(0);\n const [duration, setDuration] = React.useState(0);\n\n // Double-tap feedback animation state\n const [skipFeedback, setSkipFeedback] = useState<{ direction: 'forward' | 'backward'; interval: number } | null>(null);\n const feedbackOpacity = useRef(new Animated.Value(0)).current;\n\n useImperativeHandle(ref, () => ({\n play: async () => {\n await nativeRef.current?.play();\n },\n pause: async () => {\n await nativeRef.current?.pause();\n },\n seek: async (time: number) => {\n await nativeRef.current?.seek(time);\n },\n replay: async () => {\n await nativeRef.current?.replay();\n },\n getPanZoomState: async () => {\n const state = await nativeRef.current?.getPanZoomState();\n return state ?? { panX: 0, panY: 0, zoomLevel: 1 };\n },\n setPanZoomState: async (state: Partial<PanZoomState>) => {\n await nativeRef.current?.setPanZoomState(state.panX, state.panY, state.zoomLevel);\n },\n resetPanZoom: async () => {\n await nativeRef.current?.resetPanZoom();\n },\n }));\n\n const handleProgress = useCallback(\n (event: { nativeEvent: { currentTime: number; duration: number; progress: number } }) => {\n currentTimeRef.current = event.nativeEvent.currentTime;\n durationRef.current = event.nativeEvent.duration;\n setCurrentTime(event.nativeEvent.currentTime);\n setDuration(event.nativeEvent.duration);\n onProgress?.(event);\n },\n [onProgress]\n );\n\n const handlePlaybackStatusChange = useCallback(\n (event: { nativeEvent: { status: string; time?: number } }) => {\n isPlayingRef.current = event.nativeEvent.status === 'playing';\n onPlaybackStatusChange?.(event as any);\n },\n [onPlaybackStatusChange]\n );\n\n // Handle native double-tap skip event - show visual feedback\n const handleDoubleTapSkip = useCallback(\n (event: { nativeEvent: DoubleTapSkipEvent }) => {\n const { direction, skipInterval, newTime } = event.nativeEvent;\n\n // Update local time state\n currentTimeRef.current = newTime;\n setCurrentTime(newTime);\n\n // Show visual feedback\n setSkipFeedback({ direction, interval: skipInterval });\n feedbackOpacity.setValue(1);\n Animated.timing(feedbackOpacity, {\n toValue: 0,\n duration: 600,\n delay: 200,\n useNativeDriver: true,\n }).start(() => {\n setSkipFeedback(null);\n });\n\n // Forward the event to parent if provided\n onDoubleTapSkip?.(event);\n },\n [feedbackOpacity, onDoubleTapSkip]\n );\n\n // PlayheadBar callbacks\n const handleSeekStart = useCallback(() => {\n wasPlayingRef.current = isPlayingRef.current;\n if (isPlayingRef.current) {\n nativeRef.current?.pause();\n }\n }, []);\n\n const handlePlayheadSeek = useCallback((time: number) => {\n nativeRef.current?.seek(time);\n currentTimeRef.current = time;\n setCurrentTime(time);\n }, []);\n\n const handleSeekEnd = useCallback(() => {\n if (wasPlayingRef.current) {\n nativeRef.current?.play();\n }\n }, []);\n\n return (\n <View style={[style, { overflow: 'hidden' }]} pointerEvents=\"box-none\">\n <NativeView\n ref={nativeRef}\n {...nativeProps}\n enableDoubleTapSkip={enableDoubleTapSkip}\n doubleTapSkipInterval={doubleTapSkipInterval}\n onProgress={handleProgress}\n onPlaybackStatusChange={handlePlaybackStatusChange}\n onDoubleTapSkip={handleDoubleTapSkip}\n style={{ flex: 1 }}\n />\n {/* Double-tap skip visual feedback (shows on native event) */}\n {skipFeedback && (\n <View style={styles.feedbackContainer} pointerEvents=\"none\">\n <View style={[styles.feedbackHalf, skipFeedback.direction === 'backward' && styles.feedbackActive]}>\n {skipFeedback.direction === 'backward' && (\n <Animated.View style={[styles.feedbackCircle, { opacity: feedbackOpacity }]}>\n <Text style={styles.arrowText}>{'<<'}</Text>\n <Text style={styles.skipText}>{skipFeedback.interval}s</Text>\n </Animated.View>\n )}\n </View>\n <View style={[styles.feedbackHalf, skipFeedback.direction === 'forward' && styles.feedbackActive]}>\n {skipFeedback.direction === 'forward' && (\n <Animated.View style={[styles.feedbackCircle, { opacity: feedbackOpacity }]}>\n <Text style={styles.arrowText}>{'>>'}</Text>\n <Text style={styles.skipText}>{skipFeedback.interval}s</Text>\n </Animated.View>\n )}\n </View>\n </View>\n )}\n {showPlayheadBar && (\n <PlayheadBar\n currentTime={currentTime}\n duration={duration}\n onSeekStart={handleSeekStart}\n onSeek={handlePlayheadSeek}\n onSeekEnd={handleSeekEnd}\n />\n )}\n </View>\n );\n }\n);\n\nconst styles = StyleSheet.create({\n feedbackContainer: {\n ...StyleSheet.absoluteFillObject,\n flexDirection: 'row',\n },\n feedbackHalf: {\n flex: 1,\n justifyContent: 'center',\n alignItems: 'center',\n },\n feedbackActive: {},\n feedbackCircle: {\n width: 64,\n height: 64,\n borderRadius: 32,\n backgroundColor: 'rgba(0,0,0,0.4)',\n justifyContent: 'center',\n alignItems: 'center',\n },\n arrowText: {\n color: '#FFFFFF',\n fontSize: 20,\n fontWeight: '700',\n },\n skipText: {\n color: '#FFFFFF',\n fontSize: 12,\n fontWeight: '600',\n marginTop: 2,\n },\n});\n\nTwoStepVideoView.displayName = 'TwoStepVideoView';\n\nexport default TwoStepVideoView;\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"DoubleTapSkip.d.ts","sourceRoot":"","sources":["../../src/components/DoubleTapSkip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAU/B,MAAM,MAAM,kBAAkB,GAAG;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC,CAAC;AAIF,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EACpC,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,MAAM,GACP,EAAE,kBAAkB,qBA2FpB"}
1
+ {"version":3,"file":"DoubleTapSkip.d.ts","sourceRoot":"","sources":["../../src/components/DoubleTapSkip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAU/B,MAAM,MAAM,kBAAkB,GAAG;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC,CAAC;AAIF,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EACpC,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,MAAM,GACP,EAAE,kBAAkB,qBAiGpB"}
@@ -65,28 +65,35 @@ export default function DoubleTapSkip({ currentTime, duration, skipInterval, onS
65
65
  }
66
66
  }, [skipInterval, onSeek, showFeedback, rightOpacity]);
67
67
  return (<View style={styles.container} pointerEvents="box-none">
68
- <TouchableWithoutFeedback onPress={handleLeftTap}>
69
- <View style={styles.half}>
70
- <Animated.View style={[styles.feedback, { opacity: leftOpacity }]}>
71
- <View style={styles.feedbackCircle}>
72
- <Text style={styles.arrowText}>{'<<'}</Text>
73
- <Text style={styles.skipText}>{skipInterval}s</Text>
74
- </View>
75
- </Animated.View>
76
- </View>
77
- </TouchableWithoutFeedback>
78
- <TouchableWithoutFeedback onPress={handleRightTap}>
79
- <View style={styles.half}>
80
- <Animated.View style={[styles.feedback, { opacity: rightOpacity }]}>
81
- <View style={styles.feedbackCircle}>
82
- <Text style={styles.arrowText}>{'>>'}</Text>
83
- <Text style={styles.skipText}>{skipInterval}s</Text>
84
- </View>
85
- </Animated.View>
86
- </View>
87
- </TouchableWithoutFeedback>
68
+ {/* Left side - positioned as a smaller hit zone to allow pinch gestures elsewhere */}
69
+ <View style={styles.half} pointerEvents="box-none">
70
+ <TouchableWithoutFeedback onPress={handleLeftTap}>
71
+ <View style={styles.hitZone}>
72
+ <Animated.View style={[styles.feedback, { opacity: leftOpacity }]}>
73
+ <View style={styles.feedbackCircle}>
74
+ <Text style={styles.arrowText}>{'<<'}</Text>
75
+ <Text style={styles.skipText}>{skipInterval}s</Text>
76
+ </View>
77
+ </Animated.View>
78
+ </View>
79
+ </TouchableWithoutFeedback>
80
+ </View>
81
+ {/* Right side - positioned as a smaller hit zone to allow pinch gestures elsewhere */}
82
+ <View style={styles.half} pointerEvents="box-none">
83
+ <TouchableWithoutFeedback onPress={handleRightTap}>
84
+ <View style={styles.hitZone}>
85
+ <Animated.View style={[styles.feedback, { opacity: rightOpacity }]}>
86
+ <View style={styles.feedbackCircle}>
87
+ <Text style={styles.arrowText}>{'>>'}</Text>
88
+ <Text style={styles.skipText}>{skipInterval}s</Text>
89
+ </View>
90
+ </Animated.View>
91
+ </View>
92
+ </TouchableWithoutFeedback>
93
+ </View>
88
94
  </View>);
89
95
  }
96
+ const HIT_ZONE_SIZE = 120;
90
97
  const styles = StyleSheet.create({
91
98
  container: {
92
99
  ...StyleSheet.absoluteFillObject,
@@ -97,6 +104,14 @@ const styles = StyleSheet.create({
97
104
  justifyContent: 'center',
98
105
  alignItems: 'center',
99
106
  },
107
+ hitZone: {
108
+ // Smaller hit zone allows pinch gestures in other areas
109
+ width: HIT_ZONE_SIZE,
110
+ height: HIT_ZONE_SIZE,
111
+ borderRadius: HIT_ZONE_SIZE / 2,
112
+ justifyContent: 'center',
113
+ alignItems: 'center',
114
+ },
100
115
  feedback: {
101
116
  justifyContent: 'center',
102
117
  alignItems: 'center',