npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mega-stream-proxy

v0.2.0

Published

Stream decrypted MEGA files over HTTP byte ranges on Android (react-native-tcp-socket) and the web (Service Worker), play them with an Expo Video player + subtitles, and manage MEGA accounts with pluggable storage. Zero-config: runtime native deps are reg

Readme

mega-stream-proxy

Stream decrypted MEGA files over HTTP byte ranges — exactly like a VOD server — on Android/iOS (a local TCP proxy) and the web (a Service Worker). Ships a ready-to-use Expo Video player + subtitle engine, and MEGA account management with pluggable storage.

No server required. No full-file downloads at playback time. The video element asks for the exact range it needs and mega-stream-proxy decrypts and serves only those bytes.

mega-link ──> megajs File.fromURL ──┬─> Android/iOS: 127.0.0.1:PORT/… ──> VideoPlayer
                                   └─> Web:          Service Worker /__mmstream__/… ──> <video>

Features

  • Native proxy (/native): runs a local HTTP server via react-native-tcp-socket, answers Range requests by piping decrypted megajs chunks (maxConnections: 4).
  • Web proxy (/web + /sw): registers the bundled Service Worker, returns same-origin /__mmstream__/… URLs that the worker range-streams on demand. Graceful fallback: without a secure context it downloads+decrypts into Blob URLs.
  • Player (/player): cross-platform MegaVideoPlayer (expo-video) with custom controls, multi-part auto-advance, subtitle overlay (srt/ass/vtt), subtitle track picker, text-size slider, intro/outro skip regions.
  • Subtitles (/subtitles): SRT/ASS/VTT parser, format auto-detect including content sniffing for extension-less MEGA links.
  • Accounts (/accounts): MegaAccountManager with CRUD, live megajs sessions, Cloud-Drive tree, file export-link, and a pluggable MegaAccountStore (createLocalStorageStore for web, createAsyncStorageStore for React Native, or your own backend).

Requirements

  • Web: a modern browser at https: (or localhost) for Service Worker streaming; Node 18+ for the copySwAssets deploy step.
  • Android / iOS: React Native ≥ 0.71 (Expo SDK 52 recommended) — the native streaming module is auto-linked, no manual Gradle/Xcode edits.
  • All platforms need react, react-native, expo-video, and @expo/vector-icons (Expo apps already ship the last two).

for sdk 54 + support versions

visit url https://www.npmjs.com/package/mega-proxy?activeTab=readme

Install

# from npm (once published)
npm install mega-stream-proxy

# or from the packed tarball / a local folder
npm install ./mega-stream-proxy-0.1.0.tgz
npm install "file:../mega-stream-proxy"

Runtime dependencies are regular dependencies, so installing the package pulls in everything (megajs, buffer, react-native-tcp-socket, async-storage) with zero extra setup:

  • React Native apps: react-native-tcp-socket is auto-linked by RN autolinking — you do not edit android/build.gradle. The package ships an android/ gradle module purely for clean autolinking; it contains no native code.
  • Web apps: copySwAssets() copies the worker + vendored megajs browser build into your public/ directory. No bundler changes.

react, react-native, expo-video and @expo/vector-icons are peer/optional deps — your Expo app already provides them.

Android / iOS (native streaming)

import { useEffect, useMemo } from "react";
import { View } from "react-native";
import { ensureProxy, createStream, releaseStream } from "mega-stream-proxy/native";
import { MegaVideoPlayer } from "mega-stream-proxy/player";

export function WatchScreen({ megaUrl, subtitleUrl }) {
  const ref = useMemo(() => ({ url: null as string | null, sessionId: null as string | null }), []);

  useEffect(() => {
    let alive = true;
    void ensureProxy(); // idempotent — starts the singleton server once per app lifetime
    createStream(megaUrl).then(({ url, sessionId }) => {
      if (!alive) {
        releaseStream(sessionId);
        return;
      }
      ref.url = url;
      ref.sessionId = sessionId;
    });
    return () => {
      alive = false;
      if (ref.sessionId) releaseStream(ref.sessionId);
    };
  }, [megaUrl]);

  return (
    <View style={{ flex: 1 }}>
      <MegaVideoPlayer uri={ref.url} subtitleUrl={subtitleUrl} autoPlay />
    </View>
  );
}
  • ensureProxy(): Promise<void> — idempotent; binds the singleton local server once per app lifetime. Safe to call repeatedly; no-op until stopProxy().
  • createStream(megaUrl): Promise<{ url, sessionId }> — one keyed session per MEGA link; concurrent streams get independent routes on the same server.
  • createStreams(megaUrls): Promise<{ urls, sessionId }> — ordered parts, played back-to-back by the player, under one session.
  • releaseStream(sessionId) — tears down that session only; other sessions and the server keep running.
  • stopProxy() — unbinds the server and clears all sessions (tests / HMR).

