@zezosoft/zezo-ott-react-native-video-player 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -0
  3. package/lib/module/VideoPlayer/MediaControls/BottomControls.js +156 -0
  4. package/lib/module/VideoPlayer/MediaControls/BottomControls.js.map +1 -0
  5. package/lib/module/VideoPlayer/MediaControls/MediaControls.js +27 -0
  6. package/lib/module/VideoPlayer/MediaControls/MediaControls.js.map +1 -0
  7. package/lib/module/VideoPlayer/MediaControls/MediaControlsProvider.js +85 -0
  8. package/lib/module/VideoPlayer/MediaControls/MediaControlsProvider.js.map +1 -0
  9. package/lib/module/VideoPlayer/MediaControls/MiddleControls.js +208 -0
  10. package/lib/module/VideoPlayer/MediaControls/MiddleControls.js.map +1 -0
  11. package/lib/module/VideoPlayer/MediaControls/TopControls.js +94 -0
  12. package/lib/module/VideoPlayer/MediaControls/TopControls.js.map +1 -0
  13. package/lib/module/VideoPlayer/Styles/fonts.js +58 -0
  14. package/lib/module/VideoPlayer/Styles/fonts.js.map +1 -0
  15. package/lib/module/VideoPlayer/Styles/globalStyles.js +75 -0
  16. package/lib/module/VideoPlayer/Styles/globalStyles.js.map +1 -0
  17. package/lib/module/VideoPlayer/VideoPlayer.js +180 -0
  18. package/lib/module/VideoPlayer/VideoPlayer.js.map +1 -0
  19. package/lib/module/VideoPlayer/components/ProgressBar.js +84 -0
  20. package/lib/module/VideoPlayer/components/ProgressBar.js.map +1 -0
  21. package/lib/module/VideoPlayer/components/SkipAndNextControls.js +154 -0
  22. package/lib/module/VideoPlayer/components/SkipAndNextControls.js.map +1 -0
  23. package/lib/module/VideoPlayer/components/SubtitleView.js +57 -0
  24. package/lib/module/VideoPlayer/components/SubtitleView.js.map +1 -0
  25. package/lib/module/VideoPlayer/context/VideoPlayerConfig.js +47 -0
  26. package/lib/module/VideoPlayer/context/VideoPlayerConfig.js.map +1 -0
  27. package/lib/module/VideoPlayer/context/index.js +4 -0
  28. package/lib/module/VideoPlayer/context/index.js.map +1 -0
  29. package/lib/module/VideoPlayer/index.js +7 -0
  30. package/lib/module/VideoPlayer/index.js.map +1 -0
  31. package/lib/module/VideoPlayer/model/AudioAndSubtitles.js +257 -0
  32. package/lib/module/VideoPlayer/model/AudioAndSubtitles.js.map +1 -0
  33. package/lib/module/VideoPlayer/model/Episodes.js +272 -0
  34. package/lib/module/VideoPlayer/model/Episodes.js.map +1 -0
  35. package/lib/module/VideoPlayer/model/SettingModal.js +115 -0
  36. package/lib/module/VideoPlayer/model/SettingModal.js.map +1 -0
  37. package/lib/module/VideoPlayer/model/SpeedControls.js +140 -0
  38. package/lib/module/VideoPlayer/model/SpeedControls.js.map +1 -0
  39. package/lib/module/VideoPlayer/model/VideoPlayerSettings.js +113 -0
  40. package/lib/module/VideoPlayer/model/VideoPlayerSettings.js.map +1 -0
  41. package/lib/module/VideoPlayer/store/index.js +5 -0
  42. package/lib/module/VideoPlayer/store/index.js.map +1 -0
  43. package/lib/module/VideoPlayer/store/videoPlayer.type.js +4 -0
  44. package/lib/module/VideoPlayer/store/videoPlayer.type.js.map +1 -0
  45. package/lib/module/VideoPlayer/store/videoPlayerStore.js +147 -0
  46. package/lib/module/VideoPlayer/store/videoPlayerStore.js.map +1 -0
  47. package/lib/module/VideoPlayer/utils/Display.js +22 -0
  48. package/lib/module/VideoPlayer/utils/Display.js.map +1 -0
  49. package/lib/module/VideoPlayer/utils/PlatformSelector.js +18 -0
  50. package/lib/module/VideoPlayer/utils/PlatformSelector.js.map +1 -0
  51. package/lib/module/VideoPlayer/utils/hooks/index.js +5 -0
  52. package/lib/module/VideoPlayer/utils/hooks/index.js.map +1 -0
  53. package/lib/module/VideoPlayer/utils/hooks/useVideoPlayerBack.js +48 -0
  54. package/lib/module/VideoPlayer/utils/hooks/useVideoPlayerBack.js.map +1 -0
  55. package/lib/module/VideoPlayer/utils/hooks/useVideoResolutions.js +88 -0
  56. package/lib/module/VideoPlayer/utils/hooks/useVideoResolutions.js.map +1 -0
  57. package/lib/module/VideoPlayer/utils/index.js +10 -0
  58. package/lib/module/VideoPlayer/utils/index.js.map +1 -0
  59. package/lib/module/VideoPlayer/utils/lockOrientation.js +31 -0
  60. package/lib/module/VideoPlayer/utils/lockOrientation.js.map +1 -0
  61. package/lib/module/VideoPlayer/utils/playerEvents.js +79 -0
  62. package/lib/module/VideoPlayer/utils/playerEvents.js.map +1 -0
  63. package/lib/module/VideoPlayer/utils/timeFormatter.js +35 -0
  64. package/lib/module/VideoPlayer/utils/timeFormatter.js.map +1 -0
  65. package/lib/module/VideoPlayer/utils/useWatchReporter.js +68 -0
  66. package/lib/module/VideoPlayer/utils/useWatchReporter.js.map +1 -0
  67. package/lib/module/VideoPlayer/utils/videoControl.js +166 -0
  68. package/lib/module/VideoPlayer/utils/videoControl.js.map +1 -0
  69. package/lib/module/VideoPlayer/utils/videoRef.js +5 -0
  70. package/lib/module/VideoPlayer/utils/videoRef.js.map +1 -0
  71. package/lib/module/VideoPlayer/utils/videoSource.js +17 -0
  72. package/lib/module/VideoPlayer/utils/videoSource.js.map +1 -0
  73. package/lib/module/index.js +4 -0
  74. package/lib/module/index.js.map +1 -0
  75. package/lib/module/package.json +1 -0
  76. package/lib/typescript/package.json +1 -0
  77. package/lib/typescript/src/VideoPlayer/MediaControls/BottomControls.d.ts +3 -0
  78. package/lib/typescript/src/VideoPlayer/MediaControls/BottomControls.d.ts.map +1 -0
  79. package/lib/typescript/src/VideoPlayer/MediaControls/MediaControls.d.ts +6 -0
  80. package/lib/typescript/src/VideoPlayer/MediaControls/MediaControls.d.ts.map +1 -0
  81. package/lib/typescript/src/VideoPlayer/MediaControls/MediaControlsProvider.d.ts +14 -0
  82. package/lib/typescript/src/VideoPlayer/MediaControls/MediaControlsProvider.d.ts.map +1 -0
  83. package/lib/typescript/src/VideoPlayer/MediaControls/MiddleControls.d.ts +3 -0
  84. package/lib/typescript/src/VideoPlayer/MediaControls/MiddleControls.d.ts.map +1 -0
  85. package/lib/typescript/src/VideoPlayer/MediaControls/TopControls.d.ts +7 -0
  86. package/lib/typescript/src/VideoPlayer/MediaControls/TopControls.d.ts.map +1 -0
  87. package/lib/typescript/src/VideoPlayer/Styles/fonts.d.ts +54 -0
  88. package/lib/typescript/src/VideoPlayer/Styles/fonts.d.ts.map +1 -0
  89. package/lib/typescript/src/VideoPlayer/Styles/globalStyles.d.ts +70 -0
  90. package/lib/typescript/src/VideoPlayer/Styles/globalStyles.d.ts.map +1 -0
  91. package/lib/typescript/src/VideoPlayer/VideoPlayer.d.ts +22 -0
  92. package/lib/typescript/src/VideoPlayer/VideoPlayer.d.ts.map +1 -0
  93. package/lib/typescript/src/VideoPlayer/components/ProgressBar.d.ts +12 -0
  94. package/lib/typescript/src/VideoPlayer/components/ProgressBar.d.ts.map +1 -0
  95. package/lib/typescript/src/VideoPlayer/components/SkipAndNextControls.d.ts +12 -0
  96. package/lib/typescript/src/VideoPlayer/components/SkipAndNextControls.d.ts.map +1 -0
  97. package/lib/typescript/src/VideoPlayer/components/SubtitleView.d.ts +3 -0
  98. package/lib/typescript/src/VideoPlayer/components/SubtitleView.d.ts.map +1 -0
  99. package/lib/typescript/src/VideoPlayer/context/VideoPlayerConfig.d.ts +25 -0
  100. package/lib/typescript/src/VideoPlayer/context/VideoPlayerConfig.d.ts.map +1 -0
  101. package/lib/typescript/src/VideoPlayer/context/index.d.ts +3 -0
  102. package/lib/typescript/src/VideoPlayer/context/index.d.ts.map +1 -0
  103. package/lib/typescript/src/VideoPlayer/index.d.ts +5 -0
  104. package/lib/typescript/src/VideoPlayer/index.d.ts.map +1 -0
  105. package/lib/typescript/src/VideoPlayer/model/AudioAndSubtitles.d.ts +4 -0
  106. package/lib/typescript/src/VideoPlayer/model/AudioAndSubtitles.d.ts.map +1 -0
  107. package/lib/typescript/src/VideoPlayer/model/Episodes.d.ts +12 -0
  108. package/lib/typescript/src/VideoPlayer/model/Episodes.d.ts.map +1 -0
  109. package/lib/typescript/src/VideoPlayer/model/SettingModal.d.ts +12 -0
  110. package/lib/typescript/src/VideoPlayer/model/SettingModal.d.ts.map +1 -0
  111. package/lib/typescript/src/VideoPlayer/model/SpeedControls.d.ts +4 -0
  112. package/lib/typescript/src/VideoPlayer/model/SpeedControls.d.ts.map +1 -0
  113. package/lib/typescript/src/VideoPlayer/model/VideoPlayerSettings.d.ts +7 -0
  114. package/lib/typescript/src/VideoPlayer/model/VideoPlayerSettings.d.ts.map +1 -0
  115. package/lib/typescript/src/VideoPlayer/store/index.d.ts +3 -0
  116. package/lib/typescript/src/VideoPlayer/store/index.d.ts.map +1 -0
  117. package/lib/typescript/src/VideoPlayer/store/videoPlayer.type.d.ts +144 -0
  118. package/lib/typescript/src/VideoPlayer/store/videoPlayer.type.d.ts.map +1 -0
  119. package/lib/typescript/src/VideoPlayer/store/videoPlayerStore.d.ts +20 -0
  120. package/lib/typescript/src/VideoPlayer/store/videoPlayerStore.d.ts.map +1 -0
  121. package/lib/typescript/src/VideoPlayer/utils/Display.d.ts +8 -0
  122. package/lib/typescript/src/VideoPlayer/utils/Display.d.ts.map +1 -0
  123. package/lib/typescript/src/VideoPlayer/utils/PlatformSelector.d.ts +11 -0
  124. package/lib/typescript/src/VideoPlayer/utils/PlatformSelector.d.ts.map +1 -0
  125. package/lib/typescript/src/VideoPlayer/utils/hooks/index.d.ts +3 -0
  126. package/lib/typescript/src/VideoPlayer/utils/hooks/index.d.ts.map +1 -0
  127. package/lib/typescript/src/VideoPlayer/utils/hooks/useVideoPlayerBack.d.ts +6 -0
  128. package/lib/typescript/src/VideoPlayer/utils/hooks/useVideoPlayerBack.d.ts.map +1 -0
  129. package/lib/typescript/src/VideoPlayer/utils/hooks/useVideoResolutions.d.ts +12 -0
  130. package/lib/typescript/src/VideoPlayer/utils/hooks/useVideoResolutions.d.ts.map +1 -0
  131. package/lib/typescript/src/VideoPlayer/utils/index.d.ts +8 -0
  132. package/lib/typescript/src/VideoPlayer/utils/index.d.ts.map +1 -0
  133. package/lib/typescript/src/VideoPlayer/utils/lockOrientation.d.ts +3 -0
  134. package/lib/typescript/src/VideoPlayer/utils/lockOrientation.d.ts.map +1 -0
  135. package/lib/typescript/src/VideoPlayer/utils/playerEvents.d.ts +18 -0
  136. package/lib/typescript/src/VideoPlayer/utils/playerEvents.d.ts.map +1 -0
  137. package/lib/typescript/src/VideoPlayer/utils/timeFormatter.d.ts +9 -0
  138. package/lib/typescript/src/VideoPlayer/utils/timeFormatter.d.ts.map +1 -0
  139. package/lib/typescript/src/VideoPlayer/utils/useWatchReporter.d.ts +26 -0
  140. package/lib/typescript/src/VideoPlayer/utils/useWatchReporter.d.ts.map +1 -0
  141. package/lib/typescript/src/VideoPlayer/utils/videoControl.d.ts +17 -0
  142. package/lib/typescript/src/VideoPlayer/utils/videoControl.d.ts.map +1 -0
  143. package/lib/typescript/src/VideoPlayer/utils/videoRef.d.ts +3 -0
  144. package/lib/typescript/src/VideoPlayer/utils/videoRef.d.ts.map +1 -0
  145. package/lib/typescript/src/VideoPlayer/utils/videoSource.d.ts +10 -0
  146. package/lib/typescript/src/VideoPlayer/utils/videoSource.d.ts.map +1 -0
  147. package/lib/typescript/src/index.d.ts +2 -0
  148. package/lib/typescript/src/index.d.ts.map +1 -0
  149. package/package.json +191 -0
  150. package/src/VideoPlayer/MediaControls/BottomControls.tsx +185 -0
  151. package/src/VideoPlayer/MediaControls/MediaControls.tsx +29 -0
  152. package/src/VideoPlayer/MediaControls/MediaControlsProvider.tsx +99 -0
  153. package/src/VideoPlayer/MediaControls/MiddleControls.tsx +232 -0
  154. package/src/VideoPlayer/MediaControls/TopControls.tsx +92 -0
  155. package/src/VideoPlayer/Styles/fonts.ts +106 -0
  156. package/src/VideoPlayer/Styles/globalStyles.ts +74 -0
  157. package/src/VideoPlayer/VideoPlayer.tsx +217 -0
  158. package/src/VideoPlayer/components/ProgressBar.tsx +98 -0
  159. package/src/VideoPlayer/components/SkipAndNextControls.tsx +195 -0
  160. package/src/VideoPlayer/components/SubtitleView.tsx +53 -0
  161. package/src/VideoPlayer/context/VideoPlayerConfig.tsx +65 -0
  162. package/src/VideoPlayer/context/index.ts +5 -0
  163. package/src/VideoPlayer/index.ts +4 -0
  164. package/src/VideoPlayer/model/AudioAndSubtitles.tsx +302 -0
  165. package/src/VideoPlayer/model/Episodes.tsx +294 -0
  166. package/src/VideoPlayer/model/SettingModal.tsx +128 -0
  167. package/src/VideoPlayer/model/SpeedControls.tsx +134 -0
  168. package/src/VideoPlayer/model/VideoPlayerSettings.tsx +141 -0
  169. package/src/VideoPlayer/store/index.ts +2 -0
  170. package/src/VideoPlayer/store/videoPlayer.type.ts +192 -0
  171. package/src/VideoPlayer/store/videoPlayerStore.ts +101 -0
  172. package/src/VideoPlayer/utils/Display.ts +14 -0
  173. package/src/VideoPlayer/utils/PlatformSelector.ts +18 -0
  174. package/src/VideoPlayer/utils/hooks/index.ts +2 -0
  175. package/src/VideoPlayer/utils/hooks/useVideoPlayerBack.ts +66 -0
  176. package/src/VideoPlayer/utils/hooks/useVideoResolutions.ts +119 -0
  177. package/src/VideoPlayer/utils/index.ts +7 -0
  178. package/src/VideoPlayer/utils/lockOrientation.ts +34 -0
  179. package/src/VideoPlayer/utils/playerEvents.ts +97 -0
  180. package/src/VideoPlayer/utils/timeFormatter.ts +47 -0
  181. package/src/VideoPlayer/utils/useWatchReporter.ts +104 -0
  182. package/src/VideoPlayer/utils/videoControl.ts +192 -0
  183. package/src/VideoPlayer/utils/videoRef.ts +4 -0
  184. package/src/VideoPlayer/utils/videoSource.ts +23 -0
  185. package/src/index.tsx +1 -0
