mega-stream-proxy 0.1.9

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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +518 -0
  3. package/android/build.gradle +31 -0
  4. package/android/src/main/AndroidManifest.xml +3 -0
  5. package/dist/Player/native.d.ts +3 -0
  6. package/dist/Player/native.js +3 -0
  7. package/dist/Player/playerAndroid/MegaVideoPlayerAndroid.d.ts +47 -0
  8. package/dist/Player/playerAndroid/MegaVideoPlayerAndroid.js +624 -0
  9. package/dist/Player/playerAndroid/PlayerControlsAndroid.d.ts +54 -0
  10. package/dist/Player/playerAndroid/PlayerControlsAndroid.js +370 -0
  11. package/dist/Player/playerAndroid/SubtitleDisplayAndroid.d.ts +21 -0
  12. package/dist/Player/playerAndroid/SubtitleDisplayAndroid.js +55 -0
  13. package/dist/Player/playerAndroid/index.d.ts +3 -0
  14. package/dist/Player/playerAndroid/index.js +3 -0
  15. package/dist/Player/playerweb/MegaVideoPlayer.d.ts +92 -0
  16. package/dist/Player/playerweb/MegaVideoPlayer.js +368 -0
  17. package/dist/Player/playerweb/PlayerControls.d.ts +52 -0
  18. package/dist/Player/playerweb/PlayerControls.js +346 -0
  19. package/dist/Player/playerweb/SubtitleDisplay.d.ts +15 -0
  20. package/dist/Player/playerweb/SubtitleDisplay.js +32 -0
  21. package/dist/Player/playerweb/index.d.ts +3 -0
  22. package/dist/Player/playerweb/index.js +3 -0
  23. package/dist/Player/web.d.ts +3 -0
  24. package/dist/Player/web.js +3 -0
  25. package/dist/accounts/async-storage.d.ts +10 -0
  26. package/dist/accounts/async-storage.js +46 -0
  27. package/dist/accounts/index.d.ts +4 -0
  28. package/dist/accounts/index.js +3 -0
  29. package/dist/accounts/local-storage.d.ts +3 -0
  30. package/dist/accounts/local-storage.js +63 -0
  31. package/dist/accounts/manager.d.ts +39 -0
  32. package/dist/accounts/manager.js +149 -0
  33. package/dist/accounts/types.d.ts +35 -0
  34. package/dist/accounts/types.js +1 -0
  35. package/dist/index.d.ts +5 -0
  36. package/dist/index.js +4 -0
  37. package/dist/megaFile.d.ts +7 -0
  38. package/dist/megaFile.js +31 -0
  39. package/dist/proxy/native.d.ts +25 -0
  40. package/dist/proxy/native.js +336 -0
  41. package/dist/proxy/sw/bundle.d.ts +21 -0
  42. package/dist/proxy/sw/bundle.js +32 -0
  43. package/dist/proxy/sw/megajs.browser.js +27 -0
  44. package/dist/proxy/sw/sw.js +212 -0
  45. package/dist/proxy/types.d.ts +15 -0
  46. package/dist/proxy/types.js +2 -0
  47. package/dist/proxy/web.d.ts +24 -0
  48. package/dist/proxy/web.js +231 -0
  49. package/dist/ranges.d.ts +30 -0
  50. package/dist/ranges.js +63 -0
  51. package/dist/stream.d.ts +9 -0
  52. package/dist/stream.js +100 -0
  53. package/dist/subtitles/index.d.ts +9 -0
  54. package/dist/subtitles/index.js +2 -0
  55. package/dist/subtitles/load.d.ts +18 -0
  56. package/dist/subtitles/load.js +58 -0
  57. package/dist/subtitles/parse.d.ts +19 -0
  58. package/dist/subtitles/parse.js +95 -0
  59. package/dist/swDefine.d.ts +5 -0
  60. package/dist/swDefine.js +5 -0
  61. package/package.json +112 -0
  62. package/src/Player/playerAndroid/MegaVideoPlayerAndroid.tsx +825 -0
  63. package/src/Player/playerAndroid/PlayerControlsAndroid.tsx +824 -0
  64. package/src/Player/playerAndroid/SubtitleDisplayAndroid.tsx +80 -0
  65. package/src/Player/playerAndroid/index.ts +7 -0
  66. package/src/Player/playerweb/MegaVideoPlayer.tsx +551 -0
  67. package/src/Player/playerweb/PlayerControls.tsx +798 -0
  68. package/src/Player/playerweb/SubtitleDisplay.tsx +48 -0
  69. package/src/Player/playerweb/index.ts +3 -0
  70. package/src/accounts/async-storage.ts +55 -0
  71. package/src/accounts/index.ts +9 -0
  72. package/src/accounts/local-storage.ts +66 -0
  73. package/src/accounts/manager.ts +174 -0
  74. package/src/accounts/types.ts +38 -0
  75. package/src/index.ts +10 -0
  76. package/src/megaFile.ts +48 -0
  77. package/src/proxy/native.ts +396 -0
  78. package/src/proxy/sw/bundle.ts +37 -0
  79. package/src/proxy/sw/megajs.browser.js +27 -0
  80. package/src/proxy/sw/sw.js +212 -0
  81. package/src/proxy/types.ts +17 -0
  82. package/src/proxy/web.ts +266 -0
  83. package/src/ranges.ts +72 -0
  84. package/src/stream.ts +117 -0
  85. package/src/subtitles/index.ts +15 -0
  86. package/src/subtitles/load.ts +83 -0
  87. package/src/subtitles/parse.ts +105 -0
  88. package/src/swDefine.ts +5 -0
