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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mega-stream-proxy contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,518 @@
1
+ # mega-stream-proxy
2
+
3
+ Stream decrypted MEGA files over HTTP **byte ranges** — exactly like a VOD server —
4
+ on **Android/iOS** (a local TCP proxy) and the **web** (a Service Worker). Ships a
5
+ ready-to-use Expo Video player + subtitle engine, and MEGA **account management**
6
+ with pluggable storage.
7
+
8
+ No server required. No full-file downloads at playback time. The video element asks
9
+ for the exact range it needs and `mega-stream-proxy` decrypts and serves only those bytes.
10
+
11
+ ```
12
+ mega-link ──> megajs File.fromURL ──┬─> Android/iOS: 127.0.0.1:PORT/… ──> VideoPlayer
13
+ └─> Web: Service Worker /__mmstream__/… ──> <video>
14
+ ```
15
+
16
+ ## Features
17
+
18
+ - **Native proxy** (`/native`): runs a local HTTP server via `react-native-tcp-socket`,
19
+ answers `Range` requests by piping decrypted megajs chunks (`maxConnections: 4`).
20
+ - **Web proxy** (`/web` + `/sw`): registers the bundled Service Worker, returns
21
+ same-origin `/__mmstream__/…` URLs that the worker range-streams on demand.
22
+ Graceful fallback: without a secure context it downloads+decrypts into Blob URLs.
23
+ - **Player** (`/player`): cross-platform `MegaVideoPlayer` (expo-video) with custom
24
+ controls, multi-part auto-advance, subtitle overlay (srt/ass/vtt), subtitle track
25
+ picker, text-size slider, intro/outro skip regions.
26
+ - **Subtitles** (`/subtitles`): SRT/ASS/VTT parser, format auto-detect including
27
+ content sniffing for extension-less MEGA links.
28
+ - **Accounts** (`/accounts`): `MegaAccountManager` with CRUD, live megajs sessions,
29
+ Cloud-Drive tree, file export-link, and a pluggable `MegaAccountStore`
30
+ (`createLocalStorageStore` for web, `createAsyncStorageStore` for React Native,
31
+ or your own backend).
32
+
33
+ ## Requirements
34
+
35
+ - **Web**: a modern browser at `https:` (or `localhost`) for Service Worker
36
+ streaming; Node 18+ for the `copySwAssets` deploy step.
37
+ - **Android / iOS**: React Native ≥ 0.71 (Expo SDK 52+ recommended) — the native
38
+ streaming module is auto-linked, no manual Gradle/Xcode edits.
39
+ - All platforms need `react`, `react-native`, `expo-video`, and `@expo/vector-icons`
40
+ (Expo apps already ship the last two).
41
+
42
+ ## Install
43
+
44
+ ```sh
45
+ # from npm (once published)
46
+ npm install mega-stream-proxy
47
+
48
+ # or from the packed tarball / a local folder
49
+ npm install ./mega-stream-proxy-0.1.0.tgz
50
+ npm install "file:../mega-stream-proxy"
51
+ ```
52
+
53
+ Runtime dependencies are **regular `dependencies`**, so installing the package pulls
54
+ in everything (megajs, buffer, react-native-tcp-socket, async-storage) with **zero
55
+ extra setup**:
56
+
57
+ - React Native apps: `react-native-tcp-socket` is **auto-linked** by RN autolinking —
58
+ you do **not** edit `android/build.gradle`. The package ships an `android/` gradle
59
+ module purely for clean autolinking; it contains no native code.
60
+ - Web apps: `copySwAssets()` copies the worker + vendored megajs browser build into
61
+ your `public/` directory. No bundler changes.
62
+
63
+ `react`, `react-native`, `expo-video` and `@expo/vector-icons` are peer/optional deps —
64
+ your Expo app already provides them.
65
+
66
+ ## Android / iOS (native streaming)
67
+
68
+ ```tsx
69
+ import { useEffect, useMemo } from "react";
70
+ import { View } from "react-native";
71
+ import { ensureProxy, createStream, releaseStream } from "mega-stream-proxy/native";
72
+ import { MegaVideoPlayerWeb } from "mega-stream-proxy/player";
73
+
74
+ export function WatchScreen({ megaUrl, subtitleUrl }) {
75
+ const ref = useMemo(() => ({ url: null as string | null, sessionId: null as string | null }), []);
76
+
77
+ useEffect(() => {
78
+ let alive = true;
79
+ void ensureProxy(); // idempotent — starts the singleton server once per app lifetime
80
+ createStream(megaUrl).then(({ url, sessionId }) => {
81
+ if (!alive) {
82
+ releaseStream(sessionId);
83
+ return;
84
+ }
85
+ ref.url = url;
86
+ ref.sessionId = sessionId;
87
+ });
88
+ return () => {
89
+ alive = false;
90
+ if (ref.sessionId) releaseStream(ref.sessionId);
91
+ };
92
+ }, [megaUrl]);
93
+
94
+ return (
95
+ <View style={{ flex: 1 }}>
96
+ <MegaVideoPlayerWeb uri={ref.url} subtitleUrl={subtitleUrl} autoPlay />
97
+ </View>
98
+ );
99
+ }
100
+ ```
101
+
102
+ ## all Platfroms (streaming)
103
+
104
+ ```tsx
105
+ import { useEffect, useMemo } from "react";
106
+ import { View, Platform } from "react-native";
107
+ import { ensureProxy, createStream, releaseStream } from "mega-stream-proxy/native";
108
+ import { MegaVideoPlayerAndroid } from "mega-stream-proxy/playerAndroid";
109
+ import { MegaVideoPlayerWeb } from "mega-stream-proxy/player";
110
+ export function WatchScreen({ megaUrl, subtitleUrl }) {
111
+ const ref = useMemo(() => ({ url: null as string | null, sessionId: null as string | null }), []);
112
+
113
+ useEffect(() => {
114
+ let alive = true;
115
+ void ensureProxy(); // idempotent — starts the singleton server once per app lifetime
116
+ createStream(megaUrl).then(({ url, sessionId }) => {
117
+ if (!alive) {
118
+ releaseStream(sessionId);
119
+ return;
120
+ }
121
+ ref.url = url;
122
+ ref.sessionId = sessionId;
123
+ });
124
+ return () => {
125
+ alive = false;
126
+ if (ref.sessionId) releaseStream(ref.sessionId);
127
+ };
128
+ }, [megaUrl]);
129
+
130
+ return (
131
+ <View>
132
+ {Platform.OS === "web" ? (
133
+ <MegaVideoPlayerWeb uri={ref.url} subtitleUrl={subtitleUrl} />
134
+ ) : (
135
+ <MegaVideoPlayerAndroid uri={ref.url} subtitleUrl={subtitleUrl} />
136
+ )}
137
+ </View>
138
+ )
139
+ }
140
+
141
+ - `ensureProxy(): Promise<void>` — idempotent; binds the singleton local server once
142
+ per app lifetime. Safe to call repeatedly; no-op until `stopProxy()`.
143
+ - `createStream(megaUrl): Promise<{ url, sessionId }>` — one keyed session per MEGA
144
+ link; concurrent streams get independent routes on the same server.
145
+ - `createStreams(megaUrls): Promise<{ urls, sessionId }>` — ordered parts, played
146
+ back-to-back by the player, under one session.
147
+ - `releaseStream(sessionId)` — tears down that session only; other sessions and the
148
+ server keep running.
149
+ - `stopProxy()` — unbinds the server and clears all sessions (tests / HMR).
150
+
151
+ Android networking: loopback `http://127.0.0.1` is allowed in Expo dev builds by
152
+ default — no permissions or cleartext config is needed. For a release APK keep
153
+ `usesCleartextTraffic` enabled (your RN app templates usually set it) or the
154
+ stream URL stays on loopback, which most players accept.
155
+
156
+ ## Web
157
+
158
+ ### 1. Copy the Service Worker assets (build/deploy step, node)
159
+
160
+ ```js
161
+ import { copySwAssets } from "mega-stream-proxy/sw";
162
+
163
+ // e.g. Expo web: copy into ./public (before `expo export`)
164
+ const files = copySwAssets("public");
165
+ console.log("copied", files.sw, files.megajs);
166
+ // sw.js -> public/sw.js (the range-streaming worker)
167
+ // megajs.browser.js -> public/megajs.browser.js (vendored megajs UMD)
168
+ ```
169
+
170
+ Server from a host that allows a Service Worker (`https:` or `localhost`).
171
+
172
+ ### 2. Stream + play
173
+
174
+ ```tsx
175
+ import { useEffect, useMemo } from "react";
176
+ import { View } from "react-native";
177
+ import { ensureProxy, createStream, releaseStream } from "mega-stream-proxy/web";
178
+ import { MegaVideoPlayerWeb } from "mega-stream-proxy/player";
179
+
180
+ export function WatchScreen({ megaUrl, subtitleUrl }) {
181
+ const ref = useMemo(() => ({ url: null as string | null, sessionId: null as string | null }), []);
182
+
183
+ useEffect(() => {
184
+ let alive = true;
185
+ void ensureProxy(); // idempotent — ensures the Service Worker is registered once
186
+ createStream(megaUrl).then(({ url, sessionId }) => {
187
+ if (!alive) {
188
+ releaseStream(sessionId);
189
+ return;
190
+ }
191
+ ref.url = url; // e.g. /__mmstream__/abc/part-0.mp4
192
+ ref.sessionId = sessionId;
193
+ });
194
+ return () => {
195
+ alive = false;
196
+ if (ref.sessionId) releaseStream(ref.sessionId);
197
+ };
198
+ }, [megaUrl]);
199
+
200
+ return (
201
+ <View style={{ flex: 1 }}>
202
+ <MegaVideoPlayerWeb uri={ref.url} subtitleUrl={subtitleUrl} autoPlay />
203
+ </View>
204
+ );
205
+ }
206
+ ```
207
+
208
+ The web API matches the native one exactly: `ensureProxy()`, `createStream(megaUrl) →
209
+ { url, sessionId }`, `createStreams(megaUrls) → { urls, sessionId }`,
210
+ `releaseStream(sessionId)`, and `stopProxy()`. Each `createStream`/`createStreams` call
211
+ registers its own keyed session — several streams can coexist, and releasing one
212
+ session leaves the others untouched.
213
+
214
+ If no Service Worker is available the proxy silently falls back to full download into
215
+ Blob object URLs (still plays).
216
+
217
+ ## Using the proxy URL with your own player (expo-video / expo-av)
218
+
219
+ You don't need `MegaVideoPlayer` — the proxy returns a plain, range-capable HTTP URL,
220
+ so it works with any player that speaks HTTP ranges. The only lifecycle to remember is:
221
+ call `ensureProxy()` once, hold the `sessionId` from `createStream`, and call
222
+ `releaseStream(sessionId)` in your unmount cleanup. Import from
223
+ `mega-stream-proxy/native` on Android/iOS or `mega-stream-proxy/web` on the web — the
224
+ API is identical.
225
+
226
+ ### expo-video (VideoView)
227
+
228
+ ```tsx
229
+ import { useEffect, useRef } from "react";
230
+ import { View } from "react-native";
231
+ import { createVideoPlayer, VideoView } from "expo-video";
232
+ import { ensureProxy, createStream, releaseStream } from "mega-stream-proxy/web"; // or /native
233
+
234
+ export function WatchScreen({ megaUrl }) {
235
+ const player = useRef(createVideoPlayer(null)).current;
236
+ const sessionId = useRef<string | null>(null);
237
+
238
+ useEffect(() => {
239
+ let alive = true;
240
+ void ensureProxy(); // idempotent
241
+ createStream(megaUrl).then(({ url, sessionId: id }) => {
242
+ if (!alive) {
243
+ releaseStream(id);
244
+ return;
245
+ }
246
+ sessionId.current = id;
247
+ player.replace({ uri: url });
248
+ });
249
+ return () => {
250
+ alive = false;
251
+ if (sessionId.current) releaseStream(sessionId.current);
252
+ player.release();
253
+ };
254
+ }, [megaUrl]);
255
+
256
+ return (
257
+ <View style={{ flex: 1 }}>
258
+ <VideoView player={player} style={{ flex: 1 }} contentFit="contain" />
259
+ </View>
260
+ );
261
+ }
262
+ ```
263
+
264
+ ### expo-av (Video)
265
+
266
+ ```tsx
267
+ import { useEffect, useRef } from "react";
268
+ import { View } from "react-native";
269
+ import { Video, ResizeMode } from "expo-av";
270
+ import { ensureProxy, createStream, releaseStream } from "mega-stream-proxy/web"; // or /native
271
+
272
+ export function WatchScreen({ megaUrl }) {
273
+ const uri = useRef<string | null>(null);
274
+ const sessionId = useRef<string | null>(null);
275
+
276
+ useEffect(() => {
277
+ let alive = true;
278
+ void ensureProxy(); // idempotent
279
+ createStream(megaUrl).then(({ url, sessionId: id }) => {
280
+ if (!alive) {
281
+ releaseStream(id);
282
+ return;
283
+ }
284
+ sessionId.current = id;
285
+ uri.current = url;
286
+ });
287
+ return () => {
288
+ alive = false;
289
+ if (sessionId.current) releaseStream(sessionId.current);
290
+ };
291
+ }, [megaUrl]);
292
+
293
+ return (
294
+ <View style={{ flex: 1 }}>
295
+ <Video
296
+ style={{ flex: 1 }}
297
+ source={{ uri: uri.current ?? undefined }}
298
+ resizeMode={ResizeMode.CONTAIN}
299
+ shouldPlay
300
+ />
301
+ </View>
302
+ );
303
+ }
304
+ ```
305
+
306
+ The same pattern applies to multi-part episodes: `createStreams(megaUrls)` returns
307
+ `{ urls, sessionId }` — feed the ordered URLs to your player's queue or switch the
308
+ `source` when one part ends, then `releaseStream(sessionId)` once when you leave.
309
+
310
+ ## Player component
311
+
312
+ `import { MegaVideoPlayerWeb, PlayerControlsWeb, SubtitleDisplayWeb } from "mega-stream-proxy/player";`
313
+
314
+ This is the **same player MyMovies uses** (`components/MegaVideoPlayerWeb.tsx`),
315
+ byte-for-byte — same expo-video engine, same custom controls (scrub bar, volume,
316
+ skip ±10s, subtitle picker + text-size slider, intro/outro regions), same subtitle
317
+ overlay style, same auto-hiding chrome, and the same multi-part advance logic. It
318
+ runs identically on Android, iOS and web.
319
+
320
+ ### Simple usage — pass the subtitle URL as a prop
321
+
322
+ ```tsx
323
+ <MegaVideoPlayerWeb
324
+ uri="https://…/part-0.mp4" // or the proxy URL from /native or /web
325
+ title="Episode 1"
326
+ subtitleUrl="https://cdn.example.com/subs.srt" // <-- the subtitle file
327
+ subtitleFormat="srt" // optional (auto-detected from URL/extension/content)
328
+ subtitleLang="English" // language name shown in the player controls
329
+ autoPlay
330
+ />
331
+ ```
332
+
333
+ Pass `subtitleLang` to control the language name the player displays in the
334
+ controls (the `CC`/language chip next to the progress bar). Without it the player
335
+ shows `SRT`.
336
+
337
+ ### Selecting subtitles from the list
338
+
339
+ The player shows a subtitle selector in the controls (same UI as MyMovies):
340
+
341
+ - The **`CC` button** toggles subtitles on/off.
342
+ - The **language chip** (e.g. `ENG`) opens the subtitle menu — a dropdown with two
343
+ tabs: **Language** (the subtitle list) and **Text Size**.
344
+
345
+ ```tsx
346
+ <MegaVideoPlayerWeb
347
+ uri={url}
348
+ subtitleTracks={[
349
+ { lang: "English", url: "https://cdn.example.com/subs.en.srt" },
350
+ { lang: "Español", url: "https://cdn.example.com/subs.es.srt" },
351
+ { lang: "Français", url: "https://mega.nz/file/xyz#key", format: "srt" },
352
+ ]}
353
+ initialSubtitleLang="English"
354
+ autoPlay
355
+ />
356
+ ```
357
+
358
+ - The **Language tab lists every subtitle**; tapping one selects it, marks it with a
359
+ checkmark, loads it, and starts showing it (with a single `subtitleUrl` the list
360
+ shows that one track, selectable too).
361
+ - The **Text Size tab** drags a 10–34px slider for the overlay font.
362
+ - Requires `initialSubtitleLang`/`subtitleTracks`/`subtitleUrl` — see prop list below.
363
+ - All these controls are the exact `PlayerControls` used in the MyMovies player.
364
+
365
+ `subtitleUrl` can be:
366
+
367
+ - a **MEGA link** (`https://mega.nz/file/…`) — decrypted through the same pipeline
368
+ as the video, so no CORS/extension issues;
369
+ - a plain **http(s) URL** — the host must allow CORS;
370
+ - a **local file URI** (`file:`, `content:`, or `/`) — provide a file reader with
371
+ the `readLocalFile` option on `loadSubtitles` (see Subtitles section).
372
+
373
+ Format auto-detection order: `subtitleFormat` prop → URL extension → content sniffing
374
+ (`.srt`/`.ass`/`.ssa`/`.vtt`, plus ASS detected even from extension-less URLs).
375
+
376
+ ### Multi-language tracks
377
+
378
+ ```tsx
379
+ <MegaVideoPlayerWeb
380
+ uri={url}
381
+ subtitleTracks={[
382
+ { lang: "English", url: "https://cdn.example.com/subs.srt" },
383
+ { lang: "Français", url: "https://mega.nz/file/xyz#key", format: "srt" },
384
+ ]}
385
+ initialSubtitleLang="English"
386
+ subtitleOffset={0} // seconds to shift cues (can be negative)
387
+ autoPlay
388
+ onEnd={() => goToNextEpisode()}
389
+ onProgress={({ currentTime, duration }) => {}}
390
+ onError={(message) => {}}
391
+ />
392
+ ```
393
+
394
+ ### Full prop list
395
+
396
+ `MegaVideoPlayer` props:
397
+
398
+ - `uri` — playable URL (first part). Optional if only `sources` is given.
399
+ - `sources` — ordered part URLs; the player auto-advances when a part ends
400
+ (sequence advance, not concatenation).
401
+ - `title` — shown in the top control bar.
402
+ - `subtitleUrl` + `subtitleFormat` — single subtitle track (the common case).
403
+ - `subtitleLang` — language name shown in the controls for the single `subtitleUrl`
404
+ track (e.g. `"English"`). Defaults to `"SRT"`.
405
+ - `subtitleTracks` — `{ lang, url, format? }[]` for multi-language; the player
406
+ shows the subtitle list in the controls and selecting one switches tracks
407
+ (same behavior as MyMovies).
408
+ - `initialSubtitleLang` — preselected track language on load.
409
+ - `subtitleOffset` — seconds to shift cue timestamps (negative = earlier).
410
+ - `autoPlay` — auto-start (default `true`).
411
+ - `onEnd` — fired when the last part finishes.
412
+ - `onProgress({ currentTime, duration })`, `onError(message)`,
413
+ `onFullscreenChange(boolean)` — callbacks.
414
+
415
+ All subtitles render through `SubtitleDisplay` (dark pill, white text, centered,
416
+ resizable 10–34px via the controls' Text Size slider).
417
+
418
+ ## Subtitles
419
+
420
+ ```ts
421
+ import { loadSubtitles, parseSubtitles, detectSubtitleFormat } from "mega-stream-proxy/subtitles";
422
+
423
+ const cues = await loadSubtitles("https://mega.nz/file/xyz#key");
424
+ // MEGA links are decrypted through the same pipeline as video (no CORS issues).
425
+ // Plain http(s) hosts must allow CORS.
426
+
427
+ const cues = await loadSubtitles("/data/sub/1.srt", {
428
+ readLocalFile: (uri) => FileSystem.readAsStringAsync(uri, { encoding: FileSystem.EncodingType.UTF8 }),
429
+ });
430
+ ```
431
+
432
+ - Supported formats: SRT, ASS/SSA, WebVTT. Extension-less URLs (MEGA links) are
433
+ sniffed by content.
434
+ - `parseSubtitles(text, format?)` is import-pure and tree-shakable — batch-process
435
+ files anywhere.
436
+
437
+ ## Accounts
438
+
439
+ ```ts
440
+ import { MegaAccountManager } from "mega-stream-proxy/accounts";
441
+ ```
442
+
443
+ **Web** — persist in `localStorage` (with cross-tab live updates):
444
+
445
+ ```ts
446
+ import { createLocalStorageStore } from "mega-stream-proxy/accounts";
447
+ const accounts = new MegaAccountManager(createLocalStorageStore());
448
+
449
+ await accounts.addAccount({ email: "a@b.c", password: "…", label: "Work" });
450
+ const list = await accounts.listAccounts(); // MegaAccount[]
451
+ const tree = await accounts.loadTree(list[0]); // MegaTreeNode[] (folders, videos, subs)
452
+ const link = await accounts.exportLink(list[0], tree.find(n => n.isVideo).handle);
453
+ await accounts.deleteAccount(list[0].id);
454
+ ```
455
+
456
+ **React Native** — persist in AsyncStorage:
457
+
458
+ ```ts
459
+ import { createAsyncStorageStore } from "mega-stream-proxy/accounts";
460
+ import AsyncStorage from "@react-native-async-storage/async-storage";
461
+ const accounts = new MegaAccountManager(createAsyncStorageStore(AsyncStorage));
462
+ ```
463
+
464
+ **Bring your own backend** (Firestore, your server…):
465
+
466
+ ```ts
467
+ import type { MegaAccountStore, MegaAccount } from "mega-stream-proxy/accounts";
468
+ const myStore: MegaAccountStore = {
469
+ list: () => db.collection("accounts").get().then(q => q.docs.map(d => d.data())),
470
+ save: (a: MegaAccount) => db.doc(a.id).set(a),
471
+ remove: (id) => db.doc(id).delete(),
472
+ subscribe: (cb) => unsubFirestore(cb),
473
+ };
474
+ const accounts = new MegaAccountManager(myStore);
475
+ ```
476
+
477
+ Other helpers: `testAccount()` returns `{ name, folders, files }` (login check),
478
+ `subscribe(cb)` streams account changes, `closeSession(id)` / `closeAll()` free sessions,
479
+ `MegaAccount` — you can enrich stores (encrypt passwords, etc.) before saving.
480
+
481
+ ## Export map
482
+
483
+ | Subpath | Contents |
484
+ | ------------------ | --------------------------------------------------------------- |
485
+ | `mega-stream-proxy` | core: `loadMegaFile`, `collectMegaFileStream`, `parseRange`, `rangeHeaders`, `SW_MARKER` |
486
+ | `mega-stream-proxy/native` | `ensureProxy`, `createStream`, `createStreams`, `releaseStream`, `stopProxy` (TCP, keyed sessions on a singleton server) |
487
+ | `mega-stream-proxy/web` | same API, Service-Worker backed (Blob fallback) |
488
+ | `mega-stream-proxy/sw` | `copySwAssets(outDir)`, `swAssetPaths()` (deploy-time node helper) |
489
+ | `mega-stream-proxy/player` | `MegaVideoPlayer`, `PlayerControls`, `SubtitleDisplay` |
490
+ | `mega-stream-proxy/subtitles`| `loadSubtitles`, `parseSubtitles`, `detectSubtitleFormat`, types |
491
+ | `mega-stream-proxy/accounts`| `MegaAccountManager`, `createLocalStorageStore`, `createAsyncStorageStore`, types |
492
+
493
+ ## Android build
494
+
495
+ The package includes `android/build.gradle` (com.android.library, namespace
496
+ `com.megastreamproxy`) so RN autolinking resolves it without touching your Gradle
497
+ config. All native streaming is performed by `react-native-tcp-socket`, which is a
498
+ declared dependency and is auto-linked too. Build as usual:
499
+
500
+ ```sh
501
+ npx expo run:android # Expo apps
502
+ ./gradlew assembleDebug # bare RN apps
503
+ ```
504
+
505
+ ## Development
506
+
507
+ ```sh
508
+ npm install # installs everything (incl. dev deps for typechecking the player)
509
+ npm run build # tsc -> dist/ + copies sw assets
510
+ npm run test # build + node:test suite
511
+ npm pack # -> mega-stream-proxy-0.1.0.tgz
512
+ ```
513
+
514
+ Node 18+. TypeScript strict. ESM (`"type": "module"`).
515
+
516
+ ## License
517
+
518
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,31 @@
1
+ // mega-stream-proxy is a JS-only library — this module exists so React Native
2
+ // autolinking picks the package up cleanly and no consumer edits their own
3
+ // build.gradle. All native work is delegated to react-native-tcp-socket, which
4
+ // is a regular dependency and therefore auto-linked by the app itself.
5
+
6
+ apply plugin: "com.android.library"
7
+
8
+ def safeExtGet(prop, fallback) {
9
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
10
+ }
11
+
12
+ android {
13
+ namespace = "com.megastreamproxy"
14
+
15
+ compileSdkVersion safeExtGet("compileSdkVersion", 35)
16
+ buildToolsVersion = safeExtGet("buildToolsVersion", "35.0.0")
17
+
18
+ defaultConfig {
19
+ minSdkVersion safeExtGet("minSdkVersion", 24)
20
+ targetSdkVersion safeExtGet("targetSdkVersion", 35)
21
+ }
22
+
23
+ lintOptions {
24
+ abortOnError false
25
+ }
26
+
27
+ compileOptions {
28
+ sourceCompatibility JavaVersion.VERSION_1_8
29
+ targetCompatibility JavaVersion.VERSION_1_8
30
+ }
31
+ }
@@ -0,0 +1,3 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+
3
+ </manifest>
@@ -0,0 +1,3 @@
1
+ export { MegaVideoPlayerAndroid, type MegaVideoPlayerAndroidProps, type MegaVideoPlayerAndroidHandle, } from "./playerAndroid/MegaVideoPlayerAndroid.js";
2
+ export { PlayerControlsAndroid, type PlayerControlsAndroidProps } from "./playerAndroid/PlayerControlsAndroid.js";
3
+ export { SubtitleDisplayAndroid, type SubCueShapeAndroid } from "./playerAndroid/SubtitleDisplayAndroid.js";
@@ -0,0 +1,3 @@
1
+ export { MegaVideoPlayerAndroid, } from "./playerAndroid/MegaVideoPlayerAndroid.js";
2
+ export { PlayerControlsAndroid } from "./playerAndroid/PlayerControlsAndroid.js";
3
+ export { SubtitleDisplayAndroid } from "./playerAndroid/SubtitleDisplayAndroid.js";
@@ -0,0 +1,47 @@
1
+ import React from "react";
2
+ import { type SubtitleFormat, type SubtitleTrack } from "../../subtitles/index.js";
3
+ export type MegaVideoPlayerAndroidProps = {
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
+ * (usually "HD: <url>"), e.g. ["Default", "1080p", "720p"]. */
23
+ resolutions?: string[];
24
+ /** currently active resolution label */
25
+ resolution?: string;
26
+ /** user picked a resolution label — resolve it to a URL and update `uri`. */
27
+ onSelectResolution?: (label: string) => void;
28
+ autoPlay?: boolean;
29
+ onEnd?: () => void;
30
+ onError?: (message: string) => void;
31
+ onProgress?: (info: {
32
+ currentTime: number;
33
+ duration: number;
34
+ }) => void;
35
+ onFullscreenChange?: (fullscreen: boolean) => void;
36
+ width?: number;
37
+ height?: number;
38
+ };
39
+ export type MegaVideoPlayerAndroidHandle = {
40
+ /** Loads a new playable URL into the player. */
41
+ add: (source: string, opts?: {
42
+ title?: string;
43
+ }) => Promise<void>;
44
+ /** Stops playback and clears the current source. */
45
+ stop: () => void;
46
+ };
47
+ export declare const MegaVideoPlayerAndroid: React.ForwardRefExoticComponent<MegaVideoPlayerAndroidProps & React.RefAttributes<MegaVideoPlayerAndroidHandle>>;