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

@mostajs/media-mcu

v0.3.3

Published

MCU (Multipoint Conferencing Unit) pour l'écosystème @mostajs/* — compose N flux WebRTC en 1 flux HLS/RTMP via ffmpeg. Réception RTP via mediasoup PlainTransport. Cas d'usage : régie, replay archivage, broadcast unique.

Readme

@mostajs/media-mcu

Auteur : Dr Hamid MADANI [email protected] License : AGPL-3.0-or-later

MCU (Multipoint Conferencing Unit) pour l'écosystème @mostajs/* — compose N flux WebRTC en 1 flux composite (HLS / RTMP / file) via ffmpeg. Reçoit le RTP via mediasoup PlainTransport. Cas d'usage : régie broadcast unique, archivage post-event, mosaïque admin.

Scope v0.1.0 (MVP) : N inputs WebRTC → 1 output HLS local. Layouts grid 2×2 / focus-speaker / picture-in-picture configurables. Recording HLS sur disque (storage backend via @mostajs/storage).


Quand utiliser MCU vs SFU vs P2P ?

| Architecture | Pairs max | Output | CPU serveur | Cas d'usage | |---|---|---|---|---| | @mostajs/media-p2p | 2-4 | N streams individuels | quasi nul | Visio 1-1, petit groupe | | @mostajs/media-sfu | 5-100 | N streams forwardés | faible | Cours, broadcast 1→N | | @mostajs/media-mcu ← ici | N inputs → 1 output | 1 flux HLS/RTMP | élevé (transcoding) | Régie unique, archivage, mosaïque |

Règle simple : MCU est le bon choix uniquement si tu as besoin d'un seul flux composite output, par exemple :

  • Archive replay d'un cours mosaïque (prof + slides + apprenants vignettes)
  • Broadcast vers une plateforme externe (YouTube Live, Twitch) qui n'accepte qu'1 flux RTMP
  • Régie TV style avec switcher entre sources

Sinon SFU est presque toujours meilleur.

⚠️ Attention : MCU ≠ recording simple côté client

Si ton besoin est un seul utilisateur qui enregistre son propre flux (webcam → fichier .webm/.mp4), n'utilise PAS MCU — utilise @mostajs/media côté browser. Comparatif :

| | @mostajs/media (browser) | @mostajs/media-mcu (serveur) | |---|---|---| | Où tourne le record | Browser (MediaRecorder API native) | Serveur (mediasoup → ffmpeg) | | Install lourde ? | Zéro (lib JS pure) | Compile C++ mediasoup-worker (~3-5 min sur VPS 2 cœurs) | | Postinstall | Aucun | Tente prebuild download, fallback compile-from-source si CDN/arch ne matchent pas | | Cas d'usage | 1 user enregistre son propre flux | Mixer N publishers en 1 fichier | | Output | Blob .webm côté client (upload via XHR) | .webm / .mp4 / .m3u8 côté serveur | | Latence record | Temps réel browser-side | RTP → ffmpeg server-side (~quelques secondes lag) |

Pourquoi MCU est si lourd à installer ? mediasoup-worker est un binaire C++ natif incluant libwebrtc, libsrtp, OpenSSL, abseil, FBS schemas. npm tente d'abord un prebuild download du binaire précompilé ; en cas d'échec (DNS, arch CPU exotique), fallback compile-from-source ≈ 3-5 min sur 2 cœurs. C'est le prix à payer pour décoder du SRTP/WebRTC serveur-side@mostajs/media n'a pas besoin de ça car le browser fait tout en natif via MediaRecorder.


Quick start

Installation

npm install @mostajs/media-mcu

# Pré-requis système (sur le serveur)
sudo apt install ffmpeg

Requires Node ≥ 18 + ffmpeg dans le PATH.

Bootstrap

// lib/mcu-bootstrap.ts
import { createMcuServer } from '@mostajs/media-mcu/server'

