@umituz/react-native-video-editor 1.0.1 → 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.
Files changed (55) hide show
  1. package/package.json +6 -1
  2. package/src/domain/entities/index.ts +5 -3
  3. package/src/domain/entities/video-project.types.ts +1 -1
  4. package/src/index.ts +25 -1
  5. package/src/infrastructure/constants/animation-layer.constants.ts +1 -1
  6. package/src/infrastructure/services/image-layer-operations.service.ts +1 -1
  7. package/src/infrastructure/services/layer-manipulation.service.ts +1 -1
  8. package/src/infrastructure/services/layer-operations/layer-delete.service.ts +1 -1
  9. package/src/infrastructure/services/layer-operations/layer-duplicate.service.ts +1 -1
  10. package/src/infrastructure/services/layer-operations/layer-order.service.ts +1 -1
  11. package/src/infrastructure/services/layer-operations/layer-transform.service.ts +1 -1
  12. package/src/infrastructure/services/layer-operations.service.ts +1 -1
  13. package/src/infrastructure/services/scene-operations.service.ts +1 -1
  14. package/src/infrastructure/services/shape-layer-operations.service.ts +1 -1
  15. package/src/infrastructure/services/text-layer-operations.service.ts +1 -1
  16. package/src/player/index.ts +31 -0
  17. package/src/player/infrastructure/services/player-control.service.ts +95 -0
  18. package/src/player/presentation/components/VideoPlayer.tsx +135 -0
  19. package/src/player/presentation/hooks/useVideoPlayerControl.ts +82 -0
  20. package/src/player/presentation/hooks/useVideoVisibility.ts +62 -0
  21. package/src/player/types/index.ts +74 -0
  22. package/src/presentation/components/AnimationEditor.tsx +1 -1
  23. package/src/presentation/components/AudioEditor.tsx +1 -1
  24. package/src/presentation/components/DraggableLayer.tsx +1 -1
  25. package/src/presentation/components/EditorPreviewArea.tsx +1 -1
  26. package/src/presentation/components/EditorTimeline.tsx +1 -1
  27. package/src/presentation/components/EditorToolPanel.tsx +1 -1
  28. package/src/presentation/components/ExportDialog.tsx +1 -1
  29. package/src/presentation/components/ImageLayerEditor.tsx +1 -1
  30. package/src/presentation/components/LayerActionsMenu.tsx +1 -1
  31. package/src/presentation/components/ShapeLayerEditor.tsx +1 -1
  32. package/src/presentation/components/TextLayerEditor.tsx +1 -1
  33. package/src/presentation/components/animation-layer/AnimationTypeSelector.tsx +1 -1
  34. package/src/presentation/components/draggable-layer/LayerContent.tsx +1 -1
  35. package/src/presentation/components/export/ExportActions.tsx +2 -2
  36. package/src/presentation/components/export/ExportProgress.tsx +1 -1
  37. package/src/presentation/components/export/ProjectInfoBox.tsx +1 -1
  38. package/src/presentation/hooks/useAnimationLayerForm.ts +1 -1
  39. package/src/presentation/hooks/useAudioLayerForm.ts +1 -1
  40. package/src/presentation/hooks/useEditorActions.tsx +1 -1
  41. package/src/presentation/hooks/useEditorHistory.ts +1 -1
  42. package/src/presentation/hooks/useEditorLayers.ts +1 -1
  43. package/src/presentation/hooks/useEditorPlayback.ts +1 -1
  44. package/src/presentation/hooks/useEditorScenes.ts +1 -1
  45. package/src/presentation/hooks/useExport.ts +34 -19
  46. package/src/presentation/hooks/useExportActions.tsx +1 -1
  47. package/src/presentation/hooks/useExportForm.ts +1 -1
  48. package/src/presentation/hooks/useImageLayerForm.ts +1 -1
  49. package/src/presentation/hooks/useImageLayerOperations.ts +1 -1
  50. package/src/presentation/hooks/useLayerActions.tsx +1 -1
  51. package/src/presentation/hooks/useLayerManipulation.ts +1 -1
  52. package/src/presentation/hooks/useShapeLayerForm.ts +1 -1
  53. package/src/presentation/hooks/useTextLayerForm.ts +1 -1
  54. package/src/presentation/hooks/useTextLayerOperations.ts +1 -1
  55. package/src/infrastructure/services/export-orchestrator.service.ts +0 -122
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-video-editor",
3
- "version": "1.0.1",
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"
@@ -4,8 +4,10 @@
4
4
 
5
5
  export * from "./video-project.types";
6
6
 
