@zezosoft/zezo-ott-react-native-video-player 1.0.4 → 1.0.5

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 (65) hide show
  1. package/package.json +1 -2
  2. package/src/AdsPlayer/AdsPlayer.tsx +0 -311
  3. package/src/AdsPlayer/MediaControls/AdBottomControls.tsx +0 -191
  4. package/src/AdsPlayer/MediaControls/AdMediaControls.tsx +0 -104
  5. package/src/AdsPlayer/MediaControls/AdMediaControlsProvider.tsx +0 -62
  6. package/src/AdsPlayer/MediaControls/AdMiddleControls.tsx +0 -63
  7. package/src/AdsPlayer/MediaControls/AdTopControls.tsx +0 -191
  8. package/src/AdsPlayer/MediaControls/index.ts +0 -5
  9. package/src/AdsPlayer/components/RotatingLoader.tsx +0 -79
  10. package/src/AdsPlayer/index.ts +0 -4
  11. package/src/AdsPlayer/store/adsPlayer.type.ts +0 -29
  12. package/src/AdsPlayer/store/adsPlayerStore.ts +0 -59
  13. package/src/AdsPlayer/store/index.ts +0 -2
  14. package/src/AdsPlayer/utils/adStateReset.ts +0 -29
  15. package/src/AdsPlayer/utils/controls.ts +0 -69
  16. package/src/AdsPlayer/utils/useAdControlsAutoHide.ts +0 -32
  17. package/src/AdsPlayer/utils/useAdInitialization.ts +0 -86
  18. package/src/AdsPlayer/utils/useAdTracking.ts +0 -89
  19. package/src/AdsPlayer/utils/useAdsManager.ts +0 -215
  20. package/src/VideoPlayer/MediaControls/BottomControls.tsx +0 -210
  21. package/src/VideoPlayer/MediaControls/MediaControls.tsx +0 -30
  22. package/src/VideoPlayer/MediaControls/MediaControlsProvider.tsx +0 -104
  23. package/src/VideoPlayer/MediaControls/MiddleControls.tsx +0 -259
  24. package/src/VideoPlayer/MediaControls/TopControls.tsx +0 -100
  25. package/src/VideoPlayer/Settings/AudioAndSubtitles.tsx +0 -295
  26. package/src/VideoPlayer/Settings/Episodes.tsx +0 -297
  27. package/src/VideoPlayer/Settings/SettingModal.tsx +0 -127
  28. package/src/VideoPlayer/Settings/SpeedControls.tsx +0 -130
  29. package/src/VideoPlayer/Settings/VideoPlayerSettings.tsx +0 -141
  30. package/src/VideoPlayer/VideoPlayerCore.tsx +0 -356
  31. package/src/VideoPlayer/components/ProgressBar.tsx +0 -211
  32. package/src/VideoPlayer/components/SkipAndNextControls.tsx +0 -192
  33. package/src/VideoPlayer/components/SubtitleView.tsx +0 -53
  34. package/src/VideoPlayer/components/Toast.tsx +0 -61
  35. package/src/VideoPlayer/context/VideoPlayerConfig.tsx +0 -65
  36. package/src/VideoPlayer/context/index.ts +0 -5
  37. package/src/VideoPlayer/index.ts +0 -4
  38. package/src/VideoPlayer/store/index.ts +0 -2
  39. package/src/VideoPlayer/store/videoPlayer.type.ts +0 -214
  40. package/src/VideoPlayer/store/videoPlayerStore.ts +0 -97
  41. package/src/VideoPlayer/styles/globalStyles.ts +0 -73
  42. package/src/VideoPlayer/utils/display/Display.ts +0 -10
  43. package/src/VideoPlayer/utils/display/index.ts +0 -1
  44. package/src/VideoPlayer/utils/format/index.ts +0 -1
  45. package/src/VideoPlayer/utils/format/timeFormatter.ts +0 -44
  46. package/src/VideoPlayer/utils/hooks/index.ts +0 -5
  47. package/src/VideoPlayer/utils/hooks/useAdEventHandler.ts +0 -95
  48. package/src/VideoPlayer/utils/hooks/useOrientationLock.ts +0 -29
  49. package/src/VideoPlayer/utils/hooks/usePauseVideoOnAd.ts +0 -46
  50. package/src/VideoPlayer/utils/hooks/useVideoPlayerBack.ts +0 -66
  51. package/src/VideoPlayer/utils/hooks/useVideoResolutions.ts +0 -125
  52. package/src/VideoPlayer/utils/index.ts +0 -6
  53. package/src/VideoPlayer/utils/platform/PlatformSelector.ts +0 -13
  54. package/src/VideoPlayer/utils/platform/index.ts +0 -2
  55. package/src/VideoPlayer/utils/platform/lockOrientation.ts +0 -40
  56. package/src/VideoPlayer/utils/player/index.ts +0 -2
  57. package/src/VideoPlayer/utils/player/playerEvents.ts +0 -97
  58. package/src/VideoPlayer/utils/player/useWatchReporter.ts +0 -105
  59. package/src/VideoPlayer/utils/video/index.ts +0 -5
  60. package/src/VideoPlayer/utils/video/videoControl.ts +0 -185
  61. package/src/VideoPlayer/utils/video/videoRef.ts +0 -21
  62. package/src/VideoPlayer/utils/video/videoResume.ts +0 -23
  63. package/src/VideoPlayer/utils/video/videoSource.ts +0 -23
  64. package/src/VideoPlayer.tsx +0 -181
  65. package/src/index.tsx +0 -3
