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.1.0

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.

Downloads

150

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.


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) |

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 |