@@ -0,0 +1,185 @@
1
+ import React, { useCallback } from 'react';
2
+ import {
3
+ StyleSheet,
4
+ Text,
5
+ TouchableOpacity,
6
+ View,
7
+ type GestureResponderEvent,
8
+ type TextStyle,
9
+ type ViewStyle,
10
+ } from 'react-native';
11
+ import { scale, verticalScale, moderateScale } from 'react-native-size-matters';
12
+ import { RFValue } from 'react-native-responsive-fontsize';
13
+
14
+ import {
15
+ Settings as SettingsIcon,
16
+ SlidersHorizontal as SpeedIcon,
17
+ Subtitles as SubtitlesIcon,
18
+ ListChecks as EpisodesIcon,
19
+ } from 'lucide-react-native';
20
+
21
+ import { useVideoPlayerStore } from '../store/videoPlayerStore';
22
+ import { videoRef } from '../utils/videoRef';
23
+ import type { SettingsAction } from '../store/videoPlayer.type';
24
+ import { formatTime } from '../utils';
25
+ import ProgressBar from '../components/ProgressBar';
26
+ import { useVideoPlayerConfig } from '../context';
27
+
28
+ type ControlButtonProps = {
29
+ onPress: (event: GestureResponderEvent) => void;
30
+ icon: React.ReactNode;
31
+ label: string;
32
+ accessibilityLabel?: string;
33
+ buttonStyle?: ViewStyle;
34
+ labelStyle?: TextStyle;
35
+ colors: {
36
+ text: string;
37
+ };
38
+ };
39
+
40
+ const ControlButton: React.FC<ControlButtonProps> = ({
41
+ onPress,
42
+ icon,
43
+ label,
44
+ accessibilityLabel,
45
+ buttonStyle,
46
+ labelStyle,
47
+ colors,
48
+ }) => (
49
+ <TouchableOpacity
50
+ onPress={onPress}
51
+ style={[styles.button, buttonStyle]}
52
+ activeOpacity={0.8}
53
+ accessibilityLabel={accessibilityLabel ?? label}
54
+ >
55
+ {icon}
56
+ <Text style={[styles.buttonText, labelStyle, { color: colors.text }]}>
57
+ {label}
58
+ </Text>
59
+ </TouchableOpacity>
60
+ );
61
+
62
+ const BottomControls = () => {
63
+ const {
64
+ duration,
65
+ currentTime,
66
+ activeTrack,
67
+ playableDuration,
68
+ setSettingsModal,
69
+ setIsPaused,
70
+ setControlsVisible,
71
+ playBackRateLabel,
72
+ } = useVideoPlayerStore();
73
+
74
+ const { colors } = useVideoPlayerConfig();
75
+ const getRemainingTime = useCallback(
76
+ (totalDuration: number, currentPos: number) => {
77
+ const remainingTime = totalDuration - currentPos;
78
+ return formatTime(Math.max(remainingTime, 0));
79
+ },
80
+ []
81
+ );
82
+
83
+ const onSeek = (value: number) => {
84
+ videoRef.current?.seek(value);
85
+ };
86
+
87
+ const onOpenSettings = (type: SettingsAction) => {
88
+ setSettingsModal({ action: type, isVisible: true });
89
+ setIsPaused(true);
90
+ setControlsVisible(false);
91
+ };
92
+
93
+ return (
94
+ <View>
95
+ {/* Progress Bar Section */}
96
+ <View style={styles.sliderContainer}>
97
+ <View style={styles.sliderWrapper}>
98
+ <ProgressBar
99
+ duration={duration}
100
+ currentTime={currentTime}
101
+ bufferedTime={playableDuration}
102
+ onSeek={onSeek}
103
+ />
104
+ </View>
105
+ <Text style={[styles.timeText, { color: colors.text }]}>
106
+ {getRemainingTime(duration, currentTime)}
107
+ </Text>
108
+ </View>
109
+
110
+ {/* Action Buttons */}
111
+ <View style={styles.buttonsContainer}>
112
+ <ControlButton
113
+ onPress={() => onOpenSettings('speed')}
114
+ icon={<SpeedIcon size={moderateScale(18)} color={colors.text} />}
115
+ label={
116
+ playBackRateLabel && playBackRateLabel !== 'Normal'
117
+ ? `Speed (${playBackRateLabel})`
118
+ : 'Speed'
119
+ }
120
+ colors={colors}
121
+ />
122
+ <ControlButton
123
+ onPress={() => onOpenSettings('audioOrSubtitle')}
124
+ icon={<SubtitlesIcon size={moderateScale(19)} color={colors.text} />}
125
+ label="Audio & Subtitles"
126
+ colors={colors}
127
+ />
128
+ <ControlButton
129
+ onPress={() => onOpenSettings('settings')}
130
+ icon={<SettingsIcon size={moderateScale(18)} color={colors.text} />}
131
+ label="Settings"
132
+ colors={colors}
133
+ />
134
+ {!activeTrack?.isTrailer && activeTrack?.type === 'series' && (
135
+ <ControlButton
136
+ onPress={() => onOpenSettings('episodes')}
137
+ icon={<EpisodesIcon size={moderateScale(17)} color={colors.text} />}
138
+ label="Episodes"
139
+ colors={colors}
140
+ />
141
+ )}
142
+ </View>
143
+ </View>
144
+ );
145
+ };
146
+
147
+ export default BottomControls;
148
+
149
+ const styles = StyleSheet.create({
150
+ sliderContainer: {
151
+ flexDirection: 'row',
152
+ alignItems: 'center',
153
+ paddingHorizontal: scale(12),
154
+ },
155
+ sliderWrapper: {
156
+ flex: 1,
157
+ minWidth: 0,
158
+ },
159
+ timeText: {
160
+ width: scale(50),
161
+ textAlign: 'right',
162
+ marginLeft: scale(4),
163
+ fontSize: RFValue(12),
164
+ fontWeight: '500',
165
+ },
166
+ buttonsContainer: {
167
+ flexDirection: 'row',
168
+ justifyContent: 'center',
169
+ flexWrap: 'wrap',
170
+ paddingHorizontal: scale(10),
171
+ },
172
+ button: {
173
+ flexDirection: 'row',
174
+ alignItems: 'center',
175
+ paddingHorizontal: scale(12),
176
+ paddingVertical: verticalScale(6),
177
+ marginHorizontal: scale(6),
178
+ marginVertical: verticalScale(4),
179
+ borderRadius: moderateScale(8),
180
+ },
181
+ buttonText: {
182
+ fontSize: RFValue(13),
183
+ marginLeft: scale(6),
184
+ },
185
+ });
@@ -0,0 +1,29 @@
1
+ import { memo } from 'react';
2
+ import { StyleSheet, SafeAreaView } from 'react-native';
3
+ import TopControls from './TopControls';
4
+ import MiddleControls from './MiddleControls';
5
+ import BottomControls from './BottomControls';
6
+
7
+ export type MediaControlsProps = {
8
+ onClose?: () => void;
9
+ };
10
+
11
+ const MediaControls: React.FC<MediaControlsProps> = ({ onClose }) => {
12
+ return (
13
+ <SafeAreaView style={styles.container}>
14
+ <TopControls onClose={onClose} />
15
+ <MiddleControls />
16
+ <BottomControls />
17
+ </SafeAreaView>
18
+ );
19
+ };
20
+
21
+ export default memo(MediaControls);
22
+
23
+ const styles = StyleSheet.create({
24
+ container: {
25
+ flex: 1,
26
+ flexDirection: 'column',
27
+ justifyContent: 'space-between',
28
+ },
29
+ });
@@ -0,0 +1,99 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import {
3
+ StyleSheet,
4
+ TouchableOpacity,
5
+ View,
6
+ type ViewStyle,
7
+ Animated,
8
+ } from 'react-native';
9
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
10
+ import { moderateScale } from 'react-native-size-matters';
11
+ import MediaControls, { type MediaControlsProps } from './MediaControls';
12
+ import SettingModal from '../model/SettingModal';
13
+ import { useVideoPlayerStore } from '../store/videoPlayerStore';
14
+ import SkipAndNextControls from '../components/SkipAndNextControls';
15
+ import type { MediaEpisode } from '../store/videoPlayer.type';
16
+ import globalStyles from '../Styles/globalStyles';
17
+ import type { ExtendedWatchProgress } from '../utils/useWatchReporter';
18
+ import SubtitleView from '../components/SubtitleView';
19
+
20
+ interface MediaControlsProviderProps extends MediaControlsProps {
21
+ children: React.ReactNode;
22
+ onPressEpisode: ({ episode }: { episode: MediaEpisode }) => Promise<boolean>;
23
+ reportProgress: (event: ExtendedWatchProgress['event']) => void;
24
+ }
25
+
26
+ const MediaControlsProvider: React.FC<MediaControlsProviderProps> = ({
27
+ children,
28
+ onClose,
29
+ onPressEpisode,
30
+ reportProgress,
31
+ }) => {
32
+ const { controlsVisible, setControlsVisible, controlsTimer } =
33
+ useVideoPlayerStore();
34
+ const { top, bottom, left, right } = useSafeAreaInsets();
35
+ const timeid = React.useRef<NodeJS.Timeout | null>(null);
36
+
37
+ const fadeAnim = useRef(new Animated.Value(0)).current;
38
+
39
+ useEffect(() => {
40
+ Animated.timing(fadeAnim, {
41
+ toValue: controlsVisible ? 1 : 0,
42
+ duration: 200,
43
+ useNativeDriver: true,
44
+ }).start();
45
+
46
+ if (controlsVisible) {
47
+ timeid.current = setTimeout(() => {
48
+ setControlsVisible(false);
49
+ }, controlsTimer * 1000);
50
+ }
51
+
52
+ return () => {
53
+ if (timeid.current) clearTimeout(timeid.current);
54
+ };
55
+ }, [controlsVisible, controlsTimer, setControlsVisible, fadeAnim]);
56
+
57
+ const containerPadding: ViewStyle = {
58
+ paddingTop: top,
59
+ paddingBottom: bottom + moderateScale(5),
60
+ paddingLeft: left + moderateScale(10),
61
+ paddingRight: right + moderateScale(10),
62
+ };
63
+
64
+ return (
65
+ <View style={globalStyles.flexOne}>
66
+ {children}
67
+ <Animated.View
68
+ pointerEvents={controlsVisible ? 'auto' : 'none'}
69
+ style={[StyleSheet.absoluteFillObject, { opacity: fadeAnim }]}
70
+ >
71
+ <TouchableOpacity
72
+ onPress={() => setControlsVisible(false)}
73
+ style={[styles.container, containerPadding]}
74
+ activeOpacity={1}
75
+ >
76
+ <MediaControls onClose={onClose} />
77
+ </TouchableOpacity>
78
+ </Animated.View>
79
+ <SettingModal
80
+ onPressEpisode={onPressEpisode}
81
+ reportProgress={reportProgress}
82
+ />
83
+ <SubtitleView />
84
+ <SkipAndNextControls
85
+ onPressEpisode={onPressEpisode}
86
+ reportProgress={reportProgress}
87
+ />
88
+ </View>
89
+ );
90
+ };
91
+
92
+ export default MediaControlsProvider;
93
+
94
+ const styles = StyleSheet.create({
95
+ container: {
96
+ flex: 1,
97
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
98
+ },
99
+ });
@@ -0,0 +1,232 @@
1
+ /* eslint-disable react-native/no-inline-styles */
2
+ import React, { useState, useRef } from 'react';
3
+ import {
4
+ ActivityIndicator,
5
+ StyleSheet,
6
+ Text,
7
+ TouchableOpacity,
8
+ View,
9
+ Animated,
10
+ Easing,
11
+ } from 'react-native';
12
+ import { moderateScale, scale } from 'react-native-size-matters';
13
+ import { RFValue } from 'react-native-responsive-fontsize';
14
+ import { Play, Pause, RotateCcw, RotateCw } from 'lucide-react-native';
15
+ import { useVideoPlayerStore } from '../store/videoPlayerStore';
16
+ import { videoRef } from '../utils/videoRef';
17
+ import { handlePause } from '../utils';
18
+ import { useVideoPlayerConfig } from '../context';
19
+
20
+ const BUTTON_SIZE = moderateScale(80);
21
+ const SMALL_BUTTON_SIZE = moderateScale(60);
22
+ const FEEDBACK_DURATION = 500;
23
+ const MOVE_DISTANCE = 60;
24
+
25
+ const MiddleControls = () => {
26
+ const {
27
+ isPaused,
28
+ isBuffering,
29
+ setStartWatchTime,
30
+ error,
31
+ setIsPaused,
32
+ duration,
33
+ currentTime,
34
+ setCurrentTime,
35
+ } = useVideoPlayerStore();
36
+ const { colors } = useVideoPlayerConfig();
37
+
38
+ if (error) {
39
+ return (
40
+ <View style={[StyleSheet.absoluteFillObject, styles.container]}>
41
+ <Text style={[styles.errorText, { color: colors.text }]}>{error}</Text>
42
+ </View>
43
+ );
44
+ }
45
+
46
+ const handleSkip = (delta: number) => {
47
+ let newTime = currentTime + delta;
48
+ if (newTime < 0) newTime = 0;
49
+ if (newTime > duration) newTime = duration;
50
+ videoRef?.current?.seek(newTime);
51
+ setCurrentTime(newTime);
52
+ };
53
+ const isVideoEnded = duration > 0 && currentTime >= duration;
54
+
55
+ return (
56
+ <View style={[StyleSheet.absoluteFillObject, styles.container]}>
57
+ {/* Backward */}
58
+ <SkipButton
59
+ direction="backward"
60
+ onPress={({ second }) => handleSkip(-second)}
61
+ colors={colors}
62
+ disabled={currentTime <= 0}
63
+ />
64
+
65
+ {/* Play / Pause */}
66
+ <TouchableOpacity
67
+ onPress={() => {
68
+ if (isBuffering) return;
69
+
70
+ if (isVideoEnded) {
71
+ videoRef.current?.seek(0);
72
+ setCurrentTime(0);
73
+ setIsPaused(false);
74
+ setStartWatchTime(Date.now());
75
+ return;
76
+ }
77
+
78
+ if (!isPaused) {
79
+ handlePause();
80
+ setIsPaused(true);
81
+ } else {
82
+ setIsPaused(false);
83
+ setStartWatchTime(Date.now());
84
+ }
85
+ }}
86
+ style={[styles.button, styles.playButton]}
87
+ activeOpacity={0.8}
88
+ >
89
+ {isBuffering ? (
90
+ <ActivityIndicator size={scale(50)} color={colors.text} />
91
+ ) : isPaused ? (
92
+ <Play size={scale(50)} color={colors.text} />
93
+ ) : (
94
+ <Pause size={scale(50)} color={colors.text} />
95
+ )}
96
+ </TouchableOpacity>
97
+
98
+ {/* Forward */}
99
+ <SkipButton
100
+ direction="forward"
101
+ onPress={({ second }) => handleSkip(second)}
102
+ colors={colors}
103
+ disabled={currentTime >= duration}
104
+ />
105
+ </View>
106
+ );
107
+ };
108
+
109
+ export default MiddleControls;
110
+
111
+ type SkipButtonProps = {
112
+ direction: 'forward' | 'backward';
113
+ onPress: ({ second }: { second: number }) => void;
114
+ colors: { text: string };
115
+ disabled?: boolean;
116
+ };
117
+
118
+ const SkipButton: React.FC<SkipButtonProps> = ({
119
+ direction,
120
+ onPress,
121
+ colors,
122
+ disabled,
123
+ }) => {
124
+ const [step, setStep] = useState(0);
125
+ const anim = useRef(new Animated.Value(0)).current;
126
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
127
+
128
+ const handlePress = () => {
129
+ if (disabled) return;
130
+
131
+ const newStep = step + 10;
132
+ setStep(newStep);
133
+
134
+ onPress({ second: newStep });
135
+
136
+ anim.setValue(0);
137
+ Animated.timing(anim, {
138
+ toValue: 1,
139
+ duration: FEEDBACK_DURATION,
140
+ easing: Easing.out(Easing.quad),
141
+ useNativeDriver: true,
142
+ }).start();
143
+
144
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
145
+ timeoutRef.current = setTimeout(() => setStep(0), FEEDBACK_DURATION);
146
+ };
147
+
148
+ const translateX = anim.interpolate({
149
+ inputRange: [0, 1],
150
+ outputRange:
151
+ direction === 'forward'
152
+ ? [SMALL_BUTTON_SIZE, SMALL_BUTTON_SIZE + MOVE_DISTANCE]
153
+ : [-SMALL_BUTTON_SIZE, -SMALL_BUTTON_SIZE - MOVE_DISTANCE],
154
+ });
155
+
156
+ const opacity = anim.interpolate({
157
+ inputRange: [0, 0.8, 1],
158
+ outputRange: [1, 1, 0],
159
+ });
160
+
161
+ const scaleAnim = anim.interpolate({
162
+ inputRange: [0, 0.5, 1],
163
+ outputRange: [1, 1.3, 1],
164
+ });
165
+
166
+ return (
167
+ <TouchableOpacity
168
+ onPress={handlePress}
169
+ style={[styles.button, styles.smallButton, disabled && { opacity: 0.3 }]}
170
+ activeOpacity={disabled ? 1 : 0.7}
171
+ disabled={disabled}
172
+ >
173
+ {direction === 'forward' ? (
174
+ <RotateCw size={scale(35)} color={colors.text} />
175
+ ) : (
176
+ <RotateCcw size={scale(35)} color={colors.text} />
177
+ )}
178
+
179
+ {step > 0 && !disabled && (
180
+ <Animated.View
181
+ style={[
182
+ styles.feedbackOverlay,
183
+ { transform: [{ translateX }, { scale: scaleAnim }], opacity },
184
+ ]}
185
+ >
186
+ <Text style={[styles.feedbackText, { color: colors.text }]}>
187
+ {step}s
188
+ </Text>
189
+ </Animated.View>
190
+ )}
191
+ </TouchableOpacity>
192
+ );
193
+ };
194
+
195
+ const styles = StyleSheet.create({
196
+ container: {
197
+ flex: 1,
198
+ flexDirection: 'row',
199
+ justifyContent: 'center',
200
+ alignItems: 'center',
201
+ },
202
+ button: {
203
+ justifyContent: 'center',
204
+ alignItems: 'center',
205
+ borderRadius: 100,
206
+ },
207
+ playButton: {
208
+ width: BUTTON_SIZE,
209
+ height: BUTTON_SIZE,
210
+ marginHorizontal: moderateScale(14),
211
+ },
212
+ smallButton: {
213
+ width: SMALL_BUTTON_SIZE,
214
+ height: SMALL_BUTTON_SIZE,
215
+ marginHorizontal: moderateScale(10),
216
+ },
217
+ errorText: {
218
+ fontSize: RFValue(18),
219
+ fontWeight: '600',
220
+ textAlign: 'center',
221
+ paddingHorizontal: scale(20),
222
+ },
223
+ feedbackOverlay: {
224
+ position: 'absolute',
225
+ justifyContent: 'center',
226
+ alignItems: 'center',
227
+ },
228
+ feedbackText: {
229
+ fontSize: RFValue(15),
230
+ fontWeight: '600',
231
+ },
232
+ });
@@ -0,0 +1,92 @@
1
+ import React from 'react';
2
+ import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
3
+ import { moderateScale } from 'react-native-size-matters';
4
+ import { RFValue } from 'react-native-responsive-fontsize';
5
+ import { X } from 'lucide-react-native';
6
+ import { useVideoPlayerStore } from '../store/videoPlayerStore';
7
+ import { lockToPortrait } from '../utils/lockOrientation';
8
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
9
+ import { useVideoPlayerConfig } from '../context';
10
+
11
+ interface TopControlsProps {
12
+ onClose?: () => void;
13
+ }
14
+
15
+ const TopControls: React.FC<TopControlsProps> = ({ onClose }) => {
16
+ const { activeTrack, resetStore } = useVideoPlayerStore();
17
+ const { top } = useSafeAreaInsets();
18
+ const { colors } = useVideoPlayerConfig();
19
+ const handleClose = () => {
20
+ onClose?.();
21
+ resetStore();
22
+ lockToPortrait();
23
+ };
24
+
25
+ const showSubtitle =
26
+ activeTrack?.isTrailer ||
27
+ activeTrack?.seasonNumber !== undefined ||
28
+ activeTrack?.episodeNumber !== undefined;
29
+
30
+ return (
31
+ <View style={[styles.container, { paddingTop: top + moderateScale(10) }]}>
32
+ <View style={styles.textContainer}>
33
+ <Text
34
+ numberOfLines={1}
35
+ ellipsizeMode="tail"
36
+ style={[styles.title, { color: colors.text }]}
37
+ >
38
+ {activeTrack?.title || 'Unknown'}
39
+ </Text>
40
+ {showSubtitle && (
41
+ <Text style={[styles.subtitle, { color: colors.text }]}>
42
+ {activeTrack?.isTrailer
43
+ ? 'Trailer'
44
+ : `${activeTrack?.seasonNumber ? `S${activeTrack.seasonNumber} ` : ''}${activeTrack?.episodeNumber ? `E${activeTrack.episodeNumber}` : ''}`}
45
+ </Text>
46
+ )}
47
+ </View>
48
+
49
+ <TouchableOpacity
50
+ style={styles.closeButton}
51
+ onPress={handleClose}
52
+ accessibilityRole="button"
53
+ accessibilityLabel="Close player"
54
+ accessibilityHint="Closes the video player and exits"
55
+ >
56
+ <X size={moderateScale(25)} color={colors.text} />
57
+ </TouchableOpacity>
58
+ </View>
59
+ );
60
+ };
61
+
62
+ export default TopControls;
63
+
64
+ const styles = StyleSheet.create({
65
+ container: {
66
+ flexDirection: 'row',
67
+ justifyContent: 'space-between',
68
+ alignItems: 'center',
69
+ paddingHorizontal: moderateScale(16),
70
+ zIndex: 51,
71
+ },
72
+ textContainer: {
73
+ flex: 1,
74
+ paddingRight: moderateScale(10),
75
+ },
76
+ title: {
77
+ fontSize: RFValue(18),
78
+ fontWeight: '600',
79
+ },
80
+ subtitle: {
81
+ fontSize: RFValue(14),
82
+ fontWeight: '400',
83
+ marginTop: moderateScale(2),
84
+ },
85
+ closeButton: {
86
+ width: moderateScale(40),
87
+ height: moderateScale(40),
88
+ justifyContent: 'center',
89
+ alignItems: 'center',
90
+ borderRadius: moderateScale(20),
91
+ },
92
+ });