@@ -1,89 +0,0 @@
1
- import { useCallback } from 'react';
2
- import type { VideoAd } from '../../VideoPlayer/store/videoPlayer.type';
3
-
4
- interface UseAdTrackingParams {
5
- onAdTracking?: ({
6
- ad,
7
- trackingUrl,
8
- event,
9
- }: {
10
- ad: VideoAd;
11
- trackingUrl: string;
12
- event: string;
13
- }) => void;
14
- }
15
-
16
- /**
17
- * Reusable hook for ad tracking
18
- * Consolidates ad tracking logic across components
19
- */
20
- export const useAdTracking = ({ onAdTracking }: UseAdTrackingParams = {}) => {
21
- const trackAdEvent = useCallback(
22
- (ad: VideoAd, event: string, trackingUrl?: string) => {
23
- if (!onAdTracking) return;
24
-
25
- const url =
26
- trackingUrl || ad.tracking?.[event as keyof typeof ad.tracking];
27
- if (url) {
28
- onAdTracking({
29
- ad,
30
- trackingUrl: url,
31
- event,
32
- });
33
- }
34
- },
35
- [onAdTracking]
36
- );
37
-
38
- const trackAdImpression = useCallback(
39
- (ad: VideoAd) => {
40
- trackAdEvent(ad, 'impression', ad.tracking?.impression);
41
- },
42
- [trackAdEvent]
43
- );
44
-
45
- const trackAdStart = useCallback(
46
- (ad: VideoAd) => {
47
- trackAdEvent(ad, 'start', ad.tracking?.start);
48
- },
49
- [trackAdEvent]
50
- );
51
-
52
- const trackAdComplete = useCallback(
53
- (ad: VideoAd) => {
54
- trackAdEvent(ad, 'complete', ad.tracking?.complete);
55
- },
56
- [trackAdEvent]
57
- );
58
-
59
- const trackAdSkip = useCallback(
60
- (ad: VideoAd) => {
61
- trackAdEvent(ad, 'skip', ad.tracking?.skip);
62
- },
63
- [trackAdEvent]
64
- );
65
-
66
- const trackAdQuartile = useCallback(
67
- (ad: VideoAd, quartile: 'firstQuartile' | 'midpoint' | 'thirdQuartile') => {
68
- trackAdEvent(ad, quartile, ad.tracking?.[quartile]);
69
- },
70
- [trackAdEvent]
71
- );
72
-
73
- const trackAdClick = useCallback(
74
- (ad: VideoAd) => {
75
- trackAdEvent(ad, 'click', ad.tracking?.click);
76
- },
77
- [trackAdEvent]
78
- );
79
-
80
- return {
81
- trackAdEvent,
82
- trackAdImpression,
83
- trackAdStart,
84
- trackAdComplete,
85
- trackAdSkip,
86
- trackAdQuartile,
87
- trackAdClick,
88
- };
89
- };
@@ -1,215 +0,0 @@
1
- import { useEffect, useRef } from 'react';
2
- import { useVideoPlayerStore } from '../../VideoPlayer/store/videoPlayerStore';
3
- import { useAdsPlayerStore } from '../store/adsPlayerStore';
4
- import type { VideoAd } from '../../VideoPlayer/store/videoPlayer.type';
5
- import { useAdInitialization } from './useAdInitialization';
6
-
7
- export const useAdsManager = (
8
- ads: VideoAd[] = [],
9
- onAdTracking?: ({
10
- ad,
11
- trackingUrl,
12
- event,
13
- }: {
14
- ad: VideoAd;
15
- trackingUrl: string;
16
- event: string;
17
- }) => void
18
- ) => {
19
- const { currentTime, activeTrack } = useVideoPlayerStore();
20
-
21
- const {
22
- currentAd,
23
- isAdPlaying,
24
- pendingMidRollAds,
25
- addPendingMidRollAd,
26
- clearPendingMidRollAds,
27
- } = useAdsPlayerStore();
28
-
29
- const { startAd } = useAdInitialization({ onAdTracking });
30
-
31
- const adsInitializedRef = useRef(false);
32
- const previousAdsRef = useRef<string>('');
33
- const playedMidRollAdsRef = useRef<Set<string>>(new Set());
34
- const playedPreRollAdsRef = useRef<Set<string>>(new Set());
35
- const playedPostRollAdsRef = useRef<Set<string>>(new Set());
36
- const previousTimeRef = useRef<number>(0);
37
- const isResettingRef = useRef(false);
38
-
39
- useEffect(() => {
40
- // Prevent infinite loops
41
- if (isResettingRef.current) {
42
- return;
43
- }
44
-
45
- const adsKey = JSON.stringify(ads?.map((ad) => ad.id) || []);
46
- const hadAds = previousAdsRef.current !== '';
47
- const hasAds = ads && ads.length > 0;
48
-
49
- if (hasAds && adsKey !== previousAdsRef.current) {
50
- previousAdsRef.current = adsKey;
51
-
52
- // Don't initialize ads if it's a trailer
53
- if (activeTrack?.isTrailer) {
54
- return;
55
- }
56
-
57
- const preRollAds = ads.filter((ad) => ad.position === 'pre');
58
- const midRollAds = ads.filter((ad) => ad.position === 'mid');
59
-
60
- if (preRollAds.length > 0 && preRollAds[0] && !currentAd) {
61
- try {
62
- playedPreRollAdsRef.current.add(preRollAds[0].id);
63
- startAd(preRollAds[0], { trackImpression: false }); // Impression tracked in AdsPlayer
64
- } catch (error) {
65
- console.error('Error starting pre-roll ad:', error);
66
- }
67
- }
68
-
69
- if (!adsInitializedRef.current) {
70
- midRollAds.forEach((ad) => {
71
- if (ad.time !== undefined) {
72
- addPendingMidRollAd(ad);
73
- }
74
- });
75
- adsInitializedRef.current = true;
76
- }
77
- } else if (!hasAds && hadAds) {
78
- // Only reset when ads change from non-empty to empty
79
- isResettingRef.current = true;
80
-
81
- // Clear all ad state when ads array becomes empty
82
- adsInitializedRef.current = false;
83
- previousAdsRef.current = '';
84
- clearPendingMidRollAds();
85
-
86
- // Reset played ads tracking
87
- playedMidRollAdsRef.current.clear();
88
- playedPreRollAdsRef.current.clear();
89
- playedPostRollAdsRef.current.clear();
90
-
91
- // If an ad is currently playing, stop it
92
- const {
93
- isAdPlaying: currentIsAdPlaying,
94
- currentAd: currentAdState,
95
- resetAdsStore,
96
- } = useAdsPlayerStore.getState();
97
- if (currentIsAdPlaying || currentAdState) {
98
- resetAdsStore();
99
- }
100
-
101
- // Reset the flag after a short delay to allow state to settle
102
- setTimeout(() => {
103
- isResettingRef.current = false;
104
- }, 0);
105
- } else if (!hasAds) {
106
- // If ads are empty and were already empty, just update the ref
107
- previousAdsRef.current = '';
108
- }
109
- }, [
110
- ads,
111
- currentAd,
112
- addPendingMidRollAd,
113
- clearPendingMidRollAds,
114
- startAd,
115
- activeTrack?.isTrailer,
116
- ]);
117
-
118
- useEffect(() => {
119
- // Don't trigger mid-roll ads if it's a trailer
120
- if (activeTrack?.isTrailer) {
121
- return;
122
- }
123
-
124
- if (
125
- !isAdPlaying &&
126
- pendingMidRollAds.length > 0 &&
127
- currentTime > 0 &&
128
- !currentAd
129
- ) {
130
- const previousTime = previousTimeRef.current;
131
- const timeDifference = Math.abs(currentTime - previousTime);
132
- const isSeeking = timeDifference > 2; // If time jumps more than 2 seconds, it's a seek
133
-
134
- // Check for ads that should be triggered
135
- let nextMidRollAd = pendingMidRollAds.find(
136
- (ad) =>
137
- ad.time &&
138
- !playedMidRollAdsRef.current.has(ad.id) &&
139
- currentTime >= ad.time - 1 &&
140
- currentTime <= ad.time + 1
141
- );
142
-
143
- // If seeking, check if we skipped past any ads
144
- if (!nextMidRollAd && isSeeking && previousTime < currentTime) {
145
- const skippedAd = pendingMidRollAds.find(
146
- (ad) =>
147
- ad.time &&
148
- !playedMidRollAdsRef.current.has(ad.id) &&
149
- ad.time > previousTime &&
150
- ad.time <= currentTime
151
- );
152
- if (skippedAd) {
153
- nextMidRollAd = skippedAd;
154
- }
155
- }
156
-
157
- if (nextMidRollAd) {
158
- try {
159
- playedMidRollAdsRef.current.add(nextMidRollAd.id);
160
- const timeBeforeAd = isSeeking
161
- ? nextMidRollAd.time || currentTime
162
- : currentTime;
163
- startAd(nextMidRollAd, {
164
- resumeTime: timeBeforeAd,
165
- trackImpression: false, // Impression tracked in AdsPlayer
166
- });
167
- const remainingAds = pendingMidRollAds.filter(
168
- (ad) => ad.id !== nextMidRollAd.id
169
- );
170
- clearPendingMidRollAds();
171
- remainingAds.forEach((ad) => addPendingMidRollAd(ad));
172
- } catch (error) {
173
- console.error('Error starting mid-roll ad:', error);
174
- }
175
- }
176
-
177
- // Update previous time
178
- previousTimeRef.current = currentTime;
179
- } else if (currentTime > 0) {
180
- // Update previous time even if conditions aren't met
181
- previousTimeRef.current = currentTime;
182
- }
183
- }, [
184
- currentTime,
185
- isAdPlaying,
186
- pendingMidRollAds,
187
- currentAd,
188
- ads,
189
- addPendingMidRollAd,
190
- clearPendingMidRollAds,
191
- startAd,
192
- activeTrack?.isTrailer,
193
- ]);
194
-
195
- return {
196
- checkPostRollAds: () => {
197
- try {
198
- // Don't show post-roll ads if it's a trailer
199
- if (activeTrack?.isTrailer) {
200
- return false;
201
- }
202
- const postRollAds = ads.filter((ad) => ad.position === 'post');
203
- if (postRollAds.length > 0 && postRollAds[0]) {
204
- playedPostRollAdsRef.current.add(postRollAds[0].id);
205
- startAd(postRollAds[0], { trackImpression: false }); // Impression tracked in AdsPlayer
206
- return true;
207
- }
208
- return false;
209
- } catch (error) {
210
- console.error('Error checking post-roll ads:', error);
211
- return false;
212
- }
213
- },
214
- };
215
- };
@@ -1,210 +0,0 @@
1
- import React, { useCallback, useMemo } 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 { useAdsPlayerStore } from '../../AdsPlayer/store/adsPlayerStore';
23
- import { videoRef } from '../utils';
24
- import type { SettingsAction } from '../store/videoPlayer.type';
25
- import { formatTime } from '../utils';
26
- import ProgressBar from '../components/ProgressBar';
27
- import { useVideoPlayerConfig } from '../context';
28
-
29
- type ControlButtonProps = {
30
- onPress: (event: GestureResponderEvent) => void;
31
- icon: React.ReactNode;
32
- label: string;
33
- accessibilityLabel?: string;
34
- buttonStyle?: ViewStyle;
35
- labelStyle?: TextStyle;
36
- colors: {
37
- text: string;
38
- };
39
- };
40
-
41
- const ControlButton: React.FC<ControlButtonProps> = ({
42
- onPress,
43
- icon,
44
- label,
45
- accessibilityLabel,
46
- buttonStyle,
47
- labelStyle,
48
- colors,
49
- }) => (
50
- <TouchableOpacity
51
- onPress={onPress}
52
- style={[styles.button, buttonStyle]}
53
- activeOpacity={0.8}
54
- accessibilityLabel={accessibilityLabel ?? label}
55
- >
56
- {icon}
57
- <Text style={[styles.buttonText, labelStyle, { color: colors.text }]}>
58
- {label}
59
- </Text>
60
- </TouchableOpacity>
61
- );
62
-
63
- const BottomControls = () => {
64
- const {
65
- duration,
66
- currentTime,
67
- activeTrack,
68
- playableDuration,
69
- setSettingsModal,
70
- setIsPaused,
71
- setControlsVisible,
72
- playBackRateLabel,
73
- } = useVideoPlayerStore();
74
-
75
- const { pendingMidRollAds } = useAdsPlayerStore();
76
- const { colors } = useVideoPlayerConfig();
77
- const getRemainingTime = useCallback(
78
- (totalDuration: number, currentPos: number) => {
79
- const remainingTime = totalDuration - currentPos;
80
- return formatTime(Math.max(remainingTime, 0));
81
- },
82
- []
83
- );
84
-
85
- const onSeek = (value: number) => {
86
- videoRef.current?.seek(value);
87
- };
88
-
89
- const onOpenSettings = (type: SettingsAction) => {
90
- setSettingsModal({ action: type, isVisible: true });
91
- setIsPaused(true);
92
- setControlsVisible(false);
93
- };
94
-
95
- // Memoize ad markers to avoid recalculation on every render
96
- const adMarkers = useMemo(
97
- () =>
98
- pendingMidRollAds
99
- .filter((ad) => ad.time !== undefined && ad.time > 0)
100
- .map((ad) => ad.time as number)
101
- .sort((a, b) => a - b),
102
- [pendingMidRollAds]
103
- );
104
-
105
- // Memoize time text style
106
- const timeTextStyle = useMemo(
107
- () => [styles.timeText, { color: colors.text }],
108
- [colors.text]
109
- );
110
-
111
- return (
112
- <View style={styles.root}>
113
- {/* Progress Bar Section */}
114
- <View style={styles.sliderContainer}>
115
- <View style={styles.sliderWrapper}>
116
- <ProgressBar
117
- duration={duration}
118
- currentTime={currentTime}
119
- bufferedTime={playableDuration}
120
- onSeek={onSeek}
121
- adMarkers={adMarkers}
122
- />
123
- </View>
124
- <Text style={timeTextStyle} numberOfLines={1}>
125
- {getRemainingTime(duration, currentTime)}
126
- </Text>
127
- </View>
128
-
129
- {/* Action Buttons */}
130
- <View style={styles.buttonsContainer}>
131
- <ControlButton
132
- onPress={() => onOpenSettings('speed')}
133
- icon={<SpeedIcon size={moderateScale(18)} color={colors.text} />}
134
- label={
135
- playBackRateLabel && playBackRateLabel !== 'Normal'
136
- ? `Speed (${playBackRateLabel})`
137
- : 'Speed'
138
- }
139
- colors={colors}
140
- />
141
- <ControlButton
142
- onPress={() => onOpenSettings('audioOrSubtitle')}
143
- icon={<SubtitlesIcon size={moderateScale(19)} color={colors.text} />}
144
- label="Audio & Subtitles"
145
- colors={colors}
146
- />
147
- <ControlButton
148
- onPress={() => onOpenSettings('settings')}
149
- icon={<SettingsIcon size={moderateScale(18)} color={colors.text} />}
150
- label="Settings"
151
- colors={colors}
152
- />
153
- {!activeTrack?.isTrailer && activeTrack?.type === 'series' && (
154
- <ControlButton
155
- onPress={() => onOpenSettings('episodes')}
156
- icon={<EpisodesIcon size={moderateScale(17)} color={colors.text} />}
157
- label="Episodes"
158
- colors={colors}
159
- />
160
- )}
161
- </View>
162
- </View>
163
- );
164
- };
165
-
166
- export default BottomControls;
167
-
168
- const styles = StyleSheet.create({
169
- root: {
170
- paddingTop: verticalScale(8),
171
- paddingBottom: verticalScale(4),
172
- },
173
- sliderContainer: {
174
- flexDirection: 'row',
175
- alignItems: 'center',
176
- paddingHorizontal: scale(12),
177
- marginBottom: verticalScale(4),
178
- },
179
- sliderWrapper: {
180
- flex: 1,
181
- minWidth: 0,
182
- },
183
- timeText: {
184
- minWidth: scale(52),
185
- maxWidth: scale(56),
186
- textAlign: 'right',
187
- marginLeft: scale(6),
188
- fontSize: RFValue(12),
189
- fontWeight: '500',
190
- },
191
- buttonsContainer: {
192
- flexDirection: 'row',
193
- flexWrap: 'wrap',
194
- justifyContent: 'center',
195
- alignContent: 'center',
196
- paddingHorizontal: scale(10),
197
- gap: scale(4),
198
- },
199
- button: {
200
- flexDirection: 'row',
201
- alignItems: 'center',
202
- paddingHorizontal: scale(12),
203
- paddingVertical: verticalScale(6),
204
- borderRadius: moderateScale(8),
205
- },
206
- buttonText: {
207
- fontSize: RFValue(13),
208
- marginLeft: scale(6),
209
- },
210
- });
@@ -1,30 +0,0 @@
1
- import { memo } from 'react';
2
- import { StyleSheet, View } 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
- <View style={styles.container}>
14
- <TopControls onClose={onClose} />
15
- <MiddleControls />
16
- <BottomControls />
17
- </View>
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
- minHeight: 0,
29
- },
30
- });
@@ -1,104 +0,0 @@
1
- import React, { useEffect, useRef, useMemo } from 'react';
2
- import { TouchableOpacity, View, type ViewStyle, Animated } from 'react-native';
3
- import { type EdgeInsets } from 'react-native-safe-area-context';
4
- import { moderateScale } from 'react-native-size-matters';
5
- import MediaControls, { type MediaControlsProps } from './MediaControls';
6
- import SettingModal from '../Settings/SettingModal';
7
- import { useVideoPlayerStore } from '../store/videoPlayerStore';
8
- import SkipAndNextControls from '../components/SkipAndNextControls';
9
- import type { MediaEpisode } from '../store/videoPlayer.type';
10
- import globalStyles from '../styles/globalStyles';
11
- import type { ExtendedWatchProgress } from '../utils';
12
- import SubtitleView from '../components/SubtitleView';
13
-
14
- const HORIZONTAL_PADDING = moderateScale(12);
15
- const BOTTOM_PADDING_EXTRA = moderateScale(8);
16
- const MIN_TOP_PADDING = moderateScale(10);
17
-
18
- interface MediaControlsProviderProps extends MediaControlsProps {
19
- children: React.ReactNode;
20
- onPressEpisode: ({ episode }: { episode: MediaEpisode }) => Promise<boolean>;
21
- reportProgress: (event: ExtendedWatchProgress['event']) => void;
22
- insets: EdgeInsets;
23
- }
24
-
25
- const MediaControlsProvider: React.FC<MediaControlsProviderProps> = ({
26
- children,
27
- onClose,
28
- onPressEpisode,
29
- reportProgress,
30
- insets,
31
- }) => {
32
- const { top, bottom, left, right } = insets;
33
- const controlsVisible = useVideoPlayerStore((state) => state.controlsVisible);
34
- const setControlsVisible = useVideoPlayerStore(
35
- (state) => state.setControlsVisible
36
- );
37
- const controlsTimer = useVideoPlayerStore((state) => state.controlsTimer);
38
- const timeoutId = React.useRef<NodeJS.Timeout | null>(null);
39
- const fadeAnim = useRef(new Animated.Value(0)).current;
40
-
41
- // Safe-area padding: never negative, single source of truth (MediaControls uses View, not SafeAreaView)
42
- const containerPadding: ViewStyle = useMemo(
43
- () => ({
44
- paddingTop: top - MIN_TOP_PADDING,
45
- paddingBottom: bottom + BOTTOM_PADDING_EXTRA,
46
- paddingLeft: left + HORIZONTAL_PADDING,
47
- paddingRight: right + HORIZONTAL_PADDING,
48
- }),
49
- [top, bottom, left, right]
50
- );
51
-
52
- // Memoize animated style
53
- const animatedStyle = useMemo(
54
- () => [globalStyles.absoluteFill, { opacity: fadeAnim }],
55
- [fadeAnim]
56
- );
57
-
58
- useEffect(() => {
59
- Animated.timing(fadeAnim, {
60
- toValue: controlsVisible ? 1 : 0,
61
- duration: 200,
62
- useNativeDriver: true,
63
- }).start();
64
-
65
- if (controlsVisible) {
66
- timeoutId.current = setTimeout(() => {
67
- setControlsVisible(false);
68
- }, controlsTimer * 1000);
69
- }
70
-
71
- return () => {
72
- if (timeoutId.current) clearTimeout(timeoutId.current);
73
- };
74
- }, [controlsVisible, controlsTimer, setControlsVisible, fadeAnim]);
75
-
76
- return (
77
- <View style={globalStyles.flexOne}>
78
- {children}
79
- <Animated.View
80
- pointerEvents={controlsVisible ? 'auto' : 'none'}
81
- style={animatedStyle}
82
- >
83
- <TouchableOpacity
84
- onPress={() => setControlsVisible(false)}
85
- style={[globalStyles.controlsContainer, containerPadding]}
86
- activeOpacity={1}
87
- >
88
- <MediaControls onClose={onClose} />
89
- </TouchableOpacity>
90
- </Animated.View>
91
- <SettingModal
92
- onPressEpisode={onPressEpisode}
93
- reportProgress={reportProgress}
94
- />
95
- <SubtitleView />
96
- <SkipAndNextControls
97
- onPressEpisode={onPressEpisode}
98
- reportProgress={reportProgress}
99
- />
100
- </View>
101
- );
102
- };
103
-
104
- export default MediaControlsProvider;