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,368 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // The exact MyMovies episode player (components/MegaVideoPlayer.web.tsx),
3
+ // packaged as a cross-platform component (Android / iOS / web). A direct
4
+ // VideoPlayer instance (createVideoPlayer) driven from a class component, with
5
+ // ordered part URLs played back-to-back and auto-advancing to the next part when
6
+ // the current one ends. Sources come in as proxy URLs — from
7
+ // `mega-stream-proxy/native` (http://127.0.0.1:<port>/…) on Android/iOS or from
8
+ // the Service Worker (`/__mmstream__/…`) on the web.
9
+ import React from "react";
10
+ import { ActivityIndicator, StyleSheet, Text, View } from "react-native";
11
+ import { createVideoPlayer, VideoView } from "expo-video";
12
+ import { Ionicons } from "@expo/vector-icons";
13
+ import { PlayerControlsWeb } from "./PlayerControls.js";
14
+ import { loadSubtitles, } from "../../subtitles/index.js";
15
+ export class MegaVideoPlayerWeb extends React.Component {
16
+ player;
17
+ subs = [];
18
+ hideTimer = null;
19
+ mountedRef = true;
20
+ autoplayRef = true;
21
+ subTrackIdx = 0;
22
+ tracks = [];
23
+ sourceIndexRef = 0;
24
+ constructor(props) {
25
+ super(props);
26
+ this.state = {
27
+ playing: false,
28
+ buffering: true,
29
+ loading: false,
30
+ currentTime: 0,
31
+ duration: 0,
32
+ muted: false,
33
+ volume: 1,
34
+ visible: true,
35
+ subtitlesOn: true,
36
+ subFontSize: 14,
37
+ cues: [],
38
+ subtitleError: null,
39
+ error: null,
40
+ fullscreen: false,
41
+ width: this.props.width,
42
+ height: this.props.height,
43
+ };
44
+ this.player = createVideoPlayer(null);
45
+ this.player.loop = false;
46
+ this.player.timeUpdateEventInterval = 1;
47
+ this.player.bufferOptions = { preferredForwardBufferDuration: 60 };
48
+ this.sourceIndexRef = Math.max(0, (this.props.sources ?? []).indexOf(this.props.uri ?? ""));
49
+ }
50
+ // ---- lifecycle ----
51
+ componentDidMount() {
52
+ this.mountedRef = true;
53
+ this.autoplayRef = this.props.autoPlay !== false;
54
+ this.subs.push(this.player.addListener("statusChange", ({ status, error }) => {
55
+ if (!this.mountedRef)
56
+ return;
57
+ console.log(`[MegaVideoPlayer] status: ${status}` +
58
+ (error ? ` error: ${error.message}` : ""));
59
+ this.setState({
60
+ buffering: status === "loading",
61
+ error: status === "error"
62
+ ? (error?.message ?? "Video error")
63
+ : this.state.error,
64
+ });
65
+ if (error) {
66
+ this.props.onError?.(error.message ?? "Video error");
67
+ }
68
+ if (status === "readyToPlay") {
69
+ this.setState({
70
+ buffering: false,
71
+ duration: this.player.duration || this.state.duration,
72
+ });
73
+ if (this.autoplayRef)
74
+ this.player.play();
75
+ }
76
+ }));
77
+ this.subs.push(this.player.addListener("playingChange", ({ isPlaying }) => {
78
+ if (!this.mountedRef)
79
+ return;
80
+ console.log(`[MegaVideoPlayer] playing: ${isPlaying}`);
81
+ this.setState({ playing: isPlaying });
82
+ if (isPlaying)
83
+ this.scheduleHide();
84
+ else
85
+ this.setState({ visible: true });
86
+ }));
87
+ this.subs.push(this.player.addListener("timeUpdate", ({ currentTime }) => {
88
+ if (!this.mountedRef)
89
+ return;
90
+ this.setState({ currentTime });
91
+ this.props.onProgress?.({
92
+ currentTime,
93
+ duration: this.player.duration || this.state.duration,
94
+ });
95
+ }));
96
+ this.subs.push(this.player.addListener("playToEnd", () => {
97
+ const sources = this.props.sources ?? [];
98
+ const next = sources[this.sourceIndexRef + 1];
99
+ if (next) {
100
+ this.sourceIndexRef += 1;
101
+ console.log(`[MegaVideoPlayer] advancing to part ${this.sourceIndexRef + 1}/${sources.length}`);
102
+ void this.add(next, { title: this.props.title });
103
+ }
104
+ else {
105
+ this.props.onEnd?.();
106
+ }
107
+ }));
108
+ this.syncSubtitleTracks();
109
+ if (this.props.uri) {
110
+ void this.add(this.props.uri, { title: this.props.title });
111
+ }
112
+ }
113
+ componentDidUpdate(prevProps) {
114
+ if (this.props.uri !== prevProps.uri && this.props.uri) {
115
+ this.sourceIndexRef = Math.max(0, (this.props.sources ?? []).indexOf(this.props.uri));
116
+ void this.add(this.props.uri, { title: this.props.title });
117
+ }
118
+ if (this.props.subtitleTracks !== prevProps.subtitleTracks ||
119
+ this.props.subtitleUrl !== prevProps.subtitleUrl) {
120
+ this.syncSubtitleTracks();
121
+ }
122
+ }
123
+ componentWillUnmount() {
124
+ this.mountedRef = false;
125
+ if (this.hideTimer)
126
+ clearTimeout(this.hideTimer);
127
+ this.subs.forEach((s) => s.remove());
128
+ this.subs = [];
129
+ if (this.player) {
130
+ try {
131
+ this.player.pause();
132
+ this.player.release();
133
+ }
134
+ catch { }
135
+ }
136
+ }
137
+ // ---- source management ----
138
+ /** Loads a new playable URL into the player. */
139
+ async add(source, opts) {
140
+ if (!source)
141
+ return;
142
+ this.setState({ error: null, loading: true, buffering: true });
143
+ try {
144
+ if (!this.mountedRef)
145
+ return;
146
+ console.log(`[MegaVideoPlayer] add: ${source}`);
147
+ this.player.replace({
148
+ uri: source,
149
+ metadata: { title: opts?.title ?? this.props.title },
150
+ });
151
+ this.setState({
152
+ loading: false,
153
+ currentTime: 0,
154
+ duration: 0,
155
+ });
156
+ }
157
+ catch (e) {
158
+ if (!this.mountedRef)
159
+ return;
160
+ const message = e instanceof Error ? e.message : String(e);
161
+ console.warn("[MegaVideoPlayer] add failed:", message);
162
+ this.setState({ error: message, loading: false, buffering: false });
163
+ this.props.onError?.(message);
164
+ }
165
+ }
166
+ /** Stops playback and clears the current source. */
167
+ stop() {
168
+ try {
169
+ this.player.pause();
170
+ this.player.replace(null);
171
+ }
172
+ catch { }
173
+ }
174
+ // ---- player controllers (all on the class) ----
175
+ playPause = () => {
176
+ if (this.player.playing)
177
+ this.player.pause();
178
+ else
179
+ this.player.play();
180
+ this.setState({ playing: this.player.playing, visible: true });
181
+ this.scheduleHide();
182
+ };
183
+ skip = (delta) => {
184
+ const next = Math.min(Math.max(this.state.currentTime + delta, 0), this.state.duration);
185
+ this.player.currentTime = next;
186
+ this.setState({ currentTime: next, visible: true });
187
+ this.scheduleHide();
188
+ };
189
+ seek = (seconds) => {
190
+ const clamped = Math.min(Math.max(seconds, 0), this.state.duration || seconds);
191
+ this.player.currentTime = clamped;
192
+ this.setState({ currentTime: clamped, visible: true });
193
+ this.scheduleHide();
194
+ };
195
+ toggleMute = () => {
196
+ const next = !this.state.muted;
197
+ this.player.muted = next;
198
+ this.setState({ muted: next, visible: true });
199
+ };
200
+ changeVolume = (v) => {
201
+ this.player.volume = v;
202
+ this.setState({ volume: v });
203
+ if (v > 0 && this.state.muted) {
204
+ this.player.muted = false;
205
+ this.setState({ muted: false });
206
+ }
207
+ };
208
+ toggleSubtitles = () => {
209
+ this.setState((s) => ({ subtitlesOn: !s.subtitlesOn }));
210
+ };
211
+ toggleControls = () => {
212
+ this.setState((s) => ({ visible: !s.visible }));
213
+ if (!this.state.visible && this.state.playing)
214
+ this.scheduleHide();
215
+ };
216
+ enterFullscreen = (next) => {
217
+ // PlayerControls calls through onPress, which passes the press event as
218
+ // the first argument — ignore it (it is not the desired fullscreen value).
219
+ const target = typeof next === "boolean" ? next : !this.state.fullscreen;
220
+ this.setState({ fullscreen: target, visible: true });
221
+ this.props.onFullscreenChange?.(target);
222
+ };
223
+ changeSubtitleFontSize = (px) => {
224
+ this.setState({ subFontSize: px });
225
+ };
226
+ selectSubtitleTrack = (lang) => {
227
+ const idx = this.tracks.findIndex((t) => t.lang === lang);
228
+ if (idx >= 0) {
229
+ this.subTrackIdx = idx;
230
+ this.setState({ subtitlesOn: true });
231
+ this.loadSubtitles();
232
+ }
233
+ };
234
+ // ---- helpers ----
235
+ scheduleHide() {
236
+ if (this.hideTimer)
237
+ clearTimeout(this.hideTimer);
238
+ this.hideTimer = setTimeout(() => {
239
+ if (this.state.playing)
240
+ this.setState({ visible: false });
241
+ }, 3000);
242
+ }
243
+ syncSubtitleTracks() {
244
+ const { subtitleTracks, subtitleUrl, subtitleFormat, subtitleLang, initialSubtitleLang, } = this.props;
245
+ this.tracks = subtitleTracks?.length
246
+ ? subtitleTracks
247
+ : subtitleUrl
248
+ ? [
249
+ {
250
+ lang: subtitleLang ?? "SRT",
251
+ url: subtitleUrl,
252
+ format: subtitleFormat,
253
+ },
254
+ ]
255
+ : [];
256
+ // Preselect the requested track (from the watch screen's subtitle picker)
257
+ if (initialSubtitleLang) {
258
+ const idx = this.tracks.findIndex((t) => t.lang === initialSubtitleLang);
259
+ if (idx >= 0)
260
+ this.subTrackIdx = idx;
261
+ }
262
+ this.loadSubtitles();
263
+ }
264
+ loadSubtitles() {
265
+ const track = this.tracks[Math.min(this.subTrackIdx, this.tracks.length - 1)] ?? null;
266
+ this.setState({ cues: [], subtitleError: null });
267
+ if (!track)
268
+ return;
269
+ loadSubtitles(track.url, { format: track.format ?? undefined })
270
+ .then((c) => {
271
+ if (!this.mountedRef)
272
+ return;
273
+ this.setState({
274
+ cues: c,
275
+ subtitleError: c.length === 0 ? "No cues parsed from this subtitle file" : null,
276
+ });
277
+ })
278
+ .catch((e) => {
279
+ console.warn("[subtitles] load failed:", track.url, e);
280
+ if (this.mountedRef)
281
+ this.setState({ subtitleError: "Subtitles unavailable" });
282
+ });
283
+ }
284
+ // ---- render ----
285
+ render() {
286
+ const s = this.state;
287
+ const offset = this.props.subtitleOffset ?? 0;
288
+ const cueTime = s.currentTime - offset;
289
+ const activeCue = s.cues.find((c) => cueTime >= c.start && cueTime < c.end) ?? null;
290
+ const activeTrack = this.tracks[Math.min(this.subTrackIdx, this.tracks.length - 1)] ?? null;
291
+ if (s.error) {
292
+ return (_jsxs(View, { style: [styles.wrap, styles.center], children: [_jsx(Ionicons, { name: "alert-circle-outline", size: 40, color: "#ff6b6b" }), _jsx(Text, { style: styles.errorText, children: s.error }), _jsx(Text, { style: styles.errorHint, children: "Use \"Download & Play\" instead." })] }));
293
+ }
294
+ return (_jsxs(View, { style: [
295
+ styles.wrap,
296
+ { width: this.state.width, height: this.state.height },
297
+ s.fullscreen && styles.fullscreen,
298
+ ], children: [_jsx(VideoView, { player: this.player, style: [
299
+ StyleSheet.absoluteFill,
300
+ {
301
+ alignSelf: "center",
302
+ width: this.state.width,
303
+ height: this.state.height,
304
+ zIndex: 0
305
+ },
306
+ ], nativeControls: false, contentFit: "contain", allowsFullscreen: false }), s.loading && (_jsxs(View, { style: [styles.centerBadge, { width: this.state.width }], pointerEvents: "none", children: [_jsx(ActivityIndicator, { color: "#e50914", size: "large" }), _jsx(Text, { style: styles.loadingText, children: "Starting stream\u2026" })] })), s.subtitlesOn && s.subtitleError && !s.cues.length && (_jsx(View, { pointerEvents: "none", style: styles.subErrorWrap, children: _jsxs(Text, { style: styles.subErrorText, children: [s.subtitleError, " \u00B7 ", activeTrack?.lang ?? "no track"] }) })), _jsx(PlayerControlsWeb, { playing: s.playing, buffering: s.buffering, currentTime: s.currentTime, duration: s.duration, muted: s.muted, title: this.props.title, visible: s.visible, subtitlesOn: s.subtitlesOn, volume: s.volume, subtitleTracks: this.tracks.map((t) => t.lang), subtitleTrack: activeTrack?.lang, onSelectSubtitleTrack: this.selectSubtitleTrack, subtitleFontSize: s.subFontSize, onChangeSubtitleFontSize: this.changeSubtitleFontSize, resolutions: this.props.resolutions, resolution: this.props.resolution, onSelectResolution: this.props.onSelectResolution, onToggleVisible: this.toggleControls, onPlayPause: this.playPause, onSkip: this.skip, onSeek: this.seek, onToggleMute: this.toggleMute, onToggleSubtitles: this.toggleSubtitles, onVolume: this.changeVolume, onFullscreen: this.enterFullscreen, width: this.state.width, height: this.state.height, cue: activeCue, enabled: s.subtitlesOn, fontSize: s.subFontSize })] }));
307
+ }
308
+ }
309
+ const styles = StyleSheet.create({
310
+ wrap: {
311
+ flex: 1,
312
+ width: "100%",
313
+ height: "100%",
314
+ backgroundColor: "#000",
315
+ },
316
+ fullscreen: {
317
+ position: "absolute",
318
+ left: 0,
319
+ right: 0,
320
+ top: 0,
321
+ bottom: 0,
322
+ zIndex: 10,
323
+ },
324
+ center: {
325
+ flex: 1,
326
+ justifyContent: "center",
327
+ alignItems: "center",
328
+ gap: 12,
329
+ padding: 20,
330
+ backgroundColor: "#000",
331
+ },
332
+ errorText: {
333
+ color: "#ff6b6b",
334
+ fontSize: 14,
335
+ textAlign: "center",
336
+ },
337
+ errorHint: {
338
+ color: "#9aa0aa",
339
+ fontSize: 12,
340
+ textAlign: "center",
341
+ },
342
+ centerBadge: {
343
+ ...StyleSheet.absoluteFillObject,
344
+ justifyContent: "center",
345
+ alignItems: "center",
346
+ gap: 12,
347
+ },
348
+ loadingText: { color: "#fff", fontSize: 14 },
349
+ subErrorWrap: {
350
+ position: "absolute",
351
+ left: 0,
352
+ right: 0,
353
+ bottom: 56,
354
+ alignItems: "center",
355
+ paddingHorizontal: 24,
356
+ zIndex: 5,
357
+ },
358
+ subErrorText: {
359
+ backgroundColor: "rgba(229,9,20,0.8)",
360
+ color: "#fff",
361
+ fontSize: 12,
362
+ textAlign: "center",
363
+ paddingHorizontal: 10,
364
+ paddingVertical: 4,
365
+ borderRadius: 4,
366
+ overflow: "hidden",
367
+ },
368
+ });
@@ -0,0 +1,52 @@
1
+ export type PlayerControlsWebProps = {
2
+ playing: boolean;
3
+ buffering: boolean;
4
+ currentTime: number;
5
+ duration: number;
6
+ muted: boolean;
7
+ title?: string;
8
+ visible: boolean;
9
+ subtitlesOn: boolean;
10
+ volume: number;
11
+ /** subtitle language labels (when >1 the CC switcher is shown) */
12
+ subtitleTracks?: string[];
13
+ /** currently active subtitle language */
14
+ subtitleTrack?: string;
15
+ /** select a subtitle language from the list directly */
16
+ onSelectSubtitleTrack?: (lang: string) => void;
17
+ /** current subtitle font size (px) */
18
+ subtitleFontSize?: number;
19
+ /** adjust the subtitle font size (px) */
20
+ onChangeSubtitleFontSize?: (px: number) => void;
21
+ /** 0..1 — how much of the whole merged episode has been downloaded. Renders
22
+ * a second, thinner line under the playhead track. */
23
+ loadProgress?: number;
24
+ /** [start, end] seconds (merged timeline) of the intro — highlighted on the
25
+ * track + shows a "Skip Intro" button while the playhead is inside it. */
26
+ introRegion?: [number, number];
27
+ /** [start, end] seconds (merged timeline) of the outro */
28
+ outroRegion?: [number, number];
29
+ onSkipIntro?: () => void;
30
+ onSkipOutro?: () => void;
31
+ /** resolution labels in display order — the FIRST is the default source
32
+ * (the episode URL), e.g. ["Default", "1080p", "720p", "480p"]. */
33
+ resolutions?: string[];
34
+ /** currently active resolution label */
35
+ resolution?: string;
36
+ width?: number;
37
+ height?: number;
38
+ cue?: any;
39
+ enabled?: any;
40
+ fontSize?: any;
41
+ /** select a resolution — the parent resolves the label to its URL */
42
+ onSelectResolution?: (label: string) => void;
43
+ onToggleVisible: () => void;
44
+ onPlayPause: () => void;
45
+ onSkip: (deltaSeconds: number) => void;
46
+ onSeek: (seconds: number) => void;
47
+ onToggleMute: () => void;
48
+ onToggleSubtitles: () => void;
49
+ onVolume: (v: number) => void;
50
+ onFullscreen: () => void;
51
+ };
52
+ export declare function PlayerControlsWeb(props: PlayerControlsWebProps): import("react").JSX.Element;