export const mcu = await createMcuServer({
  listenIps: [{ ip: '0.0.0.0', announcedIp: process.env.SFU_ANNOUNCED_IP }],
  minPort: 41000,
  maxPort: 41100,
  outputDir: './data/mcu-outputs',
  ffmpegPath: '/usr/bin/ffmpeg',
})

API handlers

// app/api/mcu/sessions/route.ts
import { createMcuApiHandlers } from '@mostajs/media-mcu/api'
import { mcu } from '@/lib/mcu-bootstrap'

const handlers = createMcuApiHandlers({ mcu, permissionChecker: yourAuth })

export const POST = handlers.sessionCreate    // créer une session MCU avec layout
export const GET = handlers.sessionList
// app/api/mcu/sessions/[id]/inputs/route.ts
// Add an input (peer publishe via WHIP standard)
export const POST = (req: Request, ctx: any) => handlers.inputAdd(req, ctx.params)
// app/api/mcu/sessions/[id]/output/route.ts
// Get HLS playlist URL pour l'output composite
export const GET = (req: Request, ctx: any) => handlers.outputUrl(req, ctx.params)

Layouts disponibles

| Layout | Description | |---|---| | grid-2x2 | 4 sources max, grille équivalente | | grid-3x3 | 9 sources max | | focus-speaker | 1 grande + N petites (active speaker auto detect) | | pip-bottom-right | 1 fond plein + 1 vignette coin | | vertical-strip | 1 grande à gauche + colonne vignettes à droite | | single | 1 seule source (effectivement = passthrough, debug) | | program | bus PROGRAMME (v0.3) : 1 source diffusée plein écran + audio SOLO de cette source |

Bus programme — commuter la source diffusée (v0.3)

Pour une régie (type Studio LIVE TALK) où l'écran diffusé montre une source à la fois et où seul le PROGRAM émet l'audio : setProgram(inputId) met la source choisie en plein écran et active son audio en solo (les autres sources restent en ingest, muettes). Re-cliquer une autre source la commute instantanément.

const session = mcu.getSession(sessionId)

// le régisseur met la « Moto 1 » à l'antenne (vidéo plein écran + audio de la moto)
await session.setProgram('moto-1')      // inputId = appData.inputId de la source

// commuter vers le plateau (Studio LIVE TALK) — audio bascule sur le plateau
await session.setProgram('studio')

// revenir à un layout composé (grille…) : sortir du bus programme
await session.switchLayout('grid-2x2')  // remet programInputId = null

Côté API HTTP : POST /mcu/sessions/{id}/program avec { "inputId": "moto-1" } (handler programSet, réservé au rôle régie/write). inputId: null ⇒ retour au layout. La sortie programme (1 vidéo + 1 audio) alimente directement HLS pour la vue « C ».

Côté browser (publisher)

Identique à @mostajs/media-sfu (WHIP standard) — voir media-sfu/README.md.

Output HLS pour viewers

L'output MCU est un flux HLS standard, lisible par n'importe quel player (hls.js, native iOS Safari, VLC) :

<video controls>
  <source src="https://yourapp.com/mcu/outputs/{sessionId}/playlist.m3u8" type="application/vnd.apple.mpegurl">
</video>

Ou avec hls.js pour compat Chrome/Firefox :

import Hls from 'hls.js'
const hls = new Hls()
hls.loadSource('/mcu/outputs/abc123/playlist.m3u8')
hls.attachMedia(videoElement)

Architecture interne

   N Publishers (WHIP)
         │
         ▼
┌────────────────────────────────┐
│  mediasoup workers              │
│  PlainTransport per producer    │
│  ↓ RTP plain (out)              │
└────────────────────────────────┘
         │
         ▼
┌────────────────────────────────┐
│  ffmpeg pipeline                │
│  - N inputs UDP RTP             │
│  - filter_complex (layout)      │
│  - output HLS segments + .m3u8  │
└────────────────────────────────┘
         │
         ▼
   HLS files → @mostajs/storage bucket
         │
         ▼
   Player viewer (hls.js / video.js)

