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,346 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { ActivityIndicator, PanResponder, StyleSheet, Text, TouchableOpacity, View, } from "react-native";
4
+ import { Ionicons } from "@expo/vector-icons";
5
+ import { SubtitleDisplayWeb } from "./SubtitleDisplay.js";
6
+ const fmt = (s) => {
7
+ if (!Number.isFinite(s) || s < 0)
8
+ s = 0;
9
+ const h = Math.floor(s / 3600);
10
+ const m = Math.floor((s % 3600) / 60);
11
+ const sec = Math.floor(s % 60);
12
+ return `${h.toString().padStart(2, "0")}:${m
13
+ .toString()
14
+ .padStart(2, "0")}:${sec.toString().padStart(2, "0")}`;
15
+ };
16
+ const clamp01 = (v) => Math.min(1, Math.max(0, v));
17
+ export function PlayerControlsWeb(props) {
18
+ const { playing, buffering, currentTime, duration, muted, title, visible, subtitlesOn, volume, subtitleTracks, subtitleTrack, onSelectSubtitleTrack, subtitleFontSize, onChangeSubtitleFontSize, loadProgress, introRegion, outroRegion, onSkipIntro, onSkipOutro, resolutions, resolution, onSelectResolution, onToggleVisible, onPlayPause, onSkip, onSeek, onToggleMute, onToggleSubtitles, onVolume, onFullscreen, width, height, cue, enabled, fontSize, } = props;
19
+ const hasSubs = (subtitleTracks?.length ?? 0) > 0;
20
+ const activeRes = resolution ?? resolutions?.[0] ?? "";
21
+ const [trackW, setTrackW] = useState(0);
22
+ const [scrubPct, setScrubPct] = useState(null);
23
+ const [volW, setVolW] = useState(0);
24
+ const [sizeW, setSizeW] = useState(0);
25
+ const [settingsOpen, setSettingsOpen] = useState(false);
26
+ const pct = duration > 0 ? clamp01(currentTime / duration) : 0;
27
+ const volPct = clamp01(volume);
28
+ const inIntro = !!introRegion &&
29
+ introRegion[1] > introRegion[0] &&
30
+ currentTime >= introRegion[0] &&
31
+ currentTime <= introRegion[1] &&
32
+ !!onSkipIntro;
33
+ const inOutro = !!outroRegion &&
34
+ outroRegion[1] > outroRegion[0] &&
35
+ currentTime >= outroRegion[0] &&
36
+ currentTime <= outroRegion[1] &&
37
+ !!onSkipOutro;
38
+ const pan = useMemo(() => PanResponder.create({
39
+ onStartShouldSetPanResponder: () => true,
40
+ onMoveShouldSetPanResponder: () => true,
41
+ onPanResponderGrant: (e) => setScrubPct(trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0),
42
+ onPanResponderMove: (e) => setScrubPct(trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0),
43
+ onPanResponderRelease: (e) => {
44
+ const p = trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0;
45
+ setScrubPct(null);
46
+ onSeek(p * duration);
47
+ },
48
+ }), [trackW, duration, onSeek]);
49
+ const volPan = useMemo(() => PanResponder.create({
50
+ onStartShouldSetPanResponder: () => true,
51
+ onMoveShouldSetPanResponder: () => true,
52
+ onPanResponderGrant: (e) => onVolume(volW > 0 ? clamp01(e.nativeEvent.locationX / volW) : 1),
53
+ onPanResponderMove: (e) => onVolume(volW > 0 ? clamp01(e.nativeEvent.locationX / volW) : 1),
54
+ }), [volW, onVolume]);
55
+ const SIZE_MIN = 10;
56
+ const SIZE_MAX = 34;
57
+ const currentSize = subtitleFontSize ?? 14;
58
+ const sizePct = clamp01((currentSize - SIZE_MIN) / (SIZE_MAX - SIZE_MIN));
59
+ const sizePan = useMemo(() => PanResponder.create({
60
+ onStartShouldSetPanResponder: () => true,
61
+ onMoveShouldSetPanResponder: () => true,
62
+ onPanResponderGrant: (e) => {
63
+ if (!onChangeSubtitleFontSize)
64
+ return;
65
+ const p = sizeW > 0 ? clamp01(e.nativeEvent.locationX / sizeW) : 0;
66
+ onChangeSubtitleFontSize?.(Math.round(SIZE_MIN + p * (SIZE_MAX - SIZE_MIN)));
67
+ },
68
+ onPanResponderMove: (e) => {
69
+ if (!onChangeSubtitleFontSize)
70
+ return;
71
+ const p = sizeW > 0 ? clamp01(e.nativeEvent.locationX / sizeW) : 0;
72
+ onChangeSubtitleFontSize?.(Math.round(SIZE_MIN + p * (SIZE_MAX - SIZE_MIN)));
73
+ },
74
+ }), [sizeW, onChangeSubtitleFontSize]);
75
+ const shownPct = scrubPct ?? pct;
76
+ const volIcon = muted || volume === 0
77
+ ? "volume-mute"
78
+ : volume < 0.5
79
+ ? "volume-low"
80
+ : "volume-high";
81
+ const showSubsSection = hasSubs || !!onChangeSubtitleFontSize;
82
+ return (_jsxs(View, { style: [
83
+ styles.root,
84
+ StyleSheet.absoluteFill,
85
+ { width: width, height: height },
86
+ ], children: [_jsx(TouchableOpacity, { style: StyleSheet.absoluteFill, activeOpacity: 1, onPress: onToggleVisible }), buffering && (_jsx(View, { style: [StyleSheet.absoluteFill, styles.centerBadge], pointerEvents: "none", children: _jsx(ActivityIndicator, { color: "#e50914", size: "large" }) })), visible && (_jsxs(View, { style: [
87
+ StyleSheet.absoluteFill,
88
+ { width: "100%", height: "100%", position: "absolute" },
89
+ ], pointerEvents: "box-none", 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: [styles.centerRow, { flex: 1 }], 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: [
90
+ styles.loadLine,
91
+ { width: `${clamp01(loadProgress) * 100}%` },
92
+ ] })), introRegion &&
93
+ duration > 0 &&
94
+ introRegion[1] > introRegion[0] && (_jsx(View, { style: [
95
+ styles.introHighlight,
96
+ {
97
+ left: `${clamp01(introRegion[0] / duration) * 100}%`,
98
+ width: `${clamp01((introRegion[1] - introRegion[0]) / duration) * 100}%`,
99
+ },
100
+ ] })), outroRegion &&
101
+ duration > 0 &&
102
+ outroRegion[1] > outroRegion[0] && (_jsx(View, { style: [
103
+ styles.outroHighlight,
104
+ {
105
+ left: `${clamp01(outroRegion[0] / duration) * 100}%`,
106
+ width: `${clamp01((outroRegion[1] - outroRegion[0]) / duration) * 100}%`,
107
+ },
108
+ ] })), _jsx(View, { style: [styles.fill, { width: `${shownPct * 100}%` }] })] }), _jsx(View, { style: [
109
+ styles.thumb,
110
+ { left: `${shownPct * 100}%` },
111
+ scrubPct === null && styles.thumbIdle,
112
+ ] })] }), _jsx(Text, { style: styles.time, children: fmt(duration) }), _jsxs(View, { style: styles.settingsWrap, children: [_jsx(TouchableOpacity, { style: [styles.iconBtn, settingsOpen && styles.iconBtnOn], onPress: () => setSettingsOpen((v) => !v), 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: onToggleSubtitles, hitSlop: 6, style: { marginLeft: "auto" }, children: _jsx(View, { style: [
113
+ styles.switchTrack,
114
+ subtitlesOn && styles.switchTrackOn,
115
+ ], children: _jsx(View, { style: [
116
+ styles.switchThumb,
117
+ subtitlesOn && styles.switchThumbOn,
118
+ ] }) }) })] }), hasSubs && (_jsx(View, { style: styles.menuList, children: subtitleTracks?.map((lang) => {
119
+ const active = lang === subtitleTrack;
120
+ return (_jsxs(TouchableOpacity, { style: [
121
+ styles.menuItem,
122
+ active && styles.menuItemOn,
123
+ ], onPress: () => {
124
+ setSettingsOpen(false);
125
+ onSelectSubtitleTrack?.(lang);
126
+ }, children: [_jsx(Text, { style: [
127
+ styles.menuItemText,
128
+ active && styles.menuItemTextOn,
129
+ ], children: lang }), active ? (_jsx(Ionicons, { name: "checkmark", size: 14, color: "#fff" })) : null] }, lang));
130
+ }) })), !!onChangeSubtitleFontSize && (_jsxs(View, { style: styles.sliderRow, children: [_jsx(TouchableOpacity, { style: styles.minusBtn, onPress: () => onChangeSubtitleFontSize?.(Math.max(SIZE_MIN, Math.round((subtitleFontSize ?? 14) - 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: [
131
+ styles.sliderFill,
132
+ { width: `${sizePct * 100}%` },
133
+ ] }) }), _jsx(TouchableOpacity, { style: styles.minusBtn, onPress: () => onChangeSubtitleFontSize?.(Math.min(SIZE_MAX, Math.round((subtitleFontSize ?? 14) + 2))), children: _jsx(Ionicons, { name: "add", size: 16, color: "#fff" }) }), _jsxs(Text, { style: styles.sliderValue, children: [currentSize, "px"] })] }))] })) : null, _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: onToggleMute, 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: [
134
+ styles.sliderFill,
135
+ { width: `${volPct * 100}%` },
136
+ ] }) }) })] }), (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) => {
137
+ const active = label === activeRes;
138
+ return (_jsxs(TouchableOpacity, { style: [
139
+ styles.menuItem,
140
+ active && styles.menuItemOn,
141
+ ], onPress: () => {
142
+ setSettingsOpen(false);
143
+ onSelectResolution?.(label);
144
+ }, children: [_jsx(Text, { style: [
145
+ styles.menuItemText,
146
+ active && styles.menuItemTextOn,
147
+ ], children: label }), active ? (_jsx(Ionicons, { name: "checkmark", size: 14, color: "#fff" })) : null] }, label));
148
+ }) })] })) : null] })) : null] }), _jsx(TouchableOpacity, { onPress: onFullscreen, hitSlop: 8, children: _jsx(Ionicons, { name: "expand", size: 18, color: "#fff" }) })] })] })), _jsx(SubtitleDisplayWeb, { cue: cue, enabled: enabled, fontSize: fontSize }), 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" }) }))] }));
149
+ }
150
+ const styles = StyleSheet.create({
151
+ root: { zIndex: 2 },
152
+ centerBadge: {
153
+ justifyContent: "center",
154
+ alignItems: "center",
155
+ width: "100%",
156
+ height: "100%",
157
+ },
158
+ bar: {
159
+ position: "absolute",
160
+ left: 0,
161
+ right: 0,
162
+ flexDirection: "row",
163
+ alignItems: "center",
164
+ paddingHorizontal: 12,
165
+ backgroundColor: "rgba(0,0,0,0.45)",
166
+ gap: 8,
167
+ },
168
+ top: { top: 0, height: 42 },
169
+ bottom: { bottom: 0, height: 44 },
170
+ title: { color: "#fff", fontWeight: "700", fontSize: 13, flex: 1 },
171
+ centerRow: {
172
+ justifyContent: "center",
173
+ alignItems: "center",
174
+ flexDirection: "row",
175
+ gap: 34,
176
+ },
177
+ ppBtn: {
178
+ width: 64,
179
+ height: 64,
180
+ borderRadius: 32,
181
+ backgroundColor: "rgba(229,9,20,0.92)",
182
+ justifyContent: "center",
183
+ alignItems: "center",
184
+ },
185
+ skipBtn: { opacity: 0.9 },
186
+ time: {
187
+ color: "#ddd",
188
+ fontSize: 11,
189
+ fontVariant: ["tabular-nums"],
190
+ minWidth: 58,
191
+ },
192
+ trackWrap: { flex: 1, paddingVertical: 12, justifyContent: "center" },
193
+ track: {
194
+ height: 4,
195
+ borderRadius: 2,
196
+ backgroundColor: "#3a3f47",
197
+ overflow: "hidden",
198
+ },
199
+ fill: { height: 4, borderRadius: 2, backgroundColor: "#e50914" },
200
+ loadLine: {
201
+ position: "absolute",
202
+ left: 0,
203
+ top: 0,
204
+ height: 4,
205
+ borderRadius: 2,
206
+ backgroundColor: "rgba(255,255,255,0.35)",
207
+ },
208
+ introHighlight: {
209
+ position: "absolute",
210
+ top: 0,
211
+ height: 4,
212
+ backgroundColor: "rgba(0,229,255,0.55)",
213
+ },
214
+ outroHighlight: {
215
+ position: "absolute",
216
+ top: 0,
217
+ height: 4,
218
+ backgroundColor: "rgba(255,0,200,0.55)",
219
+ },
220
+ skipRegionBtn: {
221
+ position: "absolute",
222
+ right: 16,
223
+ bottom: 60,
224
+ flexDirection: "row",
225
+ alignItems: "center",
226
+ backgroundColor: "rgba(0,0,0,0.6)",
227
+ borderWidth: 1,
228
+ borderColor: "rgba(255,255,255,0.25)",
229
+ borderRadius: 20,
230
+ paddingHorizontal: 14,
231
+ paddingVertical: 8,
232
+ },
233
+ skipRegionText: { color: "#fff", fontWeight: "700", fontSize: 13 },
234
+ thumb: {
235
+ position: "absolute",
236
+ top: 12 - 7 + 2,
237
+ marginLeft: -6,
238
+ width: 12,
239
+ height: 12,
240
+ borderRadius: 6,
241
+ backgroundColor: "#e50914",
242
+ },
243
+ thumbIdle: { opacity: 0.85 },
244
+ iconBtn: {
245
+ width: 26,
246
+ height: 26,
247
+ borderRadius: 4,
248
+ alignItems: "center",
249
+ justifyContent: "center",
250
+ },
251
+ iconBtnOn: { backgroundColor: "rgba(125,211,252,0.18)" },
252
+ settingsWrap: { position: "relative", zIndex: 20 },
253
+ settingsMenu: {
254
+ position: "absolute",
255
+ bottom: 30,
256
+ right: 0,
257
+ zIndex: 30,
258
+ width: 250,
259
+ maxHeight: 420,
260
+ backgroundColor: "#14161c",
261
+ borderWidth: 1,
262
+ borderColor: "#2a2f3a",
263
+ borderRadius: 8,
264
+ overflow: "hidden",
265
+ paddingBottom: 6,
266
+ elevation: 12,
267
+ shadowColor: "#000",
268
+ shadowOpacity: 0.5,
269
+ shadowRadius: 10,
270
+ shadowOffset: { width: 0, height: 3 },
271
+ },
272
+ settingsTitle: {
273
+ color: "#9aa0aa",
274
+ fontSize: 11,
275
+ fontWeight: "800",
276
+ textTransform: "uppercase",
277
+ letterSpacing: 1,
278
+ paddingHorizontal: 12,
279
+ paddingTop: 10,
280
+ paddingBottom: 6,
281
+ borderBottomWidth: 1,
282
+ borderBottomColor: "#262a34",
283
+ },
284
+ section: { paddingHorizontal: 6, paddingTop: 8 },
285
+ sectionHead: {
286
+ flexDirection: "row",
287
+ alignItems: "center",
288
+ gap: 6,
289
+ paddingHorizontal: 6,
290
+ paddingBottom: 2,
291
+ },
292
+ sectionLabel: { color: "#cbd5e1", fontSize: 12, fontWeight: "700" },
293
+ menuList: { paddingVertical: 4 },
294
+ menuItem: {
295
+ flexDirection: "row",
296
+ alignItems: "center",
297
+ justifyContent: "space-between",
298
+ gap: 10,
299
+ paddingHorizontal: 14,
300
+ paddingVertical: 6,
301
+ borderRadius: 6,
302
+ },
303
+ menuItemOn: { backgroundColor: "rgba(125,211,252,0.15)" },
304
+ menuItemText: { color: "#cbd5e1", fontSize: 13, fontWeight: "600" },
305
+ menuItemTextOn: { color: "#7dd3fc", fontWeight: "800" },
306
+ sliderRow: {
307
+ flexDirection: "row",
308
+ alignItems: "center",
309
+ gap: 10,
310
+ paddingHorizontal: 10,
311
+ paddingVertical: 8,
312
+ },
313
+ minusBtn: {
314
+ width: 24,
315
+ height: 24,
316
+ borderRadius: 4,
317
+ alignItems: "center",
318
+ justifyContent: "center",
319
+ backgroundColor: "#2a2f3a",
320
+ },
321
+ sliderTrack: { flex: 1, height: 22, justifyContent: "center" },
322
+ sliderFill: { height: 4, borderRadius: 2, backgroundColor: "#7dd3fc" },
323
+ sliderValue: {
324
+ color: "#9aa0aa",
325
+ fontSize: 11,
326
+ fontWeight: "700",
327
+ minWidth: 32,
328
+ textAlign: "right",
329
+ },
330
+ switchTrack: {
331
+ width: 34,
332
+ height: 20,
333
+ borderRadius: 10,
334
+ backgroundColor: "#3a3f47",
335
+ justifyContent: "center",
336
+ paddingHorizontal: 2,
337
+ },
338
+ switchTrackOn: { backgroundColor: "rgba(229,9,20,0.85)" },
339
+ switchThumb: {
340
+ width: 16,
341
+ height: 16,
342
+ borderRadius: 8,
343
+ backgroundColor: "#fff",
344
+ },
345
+ switchThumbOn: { alignSelf: "flex-end" },
346
+ });
@@ -0,0 +1,15 @@
1
+ export type SubCueShapeWeb = {
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
+ export declare function SubtitleDisplayWeb({ cue, enabled, fontSize, }: {
12
+ cue: SubCueShapeWeb | null;
13
+ enabled: boolean;
14
+ fontSize: number;
15
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,32 @@
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
+ export function SubtitleDisplayWeb({ cue, enabled, fontSize, }) {
9
+ if (!enabled || !cue?.text)
10
+ return null;
11
+ return (_jsx(View, { pointerEvents: "none", style: styles.wrap, children: _jsx(Text, { style: [styles.text, { fontSize, lineHeight: fontSize * 1.35 }], children: cue.text }) }));
12
+ }
13
+ const styles = StyleSheet.create({
14
+ wrap: {
15
+ position: "absolute",
16
+ left: 0,
17
+ right: 0,
18
+ bottom: 56,
19
+ alignItems: "center",
20
+ paddingHorizontal: 24,
21
+ zIndex: 1,
22
+ },
23
+ text: {
24
+ backgroundColor: "rgba(0,0,0,0.65)",
25
+ color: "#fff",
26
+ textAlign: "center",
27
+ paddingHorizontal: 8,
28
+ paddingVertical: 3,
29
+ borderRadius: 4,
30
+ overflow: "hidden",
31
+ },
32
+ });
@@ -0,0 +1,3 @@
1
+ export { MegaVideoPlayerWeb, type MegaVideoPlayerWebProps } from "./MegaVideoPlayer.js";
2
+ export { PlayerControlsWeb, type PlayerControlsWebProps } from "./PlayerControls.js";
3
+ export { SubtitleDisplayWeb, type SubCueShapeWeb } from "./SubtitleDisplay.js";
@@ -0,0 +1,3 @@
1
+ export { MegaVideoPlayerWeb } from "./MegaVideoPlayer.js";
2
+ export { PlayerControlsWeb } from "./PlayerControls.js";
3
+ export { SubtitleDisplayWeb } from "./SubtitleDisplay.js";
@@ -0,0 +1,3 @@
1
+ export { MegaVideoPlayerWeb, type MegaVideoPlayerWebProps, } from "./playerweb/MegaVideoPlayer.js";
2
+ export { PlayerControlsWeb, type PlayerControlsWebProps, } from "./playerweb/PlayerControls.js";
3
+ export { SubtitleDisplayWeb, type SubCueShapeWeb, } from "./playerweb/SubtitleDisplay.js";
@@ -0,0 +1,3 @@
1
+ export { MegaVideoPlayerWeb, } from "./playerweb/MegaVideoPlayer.js";
2
+ export { PlayerControlsWeb, } from "./playerweb/PlayerControls.js";
3
+ export { SubtitleDisplayWeb, } from "./playerweb/SubtitleDisplay.js";
@@ -0,0 +1,10 @@
1
+ import type { MegaAccountStore } from "./types.js";
2
+ /** Structural subset of AsyncStorage (decoupled from its type surface). */
3
+ type AsyncStoreLike = {
4
+ getItem: (key: string) => Promise<string | null>;
5
+ setItem: (key: string, value: string) => Promise<void>;
6
+ removeItem: (key: string) => Promise<void>;
7
+ };
8
+ /** React Native account store backed by @react-native-async-storage/async-storage. */
9
+ export declare function createAsyncStorageStore(storage?: AsyncStoreLike): MegaAccountStore;
10
+ export {};
@@ -0,0 +1,46 @@
1
+ import AsyncStorage from "@react-native-async-storage/async-storage";
2
+ const STORAGE_KEY = "mega-stream-proxy.accounts";
3
+ /** React Native account store backed by @react-native-async-storage/async-storage. */
4
+ export function createAsyncStorageStore(storage) {
5
+ const s = (storage ?? AsyncStorage);
6
+ const parseStored = (raw) => {
7
+ if (!raw)
8
+ return [];
9
+ try {
10
+ const arr = JSON.parse(raw);
11
+ if (!Array.isArray(arr))
12
+ return [];
13
+ return arr
14
+ .filter((v) => typeof v === "object" && v !== null)
15
+ .map((v) => ({
16
+ id: String(v.id ?? ""),
17
+ email: String(v.email ?? ""),
18
+ password: String(v.password ?? ""),
19
+ label: String(v.label ?? ""),
20
+ createdAt: Number(v.createdAt ?? 0),
21
+ }))
22
+ .sort((a, b) => b.createdAt - a.createdAt);
23
+ }
24
+ catch {
25
+ return [];
26
+ }
27
+ };
28
+ return {
29
+ async list() {
30
+ return parseStored(await s.getItem(STORAGE_KEY));
31
+ },
32
+ async save(account) {
33
+ const accounts = parseStored(await s.getItem(STORAGE_KEY));
34
+ const idx = accounts.findIndex((a) => a.id === account.id);
35
+ if (idx >= 0)
36
+ accounts[idx] = account;
37
+ else
38
+ accounts.push(account);
39
+ await s.setItem(STORAGE_KEY, JSON.stringify(accounts));
40
+ },
41
+ async remove(id) {
42
+ const accounts = parseStored(await s.getItem(STORAGE_KEY));
43
+ await s.setItem(STORAGE_KEY, JSON.stringify(accounts.filter((a) => a.id !== id)));
44
+ },
45
+ };
46
+ }
@@ -0,0 +1,4 @@
1
+ export type { MegaAccount, MegaAccountStore, MegaTreeNode, AccountTestResult, } from "./types.js";
2
+ export { MegaAccountManager } from "./manager.js";
3
+ export { createLocalStorageStore } from "./local-storage.js";
4
+ export { createAsyncStorageStore } from "./async-storage.js";
@@ -0,0 +1,3 @@
1
+ export { MegaAccountManager } from "./manager.js";
2
+ export { createLocalStorageStore } from "./local-storage.js";
3
+ export { createAsyncStorageStore } from "./async-storage.js";
@@ -0,0 +1,3 @@
1
+ import type { MegaAccountStore } from "./types.js";
2
+ /** Persists accounts in `localStorage` (web). Subscribe listens to cross-tab `storage` events. */
3
+ export declare function createLocalStorageStore(storage?: Pick<Storage, "getItem" | "setItem" | "removeItem"> | null): MegaAccountStore;
@@ -0,0 +1,63 @@
1
+ const STORAGE_KEY = "mega-stream-proxy.accounts";
2
+ function parseStored(raw) {
3
+ if (!raw)
4
+ return [];
5
+ try {
6
+ const arr = JSON.parse(raw);
7
+ if (!Array.isArray(arr))
8
+ return [];
9
+ return arr
10
+ .filter((v) => typeof v === "object" && v !== null)
11
+ .map((v) => ({
12
+ id: String(v.id ?? ""),
13
+ email: String(v.email ?? ""),
14
+ password: String(v.password ?? ""),
15
+ label: String(v.label ?? ""),
16
+ createdAt: Number(v.createdAt ?? 0),
17
+ }));
18
+ }
19
+ catch {
20
+ return [];
21
+ }
22
+ }
23
+ function sortByNewest(a, b) {
24
+ return b.createdAt - a.createdAt;
25
+ }
26
+ /** Persists accounts in `localStorage` (web). Subscribe listens to cross-tab `storage` events. */
27
+ export function createLocalStorageStore(storage = null) {
28
+ const ls = storage ?? (typeof localStorage !== "undefined" ? localStorage : null);
29
+ const key = () => (ls ? STORAGE_KEY : "__noop__");
30
+ const readAll = async () => ls ? parseStored(ls.getItem(key())).sort(sortByNewest) : [];
31
+ const writeAll = async (accounts) => {
32
+ if (!ls)
33
+ return;
34
+ ls.setItem(key(), JSON.stringify(accounts));
35
+ };
36
+ return {
37
+ list: readAll,
38
+ async save(account) {
39
+ const accounts = await readAll();
40
+ const idx = accounts.findIndex((a) => a.id === account.id);
41
+ if (idx >= 0)
42
+ accounts[idx] = account;
43
+ else
44
+ accounts.push(account);
45
+ await writeAll(accounts);
46
+ },
47
+ async remove(id) {
48
+ const accounts = await readAll();
49
+ await writeAll(accounts.filter((a) => a.id !== id));
50
+ },
51
+ subscribe(cb, subscriptionKey = STORAGE_KEY) {
52
+ if (typeof window === "undefined" || !ls)
53
+ return () => { };
54
+ const onStorage = (e) => {
55
+ if (e.key !== subscriptionKey && e.key !== null)
56
+ return;
57
+ void readAll().then(cb);
58
+ };
59
+ window.addEventListener("storage", onStorage);
60
+ return () => window.removeEventListener("storage", onStorage);
61
+ },
62
+ };
63
+ }
@@ -0,0 +1,39 @@
1
+ import type { AccountTestResult, MegaAccount, MegaAccountStore, MegaTreeNode } from "./types.js";
2
+ /**
3
+ * Owns the MEGA accounts list (via a pluggable store) plus the live megajs
4
+ * `Storage` sessions used while browsing a saved account's cloud drive.
5
+ */
6
+ export declare class MegaAccountManager {
7
+ private readonly store;
8
+ private sessions;
9
+ constructor(store: MegaAccountStore);
10
+ listAccounts(): Promise<MegaAccount[]>;
11
+ subscribe(cb: (accounts: MegaAccount[]) => void): () => void;
12
+ addAccount(input: {
13
+ email: string;
14
+ password: string;
15
+ label?: string;
16
+ }): Promise<MegaAccount>;
17
+ deleteAccount(id: string): Promise<void>;
18
+ private openStorage;
19
+ closeSession(accountId: string): void;
20
+ closeAll(): void;
21
+ /** login only — verifies credentials and counts the cloud drive. */
22
+ testAccount(account: {
23
+ id?: string;
24
+ email: string;
25
+ password: string;
26
+ }): Promise<AccountTestResult>;
27
+ /** Full Cloud-Drive tree of a saved account, root first. */
28
+ loadTree(account: {
29
+ id: string;
30
+ email: string;
31
+ password: string;
32
+ }): Promise<MegaTreeNode[]>;
33
+ /** Export a shareable link (with key) for a file inside the logged-in account. */
34
+ exportLink(account: {
35
+ id: string;
36
+ email: string;
37
+ password: string;
38
+ }, handle: string): Promise<string>;
39
+ }