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

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 (73) hide show
  1. package/lib/module/AdsPlayer/store/adsPlayerStore.js +3 -3
  2. package/lib/module/AdsPlayer/store/adsPlayerStore.js.map +1 -1
  3. package/lib/module/VideoPlayer/store/videoPlayerStore.js +3 -3
  4. package/lib/module/VideoPlayer/store/videoPlayerStore.js.map +1 -1
  5. package/lib/typescript/src/AdsPlayer/store/adsPlayerStore.d.ts +2 -3
  6. package/lib/typescript/src/AdsPlayer/store/adsPlayerStore.d.ts.map +1 -1
  7. package/lib/typescript/src/VideoPlayer/store/videoPlayerStore.d.ts +2 -3
  8. package/lib/typescript/src/VideoPlayer/store/videoPlayerStore.d.ts.map +1 -1
  9. package/package.json +5 -3
  10. package/src/AdsPlayer/AdsPlayer.tsx +0 -311
  11. package/src/AdsPlayer/MediaControls/AdBottomControls.tsx +0 -191
  12. package/src/AdsPlayer/MediaControls/AdMediaControls.tsx +0 -104
  13. package/src/AdsPlayer/MediaControls/AdMediaControlsProvider.tsx +0 -62
  14. package/src/AdsPlayer/MediaControls/AdMiddleControls.tsx +0 -63
  15. package/src/AdsPlayer/MediaControls/AdTopControls.tsx +0 -191
  16. package/src/AdsPlayer/MediaControls/index.ts +0 -5
  17. package/src/AdsPlayer/components/RotatingLoader.tsx +0 -79
  18. package/src/AdsPlayer/index.ts +0 -4
  19. package/src/AdsPlayer/store/adsPlayer.type.ts +0 -29
  20. package/src/AdsPlayer/store/adsPlayerStore.ts +0 -59
  21. package/src/AdsPlayer/store/index.ts +0 -2
  22. package/src/AdsPlayer/utils/adStateReset.ts +0 -29
  23. package/src/AdsPlayer/utils/controls.ts +0 -69
  24. package/src/AdsPlayer/utils/useAdControlsAutoHide.ts +0 -32
  25. package/src/AdsPlayer/utils/useAdInitialization.ts +0 -86
  26. package/src/AdsPlayer/utils/useAdTracking.ts +0 -89
  27. package/src/AdsPlayer/utils/useAdsManager.ts +0 -215
  28. package/src/VideoPlayer/MediaControls/BottomControls.tsx +0 -210
  29. package/src/VideoPlayer/MediaControls/MediaControls.tsx +0 -30
  30. package/src/VideoPlayer/MediaControls/MediaControlsProvider.tsx +0 -104
  31. package/src/VideoPlayer/MediaControls/MiddleControls.tsx +0 -259
  32. package/src/VideoPlayer/MediaControls/TopControls.tsx +0 -100
  33. package/src/VideoPlayer/Settings/AudioAndSubtitles.tsx +0 -295
  34. package/src/VideoPlayer/Settings/Episodes.tsx +0 -297
  35. package/src/VideoPlayer/Settings/SettingModal.tsx +0 -127
  36. package/src/VideoPlayer/Settings/SpeedControls.tsx +0 -130
  37. package/src/VideoPlayer/Settings/VideoPlayerSettings.tsx +0 -141
  38. package/src/VideoPlayer/VideoPlayerCore.tsx +0 -356
  39. package/src/VideoPlayer/components/ProgressBar.tsx +0 -211
  40. package/src/VideoPlayer/components/SkipAndNextControls.tsx +0 -192
  41. package/src/VideoPlayer/components/SubtitleView.tsx +0 -53
  42. package/src/VideoPlayer/components/Toast.tsx +0 -61
  43. package/src/VideoPlayer/context/VideoPlayerConfig.tsx +0 -65
  44. package/src/VideoPlayer/context/index.ts +0 -5
  45. package/src/VideoPlayer/index.ts +0 -4
  46. package/src/VideoPlayer/store/index.ts +0 -2
  47. package/src/VideoPlayer/store/videoPlayer.type.ts +0 -214
  48. package/src/VideoPlayer/store/videoPlayerStore.ts +0 -97
  49. package/src/VideoPlayer/styles/globalStyles.ts +0 -73
  50. package/src/VideoPlayer/utils/display/Display.ts +0 -10
  51. package/src/VideoPlayer/utils/display/index.ts +0 -1
  52. package/src/VideoPlayer/utils/format/index.ts +0 -1
  53. package/src/VideoPlayer/utils/format/timeFormatter.ts +0 -44
  54. package/src/VideoPlayer/utils/hooks/index.ts +0 -5
  55. package/src/VideoPlayer/utils/hooks/useAdEventHandler.ts +0 -95
  56. package/src/VideoPlayer/utils/hooks/useOrientationLock.ts +0 -29
  57. package/src/VideoPlayer/utils/hooks/usePauseVideoOnAd.ts +0 -46
  58. package/src/VideoPlayer/utils/hooks/useVideoPlayerBack.ts +0 -66
  59. package/src/VideoPlayer/utils/hooks/useVideoResolutions.ts +0 -125
  60. package/src/VideoPlayer/utils/index.ts +0 -6
  61. package/src/VideoPlayer/utils/platform/PlatformSelector.ts +0 -13
  62. package/src/VideoPlayer/utils/platform/index.ts +0 -2
  63. package/src/VideoPlayer/utils/platform/lockOrientation.ts +0 -40
  64. package/src/VideoPlayer/utils/player/index.ts +0 -2
  65. package/src/VideoPlayer/utils/player/playerEvents.ts +0 -97
  66. package/src/VideoPlayer/utils/player/useWatchReporter.ts +0 -105
  67. package/src/VideoPlayer/utils/video/index.ts +0 -5
  68. package/src/VideoPlayer/utils/video/videoControl.ts +0 -185
  69. package/src/VideoPlayer/utils/video/videoRef.ts +0 -21
  70. package/src/VideoPlayer/utils/video/videoResume.ts +0 -23
  71. package/src/VideoPlayer/utils/video/videoSource.ts +0 -23
  72. package/src/VideoPlayer.tsx +0 -181
  73. package/src/index.tsx +0 -3
