@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,294 @@
1
+ /* eslint-disable react/no-unstable-nested-components */
2
+ import {
3
+ FlatList,
4
+ TouchableOpacity,
5
+ View,
6
+ StyleSheet,
7
+ Text,
8
+ } from 'react-native';
9
+ import React from 'react';
10
+ import FastImage from 'react-native-fast-image';
11
+ import { moderateScale, verticalScale } from 'react-native-size-matters';
12
+ import {
13
+ MenuProvider,
14
+ Menu,
15
+ MenuOptions,
16
+ MenuOption,
17
+ MenuTrigger,
18
+ } from 'react-native-popup-menu';
19
+ import { RFValue } from 'react-native-responsive-fontsize';
20
+ import { formatDuration, getEpisodeIndex } from '../utils';
21
+ import { useVideoPlayerStore } from '../store/videoPlayerStore';
22
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
23
+ import globalStyles from '../Styles/globalStyles';
24
+ import type { MediaEpisode } from '../store';
25
+ import { useVideoPlayerConfig } from '../context';
26
+ import type { ExtendedWatchProgress } from '../utils/useWatchReporter';
27
+
28
+ type EpisodesProps = {
29
+ onPressEpisode: ({ episode }: { episode: MediaEpisode }) => Promise<boolean>;
30
+ reportProgress: (event: ExtendedWatchProgress['event']) => void;
31
+ };
32
+
33
+ const Episodes: React.FC<EpisodesProps> = ({
34
+ onPressEpisode,
35
+ reportProgress,
36
+ }) => {
37
+ const {
38
+ activeSeason,
39
+ activeTrack,
40
+ setCurrentTrackIndex,
41
+ setActiveTrack,
42
+ playList,
43
+ setSettingsModal,
44
+ setIsPaused,
45
+ contentSeasons,
46
+ setActiveSeason,
47
+ setSelectedSubtitleTrack,
48
+ setPlayBackRate,
49
+ setActiveSubtitle,
50
+ setSelectedVideoTrack,
51
+ setMaxBitRate,
52
+ setDuration,
53
+ } = useVideoPlayerStore();
54
+ const { colors } = useVideoPlayerConfig();
55
+ const { left } = useSafeAreaInsets();
56
+
57
+ if (!activeSeason?.seasonNumber || !activeSeason?.episodes) {
58
+ return (
59
+ <View style={globalStyles.flexOneJustifyContentCenterAndAlignItemsCenter}>
60
+ <Text style={{ color: colors.text }}>
61
+ Something went wrong. Please try again.
62
+ </Text>
63
+ </View>
64
+ );
65
+ }
66
+
67
+ const handleEpisodePress = async (episode: MediaEpisode) => {
68
+ const isSuccess = await onPressEpisode({ episode });
69
+ if (!isSuccess) return;
70
+ if (episode.id === activeTrack?.episodeId) {
71
+ setSettingsModal({ isVisible: false, action: 'none' });
72
+ setIsPaused(false);
73
+ return;
74
+ }
75
+ const index = getEpisodeIndex(playList, episode.id);
76
+ setCurrentTrackIndex(index);
77
+ setDuration(0);
78
+
79
+ setActiveTrack({
80
+ type: 'series',
81
+ episodeId: episode.id,
82
+ contentId: episode.contentId,
83
+ episodeName: episode.title,
84
+ episodeNumber: episode.episodeNumber,
85
+ seasonNumber: activeSeason.seasonNumber ?? 0,
86
+ skipIntro: episode.skipIntro ?? undefined,
87
+ nextEpisodeAt: episode.nextEpisodeAt ?? undefined,
88
+ sourceLink: episode.sourceLink || '',
89
+ thumbnail: episode.thumbnail,
90
+ subtitles: episode.subtitles || null,
91
+ id: episode.id,
92
+ title: episode.title,
93
+ description: episode.description || '',
94
+ duration: episode.duration || 0,
95
+ sourceType: episode.sourceType || 'HLS',
96
+ });
97
+ setSelectedSubtitleTrack(null);
98
+ setActiveSubtitle(null);
99
+ setSelectedVideoTrack(null);
100
+ setMaxBitRate(null);
101
+ setSettingsModal({ isVisible: false, action: 'none' });
102
+ setIsPaused(false);
103
+ setPlayBackRate(1, 'Normal');
104
+ };
105
+
106
+ return (
107
+ <MenuProvider>
108
+ <View>
109
+ <Menu>
110
+ <MenuTrigger
111
+ style={[
112
+ styles.menuTrigger,
113
+ { marginLeft: left + moderateScale(8) },
114
+ ]}
115
+ customStyles={{
116
+ triggerText: {
117
+ ...menuTriggerCustomStyles.triggerText,
118
+ color: colors.text,
119
+ },
120
+ }}
121
+ text={`Season ${activeSeason?.seasonNumber}`}
122
+ />
123
+ <MenuOptions optionsContainerStyle={styles.menuOptionsContainer}>
124
+ {contentSeasons?.map((season, index) => (
125
+ <MenuOption
126
+ style={styles.menuOption}
127
+ customStyles={menuOptionCustomStyles}
128
+ key={index}
129
+ onSelect={() => setActiveSeason(season)}
130
+ text={`Season ${season.seasonNumber}`}
131
+ />
132
+ ))}
133
+ </MenuOptions>
134
+ </Menu>
135
+
136
+ <View style={styles.episodesContainer}>
137
+ <FlatList
138
+ data={activeSeason?.episodes}
139
+ horizontal
140
+ showsHorizontalScrollIndicator={false}
141
+ ItemSeparatorComponent={() => (
142
+ <View style={{ width: moderateScale(12) }} />
143
+ )}
144
+ ListHeaderComponent={() => (
145
+ <View style={{ width: left + moderateScale(8) }} />
146
+ )}
147
+ ListFooterComponent={() => (
148
+ <View style={{ width: moderateScale(16) }} />
149
+ )}
150
+ renderItem={({ item }) => (
151
+ <TouchableOpacity
152
+ onPress={() => {
153
+ reportProgress('EPISODE_CHANGE');
154
+ handleEpisodePress(item);
155
+ }}
156
+ style={styles.episodeItem}
157
+ activeOpacity={0.8}
158
+ >
159
+ <FastImage
160
+ source={{ uri: item?.thumbnail }}
161
+ style={styles.episodeThumbnail}
162
+ resizeMode={FastImage.resizeMode.cover}
163
+ />
164
+ {item.id === activeTrack?.episodeId && (
165
+ <Text
166
+ style={[
167
+ styles.nowPlayingBadge,
168
+ { color: colors.text, backgroundColor: colors.primary },
169
+ ]}
170
+ >
171
+ Now Playing
172
+ </Text>
173
+ )}
174
+
175
+ {/* Episode title */}
176
+ <Text
177
+ numberOfLines={1}
178
+ style={[styles.episodeTitle, { color: colors.text }]}
179
+ >
180
+ {item?.title}
181
+ </Text>
182
+
183
+ {/* Season & Episode Number + Duration in one row */}
184
+ <View style={styles.episodeInfoRow}>
185
+ <Text style={styles.episodeInfoText}>
186
+ {[
187
+ item.episodeNumber &&
188
+ `S${String(activeSeason?.seasonNumber).padStart(2, '0')} E${String(item.episodeNumber).padStart(2, '0')}`,
189
+ item.publishDate && `(${item.publishDate})`,
190
+ item?.duration && formatDuration(item?.duration, true),
191
+ ]
192
+ .filter(Boolean)
193
+ .join(' | ')}
194
+ </Text>
195
+ </View>
196
+ {/* Description */}
197
+ {item.description && (
198
+ <Text style={styles.episodeDescription} numberOfLines={3}>
199
+ {item?.description}
200
+ </Text>
201
+ )}
202
+ </TouchableOpacity>
203
+ )}
204
+ keyExtractor={(item) => item?.id}
205
+ />
206
+ </View>
207
+ </View>
208
+ </MenuProvider>
209
+ );
210
+ };
211
+
212
+ const styles = StyleSheet.create({
213
+ menuTrigger: {
214
+ alignSelf: 'flex-start',
215
+ backgroundColor: 'gray',
216
+ paddingHorizontal: moderateScale(16),
217
+ paddingVertical: moderateScale(8),
218
+ marginTop: verticalScale(20),
219
+ borderRadius: moderateScale(8),
220
+ },
221
+ menuOptionsContainer: {
222
+ marginTop: verticalScale(60),
223
+ width: moderateScale(150),
224
+ },
225
+ menuOption: {
226
+ paddingHorizontal: moderateScale(16),
227
+ paddingVertical: moderateScale(12),
228
+ },
229
+ episodesContainer: {
230
+ marginTop: verticalScale(20),
231
+ paddingBottom: verticalScale(20),
232
+ },
233
+ episodeItem: {
234
+ width: moderateScale(240),
235
+ backgroundColor: '#373737ff',
236
+ borderRadius: moderateScale(12),
237
+ overflow: 'hidden',
238
+ },
239
+ episodeThumbnail: {
240
+ width: '100%',
241
+ height: undefined,
242
+ aspectRatio: 16 / 9,
243
+ backgroundColor: '#444', // slightly lighter fallback
244
+ },
245
+ nowPlayingBadge: {
246
+ position: 'absolute',
247
+ top: moderateScale(6),
248
+ left: moderateScale(6),
249
+ paddingHorizontal: moderateScale(8),
250
+ paddingVertical: moderateScale(4), // slightly bigger
251
+ borderRadius: moderateScale(4),
252
+ fontSize: RFValue(12),
253
+ fontWeight: '600',
254
+ overflow: 'hidden',
255
+ },
256
+ episodeTitle: {
257
+ fontSize: RFValue(14), // bigger
258
+ fontWeight: '600', // bolder
259
+ marginHorizontal: moderateScale(8),
260
+ marginTop: verticalScale(3),
261
+ },
262
+ episodeInfoRow: {
263
+ flexDirection: 'row',
264
+ justifyContent: 'flex-start', // better alignment
265
+ marginHorizontal: moderateScale(8),
266
+ marginTop: verticalScale(2),
267
+ alignItems: 'center',
268
+ },
269
+ episodeInfoText: {
270
+ color: '#f1f1f1ff',
271
+ fontSize: RFValue(10.5),
272
+ },
273
+ episodeDescription: {
274
+ marginHorizontal: moderateScale(8),
275
+ marginVertical: verticalScale(5),
276
+ color: '#f1f1f1ff',
277
+ fontSize: RFValue(10),
278
+ lineHeight: RFValue(10),
279
+ },
280
+ });
281
+
282
+ const menuTriggerCustomStyles = {
283
+ triggerText: {
284
+ fontSize: RFValue(16),
285
+ },
286
+ };
287
+
288
+ const menuOptionCustomStyles = {
289
+ optionText: {
290
+ fontSize: RFValue(16),
291
+ },
292
+ };
293
+
294
+ export default Episodes;
@@ -0,0 +1,128 @@
1
+ import { StyleSheet, TouchableOpacity, View } from 'react-native';
2
+ import React from 'react';
3
+ import { moderateScale } from 'react-native-size-matters';
4
+ import { X } from 'lucide-react-native';
5
+ import { useVideoPlayerStore } from '../store/videoPlayerStore';
6
+
7
+ import Episodes from './Episodes';
8
+ import SpeedControls from './SpeedControls';
9
+ import VideoPlayerSettings from './VideoPlayerSettings';
10
+ import AudioAndSubtitles from './AudioAndSubtitles';
11
+ import {
12
+ useVideoResolutions,
13
+ type Resolution,
14
+ } from '../utils/hooks/useVideoResolutions';
15
+ import type { MediaEpisode, SettingsAction } from '../store/videoPlayer.type';
16
+ import { useVideoPlayerConfig } from '../context';
17
+ import type { ExtendedWatchProgress } from '../utils/useWatchReporter';
18
+
19
+ const VisibleAction: React.FC<{
20
+ settingModalAction: SettingsAction;
21
+ onPressEpisode: ({ episode }: { episode: MediaEpisode }) => Promise<boolean>;
22
+ reportProgress: (event: ExtendedWatchProgress['event']) => void;
23
+ resolutions?: Resolution[];
24
+ }> = ({ settingModalAction, onPressEpisode, reportProgress, resolutions }) => {
25
+ switch (settingModalAction) {
26
+ case 'speed':
27
+ return <SpeedControls />;
28
+ case 'audioOrSubtitle':
29
+ return <AudioAndSubtitles />;
30
+ case 'settings':
31
+ return <VideoPlayerSettings resolutions={resolutions || []} />;
32
+ case 'episodes':
33
+ return (
34
+ <Episodes
35
+ onPressEpisode={onPressEpisode}
36
+ reportProgress={reportProgress}
37
+ />
38
+ );
39
+ default:
40
+ return null;
41
+ }
42
+ };
43
+
44
+ type SettingModalProps = {
45
+ onPressEpisode: ({ episode }: { episode: MediaEpisode }) => Promise<boolean>;
46
+ reportProgress: (event: ExtendedWatchProgress['event']) => void;
47
+ };
48
+
49
+ const SettingModal: React.FC<SettingModalProps> = ({
50
+ onPressEpisode,
51
+ reportProgress,
52
+ }) => {
53
+ const {
54
+ settingsModal,
55
+ setSettingsModal,
56
+ setIsPaused,
57
+ setControlsVisible,
58
+ activeTrack,
59
+ } = useVideoPlayerStore();
60
+ const { colors } = useVideoPlayerConfig();
61
+ // Fetch available resolutions for current track
62
+ const resolutions = useVideoResolutions(activeTrack);
63
+
64
+ const onClose = () => {
65
+ setSettingsModal({ isVisible: false, action: 'none' });
66
+ setIsPaused(false);
67
+ setControlsVisible(true);
68
+ };
69
+
70
+ if (!settingsModal.isVisible) return null;
71
+
72
+ const containerStyle =
73
+ settingsModal.action === 'audioOrSubtitle'
74
+ ? [styles.contentContainer, styles.audioOrSubtitlePadding]
75
+ : styles.contentContainer;
76
+
77
+ return (
78
+ <View style={StyleSheet.absoluteFill}>
79
+ {/* Dark overlay */}
80
+ <View style={styles.overlay} />
81
+
82
+ {/* Close button */}
83
+ <TouchableOpacity onPress={onClose} style={styles.closeButton}>
84
+ <X color={colors.text} size={moderateScale(25)} />
85
+ </TouchableOpacity>
86
+
87
+ {/* Modal content */}
88
+ <View style={containerStyle}>
89
+ <VisibleAction
90
+ settingModalAction={settingsModal.action}
91
+ resolutions={resolutions}
92
+ onPressEpisode={onPressEpisode}
93
+ reportProgress={reportProgress}
94
+ />
95
+ </View>
96
+ </View>
97
+ );
98
+ };
99
+
100
+ export default SettingModal;
101
+
102
+ const styles = StyleSheet.create({
103
+ overlay: {
104
+ ...StyleSheet.absoluteFillObject,
105
+ backgroundColor: 'rgba(0,0,0,0.9)',
106
+ pointerEvents: 'none',
107
+ },
108
+ closeButton: {
109
+ position: 'absolute',
110
+ top: moderateScale(20),
111
+ right: moderateScale(60),
112
+ width: moderateScale(50),
113
+ height: moderateScale(50),
114
+ justifyContent: 'center',
115
+ alignItems: 'center',
116
+ borderRadius: moderateScale(50),
117
+ zIndex: 10000,
118
+ },
119
+ contentContainer: {
120
+ flex: 1,
121
+ zIndex: 1000,
122
+ },
123
+ audioOrSubtitlePadding: {
124
+ paddingLeft: moderateScale(16),
125
+ paddingRight: moderateScale(16),
126
+ paddingTop: moderateScale(8),
127
+ },
128
+ });
@@ -0,0 +1,134 @@
1
+ import React, { useMemo } from 'react';
2
+ import {
3
+ FlatList,
4
+ TouchableOpacity,
5
+ View,
6
+ StyleSheet,
7
+ Text,
8
+ } from 'react-native';
9
+ import { verticalScale } from 'react-native-size-matters';
10
+ import { RFValue } from 'react-native-responsive-fontsize';
11
+
12
+ import { useVideoPlayerStore } from '../store/videoPlayerStore';
13
+ import globalStyles from '../Styles/globalStyles';
14
+ import { useVideoPlayerConfig } from '../context';
15
+
16
+ // --- Constants ---
17
+ const SPEED_OPTIONS = [
18
+ { name: '0.25x', value: 0.25 },
19
+ { name: '0.5x', value: 0.5 },
20
+ { name: '0.75x', value: 0.75 },
21
+ { name: 'Normal', value: 1 },
22
+ { name: '1.25x', value: 1.25 },
23
+ { name: '1.5x', value: 1.5 },
24
+ { name: '1.75x', value: 1.75 },
25
+ { name: '2x', value: 2 },
26
+ ];
27
+
28
+ // --- UI Components ---
29
+ const ListHeader = () => <View style={styles.listHeader} />;
30
+ const ListFooter = () => <View style={styles.listFooter} />;
31
+
32
+ interface SpeedItemProps {
33
+ name: string;
34
+ value: number;
35
+ isSelected: boolean;
36
+ onPress: ({ value, name }: { value: number; name: string }) => void;
37
+ colors: {
38
+ text: string;
39
+ background: string;
40
+ };
41
+ }
42
+
43
+ const SpeedItem: React.FC<SpeedItemProps> = ({
44
+ name,
45
+ value,
46
+ isSelected,
47
+ onPress,
48
+ colors,
49
+ }) => (
50
+ <TouchableOpacity
51
+ onPress={() => onPress({ value, name })}
52
+ style={styles.itemTouchable}
53
+ activeOpacity={0.7}
54
+ >
55
+ <Text
56
+ style={[
57
+ styles.itemText,
58
+ { color: colors.text },
59
+ isSelected ? styles.itemTextSelected : styles.itemTextUnselected,
60
+ ]}
61
+ >
62
+ {name}
63
+ </Text>
64
+ </TouchableOpacity>
65
+ );
66
+
67
+ // --- Main Component ---
68
+ const SpeedControls: React.FC = () => {
69
+ const {
70
+ setIsPaused,
71
+ playBackRate,
72
+ setPlayBackRate,
73
+ setControlsVisible,
74
+ setSettingsModal,
75
+ } = useVideoPlayerStore();
76
+ const { colors } = useVideoPlayerConfig();
77
+ const speeds = useMemo(() => SPEED_OPTIONS, []);
78
+
79
+ const handleSelectSpeed = (value: number, name: string) => {
80
+ setPlayBackRate(value, name);
81
+ setSettingsModal({ action: 'none', isVisible: false });
82
+ setControlsVisible(true);
83
+ setIsPaused(false);
84
+ };
85
+
86
+ return (
87
+ <View style={globalStyles.flexOneJustifyContentCenterAndAlignItemsCenter}>
88
+ <FlatList
89
+ data={speeds}
90
+ showsVerticalScrollIndicator={false}
91
+ keyExtractor={(item) => item.name}
92
+ ListHeaderComponent={ListHeader}
93
+ ListFooterComponent={ListFooter}
94
+ renderItem={({ item }) => (
95
+ <SpeedItem
96
+ name={item.name}
97
+ value={item.value}
98
+ isSelected={playBackRate === item.value}
99
+ onPress={({ value, name }) => handleSelectSpeed(value, name)}
100
+ colors={colors}
101
+ />
102
+ )}
103
+ />
104
+ </View>
105
+ );
106
+ };
107
+
108
+ export default SpeedControls;
109
+
110
+ // --- Styles ---
111
+ const styles = StyleSheet.create({
112
+ listHeader: {
113
+ height: verticalScale(20),
114
+ },
115
+ listFooter: {
116
+ height: verticalScale(50),
117
+ },
118
+ itemTouchable: {
119
+ paddingVertical: verticalScale(12),
120
+ paddingHorizontal: verticalScale(30),
121
+ alignItems: 'center',
122
+ },
123
+ itemText: {
124
+ textAlign: 'center',
125
+ fontSize: RFValue(17), // better readability
126
+ fontWeight: '600', // semi-bold for better emphasis
127
+ },
128
+ itemTextSelected: {
129
+ opacity: 1,
130
+ },
131
+ itemTextUnselected: {
132
+ opacity: 0.6,
133
+ },
134
+ });
@@ -0,0 +1,141 @@
1
+ /* eslint-disable react/no-unstable-nested-components */
2
+ import { useCallback } from 'react';
3
+ import {
4
+ FlatList,
5
+ TouchableOpacity,
6
+ View,
7
+ StyleSheet,
8
+ Text,
9
+ } from 'react-native';
10
+ import { scale, verticalScale } from 'react-native-size-matters';
11
+ import { RFValue } from 'react-native-responsive-fontsize';
12
+ import { SelectedVideoTrackType } from 'react-native-video';
13
+ import globalStyles from '../Styles/globalStyles';
14
+ import type { Resolution } from '../utils';
15
+ import { useVideoPlayerStore } from '../store';
16
+ import { useVideoPlayerConfig } from '../context';
17
+
18
+ interface VideoPlayerSettingsProps {
19
+ resolutions: Resolution[];
20
+ }
21
+
22
+ const VideoPlayerSettings = ({ resolutions }: VideoPlayerSettingsProps) => {
23
+ const {
24
+ setMaxBitRate,
25
+ setSelectedVideoTrack,
26
+ setIsPaused,
27
+ setSettingsModal,
28
+ setControlsVisible,
29
+ maxBitRate,
30
+ } = useVideoPlayerStore();
31
+ const { colors } = useVideoPlayerConfig();
32
+ const handleSelect = useCallback(
33
+ (item: Resolution) => {
34
+ if (item.height === 'auto') {
35
+ setSelectedVideoTrack(null);
36
+ setMaxBitRate(null);
37
+ } else {
38
+ setSelectedVideoTrack({
39
+ type: SelectedVideoTrackType.RESOLUTION,
40
+ value: Number(item.height),
41
+ });
42
+ setMaxBitRate(item.bandwidth ?? null);
43
+ }
44
+ setIsPaused(false);
45
+ setSettingsModal({ isVisible: false, action: 'none' });
46
+ setControlsVisible(false);
47
+ },
48
+ [
49
+ setMaxBitRate,
50
+ setSelectedVideoTrack,
51
+ setIsPaused,
52
+ setSettingsModal,
53
+ setControlsVisible,
54
+ ]
55
+ );
56
+
57
+ const keyExtractor = useCallback(
58
+ (item: Resolution, index: number) => `${item.height}-${index}`,
59
+ []
60
+ );
61
+
62
+ const renderItem = useCallback(
63
+ ({ item }: { item: Resolution }) => {
64
+ const isSelected = maxBitRate === item.bandwidth;
65
+ return (
66
+ <TouchableOpacity
67
+ onPress={() => handleSelect(item)}
68
+ activeOpacity={0.7}
69
+ >
70
+ <Text
71
+ style={[
72
+ styles.optionText,
73
+ { color: colors.text },
74
+ isSelected
75
+ ? styles.optionTextSelected
76
+ : styles.optionTextUnselected,
77
+ ]}
78
+ >
79
+ {item.height === 'auto' ? 'Auto' : `${item.height}p`}
80
+ </Text>
81
+ </TouchableOpacity>
82
+ );
83
+ },
84
+ [colors.text, handleSelect, maxBitRate]
85
+ );
86
+
87
+ return (
88
+ <View style={[globalStyles.flexOne, styles.container]}>
89
+ <Text
90
+ style={[
91
+ styles.headerText,
92
+ { color: colors.text },
93
+ styles.marginTopBottom,
94
+ ]}
95
+ >
96
+ Video Quality
97
+ </Text>
98
+ <FlatList
99
+ data={resolutions}
100
+ renderItem={renderItem}
101
+ keyExtractor={keyExtractor}
102
+ showsVerticalScrollIndicator={false}
103
+ contentContainerStyle={{ paddingBottom: verticalScale(50) }}
104
+ ItemSeparatorComponent={() => (
105
+ <View style={{ height: verticalScale(12) }} />
106
+ )}
107
+ />
108
+ </View>
109
+ );
110
+ };
111
+
112
+ export default VideoPlayerSettings;
113
+
114
+ const styles = StyleSheet.create({
115
+ container: {
116
+ alignItems: 'center',
117
+ paddingHorizontal: 20,
118
+ },
119
+ headerText: {
120
+ fontSize: RFValue(20),
121
+ fontWeight: '700',
122
+ textAlign: 'center',
123
+ },
124
+ marginTopBottom: {
125
+ marginBottom: verticalScale(16),
126
+ paddingTop: verticalScale(24),
127
+ },
128
+ optionText: {
129
+ fontSize: RFValue(17),
130
+ paddingVertical: verticalScale(4),
131
+ paddingHorizontal: scale(12),
132
+ textAlign: 'center',
133
+ },
134
+ optionTextSelected: {
135
+ fontWeight: '700',
136
+ textDecorationLine: 'underline', // selected highlight
137
+ },
138
+ optionTextUnselected: {
139
+ opacity: 0.8,
140
+ },
141
+ });
@@ -0,0 +1,2 @@
1
+ export * from './videoPlayerStore';
2
+ export * from './videoPlayer.type';