@umituz/react-native-video-editor 1.0.2 → 1.0.3
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/package.json +6 -1
- package/src/index.ts +25 -0
- package/src/player/index.ts +31 -0
- package/src/player/infrastructure/services/player-control.service.ts +95 -0
- package/src/player/presentation/components/VideoPlayer.tsx +135 -0
- package/src/player/presentation/hooks/useVideoPlayerControl.ts +82 -0
- package/src/player/presentation/hooks/useVideoVisibility.ts +62 -0
- package/src/player/types/index.ts +74 -0
- package/src/presentation/components/export/ExportActions.tsx +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-video-editor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Professional video editor with layer-based timeline, text/image/shape/audio/animation layers, and export functionality",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -29,10 +29,15 @@
|
|
|
29
29
|
"react": ">=18.2.0",
|
|
30
30
|
"react-native": ">=0.74.0",
|
|
31
31
|
"@umituz/react-native-design-system": ">=1.0.0",
|
|
32
|
+
"expo-image": ">=1.0.0",
|
|
33
|
+
"expo-video": ">=3.0.0",
|
|
32
34
|
"zustand": ">=4.0.0"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@types/react": "~19.1.10",
|
|
38
|
+
"@umituz/react-native-design-system": "latest",
|
|
39
|
+
"expo-image": "^3.0.11",
|
|
40
|
+
"expo-video": "^3.0.15",
|
|
36
41
|
"react": "19.1.0",
|
|
37
42
|
"react-native": "0.81.5",
|
|
38
43
|
"typescript": "~5.9.2"
|
package/src/index.ts
CHANGED
|
@@ -97,3 +97,28 @@ export { useLayerActions } from "./presentation/hooks/useLayerActions";
|
|
|
97
97
|
export { useSceneActions } from "./presentation/hooks/useSceneActions";
|
|
98
98
|
export { useMenuActions } from "./presentation/hooks/useMenuActions";
|
|
99
99
|
export { useExportActions } from "./presentation/hooks/useExportActions";
|
|
100
|
+
|
|
101
|
+
// =============================================================================
|
|
102
|
+
// VIDEO PLAYER MODULE
|
|
103
|
+
// =============================================================================
|
|
104
|
+
|
|
105
|
+
export type {
|
|
106
|
+
VideoPlayerConfig,
|
|
107
|
+
VideoPlayerState,
|
|
108
|
+
VideoPlayerControls,
|
|
109
|
+
UseVideoPlayerControlResult,
|
|
110
|
+
VideoVisibilityConfig,
|
|
111
|
+
VideoPlayerProps,
|
|
112
|
+
VideoPlayerType,
|
|
113
|
+
} from "./player";
|
|
114
|
+
|
|
115
|
+
export {
|
|
116
|
+
safePlay,
|
|
117
|
+
safePause,
|
|
118
|
+
safeToggle,
|
|
119
|
+
isPlayerReady,
|
|
120
|
+
configurePlayer,
|
|
121
|
+
useVideoPlayerControl,
|
|
122
|
+
useVideoVisibility,
|
|
123
|
+
VideoPlayer,
|
|
124
|
+
} from "./player";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Video Player Module
|
|
3
|
+
* Exports for video playback functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Types
|
|
7
|
+
export type {
|
|
8
|
+
VideoPlayerConfig,
|
|
9
|
+
VideoPlayerState,
|
|
10
|
+
VideoPlayerControls,
|
|
11
|
+
UseVideoPlayerControlResult,
|
|
12
|
+
VideoVisibilityConfig,
|
|
13
|
+
VideoPlayerProps,
|
|
14
|
+
VideoPlayer as VideoPlayerType,
|
|
15
|
+
} from "./types";
|
|
16
|
+
|
|
17
|
+
// Services
|
|
18
|
+
export {
|
|
19
|
+
safePlay,
|
|
20
|
+
safePause,
|
|
21
|
+
safeToggle,
|
|
22
|
+
isPlayerReady,
|
|
23
|
+
configurePlayer,
|
|
24
|
+
} from "./infrastructure/services/player-control.service";
|
|
25
|
+
|
|
26
|
+
// Hooks
|
|
27
|
+
export { useVideoPlayerControl } from "./presentation/hooks/useVideoPlayerControl";
|
|
28
|
+
export { useVideoVisibility } from "./presentation/hooks/useVideoVisibility";
|
|
29
|
+
|
|
30
|
+
// Components
|
|
31
|
+
export { VideoPlayer } from "./presentation/components/VideoPlayer";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Player Control Service
|
|
3
|
+
* Safe operations for video player control
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { VideoPlayer } from "expo-video";
|
|
7
|
+
|
|
8
|
+
declare const __DEV__: boolean;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Safely play video with error handling
|
|
12
|
+
*/
|
|
13
|
+
export const safePlay = (player: VideoPlayer | null): boolean => {
|
|
14
|
+
if (!player) return false;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
player.play();
|
|
18
|
+
return true;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
21
|
+
// eslint-disable-next-line no-console
|
|
22
|
+
console.log("[VideoPlayer] Play error ignored:", error);
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Safely pause video with error handling
|
|
30
|
+
*/
|
|
31
|
+
export const safePause = (player: VideoPlayer | null): boolean => {
|
|
32
|
+
if (!player) return false;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
player.pause();
|
|
36
|
+
return true;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
39
|
+
// eslint-disable-next-line no-console
|
|
40
|
+
console.log("[VideoPlayer] Pause error ignored:", error);
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Safely toggle play/pause state
|
|
48
|
+
*/
|
|
49
|
+
export const safeToggle = (
|
|
50
|
+
player: VideoPlayer | null,
|
|
51
|
+
isPlaying: boolean,
|
|
52
|
+
): boolean => {
|
|
53
|
+
if (!player) return false;
|
|
54
|
+
|
|
55
|
+
return isPlaying ? safePause(player) : safePlay(player);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if player has valid native object
|
|
60
|
+
*/
|
|
61
|
+
export const isPlayerReady = (
|
|
62
|
+
player: VideoPlayer | null,
|
|
63
|
+
source: string | null,
|
|
64
|
+
): boolean => {
|
|
65
|
+
return Boolean(player && source && source.length > 0);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Configure player with initial settings
|
|
70
|
+
*/
|
|
71
|
+
export const configurePlayer = (
|
|
72
|
+
player: VideoPlayer,
|
|
73
|
+
options: {
|
|
74
|
+
loop?: boolean;
|
|
75
|
+
muted?: boolean;
|
|
76
|
+
autoPlay?: boolean;
|
|
77
|
+
},
|
|
78
|
+
): void => {
|
|
79
|
+
try {
|
|
80
|
+
if (options.loop !== undefined) {
|
|
81
|
+
player.loop = options.loop;
|
|
82
|
+
}
|
|
83
|
+
if (options.muted !== undefined) {
|
|
84
|
+
player.muted = options.muted;
|
|
85
|
+
}
|
|
86
|
+
if (options.autoPlay) {
|
|
87
|
+
player.play();
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
91
|
+
// eslint-disable-next-line no-console
|
|
92
|
+
console.log("[VideoPlayer] Configure error ignored:", error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VideoPlayer Component
|
|
3
|
+
* Reusable video player with thumbnail and controls
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useState, useCallback, useMemo } from "react";
|
|
7
|
+
import {
|
|
8
|
+
View,
|
|
9
|
+
TouchableOpacity,
|
|
10
|
+
StyleSheet,
|
|
11
|
+
useWindowDimensions,
|
|
12
|
+
} from "react-native";
|
|
13
|
+
import { Image } from "expo-image";
|
|
14
|
+
import { VideoView } from "expo-video";
|
|
15
|
+
import { useAppDesignTokens, AtomicIcon } from "@umituz/react-native-design-system";
|
|
16
|
+
|
|
17
|
+
import type { VideoPlayerProps } from "../../types";
|
|
18
|
+
import { useVideoPlayerControl } from "../hooks/useVideoPlayerControl";
|
|
19
|
+
|
|
20
|
+
const ASPECT_RATIO = 16 / 9;
|
|
21
|
+
|
|
22
|
+
export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|
23
|
+
source,
|
|
24
|
+
thumbnailUrl,
|
|
25
|
+
loop = true,
|
|
26
|
+
muted = false,
|
|
27
|
+
autoPlay = false,
|
|
28
|
+
nativeControls = true,
|
|
29
|
+
contentFit = "cover",
|
|
30
|
+
style,
|
|
31
|
+
}) => {
|
|
32
|
+
const tokens = useAppDesignTokens();
|
|
33
|
+
const { width } = useWindowDimensions();
|
|
34
|
+
const [showVideo, setShowVideo] = useState(autoPlay);
|
|
35
|
+
|
|
36
|
+
const { player, state } = useVideoPlayerControl({
|
|
37
|
+
source: showVideo ? source : null,
|
|
38
|
+
loop,
|
|
39
|
+
muted,
|
|
40
|
+
autoPlay,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const handlePlay = useCallback(() => {
|
|
44
|
+
setShowVideo(true);
|
|
45
|
+
// Player will auto-configure with autoPlay from hook
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
const containerStyle = useMemo(() => ({
|
|
49
|
+
width: "100%" as const,
|
|
50
|
+
aspectRatio: ASPECT_RATIO,
|
|
51
|
+
backgroundColor: tokens.colors.surface,
|
|
52
|
+
borderRadius: 16,
|
|
53
|
+
overflow: "hidden" as const,
|
|
54
|
+
}), [tokens.colors.surface]);
|
|
55
|
+
|
|
56
|
+
const styles = useMemo(
|
|
57
|
+
() =>
|
|
58
|
+
StyleSheet.create({
|
|
59
|
+
video: {
|
|
60
|
+
width: "100%",
|
|
61
|
+
height: "100%",
|
|
62
|
+
},
|
|
63
|
+
thumbnailContainer: {
|
|
64
|
+
width: "100%",
|
|
65
|
+
height: "100%",
|
|
66
|
+
justifyContent: "center",
|
|
67
|
+
alignItems: "center",
|
|
68
|
+
},
|
|
69
|
+
thumbnail: {
|
|
70
|
+
width: "100%",
|
|
71
|
+
height: "100%",
|
|
72
|
+
},
|
|
73
|
+
placeholder: {
|
|
74
|
+
width: "100%",
|
|
75
|
+
height: "100%",
|
|
76
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
77
|
+
},
|
|
78
|
+
playButtonContainer: {
|
|
79
|
+
...StyleSheet.absoluteFillObject,
|
|
80
|
+
justifyContent: "center",
|
|
81
|
+
alignItems: "center",
|
|
82
|
+
},
|
|
83
|
+
playButton: {
|
|
84
|
+
width: 64,
|
|
85
|
+
height: 64,
|
|
86
|
+
borderRadius: 32,
|
|
87
|
+
backgroundColor: tokens.colors.primary,
|
|
88
|
+
justifyContent: "center",
|
|
89
|
+
alignItems: "center",
|
|
90
|
+
paddingLeft: 4,
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
[tokens]
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Show video player when playing
|
|
97
|
+
if (showVideo && state.isPlayerValid && player) {
|
|
98
|
+
return (
|
|
99
|
+
<View style={[containerStyle, style]}>
|
|
100
|
+
<VideoView
|
|
101
|
+
player={player}
|
|
102
|
+
style={styles.video}
|
|
103
|
+
contentFit={contentFit}
|
|
104
|
+
nativeControls={nativeControls}
|
|
105
|
+
/>
|
|
106
|
+
</View>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Show thumbnail with play button
|
|
111
|
+
return (
|
|
112
|
+
<TouchableOpacity
|
|
113
|
+
style={[containerStyle, style]}
|
|
114
|
+
onPress={handlePlay}
|
|
115
|
+
activeOpacity={0.8}
|
|
116
|
+
>
|
|
117
|
+
<View style={styles.thumbnailContainer}>
|
|
118
|
+
{thumbnailUrl ? (
|
|
119
|
+
<Image
|
|
120
|
+
source={{ uri: thumbnailUrl }}
|
|
121
|
+
style={styles.thumbnail}
|
|
122
|
+
contentFit="cover"
|
|
123
|
+
/>
|
|
124
|
+
) : (
|
|
125
|
+
<View style={styles.placeholder} />
|
|
126
|
+
)}
|
|
127
|
+
<View style={styles.playButtonContainer}>
|
|
128
|
+
<View style={styles.playButton}>
|
|
129
|
+
<AtomicIcon name="play" customSize={32} color="onPrimary" />
|
|
130
|
+
</View>
|
|
131
|
+
</View>
|
|
132
|
+
</View>
|
|
133
|
+
</TouchableOpacity>
|
|
134
|
+
);
|
|
135
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useVideoPlayerControl Hook
|
|
3
|
+
* Main hook for video player control with safe operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback, useMemo } from "react";
|
|
7
|
+
import { useVideoPlayer as useExpoVideoPlayer } from "expo-video";
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
VideoPlayerConfig,
|
|
11
|
+
VideoPlayerState,
|
|
12
|
+
VideoPlayerControls,
|
|
13
|
+
UseVideoPlayerControlResult,
|
|
14
|
+
} from "../../types";
|
|
15
|
+
import {
|
|
16
|
+
safePlay,
|
|
17
|
+
safePause,
|
|
18
|
+
safeToggle,
|
|
19
|
+
isPlayerReady,
|
|
20
|
+
configurePlayer,
|
|
21
|
+
} from "../../infrastructure/services/player-control.service";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Hook for managing video player with safe operations
|
|
25
|
+
*/
|
|
26
|
+
export const useVideoPlayerControl = (
|
|
27
|
+
config: VideoPlayerConfig,
|
|
28
|
+
): UseVideoPlayerControlResult => {
|
|
29
|
+
const { source, loop = true, muted = false, autoPlay = false } = config;
|
|
30
|
+
|
|
31
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
32
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
33
|
+
|
|
34
|
+
const player = useExpoVideoPlayer(source ?? "", (p) => {
|
|
35
|
+
if (source && p) {
|
|
36
|
+
configurePlayer(p, { loop, muted, autoPlay });
|
|
37
|
+
setIsLoading(false);
|
|
38
|
+
if (autoPlay) {
|
|
39
|
+
setIsPlaying(true);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const isPlayerValid = useMemo(
|
|
45
|
+
() => isPlayerReady(player, source),
|
|
46
|
+
[player, source],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const play = useCallback(() => {
|
|
50
|
+
if (!isPlayerValid) return;
|
|
51
|
+
const success = safePlay(player);
|
|
52
|
+
if (success) setIsPlaying(true);
|
|
53
|
+
}, [player, isPlayerValid]);
|
|
54
|
+
|
|
55
|
+
const pause = useCallback(() => {
|
|
56
|
+
if (!isPlayerValid) return;
|
|
57
|
+
const success = safePause(player);
|
|
58
|
+
if (success) setIsPlaying(false);
|
|
59
|
+
}, [player, isPlayerValid]);
|
|
60
|
+
|
|
61
|
+
const toggle = useCallback(() => {
|
|
62
|
+
if (!isPlayerValid) return;
|
|
63
|
+
const success = safeToggle(player, isPlaying);
|
|
64
|
+
if (success) setIsPlaying(!isPlaying);
|
|
65
|
+
}, [player, isPlayerValid, isPlaying]);
|
|
66
|
+
|
|
67
|
+
const state: VideoPlayerState = useMemo(
|
|
68
|
+
() => ({
|
|
69
|
+
isPlaying,
|
|
70
|
+
isPlayerValid,
|
|
71
|
+
isLoading: isLoading && Boolean(source),
|
|
72
|
+
}),
|
|
73
|
+
[isPlaying, isPlayerValid, isLoading, source],
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const controls: VideoPlayerControls = useMemo(
|
|
77
|
+
() => ({ play, pause, toggle }),
|
|
78
|
+
[play, pause, toggle],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return { player, state, controls };
|
|
82
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useVideoVisibility Hook
|
|
3
|
+
* Handles auto play/pause based on visibility
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback, useEffect } from "react";
|
|
7
|
+
|
|
8
|
+
import type { VideoVisibilityConfig } from "../../types";
|
|
9
|
+
import {
|
|
10
|
+
safePlay,
|
|
11
|
+
safePause,
|
|
12
|
+
} from "../../infrastructure/services/player-control.service";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Optional navigation focus hook type
|
|
16
|
+
*/
|
|
17
|
+
type UseFocusEffectType = (callback: () => (() => void) | undefined) => void;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Hook for managing video visibility-based playback
|
|
21
|
+
* @param config - Visibility configuration
|
|
22
|
+
* @param useFocusEffect - Optional navigation focus hook (from @react-navigation/native)
|
|
23
|
+
*/
|
|
24
|
+
export const useVideoVisibility = (
|
|
25
|
+
config: VideoVisibilityConfig,
|
|
26
|
+
useFocusEffect?: UseFocusEffectType,
|
|
27
|
+
): void => {
|
|
28
|
+
const { isVisible, player, isPlayerValid, onPlayingChange } = config;
|
|
29
|
+
const [isScreenFocused, setIsScreenFocused] = useState(true);
|
|
30
|
+
|
|
31
|
+
// Handle screen focus if navigation hook provided
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!useFocusEffect) return;
|
|
34
|
+
|
|
35
|
+
useFocusEffect(
|
|
36
|
+
useCallback(() => {
|
|
37
|
+
setIsScreenFocused(true);
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
setIsScreenFocused(false);
|
|
41
|
+
if (isPlayerValid) {
|
|
42
|
+
safePause(player);
|
|
43
|
+
onPlayingChange?.(false);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}, [isPlayerValid, player, onPlayingChange]),
|
|
47
|
+
);
|
|
48
|
+
}, [useFocusEffect, isPlayerValid, player, onPlayingChange]);
|
|
49
|
+
|
|
50
|
+
// Handle visibility changes
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!isPlayerValid) return;
|
|
53
|
+
|
|
54
|
+
if (isVisible && isScreenFocused) {
|
|
55
|
+
const success = safePlay(player);
|
|
56
|
+
if (success) onPlayingChange?.(true);
|
|
57
|
+
} else {
|
|
58
|
+
const success = safePause(player);
|
|
59
|
+
if (success) onPlayingChange?.(false);
|
|
60
|
+
}
|
|
61
|
+
}, [isVisible, isScreenFocused, isPlayerValid, player, onPlayingChange]);
|
|
62
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Video Player Types
|
|
3
|
+
* Core type definitions for video playback functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { VideoPlayer } from "expo-video";
|
|
7
|
+
import type { ViewStyle } from "react-native";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Configuration for video player initialization
|
|
11
|
+
*/
|
|
12
|
+
export interface VideoPlayerConfig {
|
|
13
|
+
readonly source: string | null;
|
|
14
|
+
readonly loop?: boolean;
|
|
15
|
+
readonly muted?: boolean;
|
|
16
|
+
readonly autoPlay?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Current state of the video player
|
|
21
|
+
*/
|
|
22
|
+
export interface VideoPlayerState {
|
|
23
|
+
readonly isPlaying: boolean;
|
|
24
|
+
readonly isPlayerValid: boolean;
|
|
25
|
+
readonly isLoading: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Video player control actions
|
|
30
|
+
*/
|
|
31
|
+
export interface VideoPlayerControls {
|
|
32
|
+
readonly play: () => void;
|
|
33
|
+
readonly pause: () => void;
|
|
34
|
+
readonly toggle: () => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Combined hook return type
|
|
39
|
+
*/
|
|
40
|
+
export interface UseVideoPlayerControlResult {
|
|
41
|
+
readonly player: VideoPlayer | null;
|
|
42
|
+
readonly state: VideoPlayerState;
|
|
43
|
+
readonly controls: VideoPlayerControls;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Visibility hook configuration
|
|
48
|
+
*/
|
|
49
|
+
export interface VideoVisibilityConfig {
|
|
50
|
+
readonly isVisible: boolean;
|
|
51
|
+
readonly player: VideoPlayer | null;
|
|
52
|
+
readonly isPlayerValid: boolean;
|
|
53
|
+
readonly onPlayingChange?: (isPlaying: boolean) => void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Video player component props
|
|
58
|
+
*/
|
|
59
|
+
export interface VideoPlayerProps {
|
|
60
|
+
readonly source: string | null;
|
|
61
|
+
readonly isVisible?: boolean;
|
|
62
|
+
readonly loop?: boolean;
|
|
63
|
+
readonly muted?: boolean;
|
|
64
|
+
readonly autoPlay?: boolean;
|
|
65
|
+
readonly showControls?: boolean;
|
|
66
|
+
readonly nativeControls?: boolean;
|
|
67
|
+
readonly onPlayingChange?: (isPlaying: boolean) => void;
|
|
68
|
+
readonly onError?: (error: Error) => void;
|
|
69
|
+
readonly style?: ViewStyle;
|
|
70
|
+
readonly contentFit?: "contain" | "cover" | "fill";
|
|
71
|
+
readonly thumbnailUrl?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type { VideoPlayer } from "expo-video";
|
|
@@ -8,11 +8,11 @@ import {
|
|
|
8
8
|
View,
|
|
9
9
|
StyleSheet,
|
|
10
10
|
TouchableOpacity,
|
|
11
|
-
ActivityIndicator,
|
|
12
11
|
} from "react-native";
|
|
13
12
|
import {
|
|
14
13
|
AtomicText,
|
|
15
14
|
AtomicIcon,
|
|
15
|
+
AtomicSpinner,
|
|
16
16
|
useAppDesignTokens,
|
|
17
17
|
} from "@umituz/react-native-design-system";
|
|
18
18
|
|
|
@@ -64,7 +64,7 @@ export const ExportActions: React.FC<ExportActionsProps> = ({
|
|
|
64
64
|
disabled={isExporting}
|
|
65
65
|
>
|
|
66
66
|
{isExporting ? (
|
|
67
|
-
<
|
|
67
|
+
<AtomicSpinner size="sm" color="white" />
|
|
68
68
|
) : (
|
|
69
69
|
<AtomicIcon name="Download" size="sm" color="onSurface" />
|
|
70
70
|
)}
|