SelixyWatch
Vidéos et musique regardées ensemble, à la même seconde
SelixyWatch permet de regarder des vidéos et d’écouter de la musique à plusieurs, à la même seconde, chacun chez soi. Le dépôt commence le 14 octobre 2026 avec une écoute musicale synchronisée, puis s’est élargi en une quinzaine de jours : playlists, comptes, partage, vidéo, chat. Il est en cours, et plusieurs choix décrits plus bas sont encore à l’essai.
Playlists et rooms
Le site a deux pages. Les playlists rangent des médias dans des dossiers imbriqués, avec un rangement par glisser-déposer. Un même média peut être dans plusieurs playlists, et il est supprimé quand il n’est plus dans aucune. On y importe des fichiers, on cherche sur YouTube, ou on colle un lien YouTube ou Spotify : le serveur lit les titres et les durées, et retrouve chaque morceau Spotify sur YouTube en prenant la durée la plus proche.
La room joue la playlist dans laquelle on est entré. Tous ceux qui y sont voient et entendent la même chose. Elle a un lecteur façon YouTube avec ses raccourcis clavier, un mode cinéma, une file « À suivre » avec boucle et aléatoire partagés par toute la room, un chat et des réactions emoji qui flottent sur la vidéo avec l’avatar de celui qui réagit. Le son reste local : couper le sien ne coupe celui de personne.
Comment la synchro marche
Le serveur garde l’état de chaque room (média, position, lecture ou pause) et le diffuse en SSE. Chaque navigateur mesure son écart avec l’horloge du serveur, calcule la position cible et corrige toute dérive de plus de 0,4 s, vérifiée chaque seconde. La position affichée vient toujours de l’état du serveur, jamais du lecteur local.
À la fin d’un média, le premier lecteur qui le signale fait passer au suivant ; sinon le serveur enchaîne seul une seconde plus tard. Les navigateurs exigent un geste avant de jouer du son, donc un bouton « Activer le son » apparaît quand il le faut.
J’ai pris SSE plutôt que des WebSockets parce que le flux ne va que dans un sens : le serveur diffuse, et les commandes (lecture, pause, saut) repartent par de simples requêtes POST.
YouTube et ses limites
Les vidéos YouTube sont jouées par le lecteur intégré de YouTube, synchronisé comme le reste. Rien n’est téléchargé. Ça impose des limites que le site ne peut pas lever, parce que le lecteur vit dans un cadre d’un autre site. Il ne peut pas retirer les pubs (chacun garde son bloqueur), mais quelqu’un qui en subit une est remis à la bonne seconde quand elle finit. YouTube affiche aussi ses étiquettes pendant environ cinq secondes après chaque démarrage ou saut, sans réglage possible. Une règle de filtre pour bloqueur de contenu les retire, et le site en publie la liste.
J’avais d’abord essayé de ne plus dépendre du lecteur de YouTube. Un petit service dans le pod résout les pistes avec yt-dlp et les relaie, et le navigateur les assemble avec les Media Source Extensions. Ça marche techniquement, mais YouTube refuse les requêtes venant de l’IP du VPS (« Sign in to confirm you’re not a bot »), même avec des cookies d’un compte secondaire. Le lecteur est donc revenu à l’iframe. Le service et son code restent dans le dépôt, non branchés, en attendant une IP de sortie acceptée.
Chat vocal et vidéo
La room a aussi un chat vocal et vidéo en WebRTC, avec un menu profil pour le micro, la caméra et la sourdine, et une rangée des comptes présents. Le serveur ne fait que relayer les signaux d’établissement de la connexion. Pour que le micro ne renvoie pas le son de la vidéo, la référence d’écho passe par le partage de l’onglet, ce qui retire aussi le son de l’iframe YouTube. Je n’ai pas de recul sur la tenue de ce vocal à plus de quelques personnes.
Comptes et partage
Les comptes se créent avec un pseudo et un mot de passe, ou en anonyme : le compte « Invité » est éphémère et supprimé à la déconnexion avec tout ce qui n’est lié qu’à lui. Les pseudos sont uniques sans tenir compte de la casse ni des sosies (« Intel » et « Іntel » en cyrillique sont le même). Les mots de passe sont hachés en scrypt, et seul le hachage du jeton de session est en base.
Un dossier ou une playlist se partage par lien d’invitation, avec le rôle d’éditeur. Le partage vaut pour tout le sous-arbre, et chacun peut ensuite ranger et renommer à sa façon sans toucher à la bibliothèque du propriétaire. Il y a une adresse e-mail à valider, une récupération de compte par courriel, et deux offres : l’envoi de fichiers est réservé aux comptes Pro, avec 2 Go par compte et 4 Go par média, et un compte administrateur n’a pas de limite.
Technique
L’interface est un site statique en modules JavaScript, sans compilation ni bibliothèque. Une coque garde les trois vues chargées (room, playlists, profil), chacune dans son cadre, si bien que la lecture continue pendant qu’on parcourt les playlists. Les vues se parlent par postMessage.
L’API est un petit serveur Python avec PostgreSQL, dans un pod Podman derrière nginx avec le service de flux. Seul l’état de lecture des rooms reste en mémoire, parce qu’il change chaque seconde et se reconstruit au redémarrage. Le déploiement se fait par un git push : un hook installe l’interface, applique les migrations et redémarre l’API. Une sauvegarde de la base tourne chaque jour.
Je m’impose des règles strictes : 200 lignes et cinq fonctions au plus par fichier, une responsabilité par fichier, aucun commentaire dans le code (le pourquoi va dans le README du module). Un script d’audit les mesure et bloque le commit. Le code hérité des premières versions, écrit avant ces règles, est listé écart par écart dans un fichier d’exceptions qui ne peut que diminuer.
Où j'en suis
La synchro, les playlists, les comptes, le partage et le chat fonctionnent. Je teste sur un banc local avec une base PostgreSQL jetable, et les tests couvrent surtout le serveur et quelques modules de l’interface. Le vocal et la vidéo sont récents et peu éprouvés, et la lecture YouTube dépend de ce que YouTube tolère. Il reste du code hérité à remettre aux règles.