veza/docs/archive/V0_703_RELEASE_SCOPE.md

432 lines
18 KiB
Markdown
Raw Normal View History

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