# @mostajs/media — fiche LLM > Capture image/vidéo, enregistrement d'écran, orchestrateur de sessions multi-prises, éditeur vidéo client (ffmpeg.wasm) et composants d'upload pour React/Next.js. - Version: 2.4.0 · Licence: AGPL-3.0-or-later · Auteur: Dr Hamid MADANI - Chemin: mostajs/mosta-media · Statut audit: complet (dist/) ## RÔLE Boîte à outils média client-first pour apps @mostajs : capture photo/vidéo via webcam, enregistrement d'écran et audio, sessions multi-prises (multi-take) avec persistance IndexedDB cross-reload, éditeur vidéo navigateur (clips, sous-titres, overlays emoji, logo) encodé via ffmpeg.wasm ou composition serveur, consommation de flux SFU distant (modes viewer / composite-split), et publication YouTube. Fournit aussi des pages clé en main et un jeu de routes serveur (projets, compose). ## INSTALLATION npm i @mostajs/media Peers : react (>=18), lucide-react. Optionnelles : @ffmpeg/ffmpeg + @ffmpeg/util (export ffmpeg.wasm), @mostajs/qrpanel (>=0.5.0, QR du mode SFU viewer), @mostajs/menu (mediaMenuContribution), @mostajs/orm + better-sqlite3 + next (routes serveur). Les modes SFU peuvent requérir @mostajs/media-sfu (import dynamique). ## EXPORTS Composants (default ré-exportés) : ImageCapture, VideoCapture, ImageEditor, MediaGallery, ScreenRecorder, RecorderPreview, AudioLevelMeter, AudioOnlyRecorder, SfuViewer, SfuViewerRecorder, YouTubePublishButton, VideoEditor, MultiTakeRecorder. Hooks : useCamera, useVideoRecorder, useScreenCapture, useMultiTakeSession, useVideoEditor, useRemoteSource (useCompositeCapture exposé via les hooks de session). lib (capture/storage) : createChunkStore, pickAudioMime, pickVideoMime, reconstructFromIndexedDB, listIndexedDBSessions, deleteIndexedDBSession, getStorageEstimate, createAudioStore. lib (sessions multi-take) : generateSessionId, takeBaseId, takesToEditorInput, appendTakeToSession, removeTakeFromSession, listOpenSessions, loadSession, finalizeSession, discardSession, gcExpiredSessions. lib (éditeur) : composeClipsServer, toSRT, toVTT, parseSRT, downloadText, OVERLAY_ICONS, SUBTITLE_LANGUAGES. lib (image) : resizeImage, rotateImage, flipImage, cropImage, adjustBrightness, adjustContrast, dataUrlToBlob, fileToDataUrl. Intégration : mediaMenuContribution. ## EXPORTS PAR SOUS-CHEMIN - "." → barrel principal (composants, hooks, lib, types ci-dessus) - "./components/" → composant isolé (ImageCapture, VideoCapture, ImageEditor, MediaGallery, ScreenRecorder, RecorderPreview, AudioLevelMeter, AudioOnlyRecorder, YouTubePublishButton, VideoEditor) - "./hooks/" → useCamera, useVideoRecorder, useScreenCapture, useVideoEditor - "./lib/" → image-utils, audio-storage, ffmpeg-compose, server-compose, emoji, menu - "./types", "./types/editor" → types publics - "./pages/" → pages clé en main : CapturePage, GalleryPage, VideoPage, ScreenPage, CaptureEditorPage, SfuViewerPage - "./server/" → routes Node : project-db, compose-route, projects-list-route, projects-id-route - "./register" → register(registry) (enregistrement module @mostajs/socle) ## API — SIGNATURES - useCamera(): UseCameraReturn — videoRef, active, start(opts?), stop(), capture(opts?), switchCamera(), facingMode, error - useVideoRecorder(): UseVideoRecorderReturn — startCamera/stopCamera, startRecording/pause/resume/stopRecording (=> { blob; url }), recording, paused, duration, error - useScreenCapture() — capture écran + webcam overlay (canvas compositor, swap), getRecordingStream() - useMultiTakeSession(opts?) — orchestre une session : startTake, stopTake, startNewTake, deleteTake, reorderTakes, finalizeSession, restoreSession, getRecordingStream() - useVideoEditor(input) — état éditeur : clips, split, trim, overlays, subtitles, adSlots, insertVideoClip, exportClips, getProjectData/setProjectData - useRemoteSource({ protocol, apiBase, roomId, waitForPublisher?, autoConnect? }) — abonnement flux distant SFU ; status idle|waiting|connecting|connected|error - function composeClipsServer(clips: Clip[], options: ComposeClipsServerOptions): Promise - function takesToEditorInput(takes: MediaTake[]): TakesToEditorInputResult - function resizeImage / rotateImage / flipImage / cropImage / adjustBrightness / adjustContrast (data URL → data URL) - function toSRT/toVTT(subtitles): string ; parseSRT(text): Subtitle[] ; downloadText(name, text) - function generateSessionId(): string ; takeBaseId(sessionId, index): string - function glyphToPngBlob(glyph, sizePx): Promise (./lib/emoji) - server/project-db : listProjects(), getProject(id), createProject(name, data), updateProject(id, name, data), deleteProject(id), ProjectSchema ## TYPES CLÉS - CameraOptions { facingMode?='user'|'environment'; width?; height?; mirror? } - ImageCaptureProps { onCapture(dataUrl); onClear?; photo?; cameraOptions?; maxWidth?=800; maxHeight?=800; quality?=0.85; format?; allowUpload?; ... } - VideoCaptureProps { onCapture(blob, url); maxDuration?=60; cameraOptions?; mimeType?='video/webm'; ... } - MediaItem { id; url: string; type: 'image'|'video'; name?; thumbnail?; createdAt? } - Clip { id; kind: 'video'|'image'; sourceUrl: string; srcStartSec?; srcEndSec?; speed: number; durationSec: number; overlays: Overlay[] } - Overlay { id; icon: OverlayIcon; xFrac; yFrac; sizePx; startSec; endSec: number } - Subtitle { id; text; lang: string; startSec; endSec; fontSize; color; bgColor; position: 'bottom'|'top'|'center' } - MediaTake { id; index: number; videoResult: ChunkRecordResult; audioResult?; durationMs; startedAt: number; title? } - MediaSessionState { sessionId: string; status: 'idle'|'streaming'|'recording'|'paused'|'stopped'; takes: MediaTake[]; currentTakeDurationSec; sourcesUsed: SessionSource[] } - ChunkStorage = 'memory'|'indexeddb'|'filesystem'|'server' - MultiTakeRecordingMode = 'screen-record'|'audio-only'|'sfu-viewer'|'composite-split' - RemoteProtocol = 'sfu'|'p2p'|'mcu' ; OutputFormat = 'webm'|'gif'|'mp4' - ComposeManifest / ComposeClipsServerOptions — payload de composition serveur (clips, audioTracks, logo, subtitles) - ProjectFile { version: 1|2; name?; clips[]; subtitles[]; settings; adSlots?; logo?; audioTracks? } ## PATTERN ```tsx // Capture photo import { ImageCapture } from '@mostajs/media'; save(dataUrl)} allowUpload maxWidth={1024} /> // Enregistrement vidéo via hook import { useVideoRecorder } from '@mostajs/media'; const rec = useVideoRecorder(); await rec.startCamera(); rec.startRecording(); const out = await rec.stopRecording(); // { blob, url } // Boucle record ↔ édition // ; // au retour d'un mini-recorder → editorRef.current.insertVideoClip(blob, sec) // Composition serveur d'un montage import { composeClipsServer } from '@mostajs/media'; const result = await composeClipsServer(clips, { url: '/api/compose', format: 'mp4' }); ``` ## DÉPEND DE - @mostajs/qrpanel (peer, >=0.5.0) — QR codes des modes SFU viewer / publish - @mostajs/menu (peer optionnel, >=1.0.2) — type de mediaMenuContribution - @mostajs/socle (peer, >=2.0.0) — ModuleRegistration (./register) - @mostajs/config (peer optionnel) — configuration - @mostajs/orm (peer optionnel, >=1.11.0) + better-sqlite3 — routes serveur ./server/* (projets) ## PIÈGES - Module client-first : les composants/hooks de capture s'exécutent dans le navigateur (getUserMedia, MediaRecorder, IndexedDB) — ne pas les rendre côté serveur. - audio-only : un take audio ne doit PAS être envoyé à VideoEditor (ffmpeg-compose ne décode pas un blob audio en clip vidéo) — `takesToEditorInput` filtre ces takes. - ffmpeg.wasm : encode en VP8 (libvpx), PAS en VP9 (crash WASM out-of-bounds). - L'export ffmpeg.wasm requiert COEP/COOP (cross-origin isolation) et les peers optionnels @ffmpeg/ffmpeg + @ffmpeg/util ; `composeClipsServer` est l'alternative serveur. - Les modes sfu-viewer / publish reposent sur un import dynamique de @mostajs/media-sfu (peer optionnel) — throw au runtime s'il est absent. p2p / mcu non encore implémentés. - Les routes `./server/*` requièrent @mostajs/orm + better-sqlite3 + next : ne pas les importer dans un bundle client. - Les overlays emoji sont rendus client-side en PNG (`glyphToPngBlob`) puis transmis au ffmpeg en sprites — la police emoji du navigateur influe sur le rendu. - Les sessions multi-take persistent en IndexedDB ; appeler `gcExpiredSessions` pour le GC. ## RÉFÉRENCES - README.md - CHANGELOG.md - docs/, examples/, index.ts, register.ts, components/, hooks/, lib/, pages/, server/, types/