Android networking: loopback http://127.0.0.1 is allowed in Expo dev builds by default — no permissions or cleartext config is needed. For a release APK keep usesCleartextTraffic enabled (your RN app templates usually set it) or the stream URL stays on loopback, which most players accept.

Web

1. Copy the Service Worker assets (build/deploy step, node)

import { copySwAssets } from "mega-stream-proxy/sw";

// e.g. Expo web: copy into ./public (before `expo export`)
const files = copySwAssets("public");
console.log("copied", files.sw, files.megajs);
// sw.js      -> public/sw.js          (the range-streaming worker)
// megajs.browser.js -> public/megajs.browser.js  (vendored megajs UMD)

Server from a host that allows a Service Worker (https: or localhost).

2. Stream + play

import { useEffect, useMemo } from "react";
import { View } from "react-native";
import { ensureProxy, createStream, releaseStream } from "mega-stream-proxy/web";
import { MegaVideoPlayer } from "mega-stream-proxy/player";

export function WatchScreen({ megaUrl, subtitleUrl }) {
  const ref = useMemo(() => ({ url: null as string | null, sessionId: null as string | null }), []);

  useEffect(() => {
    let alive = true;
    void ensureProxy(); // idempotent — ensures the Service Worker is registered once
    createStream(megaUrl).then(({ url, sessionId }) => {
      if (!alive) {
        releaseStream(sessionId);
        return;
      }
      ref.url = url; // e.g. /__mmstream__/abc/part-0.mp4
      ref.sessionId = sessionId;
    });
    return () => {
      alive = false;
      if (ref.sessionId) releaseStream(ref.sessionId);
    };
  }, [megaUrl]);

  return (
    <View style={{ flex: 1 }}>
      <MegaVideoPlayer uri={ref.url} subtitleUrl={subtitleUrl} autoPlay />
    </View>
  );
}

The web API matches the native one exactly: ensureProxy(), createStream(megaUrl) → { url, sessionId }, createStreams(megaUrls) → { urls, sessionId }, releaseStream(sessionId), and stopProxy(). Each createStream/createStreams call registers its own keyed session — several streams can coexist, and releasing one session leaves the others untouched.

If no Service Worker is available the proxy silently falls back to full download into Blob object URLs (still plays).

Using the proxy URL with your own player (expo-video / expo-av)

You don't need MegaVideoPlayer — the proxy returns a plain, range-capable HTTP URL, so it works with any player that speaks HTTP ranges. The only lifecycle to remember is: call ensureProxy() once, hold the sessionId from createStream, and call releaseStream(sessionId) in your unmount cleanup. Import from mega-stream-proxy/native on Android/iOS or mega-stream-proxy/web on the web — the API is identical.

expo-video (VideoView)

import { useEffect, useRef } from "react";
import { View } from "react-native";
import { createVideoPlayer, VideoView } from "expo-video";
import { ensureProxy, createStream, releaseStream } from "mega-stream-proxy/web"; // or /native

export function WatchScreen({ megaUrl }) {
  const player = useRef(createVideoPlayer(null)).current;
  const sessionId = useRef<string | null>(null);

  useEffect(() => {
    let alive = true;
    void ensureProxy(); // idempotent
    createStream(megaUrl).then(({ url, sessionId: id }) => {
      if (!alive) {
        releaseStream(id);
        return;
      }
      sessionId.current = id;
      player.replace({ uri: url });
    });
    return () => {
      alive = false;
      if (sessionId.current) releaseStream(sessionId.current);
      player.release();
    };
  }, [megaUrl]);

  return (
    <View style={{ flex: 1 }}>
      <VideoView player={player} style={{ flex: 1 }} contentFit="contain" />
    </View>
  );
}

expo-av (Video)

import { useEffect, useRef } from "react";
import { View } from "react-native";
import { Video, ResizeMode } from "expo-av";
import { ensureProxy, createStream, releaseStream } from "mega-stream-proxy/web"; // or /native

