# @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 <drmdh@msn.com>
- 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/<Nom>" → composant isolé (ImageCapture, VideoCapture, ImageEditor,
  MediaGallery, ScreenRecorder, RecorderPreview, AudioLevelMeter, AudioOnlyRecorder,
  YouTubePublishButton, VideoEditor)
- "./hooks/<nom>" → useCamera, useVideoRecorder, useScreenCapture, useVideoEditor
- "./lib/<nom>" → image-utils, audio-storage, ffmpeg-compose, server-compose, emoji, menu
- "./types", "./types/editor" → types publics
- "./pages/<Nom>" → pages clé en main : CapturePage, GalleryPage, VideoPage, ScreenPage,
  CaptureEditorPage, SfuViewerPage
- "./server/<nom>" → 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<ExportResult>
- 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<Blob>  (./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';
<ImageCapture onCapture={(dataUrl) => 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
// <VideoEditor ref={editorRef} onAddTakeRequested={...} /> ;
// 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/
