Field 01 of 07

Video streaming

A vertical feed that starts in under a second, even on 3G.

Proven on :FlokDstok

Since December 2023

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

Real publications, public feed of beta.flokcity.com

Loading the Flok feed…

Live telemetry

720p
Time to first frame
Buffer level0 s
Active rendition
720p
Quality switches
0
Estimated bandwidth
Dropped frames
0
Bytes loaded
Active source
MP4

moov atom

not checked

Player pool state

N−1
empty
N
empty
N+1
empty

Fast start is verified server-side on the media itself, not merely claimed.

4
Paliers de transcodage

240p à 1080p

3
Lecteurs vivants au maximum

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

The mechanism

How it works.

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.

The trade-offs

What I ruled out, and why.

A technical choice without its cost is not a choice, it is a preference.

01

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

The problem

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.

The options

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

What I chose

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

The cost

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 ?

The problem

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.

The 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.

What I chose

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

The cost

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 ?

The problem

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.

The options

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

What I chose

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

The cost

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.

Proof in the code

Two real extracts.

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