export function WatchScreen({ megaUrl }) {
  const uri = useRef<string | null>(null);
  const sessionId = useRef<string | null>(null);

  useEffect(() => {
    let alive = true;
    void ensureProxy(); // idempotent
    createStream(megaUrl).then(({ url, sessionId: id }) => {
      if (!alive) {
        releaseStream(id);
        return;
      }
      sessionId.current = id;
      uri.current = url;
    });
    return () => {
      alive = false;
      if (sessionId.current) releaseStream(sessionId.current);
    };
  }, [megaUrl]);

  return (
    <View style={{ flex: 1 }}>
      <Video
        style={{ flex: 1 }}
        source={{ uri: uri.current ?? undefined }}
        resizeMode={ResizeMode.CONTAIN}
        shouldPlay
      />
    </View>
  );
}

The same pattern applies to multi-part episodes: createStreams(megaUrls) returns { urls, sessionId } — feed the ordered URLs to your player's queue or switch the source when one part ends, then releaseStream(sessionId) once when you leave.

Player component

import { MegaVideoPlayer, PlayerControls, SubtitleDisplay } from "mega-stream-proxy/player";

This is the same player MyMovies uses (components/MegaVideoPlayer.web.tsx), byte-for-byte — same expo-video engine, same custom controls (scrub bar, volume, skip ±10s, subtitle picker + text-size slider, intro/outro regions), same subtitle overlay style, same auto-hiding chrome, and the same multi-part advance logic. It runs identically on Android, iOS and web.

Simple usage — pass the subtitle URL as a prop

<MegaVideoPlayer
  uri="https://…/part-0.mp4"          // or the proxy URL from /native or /web
  title="Episode 1"
  subtitleUrl="https://cdn.example.com/subs.srt"  // <-- the subtitle file
  subtitleFormat="srt"                 // optional (auto-detected from URL/extension/content)
  subtitleLang="English"               // language name shown in the player controls
  autoPlay
/>

Pass subtitleLang to control the language name the player displays in the controls (the CC/language chip next to the progress bar). Without it the player shows SRT.

Selecting subtitles from the list

The player shows a subtitle selector in the controls (same UI as MyMovies):

  • The CC button toggles subtitles on/off.
  • The language chip (e.g. ENG) opens the subtitle menu — a dropdown with two tabs: Language (the subtitle list) and Text Size.
<MegaVideoPlayer
  uri={url}
  subtitleTracks={[
    { lang: "English", url: "https://cdn.example.com/subs.en.srt" },
    { lang: "Español", url: "https://cdn.example.com/subs.es.srt" },
    { lang: "Français", url: "https://mega.nz/file/xyz#key", format: "srt" },
  ]}
  initialSubtitleLang="English"
  autoPlay
/>
  • The Language tab lists every subtitle; tapping one selects it, marks it with a checkmark, loads it, and starts showing it (with a single subtitleUrl the list shows that one track, selectable too).
  • The Text Size tab drags a 10–34px slider for the overlay font.
  • Requires initialSubtitleLang/subtitleTracks/subtitleUrl — see prop list below.
  • All these controls are the exact PlayerControls used in the MyMovies player.

