veza/docs/archive/V0_703_RELEASE_SCOPE.md
senke 63867f1d09
Some checks failed
Backend API CI / test-unit (push) Failing after 0s
Backend API CI / test-integration (push) Failing after 0s
Frontend CI / test (push) Failing after 0s
Storybook Audit / Build & audit Storybook (push) Failing after 0s
feat(v0.703): Go Live & Streaming Complet
- Backend: room creation for live streams, permissions CanJoin/CanSend/CanRead for stream rooms
- LiveViewChat: useLiveStreamChat hook, WebSocket connection, stream_id as room
- LiveViewPlayer: real-time viewer count via polling (5s)
- Media Session: seekbackward/seekforward handlers (10s step)
- GoLiveView.stories.tsx: Default, Loading, Error, StreamKeyVisible
- Docs: API_REFERENCE, CHANGELOG, PROJECT_STATE, FEATURE_STATUS, RETROSPECTIVE_V0703
- SCOPE_CONTROL, .cursorrules: update to v0.801
- Archive V0_703_RELEASE_SCOPE.md
2026-02-25 09:35:22 +01:00

431 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.5x2x |
| **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<void>;
onRegenerateKey: () => Promise<void>;
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.5x2x), 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")