Expertise 01 / 07

Streaming vidéo

Un feed vertical qui démarre en moins d'une seconde, même en 3G.

Prouvé sur :FlokDstok

Depuis décembre 2023

  • HLS
  • atome moov / fast start
  • ABR
  • ladder de bitrates
  • fenêtre glissante N±1
  • préchargement K-ahead
  • ExoPlayer / AVPlayer
  • TTFF
  • éviction LRU

Publications réelles, feed public de beta.flokcity.com

Chargement du feed Flok…

Télémétrie en direct

720p
Première image (TTFF)
Niveau de tampon0 s
Palier actif
720p
Changements de qualité
0
Bande passante estimée
Images perdues
0
Octets chargés
Source active
MP4

Atome moov

non vérifié

État du pool de lecteurs

N−1
vide
N
vide
N+1
vide

Le fast start est vérifié côté serveur sur le média lui-même, pas déclaré.

4
Paliers de transcodage

240p à 1080p

3
Lecteurs vivants au maximum

fenêtre N−1 · N · N+1

Le mécanisme

Comment ça marche.

Capture mobile

Flutter · H.264

API Laravel 12

upload · job en file

Transcodage HLS

4 paliers · moov en tête\n240p / 480p / 720p / 1080p

Webhook encodé

statut 4 → prêt

Meilisearch

réindexation du feed

CDN pull zone

playlist.m3u8

Pool 3 lecteurs

N−1 · N · N+1 · préchargement K-ahead · dispose au recyclage

Côté serveur — l'ingestion.

La vidéo remonte vers l'API, qui crée immédiatement l'asset chez le transcodeur et rend la main. Le transcodage produit un ladder de quatre paliers et une playlist maître ; l'atome moov est replacé en tête de chaque rendu MP4 pour que la lecture démarre avant la fin du téléchargement. Un webhook signale la fin de l'encodage, met à jour le statut et déclenche la réindexation. Rien ne bloque l'utilisateur : son clip est publié avant même d'être encodé.

Côté client — la fenêtre glissante.

Au plus trois lecteurs vivent en même temps : N−1 en pause, N en lecture, N+1 déjà initialisé et bufferisé. Au swipe, le lecteur N+1 est déjà prêt. Chaque recyclage appelle dispose() pour rendre le décodeur natif au système, sans quoi l'application sature après une trentaine de clips. Le préchargement anticipe K items, déduplique par URL et se restreint au Wi-Fi : en données mobiles, on tombe volontairement sur le palier 480p.

Les arbitrages

Ce que j'ai écarté, et pourquoi.

Un choix technique sans son coût n'est pas un choix, c'est une préférence.

01

Transcoder soi-même ou déléguer ?

Le problème

Encoder du HLS multi-paliers demande du CPU par rafales. Une VM dimensionnée pour le pic reste inutilisée le reste du temps ; dimensionnée pour la moyenne, elle prend des heures de retard à la moindre vague de publications.

Les options

FFmpeg sur une file de jobs maison · un service managé de transcodage · un encodage côté client avant l'upload.

Ce que j'ai choisi

Service managé, avec webhook de fin d'encodage et publication optimiste avant que l'encodage soit terminé.

Le coût

Une dépendance externe et un coût au gigaoctet. En contrepartie : aucun serveur d'encodage à exploiter, une distribution CDN incluse. La couche d'abstraction est isolée — basculer vers du FFmpeg auto-hébergé reste possible sans toucher au reste.

02

Combien de lecteurs garder en vie ?

Le problème

Plus on garde de lecteurs initialisés, plus le swipe est instantané — mais chaque lecteur mobilise un décodeur matériel, et un téléphone d'entrée de gamme en compte très peu. Au-delà, le décodage bascule en logiciel et l'appareil chauffe.

Les options

Un seul lecteur recréé à chaque swipe · une fenêtre de 3 · une fenêtre de 5 à 7 comme sur les feeds les plus riches.

Ce que j'ai choisi

Fenêtre de 3 (N−1, N, N+1), un seul volume actif, dispose() systématique au recyclage.

Le coût

Un swipe rapide de plus de deux positions perd l'avantage du préchargement. C'est un cas minoritaire ; l'alternative était de faire chauffer les appareils d'entrée de gamme, majoritaires sur le marché visé.

03

Précharger agressivement ou économiser les données ?

Le problème

Précharger cinq clips en avance rend le feed parfaitement fluide — et consomme le forfait d'un utilisateur qui n'en regardera que deux. Sur le marché ouest-africain, le forfait data est une contrainte de premier ordre.

Les options

Préchargement constant · aucun préchargement · préchargement conditionné au type de réseau.

Ce que j'ai choisi

Préchargement K-ahead réservé au Wi-Fi, bascule automatique sur le palier 480p en données mobiles, respect du signal save-data.

Le coût

L'expérience en 3G est mesurablement moins fluide qu'en Wi-Fi, et l'assumer était le bon calcul : un feed magnifique qui vide le forfait se fait désinstaller.

Preuves dans le code

Deux extraits réels.

lib/services/video_controller_pool.dart
/// Pool borné de controllers vidéo pour un feed type TikTok.
/// Streaming (démarrage rapide via faststart) + pré-init du
/// voisin (N+1 bufferisé en pause → swipe quasi-instantané).
/// Invariants :
///   - au plus 3 controllers vivants (N-1, N, N+1) ;
///   - un seul controller avec volume > 0 (l'actif) ;
///   - dispose() systématique au recyclage.
class VideoControllerPool {
  final PooledControllerFactory _factory;
  final Map<int, PooledVideoController> _controllers = {};
lib/services/video_prefetch_service.dart
/// URLs des prochains items vidéo après [active], dans la
/// fenêtre [aheadCount]. Choisit le 480p en basse bande passante.
List<String> selectUrls(int active, List<VerticalFeedItem> items,
    {bool lowBitrate = false}) {
  final out = <String>[];
  final end = active + _ahead;
  for (var i = active + 1; i <= end && i < items.length; i++) {
    final url = items[i].resolveVideoUrl(lowBitrate: lowBitrate);
    if (url != null && url.isNotEmpty) out.add(url);
  }

Code Mes Rêves

Alexis Kouakou