subtitleUrl can be:

  • a MEGA link (https://mega.nz/file/…) — decrypted through the same pipeline as the video, so no CORS/extension issues;
  • a plain http(s) URL — the host must allow CORS;
  • a local file URI (file:, content:, or /) — provide a file reader with the readLocalFile option on loadSubtitles (see Subtitles section).

Format auto-detection order: subtitleFormat prop → URL extension → content sniffing (.srt/.ass/.ssa/.vtt, plus ASS detected even from extension-less URLs).

Multi-language tracks

<MegaVideoPlayer
  uri={url}
  subtitleTracks={[
    { lang: "English", url: "https://cdn.example.com/subs.srt" },
    { lang: "Français", url: "https://mega.nz/file/xyz#key", format: "srt" },
  ]}
  initialSubtitleLang="English"
  subtitleOffset={0}                       // seconds to shift cues (can be negative)
  autoPlay
  onEnd={() => goToNextEpisode()}
  onProgress={({ currentTime, duration }) => {}}
  onError={(message) => {}}
/>

Full prop list

MegaVideoPlayer props:

  • uri — playable URL (first part). Optional if only sources is given.
  • sources — ordered part URLs; the player auto-advances when a part ends (sequence advance, not concatenation).
  • title — shown in the top control bar.
  • subtitleUrl + subtitleFormat — single subtitle track (the common case).
  • subtitleLang — language name shown in the controls for the single subtitleUrl track (e.g. "English"). Defaults to "SRT".
  • subtitleTracks — { lang, url, format? }[] for multi-language; the player shows the subtitle list in the controls and selecting one switches tracks (same behavior as MyMovies).
  • initialSubtitleLang — preselected track language on load.
  • subtitleOffset — seconds to shift cue timestamps (negative = earlier).
  • autoPlay — auto-start (default true).
  • onEnd — fired when the last part finishes.
  • onProgress({ currentTime, duration }), onError(message), onFullscreenChange(boolean) — callbacks.

All subtitles render through SubtitleDisplay (dark pill, white text, centered, resizable 10–34px via the controls' Text Size slider).

Subtitles

import { loadSubtitles, parseSubtitles, detectSubtitleFormat } from "mega-stream-proxy/subtitles";

const cues = await loadSubtitles("https://mega.nz/file/xyz#key");
// MEGA links are decrypted through the same pipeline as video (no CORS issues).
// Plain http(s) hosts must allow CORS.

const cues = await loadSubtitles("/data/sub/1.srt", {
  readLocalFile: (uri) => FileSystem.readAsStringAsync(uri, { encoding: FileSystem.EncodingType.UTF8 }),
});
  • Supported formats: SRT, ASS/SSA, WebVTT. Extension-less URLs (MEGA links) are sniffed by content.
  • parseSubtitles(text, format?) is import-pure and tree-shakable — batch-process files anywhere.

Accounts

import { MegaAccountManager } from "mega-stream-proxy/accounts";

Web — persist in localStorage (with cross-tab live updates):

import { createLocalStorageStore } from "mega-stream-proxy/accounts";
const accounts = new MegaAccountManager(createLocalStorageStore());

await accounts.addAccount({ email: "[email protected]", password: "…", label: "Work" });
const list = await accounts.listAccounts();        // MegaAccount[]
const tree = await accounts.loadTree(list[0]);     // MegaTreeNode[] (folders, videos, subs)
const link = await accounts.exportLink(list[0], tree.find(n => n.isVideo).handle);
await accounts.deleteAccount(list[0].id);

React Native — persist in AsyncStorage:

import { createAsyncStorageStore } from "mega-stream-proxy/accounts";
import AsyncStorage from "@react-native-async-storage/async-storage";
const accounts = new MegaAccountManager(createAsyncStorageStore(AsyncStorage));

Bring your own backend (Firestore, your server…):

import type { MegaAccountStore, MegaAccount } from "mega-stream-proxy/accounts";
const myStore: MegaAccountStore = {
  list: () => db.collection("accounts").get().then(q => q.docs.map(d => d.data())),
  save: (a: MegaAccount) => db.doc(a.id).set(a),
  remove: (id) => db.doc(id).delete(),
  subscribe: (cb) => unsubFirestore(cb),
};
const accounts = new MegaAccountManager(myStore);

Other helpers: testAccount() returns { name, folders, files } (login check), subscribe(cb) streams account changes, closeSession(id) / closeAll() free sessions, MegaAccount — you can enrich stores (encrypt passwords, etc.) before saving.

Export map

| Subpath | Contents | | ------------------ | --------------------------------------------------------------- | | mega-stream-proxy | core: loadMegaFile, collectMegaFileStream, parseRange, rangeHeaders, SW_MARKER | | mega-stream-proxy/native | ensureProxy, createStream, createStreams, releaseStream, stopProxy (TCP, keyed sessions on a singleton server) | | mega-stream-proxy/web | same API, Service-Worker backed (Blob fallback) | | mega-stream-proxy/sw | copySwAssets(outDir), swAssetPaths() (deploy-time node helper) | | mega-stream-proxy/player | MegaVideoPlayer, PlayerControls, SubtitleDisplay | | mega-stream-proxy/subtitles| loadSubtitles, parseSubtitles, detectSubtitleFormat, types | | mega-stream-proxy/accounts| MegaAccountManager, createLocalStorageStore, createAsyncStorageStore, types |

Android build

The package includes android/build.gradle (com.android.library, namespace com.megastreamproxy) so RN autolinking resolves it without touching your Gradle config. All native streaming is performed by react-native-tcp-socket, which is a declared dependency and is auto-linked too. Build as usual:

npx expo run:android   # Expo apps
./gradlew assembleDebug # bare RN apps

Development

npm install          # installs everything (incl. dev deps for typechecking the player)
npm run build        # tsc -> dist/ + copies sw assets
npm run test         # build + node:test suite
npm pack             # -> mega-stream-proxy-0.1.0.tgz

Node 18+. TypeScript strict. ESM ("type": "module").

License

MIT — see LICENSE.