@@ -0,0 +1,370 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { ActivityIndicator, PanResponder, Pressable, StyleSheet, Text, TouchableOpacity, View, } from "react-native";
4
+ import { Ionicons } from "@expo/vector-icons";
5
+ const fmt = (s) => {
6
+ if (!Number.isFinite(s) || s < 0)
7
+ s = 0;
8
+ const h = Math.floor(s / 3600);
9
+ const m = Math.floor((s % 3600) / 60);
10
+ const sec = Math.floor(s % 60);
11
+ return `${h.toString().padStart(2, "0")}:${m
12
+ .toString()
13
+ .padStart(2, "0")}:${sec.toString().padStart(2, "0")}`;
14
+ };
15
+ const clamp01 = (v) => Math.min(1, Math.max(0, v));
16
+ const SIZE_MIN = 10;
17
+ const SIZE_MAX = 34;
18
+ export function PlayerControlsAndroid(props) {
19
+ const { playing, buffering, currentTime, duration, muted, title, visible, subtitlesOn, volume, subtitleTracks, subtitleTrack, onSelectSubtitleTrack, subtitleFontSize, onChangeSubtitleFontSize, loadProgress, resolutions, resolution, onSelectResolution, introRegion, outroRegion, onSkipIntro, onSkipOutro, onToggleVisible, onPlayPause, onSkip, onSeek, onToggleMute, onToggleSubtitles, onVolume, onFullscreen, fullscreen = false, onInteract, width, height, } = props;
20
+ const hasSubs = (subtitleTracks?.length ?? 0) > 0;
21
+ const showSubsSection = hasSubs || !!onChangeSubtitleFontSize;
22
+ // empty resolution = the implicit default (first row, usually "HD: <url>")
23
+ const activeRes = (resolution && resolution.trim()) || resolutions?.[0] || "";
24
+ const [trackW, setTrackW] = useState(0);
25
+ const [scrubPct, setScrubPct] = useState(null);
26
+ const [volW, setVolW] = useState(0);
27
+ const [sizeW, setSizeW] = useState(0);
28
+ const [settingsOpen, setSettingsOpen] = useState(false);
29
+ const pct = duration > 0 ? clamp01(currentTime / duration) : 0;
30
+ const volPct = clamp01(volume);
31
+ const inIntro = !!introRegion &&
32
+ introRegion[1] > introRegion[0] &&
33
+ currentTime >= introRegion[0] &&
34
+ currentTime <= introRegion[1] &&
35
+ !!onSkipIntro;
36
+ const inOutro = !!outroRegion &&
37
+ outroRegion[1] > outroRegion[0] &&
38
+ currentTime >= outroRegion[0] &&
39
+ currentTime <= outroRegion[1] &&
40
+ !!onSkipOutro;
41
+ const currentSize = subtitleFontSize ?? 14;
42
+ const sizePct = clamp01((currentSize - SIZE_MIN) / (SIZE_MAX - SIZE_MIN));
43
+ const shownPct = scrubPct ?? pct;
44
+ const volIcon = (muted || volume === 0
45
+ ? "volume-mute"
46
+ : volume < 0.5
47
+ ? "volume-low"
48
+ : "volume-high");
49
+ const pan = useMemo(() => PanResponder.create({
50
+ onStartShouldSetPanResponder: () => true,
51
+ onMoveShouldSetPanResponder: () => true,
52
+ onPanResponderGrant: (e) => {
53
+ onInteract?.();
54
+ setScrubPct(trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0);
55
+ },
56
+ onPanResponderMove: (e) => setScrubPct(trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0),
57
+ onPanResponderRelease: (e) => {
58
+ const p = trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0;
59
+ setScrubPct(null);
60
+ onInteract?.();
61
+ onSeek(p * duration);
62
+ },
63
+ }), [trackW, duration, onSeek, onInteract]);
64
+ const volPan = useMemo(() => PanResponder.create({
65
+ onStartShouldSetPanResponder: () => true,
66
+ onMoveShouldSetPanResponder: () => true,
67
+ onPanResponderGrant: (e) => {
68
+ onInteract?.();
69
+ onVolume(volW > 0 ? clamp01(e.nativeEvent.locationX / volW) : 1);
70
+ },
71
+ onPanResponderMove: (e) => onVolume(volW > 0 ? clamp01(e.nativeEvent.locationX / volW) : 1),
72
+ }), [volW, onVolume, onInteract]);
73
+ const sizePan = useMemo(() => PanResponder.create({
74
+ onStartShouldSetPanResponder: () => true,
75
+ onMoveShouldSetPanResponder: () => true,
76
+ onPanResponderGrant: (e) => {
77
+ if (!onChangeSubtitleFontSize)
78
+ return;
79
+ const p = sizeW > 0 ? clamp01(e.nativeEvent.locationX / sizeW) : 0;
80
+ onChangeSubtitleFontSize(Math.round(SIZE_MIN + p * (SIZE_MAX - SIZE_MIN)));
81
+ },
82
+ onPanResponderMove: (e) => {
83
+ if (!onChangeSubtitleFontSize)
84
+ return;
85
+ const p = sizeW > 0 ? clamp01(e.nativeEvent.locationX / sizeW) : 0;
86
+ onChangeSubtitleFontSize(Math.round(SIZE_MIN + p * (SIZE_MAX - SIZE_MIN)));
87
+ },
88
+ }), [sizeW, onChangeSubtitleFontSize]);
89
+ const interact = () => onInteract?.();
90
+ return (_jsxs(View, { style: [
91
+ styles.root,
92
+ StyleSheet.absoluteFill,
93
+ { width: width ? width : "100%", height: height ? height : "100%" },
94
+ ], children: [_jsx(TouchableOpacity, { style: StyleSheet.absoluteFill, activeOpacity: 1, onPress: onToggleVisible }), buffering && (_jsx(View, { style: styles.centerBadge, pointerEvents: "none", children: _jsx(ActivityIndicator, { color: "#e50914", size: "large" }) })), settingsOpen && (_jsx(Pressable, { style: StyleSheet.absoluteFill, onPress: () => setSettingsOpen(false) })), visible && (_jsxs(_Fragment, { children: [_jsx(View, { style: [styles.bar, styles.top], pointerEvents: "box-none", children: !!title && (_jsx(Text, { numberOfLines: 1, style: styles.title, children: title })) }), _jsxs(View, { style: [
95
+ styles.centerRow,
96
+ StyleSheet.absoluteFill,
97
+ { width: width },
98
+ ], pointerEvents: "box-none", children: [_jsx(TouchableOpacity, { style: styles.skipBtn, onPress: () => onSkip(-10), children: _jsx(Ionicons, { name: "play-back", size: 30, color: "#fff" }) }), _jsx(TouchableOpacity, { style: styles.ppBtn, onPress: onPlayPause, children: _jsx(Ionicons, { name: buffering ? "ellipsis-horizontal" : playing ? "pause" : "play", size: 38, color: "#fff" }) }), _jsx(TouchableOpacity, { style: styles.skipBtn, onPress: () => onSkip(10), children: _jsx(Ionicons, { name: "play-forward", size: 30, color: "#fff" }) })] }), _jsxs(View, { style: [styles.bar, styles.bottom], pointerEvents: "box-none", children: [_jsx(Text, { style: styles.time, children: fmt(scrubPct !== null ? scrubPct * duration : currentTime) }), _jsxs(View, { style: styles.trackWrap, onLayout: (e) => setTrackW(e.nativeEvent.layout.width), ...pan.panHandlers, children: [_jsxs(View, { style: styles.track, children: [loadProgress != null && loadProgress > 0 && (_jsx(View, { style: [
99
+ styles.loadLine,
100
+ { width: `${clamp01(loadProgress) * 100}%` },
101
+ ] })), introRegion &&
102
+ duration > 0 &&
103
+ introRegion[1] > introRegion[0] && (_jsx(View, { style: [
104
+ styles.introHighlight,
105
+ {
106
+ left: `${clamp01(introRegion[0] / duration) * 100}%`,
107
+ width: `${clamp01((introRegion[1] - introRegion[0]) / duration) * 100}%`,
108
+ },
109
+ ] })), outroRegion &&
110
+ duration > 0 &&
111
+ outroRegion[1] > outroRegion[0] && (_jsx(View, { style: [
112
+ styles.outroHighlight,
113
+ {
114
+ left: `${clamp01(outroRegion[0] / duration) * 100}%`,
115
+ width: `${clamp01((outroRegion[1] - outroRegion[0]) / duration) * 100}%`,
116
+ },
117
+ ] })), _jsx(View, { style: [styles.fill, { width: `${shownPct * 100}%` }] })] }), _jsx(View, { style: [
118
+ styles.thumb,
119
+ { left: `${shownPct * 100}%` },
120
+ scrubPct === null && styles.thumbIdle,
121
+ ] })] }), _jsx(Text, { style: styles.time, children: fmt(duration) }), _jsxs(View, { style: styles.settingsWrap, children: [_jsx(TouchableOpacity, { style: [styles.iconBtn, settingsOpen && styles.iconBtnOn], onPress: () => {
122
+ setSettingsOpen((v) => !v);
123
+ interact();
124
+ }, hitSlop: 6, children: _jsx(Ionicons, { name: "settings-outline", size: 17, color: settingsOpen ? "#7dd3fc" : "#fff" }) }), settingsOpen && (_jsxs(View, { style: styles.settingsMenu, children: [_jsx(Text, { style: styles.settingsTitle, children: "Settings" }), showSubsSection && (_jsxs(View, { style: styles.section, children: [_jsxs(View, { style: styles.sectionHead, children: [_jsx(Ionicons, { name: "chatbox-ellipses-outline", size: 15, color: "#9aa0aa" }), _jsx(Text, { style: styles.sectionLabel, children: "Subtitles" }), _jsx(TouchableOpacity, { onPress: () => {
125
+ onToggleSubtitles();
126
+ interact();
127
+ }, hitSlop: 6, style: { marginLeft: "auto" }, children: _jsx(View, { style: [
128
+ styles.switchTrack,
129
+ subtitlesOn && styles.switchTrackOn,
130
+ ], children: _jsx(View, { style: [
131
+ styles.switchThumb,
132
+ subtitlesOn && styles.switchThumbOn,
133
+ ] }) }) })] }), hasSubs && (_jsx(View, { style: styles.menuList, children: subtitleTracks?.map((lang) => {
134
+ const active = lang === subtitleTrack;
135
+ return (_jsxs(TouchableOpacity, { style: [
136
+ styles.menuItem,
137
+ active && styles.menuItemOn,
138
+ ], onPress: () => {
139
+ setSettingsOpen(false);
140
+ interact();
141
+ onSelectSubtitleTrack?.(lang);
142
+ }, children: [_jsx(Text, { numberOfLines: 1, style: [
143
+ styles.menuItemText,
144
+ active && styles.menuItemTextOn,
145
+ ], children: lang }), active ? (_jsx(Ionicons, { name: "checkmark", size: 14, color: "#fff" })) : null] }, lang));
146
+ }) })), !!onChangeSubtitleFontSize && (_jsxs(View, { style: styles.sliderRow, children: [_jsx(TouchableOpacity, { style: styles.minusBtn, onPress: () => onChangeSubtitleFontSize(Math.max(SIZE_MIN, Math.round(currentSize - 2))), children: _jsx(Ionicons, { name: "remove", size: 16, color: "#fff" }) }), _jsx(View, { style: styles.sliderTrack, onLayout: (e) => setSizeW(e.nativeEvent.layout.width), ...sizePan.panHandlers, children: _jsx(View, { style: [
147
+ styles.sliderFill,
148
+ { width: `${sizePct * 100}%` },
149
+ ] }) }), _jsx(TouchableOpacity, { style: styles.minusBtn, onPress: () => onChangeSubtitleFontSize(Math.min(SIZE_MAX, Math.round(currentSize + 2))), children: _jsx(Ionicons, { name: "add", size: 16, color: "#fff" }) }), _jsxs(Text, { style: styles.sliderValue, children: [currentSize, "px"] })] }))] })), _jsxs(View, { style: styles.section, children: [_jsxs(View, { style: styles.sectionHead, children: [_jsx(Ionicons, { name: "volume-medium-outline", size: 15, color: "#9aa0aa" }), _jsx(Text, { style: styles.sectionLabel, children: "Volume" }), _jsx(TouchableOpacity, { onPress: () => {
150
+ onToggleMute();
151
+ interact();
152
+ }, hitSlop: 6, style: { marginLeft: "auto" }, children: _jsx(Ionicons, { name: volIcon, size: 17, color: muted ? "#e50914" : "#fff" }) }), _jsx(Text, { style: styles.sliderValue, children: Math.round(volPct * 100) })] }), _jsx(View, { style: styles.sliderRow, children: _jsx(View, { style: styles.sliderTrack, onLayout: (e) => setVolW(e.nativeEvent.layout.width), ...volPan.panHandlers, children: _jsx(View, { style: [
153
+ styles.sliderFill,
154
+ { width: `${volPct * 100}%` },
155
+ ] }) }) })] }), (resolutions?.length ?? 0) > 0 && (_jsxs(View, { style: styles.section, children: [_jsxs(View, { style: styles.sectionHead, children: [_jsx(Ionicons, { name: "resize-outline", size: 15, color: "#9aa0aa" }), _jsx(Text, { style: styles.sectionLabel, children: "Resolution" })] }), _jsx(View, { style: styles.menuList, children: resolutions?.map((label) => {
156
+ const active = label === activeRes;
157
+ return (_jsxs(TouchableOpacity, { style: [
158
+ styles.menuItem,
159
+ active && styles.menuItemOn,
160
+ ], onPress: () => {
161
+ setSettingsOpen(false);
162
+ interact();
163
+ onSelectResolution?.(label);
164
+ }, children: [_jsx(Text, { numberOfLines: 1, style: [
165
+ styles.menuItemText,
166
+ active && styles.menuItemTextOn,
167
+ ], children: label }), active ? (_jsx(Ionicons, { name: "checkmark", size: 14, color: "#fff" })) : null] }, label));
168
+ }) })] }))] }))] }), _jsx(TouchableOpacity, { onPress: () => {
169
+ onFullscreen();
170
+ interact();
171
+ }, hitSlop: 8, children: _jsx(Ionicons, { name: fullscreen ? "contract" : "expand", size: 18, color: "#fff" }) })] })] })), visible && inIntro && (_jsx(TouchableOpacity, { style: styles.skipRegionBtn, onPress: onSkipIntro, children: _jsx(Text, { style: styles.skipRegionText, children: "Skip Intro \u203A" }) })), visible && inOutro && (_jsx(TouchableOpacity, { style: styles.skipRegionBtn, onPress: onSkipOutro, children: _jsx(Text, { style: styles.skipRegionText, children: "Skip Outro \u203A" }) }))] }));
172
+ }
173
+ const styles = StyleSheet.create({
174
+ root: { zIndex: 2 },
175
+ centerBadge: {
176
+ justifyContent: "center",
177
+ alignItems: "center",
178
+ width: "100%",
179
+ height: "100%",
180
+ },
181
+ bar: {
182
+ position: "absolute",
183
+ left: 0,
184
+ right: 0,
185
+ flexDirection: "row",
186
+ alignItems: "center",
187
+ paddingHorizontal: 12,
188
+ backgroundColor: "rgba(0,0,0,0.45)",
189
+ gap: 8,
190
+ zIndex: 5,
191
+ },
192
+ top: { top: 0, height: 42 },
193
+ bottom: { bottom: 0, height: 44 },
194
+ title: { color: "#fff", fontWeight: "700", fontSize: 13, flex: 1 },
195
+ centerRow: {
196
+ justifyContent: "center",
197
+ alignItems: "center",
198
+ flexDirection: "row",
199
+ gap: 34,
200
+ },
201
+ ppBtn: {
202
+ width: 64,
203
+ height: 64,
204
+ borderRadius: 32,
205
+ backgroundColor: "rgba(229,9,20,0.92)",
206
+ justifyContent: "center",
207
+ alignItems: "center",
208
+ },
209
+ skipBtn: { opacity: 0.9 },
210
+ time: {
211
+ color: "#ddd",
212
+ fontSize: 11,
213
+ fontVariant: ["tabular-nums"],
214
+ minWidth: 58,
215
+ },
216
+ trackWrap: { flex: 1, paddingVertical: 12, justifyContent: "center" },
217
+ track: {
218
+ height: 4,
219
+ borderRadius: 2,
220
+ backgroundColor: "#3a3f47",
221
+ overflow: "hidden",
222
+ },
223
+ fill: { height: 4, borderRadius: 2, backgroundColor: "#e50914" },
224
+ loadLine: {
225
+ position: "absolute",
226
+ left: 0,
227
+ top: 0,
228
+ height: 4,
229
+ borderRadius: 2,
230
+ backgroundColor: "rgba(255,255,255,0.35)",
231
+ },
232
+ introHighlight: {
233
+ position: "absolute",
234
+ top: 0,
235
+ height: 4,
236
+ backgroundColor: "rgba(0,229,255,0.55)",
237
+ },
238
+ outroHighlight: {
239
+ position: "absolute",
240
+ top: 0,
241
+ height: 4,
242
+ backgroundColor: "rgba(255,0,200,0.55)",
243
+ },
244
+ skipRegionBtn: {
245
+ position: "absolute",
246
+ right: 16,
247
+ bottom: 60,
248
+ flexDirection: "row",
249
+ alignItems: "center",
250
+ backgroundColor: "rgba(0,0,0,0.6)",
251
+ borderWidth: 1,
252
+ borderColor: "rgba(255,255,255,0.25)",
253
+ borderRadius: 20,
254
+ paddingHorizontal: 14,
255
+ paddingVertical: 8,
256
+ },
257
+ skipRegionText: { color: "#fff", fontWeight: "700", fontSize: 13 },
258
+ thumb: {
259
+ position: "absolute",
260
+ top: 12 - 7 + 2,
261
+ marginLeft: -6,
262
+ width: 12,
263
+ height: 12,
264
+ borderRadius: 6,
265
+ backgroundColor: "#e50914",
266
+ },
267
+ thumbIdle: { opacity: 0.85 },
268
+ iconBtn: {
269
+ width: 26,
270
+ height: 26,
271
+ borderRadius: 4,
272
+ alignItems: "center",
273
+ justifyContent: "center",
274
+ },
275
+ iconBtnOn: { backgroundColor: "rgba(125,211,252,0.18)" },
276
+ settingsWrap: { position: "relative", zIndex: 20 },
277
+ settingsMenu: {
278
+ position: "absolute",
279
+ bottom: 30,
280
+ right: 0,
281
+ zIndex: 30,
282
+ width: 250,
283
+ maxHeight: 420,
284
+ backgroundColor: "#14161c",
285
+ borderWidth: 1,
286
+ borderColor: "#2a2f3a",
287
+ borderRadius: 8,
288
+ overflow: "hidden",
289
+ paddingBottom: 6,
290
+ elevation: 12,
291
+ shadowColor: "#000",
292
+ shadowOpacity: 0.5,
293
+ shadowRadius: 10,
294
+ shadowOffset: { width: 0, height: 3 },
295
+ },
296
+ settingsTitle: {
297
+ color: "#9aa0aa",
298
+ fontSize: 11,
299
+ fontWeight: "800",
300
+ textTransform: "uppercase",
301
+ letterSpacing: 1,
302
+ paddingHorizontal: 12,
303
+ paddingTop: 10,
304
+ paddingBottom: 6,
305
+ borderBottomWidth: 1,
306
+ borderBottomColor: "#262a34",
307
+ },
308
+ section: { paddingHorizontal: 6, paddingTop: 8 },
309
+ sectionHead: {
310
+ flexDirection: "row",
311
+ alignItems: "center",
312
+ gap: 6,
313
+ paddingHorizontal: 6,
314
+ paddingBottom: 2,
315
+ },
316
+ sectionLabel: { color: "#cbd5e1", fontSize: 12, fontWeight: "700" },
317
+ menuList: { paddingVertical: 4 },
318
+ menuItem: {
319
+ flexDirection: "row",
320
+ alignItems: "center",
321
+ justifyContent: "space-between",
322
+ gap: 10,
323
+ paddingHorizontal: 14,
324
+ paddingVertical: 6,
325
+ borderRadius: 6,
326
+ },
327
+ menuItemOn: { backgroundColor: "rgba(125,211,252,0.15)" },
328
+ menuItemText: { color: "#cbd5e1", fontSize: 13, fontWeight: "600" },
329
+ menuItemTextOn: { color: "#7dd3fc", fontWeight: "800" },
330
+ sliderRow: {
331
+ flexDirection: "row",
332
+ alignItems: "center",
333
+ gap: 10,
334
+ paddingHorizontal: 10,
335
+ paddingVertical: 8,
336
+ },
337
+ minusBtn: {
338
+ width: 24,
339
+ height: 24,
340
+ borderRadius: 4,
341
+ alignItems: "center",
342
+ justifyContent: "center",
343
+ backgroundColor: "#2a2f3a",
344
+ },
345
+ sliderTrack: { flex: 1, height: 22, justifyContent: "center" },
346
+ sliderFill: { height: 4, borderRadius: 2, backgroundColor: "#7dd3fc" },
347
+ sliderValue: {
348
+ color: "#9aa0aa",
349
+ fontSize: 11,
350
+ fontWeight: "700",
351
+ minWidth: 32,
352
+ textAlign: "right",
353
+ },
354
+ switchTrack: {
355
+ width: 34,
356
+ height: 20,
357
+ borderRadius: 10,
358
+ backgroundColor: "#3a3f47",
359
+ justifyContent: "center",
360
+ paddingHorizontal: 2,
361
+ },
362
+ switchTrackOn: { backgroundColor: "rgba(229,9,20,0.85)" },
363
+ switchThumb: {
364
+ width: 16,
365
+ height: 16,
366
+ borderRadius: 8,
367
+ backgroundColor: "#fff",
368
+ },
369
+ switchThumbOn: { alignSelf: "flex-end" },
370
+ });
@@ -0,0 +1,21 @@
1
+ export type SubCueShapeAndroid = {
2
+ start: number;
3
+ end: number;
4
+ text: string;
5
+ };
6
+ /**
7
+ * Renders the active subtitle cue over the video surface. Positioned
8
+ * absolutely with zIndex 1 (below the controls at zIndex 2, above the
9
+ * video background at zIndex 0).
10
+ *
11
+ * When `width`/`height` are supplied they are used to size the overlay to the
12
+ * exact area the video occupies, so cues stay aligned with the picture (e.g.
13
+ * when the player is letterboxed inside a wider container).
14
+ */
15
+ export declare function SubtitleDisplayAndroid({ cue, enabled, fontSize, width, height, }: {
16
+ cue: SubCueShapeAndroid | null;
17
+ enabled: boolean;
18
+ fontSize: number;
19
+ width?: number;
20
+ height?: number;
21
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,55 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { StyleSheet, Text, View } from "react-native";
3
+ /**
4
+ * Renders the active subtitle cue over the video surface. Positioned
5
+ * absolutely with zIndex 1 (below the controls at zIndex 2, above the
6
+ * video background at zIndex 0).
7
+ *
8
+ * When `width`/`height` are supplied they are used to size the overlay to the
9
+ * exact area the video occupies, so cues stay aligned with the picture (e.g.
10
+ * when the player is letterboxed inside a wider container).
11
+ */
12
+ export function SubtitleDisplayAndroid({ cue, enabled, fontSize, width, height, }) {
13
+ if (!enabled || !cue?.text)
14
+ return null;
15
+ const sized = width != null || height != null;
16
+ return (_jsx(View, { pointerEvents: "none", style: sized
17
+ ? [
18
+ styles.videoArea,
19
+ {
20
+ width: width ?? "100%",
21
+ height: height ?? "100%",
22
+ },
23
+ ]
24
+ : styles.wrap, children: _jsx(Text, { style: [styles.text, { fontSize, lineHeight: fontSize * 1.35 }], children: cue.text }) }));
25
+ }
26
+ const styles = StyleSheet.create({
27
+ wrap: {
28
+ position: "absolute",
29
+ left: 0,
30
+ right: 0,
31
+ bottom: 56,
32
+ alignItems: "center",
33
+ paddingHorizontal: 24,
34
+ zIndex: 1,
35
+ },
36
+ videoArea: {
37
+ position: "absolute",
38
+ left: 0,
39
+ top: 0,
40
+ justifyContent: "flex-end",
41
+ alignItems: "center",
42
+ paddingHorizontal: 24,
43
+ paddingBottom: 56,
44
+ zIndex: 1,
45
+ },
46
+ text: {
47
+ backgroundColor: "rgba(0,0,0,0.65)",
48
+ color: "#fff",
49
+ textAlign: "center",
50
+ paddingHorizontal: 8,
51
+ paddingVertical: 3,
52
+ borderRadius: 4,
53
+ overflow: "hidden",
54
+ },
55
+ });
@@ -0,0 +1,3 @@
1
+ export { MegaVideoPlayerAndroid, type MegaVideoPlayerAndroidProps, type MegaVideoPlayerAndroidHandle, } from "./MegaVideoPlayerAndroid.js";
2
+ export { PlayerControlsAndroid, type PlayerControlsAndroidProps } from "./PlayerControlsAndroid.js";
3
+ export { SubtitleDisplayAndroid, type SubCueShapeAndroid } from "./SubtitleDisplayAndroid.js";
@@ -0,0 +1,3 @@
1
+ export { MegaVideoPlayerAndroid, } from "./MegaVideoPlayerAndroid.js";
2
+ export { PlayerControlsAndroid } from "./PlayerControlsAndroid.js";
3
+ export { SubtitleDisplayAndroid } from "./SubtitleDisplayAndroid.js";
@@ -0,0 +1,92 @@
1
+ import React from "react";
2
+ import { type SubCue, type SubtitleFormat, type SubtitleTrack } from "../../subtitles/index.js";
3
+ export type MegaVideoPlayerWebProps = {
4
+ /** A playable URL — a direct stream or the first part URL. */
5
+ uri?: string | null;
6
+ /** Ordered additional part URLs for multi-part episodes, played in sequence. */
7
+ sources?: string[];
8
+ title?: string;
9
+ /** Pass your subtitle file here — MEGA links, plain http(s) CORS hosts, or a known extension (.srt/.ass/.vtt). */
10
+ subtitleUrl?: string;
11
+ /** optional format hint (defaults to URL extension, then content sniffing) */
12
+ subtitleFormat?: SubtitleFormat;
13
+ /** language name shown in the player controls for the single `subtitleUrl` track
14
+ * (e.g. "English", "French"). Defaults to "SRT". */
15
+ subtitleLang?: string;
16
+ subtitleTracks?: SubtitleTrack[];
17
+ /** preselect a subtitle track by language on load */
18
+ initialSubtitleLang?: string | null;
19
+ /** shift the cue timestamps by an offset in seconds (can be negative) */
20
+ subtitleOffset?: number;
21
+ /** resolution labels in display order — the FIRST is the default source
22
+ * (the episode URL), e.g. ["Default", "1080p", "720p", "480p"]. */
23
+ resolutions?: string[];
24
+ /** currently active resolution label */
25
+ resolution?: string;
26
+ /** select a resolution — the parent resolves the label to its URL and
27
+ * updates `uri` so the new source is loaded. */
28
+ onSelectResolution?: (label: string) => void;
29
+ autoPlay?: boolean;
30
+ onEnd?: () => void;
31
+ onError?: (message: string) => void;
32
+ onProgress?: (info: {
33
+ currentTime: number;
34
+ duration: number;
35
+ }) => void;
36
+ onFullscreenChange?: (fullscreen: boolean) => void;
37
+ width?: number;
38
+ height?: number;
39
+ };
40
+ type MegaVideoPlayerWebState = {
41
+ playing: boolean;
42
+ buffering: boolean;
43
+ loading: boolean;
44
+ currentTime: number;
45
+ duration: number;
46
+ muted: boolean;
47
+ volume: number;
48
+ visible: boolean;
49
+ subtitlesOn: boolean;
50
+ subFontSize: number;
51
+ cues: SubCue[];
52
+ subtitleError: string | null;
53
+ error: string | null;
54
+ fullscreen: boolean;
55
+ width?: number;
56
+ height?: number;
57
+ };
58
+ export declare class MegaVideoPlayerWeb extends React.Component<MegaVideoPlayerWebProps, MegaVideoPlayerWebState> {
59
+ private player;
60
+ private subs;
61
+ private hideTimer;
62
+ private mountedRef;
63
+ private autoplayRef;
64
+ private subTrackIdx;
65
+ private tracks;
66
+ private sourceIndexRef;
67
+ constructor(props: MegaVideoPlayerWebProps);
68
+ componentDidMount(): void;
69
+ componentDidUpdate(prevProps: MegaVideoPlayerWebProps): void;
70
+ componentWillUnmount(): void;
71
+ /** Loads a new playable URL into the player. */
72
+ add(source: string, opts?: {
73
+ title?: string;
74
+ }): Promise<void>;
75
+ /** Stops playback and clears the current source. */
76
+ stop(): void;
77
+ playPause: () => void;
78
+ skip: (delta: number) => void;
79
+ seek: (seconds: number) => void;
80
+ toggleMute: () => void;
81
+ changeVolume: (v: number) => void;
82
+ toggleSubtitles: () => void;
83
+ toggleControls: () => void;
84
+ enterFullscreen: (next?: boolean | unknown) => void;
85
+ changeSubtitleFontSize: (px: number) => void;
86
+ selectSubtitleTrack: (lang: string) => void;
87
+ private scheduleHide;
88
+ private syncSubtitleTracks;
89
+ private loadSubtitles;
90
+ render(): React.JSX.Element;
91
+ }
92
+ export {};