La une.Ce que c'est
En chantier
La vidéothèque sans pub
Toutes mes vidéos longues au même endroit, sur un stockage qui m'appartient, sans publicité.
Elle roule sur une adresse temporaire en attendant le nom de domaine.

Composé.La pile techno
Sur quoi ça roule
Une façade statique sur Cloudflare, une petite boîte pour les comptes, et les fichiers dans mon propre compartiment.
Façade
- Astro7.3un catalogue pré-rendu
- Preact10.29l'îlot du lecteur
- Cloudflare Workersl'hébergement
Boîte
- Bun + Hono4.13l'API
- Better Auth1.7les comptes
- Polarl'abonnement
- Postgres17avec Drizzle 0.45, sur Railway
Médias
- Cloudflare R2les fichiers, sans frais de sortie
- Trigger.dev4.5les formats et les aperçus avec ffmpeg
- Payloadune ligne par vidéo
- <video>aucun lecteur tiers, aucun DRM
Sous presse.Comment ça marche
5 étapes, dans l'ordre
D'un seul téléversement à un nombre de vues honnête.
Téléverser une fois
L'original monte par CCC, qui écrit la fiche CMS de la vidéo.
Transcoder en arrière-plan
Une tâche en arrière-plan découpe les formats et un aperçu au survol avec ffmpeg, directement dans R2.
Rebâtir le catalogue
Un envoi rebâtit chaque page du catalogue en HTML statique en environ 40 secondes.

Jouer par un lien signé
Le lecteur demande une URL signée de courte durée. Les vidéos réservées aux membres vérifient d'abord l'abonnement.

Compter honnêtement
Une vue compte après 3 secondes de vraie lecture, une fois par spectateur par jour.
L'épreuve.Le code
Ce qui compte comme une vue
// A VIEW, as the watch page reports it: the viewer pressed play and has watched at least// VIEW_SEC seconds of the video. The box counts it once per viewer per 24 hours and keeps the// rules (box/src/viewing.ts); this file only measures.export const VIEW_SEC = 3/** * Seconds actually played on this page, from `player.played` — the stretches of the video the * viewer watched. A seek adds nothing and a stretch replayed counts once, so no timer and no * per-second event is needed to know it: it is read at the moments the page already saves. */export function playedSeconds(ranges: Pick<TimeRanges, 'length' | 'start' | 'end'>): number { let sum = 0 for (let i = 0; i < ranges.length; i++) sum += ranges.end(i) - ranges.start(i) return sum}Un saut n'ajoute rien et une reprise compte une seule fois : pas de minuterie, pas d'écriture à chaque seconde.
La bobine.La démo
Le voir tourner
Corrections.Ce que ça règle
Les problèmes que ça règle
La plateforme garde les fichiers et l'auditoire.Les fichiers sont dans mon propre compartiment, et la liste est à moi.
De la pub sur chaque vidéo, et moins de portée quand je renvoie vers mon propre travail.Pas de pub. Renvoyer vers mon travail ne coûte rien.
Des factures de bande passante qui grossissent à chaque vue.R2 ne facture pas la sortie, et le catalogue est statique.
Des nombres de vues que personne ne peut vérifier.Une vue, c'est 3 secondes, une fois par jour, et la règle est testée.
Le mot de l'éditeur.Pourquoi je l'ai bâti
Pourquoi ça existe
Posséder les données. Tous les fournisseurs d'hébergement vidéo étaient exclus dès le départ.
Je veux faire connaître mon propre travail sans qu'une plateforme me punisse pour ça. Chaque vidéo est donc publiée ici d'abord, et les copies ailleurs renvoient vers la version sans pub.

Face au présentoir.Mieux que la moyenne
Face aux outils habituels
Face à Vimeo OTT, Uscreen et la vidéo Patreon. Leur côté a été lu sur les pages de chaque fournisseur le 2026-09-27.
| Quoi | La moyenne du marché | Cette appli |
|---|---|---|
| Où vivent les fichiers | Sur les serveurs du fournisseur. | Dans mon propre compartiment R2.page du fournisseur |
| Le coût quand l'auditoire grandit | Des frais par abonné payant, ou une part de chaque vente. | Pas de frais par abonné ni de part; R2 ne facture pas la sortie.page du fournisseur |
| Ce qui compte comme une vue | Tout démarrage de la lecture (Vimeo). | 3 secondes de vraie lecture, une fois par spectateur par jour, testé.page du fournisseur |
| Vie privée | Des témoins d'analyse du lecteur activés par défaut (Vimeo). | Les adresses IP des spectateurs non connectés restent en mémoire seulement.page du fournisseur |
L'achevé d'imprimer.Le temps que ça a pris
64 commits en 11 jours
- commits sur le site
- 64
- jours avec un commit
- 10
- jours, du premier au dernier commit
- 11
- Premier commit
- Dernier commit du site
- Plus 25 commits du côté de l'ingestion et du transcodage
Mesuré : git log du dépôt du site, 2026-09-27.
Colophon.Sa place au registre
Encore en chantier. Rien de livré n'est revendiqué pour ce projet.
← Retour au registre