7
+ import type { VideoProject, Scene } from "./video-project.types";
8
+
7
9
  export interface EditorState {
8
- project: import("./video-project.types").VideoProject | null;
10
+ project: VideoProject | null;
9
11
  currentSceneIndex: number;
10
12
  selectedLayerId: string | null;
11
13
  isPlaying: boolean;
@@ -14,13 +16,13 @@ export interface EditorState {
14
16
 
15
17
  export interface LayerOperationResult {
16
18
  success: boolean;
17
- updatedScenes: import("./video-project.types").Scene[];
19
+ updatedScenes: Scene[];
18
20
  error?: string;
19
21
  }
20
22
 
21
23
  export interface SceneOperationResult {
22
24
  success: boolean;
23
- updatedScenes: import("./video-project.types").Scene[];
25
+ updatedScenes: Scene[];
24
26
  newSceneIndex?: number;
25
27
  error?: string;
26
28
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Video Project Entity Types
2
+ * Video Project Types
3
3
  * Core domain entities for video editor
4
4
  */
5
5
 
package/src/index.ts CHANGED
@@ -47,7 +47,6 @@ export { textLayerOperationsService } from "./infrastructure/services/text-layer
47
47
  export { imageLayerOperationsService } from "./infrastructure/services/image-layer-operations.service";
48
48
  export { shapeLayerOperationsService } from "./infrastructure/services/shape-layer-operations.service";
49
49
  export { layerManipulationService } from "./infrastructure/services/layer-manipulation.service";
50
- export { exportOrchestratorService } from "./infrastructure/services/export-orchestrator.service";
51
50
 
52
51
  export {
53
52
  layerDeleteService,
@@ -98,3 +97,28 @@ export { useLayerActions } from "./presentation/hooks/useLayerActions";
98
97
  export { useSceneActions } from "./presentation/hooks/useSceneActions";
99
98
  export { useMenuActions } from "./presentation/hooks/useMenuActions";
100
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";
@@ -3,7 +3,7 @@
3
3
  * Centralized constants for animation layer editor
4
4
  */
5
5
 
6
- import type { AnimationType } from "@domains/video";
6
+ import type { AnimationType } from "../../../domain/entities";
7
7
 
8
8
  export type Easing = "linear" | "ease-in" | "ease-out" | "ease-in-out";
9
9
 
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { generateUUID } from "@umituz/react-native-uuid";
7
- import type { Scene, ImageLayer } from "@domains/video";
7
+ import type { Scene, ImageLayer } from "../../../domain/entities";
8
8
  import type { LayerOperationResult, AddImageLayerData } from "../../types";
9
9
 
10
10
  class ImageLayerOperationsService {
@@ -3,7 +3,7 @@
3
3
  * Orchestrator service that delegates to specialized layer operation services
4
4
  */
5
5
 
6
- import type { Scene, Animation } from "@domains/video";
6
+ import type { Scene, Animation } from "../../../domain/entities";
7
7
  import type { LayerOperationResult, LayerOrderAction } from "../../types";
8
8
  import {
9
9
  layerDeleteService,
@@ -3,7 +3,7 @@
3
3
  * Single Responsibility: Handle layer deletion operations
4
4
  */
5
5
 
6
- import type { Scene } from "@domains/video";
6
+ import type { Scene } from "../../../domain/entities";
7
7
  import type { LayerOperationResult } from "../../../types";
8
8
 
9
9
  class LayerDeleteService {
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { generateUUID } from "@umituz/react-native-uuid";
7
- import type { Scene } from "@domains/video";
7
+ import type { Scene } from "../../../domain/entities";
8
8
  import type { LayerOperationResult } from "../../../types";
9
9
 
10
10
  class LayerDuplicateService {
@@ -3,7 +3,7 @@
3
3
  * Single Responsibility: Handle layer ordering operations
4
4
  */
5
5
 
6
- import type { Scene } from "@domains/video";
6
+ import type { Scene } from "../../../domain/entities";
7
7
  import type { LayerOperationResult, LayerOrderAction } from "../../../types";
8
8
 
9
9
  class LayerOrderService {
@@ -3,7 +3,7 @@
3
3
  * Single Responsibility: Handle layer position, size, and animation updates
4
4
  */
5
5
 
6
- import type { Scene, Animation, Layer } from "@domains/video";
6
+ import type { Scene, Animation, Layer } from "../../../domain/entities";
7
7
  import type { LayerOperationResult } from "../../../types";
8
8
 
9
9
  class LayerTransformService {
@@ -7,7 +7,7 @@ import { textLayerOperationsService } from "./text-layer-operations.service";
7
7
  import { imageLayerOperationsService } from "./image-layer-operations.service";
8
8
  import { shapeLayerOperationsService } from "./shape-layer-operations.service";
9
9
  import { layerManipulationService } from "./layer-manipulation.service";
10
- import type { Scene, TextLayer, ImageLayer, Animation } from "@domains/video";
10
+ import type { Scene, TextLayer, ImageLayer, Animation } from "../../../domain/entities";
11
11
  import type {
12
12
  LayerOperationResult,
13
13
  LayerOrderAction,
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { generateUUID } from "@umituz/react-native-uuid";
7
- import type { Scene, Audio } from "@domains/video";
7
+ import type { Scene, Audio } from "../../../domain/entities";
8
8
  import type { SceneOperationResult } from "../../types";
9
9
 
10
10
  class SceneOperationsService {
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { generateUUID } from "@umituz/react-native-uuid";
7
- import type { Scene, ShapeLayer } from "@domains/video";
7
+ import type { Scene, ShapeLayer } from "../../../domain/entities";
8
8
  import type { LayerOperationResult, AddShapeLayerData } from "../../types";
9
9
 
10
10
  class ShapeLayerOperationsService {
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { generateUUID } from "@umituz/react-native-uuid";
7
- import type { Scene, TextLayer } from "@domains/video";
7
+ import type { Scene, TextLayer } from "../../../domain/entities";
8
8
  import type { LayerOperationResult, AddTextLayerData } from "../../types";
9
9
 
10
10
  class TextLayerOperationsService {
@@ -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
+ };