L'astuce : on utilise mediasoup en mode receiver uniquement (les peers WHIP créent des Producers, mais au lieu de redistribuer vers des Consumers, on pipeProducer vers un PlainTransport qui envoie en RTP brut vers ffmpeg local sur ports UDP fixes).

ffmpeg consomme N inputs RTP (udp://127.0.0.1:PORT?ssrc=...) et compose via filter_complex selon le layout choisi.


API publique

Server bootstrap

interface CreateMcuOptions {
  listenIps: Array<{ ip: string; announcedIp?: string }>
  minPort: number
  maxPort: number
  outputDir: string          // path FS pour les segments HLS
  ffmpegPath?: string        // default '/usr/bin/ffmpeg'
  storageBucket?: string     // si @mostajs/storage en backend, default 'mcu-outputs'
}

interface McuServer {
  createSession(opts: {
    layout: 'grid-2x2' | 'grid-3x3' | 'focus-speaker' | 'pip-bottom-right' | 'vertical-strip' | 'single'
    resolution?: '720p' | '1080p'  // default 720p
    audioMix?: 'all' | 'speaker-only'  // default all
  }): Promise<McuSession>
  getSession(sessionId: string): McuSession | undefined
  closeSession(sessionId: string): Promise<void>
  listSessions(): McuSessionInfo[]
}

interface McuSession {
  id: string
  layout: string
  inputs: McuInput[]
  outputPlaylistPath: string  // chemin HLS .m3u8
  addInput(producerId: string): Promise<McuInput>
  removeInput(producerId: string): Promise<void>
  switchLayout(newLayout: string): Promise<void>
  close(): Promise<void>
}

API handlers

interface McuApiHandlers {
  sessionCreate: (req: Request) => Promise<Response>
  sessionList: (req: Request) => Promise<Response>
  sessionClose: (req: Request, ctx: { sessionId: string }) => Promise<Response>

  inputAdd: (req: Request, ctx: { sessionId: string }) => Promise<Response>    // WHIP-like
  inputRemove: (req: Request, ctx: { sessionId: string; inputId: string }) => Promise<Response>

  outputUrl: (req: Request, ctx: { sessionId: string }) => Promise<Response>   // signed URL HLS

  layoutSwitch: (req: Request, ctx: { sessionId: string }) => Promise<Response>
}

Coût CPU / performance

| Layout | Resolution | Sources | CPU estimé | |---|---|---|---| | grid-2x2 | 720p | 4 | ~50-80% 1 core | | grid-3x3 | 720p | 9 | ~100-150% (1.5 core) | | focus-speaker | 720p | 4 | ~70% 1 core | | pip-bottom-right | 720p | 2 | ~30% 1 core |

Pour scale : 1 process ffmpeg par session MCU. Si plusieurs sessions concurrent, prévoir multi-core ou GPU encoding (NVENC/QSV — v0.3).

Recommandation : MCU = use sparingly. Pour la majorité des cas, SFU + composite côté client (Canvas/WebGL) est plus économique.


Roadmap

  • v0.1.0 : MVP layouts fixes + HLS output ← current
  • v0.2.0 : Custom layouts (JSON spec), active-speaker detection (voice activity)
  • v0.3.0 : GPU encoding (NVENC/QSV/VAAPI), RTMP output (vers YouTube Live, Twitch)
  • v0.4.0 : Time-shifted recording (start/stop dynamique), chapter markers

Comparaison @mostajs/media-* (résumé)

| Module | Quand l'utiliser | Doc | |---|---|---| | @mostajs/media-p2p | ≤ 4 pairs, latence critique | mosta-media-p2p/README.md | | @mostajs/media-sfu | 5-100 pairs, broadcast 1→N | mosta-media-sfu/README.md | | @mostajs/media-mcu | Composite 1 flux output | ici |