# V0.703 Release Scope — Go Live & Streaming Complet **Statut** : En cours **Phase** : 7 (Production Readiness — Finale) **Prérequis** : v0.702 (taguée) **Date cible** : TBD **Estimation** : ~3 sprints (15 jours ouvrés) **Précédente** : [v0.702](archive/V0_702_RELEASE_SCOPE.md) --- ## 1. Objectif Dernière version de la Phase 7. Complète le module Live Streaming en ajoutant la fonctionnalité **Go Live** (démarrage de stream), actuellement bloquée par un toast "coming soon" dans la Navbar. Ajoute la **gestion de stream keys**, les endpoints **start/stop/schedule**, le **chat live connecté WebSocket**, et des **améliorations player** (vitesse de lecture, Media Session API). --- ## 2. État actuel (post-v0.702) | Composant | État | Détail | |-----------|------|--------| | **LiveStream model** | ✅ Livré | `live_streams` table, GORM model avec StreamKey (json:"-") | | **GET /live/streams** | ✅ Livré | Liste publique, filtre `?is_live=true` | | **GET /live/streams/:id** | ✅ Livré | Détail d'un stream | | **POST /live/streams** | ✅ Livré | Création (auth required), mais pas de stream key generation | | **Stream events handler** | ✅ Livré | `POST /internal/stream-events` (stream_started, stream_ended, listener_joined, listener_left) | | **LiveView (frontend)** | ✅ Livré | Visualisation de streams (player, info, recommended, chat mock) | | **Go Live (frontend)** | ❌ Absent | Navbar "Go Live" → toast "coming soon" | | **Stream key generation** | ❌ Absent | StreamKey field exists mais jamais généré ni exposé | | **GET /live/streams/my/key** | ❌ Absent | Endpoint pour récupérer sa stream key | | **PUT /live/streams/:id** | ❌ Absent | Mise à jour métadonnées pendant le live | | **Stream scheduling** | ❌ Absent | Pas de scheduled_at pour planifier un stream | | **Live chat WebSocket** | ❌ Absent | LiveViewChat utilise des données mock statiques | | **VOD (replays)** | ❌ Absent | Streams terminés non consultables | | **Playback speed** | ❌ Absent | Pas de contrôle 0.5x–2x | | **Media Session API** | ❌ Absent | Pas de contrôles OS (notification area) | --- ## 3. Lots ### Lot GL1 — Go Live Backend **Objectif** : Compléter l'API live streaming avec stream key, mise à jour, scheduling et VOD. | # | Tâche | Fichiers impactés | Effort | |---|-------|--------------------|--------| | GL1-01 | Migration `117_live_streams_go_live.sql` — ajout colonnes `scheduled_at`, `stream_url`, `is_vod` ; update `stream_key` NOT NULL avec default UUID | `migrations/117_live_streams_go_live.sql` | S | | GL1-02 | Mise à jour modèle `LiveStream` — ajout champs `ScheduledAt`, `StreamURL`, `IsVOD` | `internal/models/live_stream.go` | S | | GL1-03 | `GenerateStreamKey` dans service — génère un UUID v4 unique pour stream_key lors du Create | `internal/services/live_stream_service.go` | S | | GL1-04 | `GET /live/streams/me` — liste les streams de l'utilisateur authentifié (incluant stream_key) | `internal/handlers/live_stream_handler.go`, routes | M | | GL1-05 | `GET /live/streams/me/key` — retourne la stream key de l'utilisateur (crée un stream draft si nécessaire) | `internal/handlers/live_stream_handler.go`, routes | S | | GL1-06 | `POST /live/streams/me/key/regenerate` — régénère la stream key | `internal/handlers/live_stream_handler.go`, routes | S | | GL1-07 | `PUT /live/streams/:id` — mise à jour titre/description/category/tags pendant ou avant le live | `internal/handlers/live_stream_handler.go`, routes | S | | GL1-08 | `GET /live/streams?is_vod=true` — listing des streams terminés (VOD replays) | `internal/services/live_stream_service.go`, repository | S | | GL1-09 | Prometheus metrics — `veza_live_streams_active`, `veza_live_stream_viewers_total` | `internal/monitoring/metrics.go` | S | | GL1-10 | Tests unitaires — stream key generation, CRUD complet, ownership check, VOD filter | `internal/services/live_stream_service_test.go` (nouveau) | M | ### Lot GL2 — Go Live Frontend **Objectif** : Page Go Live pour configurer et démarrer un stream, afficher la stream key, instructions OBS. | # | Tâche | Fichiers impactés | Effort | |---|-------|--------------------|--------| | GL2-01 | `GoLiveView` — formulaire (titre, description, catégorie, tags, thumbnail), affichage stream key avec bouton copy, instructions OBS/Streamlabs | `apps/web/src/features/live/pages/go-live-page/GoLiveView.tsx` (nouveau) | L | | GL2-02 | `GoLivePage` — wrapper avec fetch stream key, state management | `apps/web/src/features/live/pages/GoLivePage.tsx` (nouveau) | M | | GL2-03 | Lazy export `LazyGoLive` — createLazyComponent pour GoLivePage | `apps/web/src/components/ui/lazy-component/lazyExports.ts` | S | | GL2-04 | Route `/live/go-live` dans routeConfig — protégée auth | `apps/web/src/router/routeConfig.tsx` | S | | GL2-05 | Navbar "Go Live" → navigate `/live/go-live` au lieu du toast | `apps/web/src/components/layout/Navbar.tsx` | S | | GL2-06 | Service frontend `liveService` — `getMyStreams`, `getMyStreamKey`, `regenerateStreamKey`, `updateStream`, `createStream` | `apps/web/src/services/liveService.ts` (modifier) | M | | GL2-07 | MSW handlers — mock GET /live/streams/me, GET /live/streams/me/key, POST regenerate, PUT :id | `apps/web/src/mocks/handlers-live.ts` (nouveau) | M | | GL2-08 | Story `GoLiveView.stories.tsx` — états Default, Loading, Error, StreamKeyVisible | `apps/web/src/features/live/pages/go-live-page/GoLiveView.stories.tsx` (nouveau) | M | ### Lot GL3 — Live Chat & Interaction **Objectif** : Connecter le chat live aux WebSocket existants pour un chat temps réel par stream. | # | Tâche | Fichiers impactés | Effort | |---|-------|--------------------|--------| | GL3-01 | Chat room par stream — utiliser le système de conversation WebSocket existant (JoinConversation avec stream_id comme room) | `internal/websocket/chat/handlers.go` | M | | GL3-02 | `LiveViewChat` connecté WebSocket — remplacer les données mock par le hook useChat existant | `apps/web/src/features/live/pages/live-page/LiveViewChat.tsx` | M | | GL3-03 | Viewer count temps réel — écouter les events `listener_joined`/`listener_left` via WebSocket | `apps/web/src/features/live/pages/live-page/LiveViewPlayer.tsx` | S | | GL3-04 | Tests — chat join/leave stream room, message broadcast | Tests | M | ### Lot GL4 — Player Enhancements **Objectif** : Vitesse de lecture et intégration Media Session API pour contrôles OS. | # | Tâche | Fichiers impactés | Effort | |---|-------|--------------------|--------| | GL4-01 | Playback speed control — bouton 0.5x, 0.75x, 1x, 1.25x, 1.5x, 2x dans le player | `apps/web/src/features/player/components/audio-player/` | M | | GL4-02 | Media Session API — navigator.mediaSession avec metadata (title, artist, artwork), action handlers (play, pause, seekbackward, seekforward, previoustrack, nexttrack) | `apps/web/src/features/player/hooks/` | M | | GL4-03 | Tests — playback speed persistence, media session metadata update | Tests | S | ### Lot QA1 — Tests & Release | # | Tâche | Fichiers impactés | Effort | |---|-------|--------------------|--------| | QA1-01 | Smoke test v0.703 — checklist complète | `docs/SMOKE_TEST_V0703.md` | S | | QA1-02 | Mise à jour PROJECT_STATE, FEATURE_STATUS, CHANGELOG | `docs/` | S | | QA1-03 | Rétrospective v0.703, archivage scope, placeholder v0.801, tag | `docs/`, Git | S | --- ## 4. Hors scope v0.703 | Élément | Version cible | |---------|---------------| | 2FA SMS / Passkeys | v0.104 | | Themes (high contrast, compact) | v0.801 | | Accessibilité WCAG AA | v0.801 | | Cloud Storage avancé (versioning, backup) | v0.802 | | OpenAPI/Swagger auto-gen | v0.803 | | Wishlist marketplace | v0.901 | | Promotions dynamiques (flash sales) | v0.901 | | Dispute / réclamation client | v0.901 | | Multi-camera support | v2.0 | | Donations/tips live | v2.0 | | Clipping moments | v2.0 | --- ## 5. Détail technique — Lot GL1 ### 5.1 Migration 117_live_streams_go_live.sql ```sql ALTER TABLE live_streams ADD COLUMN IF NOT EXISTS scheduled_at TIMESTAMPTZ, ADD COLUMN IF NOT EXISTS stream_url TEXT DEFAULT '', ADD COLUMN IF NOT EXISTS is_vod BOOLEAN NOT NULL DEFAULT false; UPDATE live_streams SET stream_key = gen_random_uuid()::text WHERE stream_key = '' OR stream_key IS NULL; ALTER TABLE live_streams ALTER COLUMN stream_key SET NOT NULL; ALTER TABLE live_streams ALTER COLUMN stream_key SET DEFAULT gen_random_uuid()::text; CREATE INDEX IF NOT EXISTS idx_live_streams_user_id ON live_streams(user_id); CREATE INDEX IF NOT EXISTS idx_live_streams_is_live ON live_streams(is_live) WHERE is_live = true; CREATE INDEX IF NOT EXISTS idx_live_streams_scheduled ON live_streams(scheduled_at) WHERE scheduled_at IS NOT NULL; ``` ### 5.2 Modèle LiveStream mis à jour ```go type LiveStream struct { // ... champs existants ... ScheduledAt *time.Time `json:"scheduled_at,omitempty" db:"scheduled_at"` StreamURL string `gorm:"type:text;default:''" json:"stream_url,omitempty" db:"stream_url"` IsVOD bool `gorm:"default:false" json:"is_vod" db:"is_vod"` } ``` ### 5.3 Stream Key Generation ```go func (s *LiveStreamService) Create(ctx context.Context, userID uuid.UUID, stream *models.LiveStream) (*models.LiveStream, error) { if stream.Title == "" { return nil, errors.New("title is required") } stream.UserID = userID stream.StreamKey = uuid.New().String() if stream.StreamerName == "" { stream.StreamerName = "Streamer" } if err := s.repo.Create(ctx, stream); err != nil { return nil, err } return stream, nil } ``` ### 5.4 GET /live/streams/me/key ```go func (h *LiveStreamHandler) GetMyStreamKey(c *gin.Context) { userID, ok := GetUserIDUUID(c) if !ok { return } streams, err := h.service.ListByUser(c.Request.Context(), userID) if err != nil { RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get streams", err)) return } var streamKey string if len(streams) > 0 { streamKey = streams[0].StreamKey } else { draft := &models.LiveStream{Title: "My Stream"} created, err := h.service.Create(c.Request.Context(), userID, draft) if err != nil { RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to create stream", err)) return } streamKey = created.StreamKey } RespondSuccess(c, http.StatusOK, gin.H{ "stream_key": streamKey, "rtmp_url": "rtmp://stream.veza.app/live", }) } ``` ### 5.5 VOD — Streams terminés Quand `SetIsLive(false)` est appelé (stream_ended event), le stream reste en DB avec `ended_at` set. On ajoute un filtre `is_vod` qui retourne les streams avec `ended_at IS NOT NULL AND is_live = false`. ### 5.6 Scheduled Streams Le champ `scheduled_at` permet de planifier un stream futur. L'endpoint `POST /live/streams` accepte `scheduled_at` dans le body. Le frontend affiche les streams planifiés avec un badge "Scheduled" et un countdown. --- ## 6. Détail technique — Lot GL2 ### 6.1 GoLiveView ```typescript interface GoLiveViewProps { streamKey: string | null; rtmpUrl: string; onCreateStream: (data: CreateStreamData) => Promise; onRegenerateKey: () => Promise; isLoading: boolean; } interface CreateStreamData { title: string; description: string; category: string; tags: string[]; thumbnailUrl?: string; scheduledAt?: string; } ``` Sections : 1. **Stream Configuration** — formulaire titre/description/category/tags 2. **Stream Key** — affichage masqué par défaut, bouton reveal, bouton copy, bouton regenerate 3. **Connection Info** — RTMP URL + stream key, instructions OBS/Streamlabs 4. **Preview** — thumbnail upload, scheduled_at picker 5. **Actions** — bouton "Go Live Now" ou "Schedule Stream" ### 6.2 Navbar update ```typescript // Avant (toast) onClick={() => toast.info('Live streaming feature coming soon')} // Après (navigation) onClick={() => { onNavigate('go-live'); setShowUserMenu(false); }} ``` --- ## 7. Détail technique — Lot GL4 ### 7.1 Playback Speed ```typescript const SPEED_OPTIONS = [0.5, 0.75, 1, 1.25, 1.5, 2]; function PlaybackSpeedButton({ currentSpeed, onSpeedChange }: Props) { // Cycle through speeds on click, or dropdown on hover } ``` ### 7.2 Media Session API ```typescript function useMediaSession(track: Track | null, actions: MediaSessionActions) { useEffect(() => { if (!('mediaSession' in navigator) || !track) return; navigator.mediaSession.metadata = new MediaMetadata({ title: track.title, artist: track.artist, artwork: track.coverUrl ? [{ src: track.coverUrl, sizes: '512x512' }] : [], }); navigator.mediaSession.setActionHandler('play', actions.onPlay); navigator.mediaSession.setActionHandler('pause', actions.onPause); navigator.mediaSession.setActionHandler('previoustrack', actions.onPrevious); navigator.mediaSession.setActionHandler('nexttrack', actions.onNext); navigator.mediaSession.setActionHandler('seekbackward', actions.onSeekBackward); navigator.mediaSession.setActionHandler('seekforward', actions.onSeekForward); }, [track, actions]); } ``` --- ## 8. Fichiers impactés (récapitulatif) ### Backend Go (nouveau) | Fichier | Action | |---------|--------| | `migrations/117_live_streams_go_live.sql` | Nouveau — colonnes scheduled_at, stream_url, is_vod | | `internal/services/live_stream_service_test.go` | Nouveau — tests unitaires live stream | ### Backend Go (modifier) | Fichier | Action | |---------|--------| | `internal/models/live_stream.go` | Ajout ScheduledAt, StreamURL, IsVOD | | `internal/services/live_stream_service.go` | Stream key generation, ListByUser, RegenerateKey | | `internal/handlers/live_stream_handler.go` | GET me, GET me/key, POST regenerate, PUT :id | | `internal/api/routes_live.go` | Ajout routes protégées (me, key, regenerate, PUT) | | `internal/repositories/live_stream_repository.go` | ListByUserID, filtre VOD | | `internal/monitoring/metrics.go` | Métriques live streams | ### Frontend (nouveau) | Fichier | Action | |---------|--------| | `apps/web/src/features/live/pages/GoLivePage.tsx` | Nouveau — page wrapper | | `apps/web/src/features/live/pages/go-live-page/GoLiveView.tsx` | Nouveau — formulaire Go Live | | `apps/web/src/features/live/pages/go-live-page/GoLiveView.stories.tsx` | Nouveau — stories | | `apps/web/src/mocks/handlers-live.ts` | Nouveau — MSW handlers live | ### Frontend (modifier) | Fichier | Action | |---------|--------| | `apps/web/src/components/layout/Navbar.tsx` | Remplacer toast par navigation | | `apps/web/src/services/liveService.ts` | Ajout getMyStreams, getMyStreamKey, regenerateStreamKey, updateStream | | `apps/web/src/router/routeConfig.tsx` | Route /live/go-live | | `apps/web/src/components/ui/lazy-component/lazyExports.ts` | LazyGoLive | | `apps/web/src/features/live/pages/live-page/LiveViewChat.tsx` | Connecter WebSocket | | `apps/web/src/features/live/pages/live-page/LiveViewPlayer.tsx` | Viewer count real-time | | `apps/web/src/features/player/components/audio-player/` | Playback speed control | | `apps/web/src/features/player/hooks/` | Media Session API hook | ### Documentation | Fichier | Action | |---------|--------| | `docs/API_REFERENCE.md` | Ajout section Live Streaming | | `docs/SMOKE_TEST_V0703.md` | Nouveau — checklist | | `docs/PROJECT_STATE.md` | Section v0.703 | | `docs/FEATURE_STATUS.md` | Mise à jour | | `CHANGELOG.md` | Section v0.703 | | `docs/RETROSPECTIVE_V0703.md` | Nouveau (fin de release) | --- ## 9. Critères d'acceptation globaux - [ ] Stream key générée automatiquement lors du `POST /live/streams` - [ ] `GET /live/streams/me/key` retourne stream key + RTMP URL (auth required) - [ ] `POST /live/streams/me/key/regenerate` génère une nouvelle stream key - [ ] `PUT /live/streams/:id` met à jour les métadonnées (ownership check) - [ ] `GET /live/streams?is_vod=true` retourne les streams terminés - [ ] Frontend GoLivePage : formulaire, stream key visible/copiable, instructions OBS - [ ] Navbar "Go Live" navigue vers `/live/go-live` au lieu du toast - [ ] LiveViewChat connecté WebSocket (messages en temps réel) - [ ] Viewer count mis à jour en temps réel via events - [ ] Playback speed : 6 vitesses (0.5x–2x), persistance - [ ] Media Session API : metadata (title, artist, artwork), action handlers - [ ] Stories GoLiveView : Default, Loading, Error, StreamKeyVisible - [ ] MSW handlers live : GET me, GET me/key, POST regenerate, PUT :id - [ ] Tests unitaires backend : stream key, CRUD, ownership, VOD - [ ] Tag v0.703 créé --- ## 10. Risques | Risque | Mitigation | |--------|------------| | Stream key exposée en clair dans le frontend | Masquée par défaut, reveal on click, HTTPS obligatoire | | RTMP server non configuré (stream server absent) | Les endpoints backend sont prêts, stream server Rust à configurer séparément | | LiveViewChat charge excessive avec beaucoup de viewers | Rate limiting WebSocket existant (v0.503), pagination historique | | Media Session API non supportée par tous les navigateurs | Feature detection avec `if ('mediaSession' in navigator)` | | VOD storage (streams enregistrés) | v0.703 = métadonnées uniquement, pas de stockage vidéo réel (future: S3 segments) | --- ## 11. Architecture ```mermaid graph TD subgraph backend [Backend Go] API[API Routes /live/*] SVC[LiveStreamService] REPO[LiveStreamRepository] DB[(PostgreSQL)] WS[WebSocket Hub] end subgraph frontend [Frontend React] GL[GoLivePage] LV[LiveView] LC[LiveViewChat] PL[Player] end subgraph external [External] OBS[OBS/Streamlabs] SS[Stream Server Rust] end GL -->|"POST /live/streams"| API GL -->|"GET /live/streams/me/key"| API LV -->|"GET /live/streams"| API LC -->|"WebSocket /ws"| WS PL -->|"Media Session API"| PL OBS -->|"RTMP stream_key"| SS SS -->|"POST /internal/stream-events"| API API --> SVC SVC --> REPO REPO --> DB WS -->|"chat messages"| LC ``` --- ## 12. Références - [RETROSPECTIVE_V0702.md](RETROSPECTIVE_V0702.md) - [V0_702_RELEASE_SCOPE.md](archive/V0_702_RELEASE_SCOPE.md) - [SCOPE_CONTROL.md](SCOPE_CONTROL.md) - `veza-backend-api/internal/handlers/live_stream_handler.go` (handlers existants) - `veza-backend-api/internal/services/live_stream_service.go` (service existant) - `veza-backend-api/internal/models/live_stream.go` (modèle existant) - `apps/web/src/features/live/pages/live-page/LiveView.tsx` (composant vue) - `apps/web/src/components/layout/Navbar.tsx` L223 (toast "coming soon")