@@ -1,69 +0,0 @@
1
- import { Linking } from 'react-native';
2
- import type { VideoAd } from '../../VideoPlayer/store/videoPlayer.type';
3
- import { useVideoPlayerStore } from '../../VideoPlayer/store/videoPlayerStore';
4
- import { useAdsPlayerStore } from '../store/adsPlayerStore';
5
- import { createRef } from 'react';
6
- import type { AdsPlayerRef } from '../AdsPlayer';
7
- export const adVideoRef = createRef<AdsPlayerRef>();
8
-
9
- export const getDomainFromUrl = (url: string | undefined): string => {
10
- if (!url) return '';
11
- const domain = url.replace(/^https?:\/\//, '').split('/')[0];
12
- return domain || '';
13
- };
14
-
15
- export const handleAdClickThrough = async (
16
- ad: VideoAd,
17
- onAdTracking?: ({
18
- ad,
19
- trackingUrl,
20
- event,
21
- }: {
22
- ad: VideoAd;
23
- trackingUrl: string;
24
- event: string;
25
- }) => void
26
- ): Promise<void> => {
27
- if (!ad.clickThroughUrl) return;
28
-
29
- try {
30
- const canOpen = await Linking.canOpenURL(ad.clickThroughUrl);
31
- if (canOpen) {
32
- await Linking.openURL(ad.clickThroughUrl);
33
- if (ad.tracking?.click && onAdTracking) {
34
- onAdTracking({
35
- ad,
36
- trackingUrl: ad.tracking.click,
37
- event: 'click',
38
- });
39
- }
40
- }
41
- } catch (error) {
42
- console.error('Error opening ad URL:', error);
43
- }
44
- };
45
-
46
- export const hideAdControls = (): void => {
47
- const { setControlsVisible } = useVideoPlayerStore.getState();
48
- const { isAdPaused } = useAdsPlayerStore.getState();
49
- if (isAdPaused) {
50
- return;
51
- }
52
- setControlsVisible(false);
53
- };
54
-
55
- export const showAdControls = (): void => {
56
- const { setControlsVisible } = useVideoPlayerStore.getState();
57
- setControlsVisible(true);
58
- };
59
-
60
- export const toggleAdControls = (): void => {
61
- const { controlsVisible, setControlsVisible } =
62
- useVideoPlayerStore.getState();
63
- const { isAdPaused } = useAdsPlayerStore.getState();
64
- if (isAdPaused) {
65
- setControlsVisible(true);
66
- } else {
67
- setControlsVisible(!controlsVisible);
68
- }
69
- };
@@ -1,32 +0,0 @@
1
- import { useEffect, useRef } from 'react';
2
- import { Animated } from 'react-native';
3
- import { useVideoPlayerStore } from '../../VideoPlayer/store/videoPlayerStore';
4
- import { useAdsPlayerStore } from '../store/adsPlayerStore';
5
- import { hideAdControls } from './controls';
6
-
7
- export const useAdControlsAutoHide = (): Animated.Value => {
8
- const { controlsVisible, controlsTimer } = useVideoPlayerStore();
9
- const { isAdPaused } = useAdsPlayerStore();
10
- const timeoutId = useRef<NodeJS.Timeout | null>(null);
11
- const fadeAnim = useRef(new Animated.Value(0)).current;
12
-
13
- useEffect(() => {
14
- Animated.timing(fadeAnim, {
15
- toValue: controlsVisible ? 1 : 0,
16
- duration: 200,
17
- useNativeDriver: true,
18
- }).start();
19
-
20
- if (controlsVisible && !isAdPaused) {
21
- timeoutId.current = setTimeout(() => {
22
- hideAdControls();
23
- }, controlsTimer * 1000);
24
- }
25
-
26
- return () => {
27
- if (timeoutId.current) clearTimeout(timeoutId.current);
28
- };
29
- }, [controlsVisible, controlsTimer, fadeAnim, isAdPaused]);
30
-
31
- return fadeAnim;
32
- };
@@ -1,86 +0,0 @@
1
- import { useCallback } from 'react';
2
- import { useAdsPlayerStore } from '../store/adsPlayerStore';
3
- import { useVideoPlayerStore } from '../../VideoPlayer/store/videoPlayerStore';
4
- import { handlePause } from '../../VideoPlayer/utils';
5
- import type { VideoAd } from '../../VideoPlayer/store/videoPlayer.type';
6
-
7
- interface UseAdInitializationParams {
8
- onAdTracking?: ({
9
- ad,
10
- trackingUrl,
11
- event,
12
- }: {
13
- ad: VideoAd;
14
- trackingUrl: string;
15
- event: string;
16
- }) => void;
17
- }
18
-
19
- /**
20
- * Reusable hook for initializing and starting ads
21
- * Consolidates duplicate ad initialization logic from useAdsManager
22
- */
23
- export const useAdInitialization = ({
24
- onAdTracking,
25
- }: UseAdInitializationParams = {}) => {
26
- const { setCurrentAd, setIsAdPlaying, setIsAdPaused, setResumeTime } =
27
- useAdsPlayerStore();
28
- const { setIsPaused } = useVideoPlayerStore();
29
-
30
- const startAd = useCallback(
31
- (
32
- ad: VideoAd,
33
- options: {
34
- resumeTime?: number;
35
- trackImpression?: boolean;
36
- } = {}
37
- ) => {
38
- try {
39
- // Always pause main video first before starting ad
40
- // Use handlePause to properly pause video and save watch time
41
- handlePause();
42
- setIsPaused(true);
43
-
44
- // Set ad state - ensure ad is ready to play
45
- setCurrentAd(ad);
46
- setIsAdPlaying(true);
47
- setIsAdPaused(false);
48
-
49
- if (options.resumeTime !== undefined) {
50
- setResumeTime(options.resumeTime);
51
- }
52
-
53
- // Track impression is handled in AdsPlayer component to avoid duplication
54
- // Only track here if explicitly requested
55
- if (
56
- options.trackImpression === true &&
57
- ad.tracking?.impression &&
58
- onAdTracking
59
- ) {
60
- onAdTracking({
61
- ad,
62
- trackingUrl: ad.tracking.impression,
63
- event: 'impression',
64
- });
65
- }
66
- } catch (error) {
67
- console.error('Error starting ad:', error);
68
- // Reset state on error
69
- setCurrentAd(null);
70
- setIsAdPlaying(false);
71
- setIsAdPaused(false);
72
- throw error;
73
- }
74
- },
75
- [
76
- setCurrentAd,
77
- setIsAdPlaying,
78
- setIsAdPaused,
79
- setIsPaused,
80
- setResumeTime,
81
- onAdTracking,
82
- ]
83
- );
84
-
85
- return { startAd };
86
- };
@@ -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
- });