2 points par GN⁺ 2025-06-21 | Aucun commentaire pour le moment. | Partager sur WhatsApp
  • YouTube teste actuellement en A/B une méthode qui inflige aux utilisateurs de bloqueurs de publicité un délai ressemblant à un faux buffering avant le début de la vidéo, au lieu d’afficher une publicité
  • Ce délai est implémenté comme un backoff du streaming SABR, InnerTube répondant à la première requête de contenu /videoplayback qu’il faut attendre l’équivalent de 80 % de la durée de la publicité
  • Les utilisateurs qui ne bloquent pas les publicités perçoivent à peine ce même backoff, car le chargement du contenu progresse pendant la lecture de la publicité
  • En définissant isInlinePlaybackNoAd sur true, InnerTube ne fournit pas de publicité, ce qui supprime aussi le backoff du flux GVS, et la liste de filtres par défaut de uBlock Origin inclut un contournement associé
  • Dans certaines expériences, YouTube perturbe les hooks de filtres avec un locker script qui verrouille des objets globaux comme JSON.stringify, et sur Chromium le contournement passe par un hook sur Object.assign

Endroit où apparaît le faux buffering

  • YouTube teste en A/B depuis quelques mois de nouvelles mesures anti-blocage de publicité
  • L’une des mesures observées est un faux buffering qui n’apparaît qu’au démarrage de la vidéo
    • Il ne se produit pas en cours de lecture
    • La durée du délai correspond à 80 % de la durée de la publicité qui aurait normalement été affichée
    • Même avec un bloqueur de publicité, cela fait encore gagner du temps par rapport au visionnage complet de la publicité
  • Exemples
    • S’il y a une publicité de 15 secondes, un backoff de 12 secondes se produit
    • Si une publicité non skippable de 6 secondes et une publicité de 15 secondes sont combinées, un backoff de 16,8 secondes se produit

Rôle d’InnerTube, GVS et SABR

  • InnerTube est l’API interne utilisée par le client web YouTube et les applications mobiles pour récupérer les informations et détails des vidéos
    • Les endpoints prennent la forme https://www.youtube.com/youtubei/
    • /youtubei/v1/player est appelé lorsqu’un utilisateur clique sur une vidéo afin d’obtenir les données d’URL et les URL de flux GVS
  • GVS est Google Video Services, qui fournit les flux vidéo pour YouTube, Google Drive et Google Photos
    • Pour diffuser une vidéo depuis GVS, il faut obtenir une URL /videoplayback via InnerTube ou via les API internes de Drive/Photos
    • Les URL GVS sont signées et expirent généralement après 6 heures, elles ne peuvent donc pas être fabriquées directement
    • Les FAI peuvent héberger des serveurs Google Global Cache dans leur propre infrastructure afin de servir le trafic YouTube sans le faire sortir de leur réseau
    • GGC ne sert que des vidéos YouTube publiques ou partiellement publiques ; les vidéos YouTube privées ainsi que les vidéos Drive/Photos sont toujours servies depuis les centres de données Google
  • À l’origine, le client web YouTube diffusait le contenu en indiquant la plage vidéo nécessaire via les paramètres de requête de l’URL GVS
  • YouTube utilise ensuite SABR
    • SABR signifie Server ABR, un protocole binaire propriétaire de YouTube pour le débit adaptatif côté serveur
    • Il est conçu pour mieux éviter le buffering que des formats ouverts comme MPEG-DASH
    • Au lieu d’envoyer immédiatement les données vidéo et audio au client, le serveur peut lui indiquer d’attendre un certain temps avant de réessayer via un backoff

Le backoff s’applique indépendamment de la détection d’un bloqueur de publicité

  • Le faux buffering se produit parce qu’InnerTube fournit, pour la première requête de contenu /videoplayback, un flux GVS contenant un backoff équivalant à 80 % de la durée de la publicité
    • Il s’applique au flux de la vidéo de contenu, pas au flux publicitaire
    • Les flux publicitaires et les flux de contenu restent séparés
    • Ce n’est pas une insertion publicitaire côté serveur, et les expérimentations de YouTube sur l’insertion publicitaire côté serveur se poursuivent séparément
  • La boîte de dialogue « Experiencing interruptions » peut probablement être déclenchée par le long backoff de GVS
  • Si l’utilisateur fait partie du test A/B, ce backoff se produit toujours, que YouTube le considère ou non comme utilisateur de bloqueur de publicité
    • Si les publicités ne sont pas bloquées, le client web charge la vidéo de contenu pendant la lecture de la publicité
    • La différence perçue par l’utilisateur est que le buffering du contenu ne commence qu’une fois 80 % de la publicité écoulés
  • Les affirmations en ligne selon lesquelles YouTube ferait exploser l’usage CPU des utilisateurs de bloqueurs de publicité au point d’endommager leur ordinateur sont fausses
    • YouTube n’utilise pas le CPU pendant l’attente de l’expiration du backoff
    • Même si l’attente était implémentée via une spinloop, utiliser un seul cœur à fond pendant moins de 30 secondes n’endommagerait pas un CPU

Éviter la diffusion de publicités avec isInlinePlaybackNoAd

  • Pour éviter le backoff, il faut empêcher que la publicité soit fournie dès le départ, au lieu d’attendre la fin d’une publicité non skippable
  • Si la propriété playbackContext.contentPlaybackContext.isInlinePlaybackNoAd de la requête player est définie sur true, InnerTube ne fournit pas de publicité
    • Si aucune publicité n’est fournie, le flux GVS n’inclut pas non plus de backoff
  • Il est possible de créer une règle de filtre qui ajoute "isInlinePlaybackNoAd":true lorsque le client web sérialise en chaîne le JSON de la requête serveur
www.youtube.com##+js(trusted-replace-outbound-text, JSON.stringify, 'contentPlaybackContext":{', 'contentPlaybackContext":{"isInlinePlaybackNoAd":true,', condition, 'contentPlaybackContext')
  • La propriété isInlinePlaybackNoAd est référencée dans le JavaScript frontend
  • Avec req2proto, il est possible d’extraire les définitions protobuf utilisées pour les appels /youtubei/v1/player et d’y trouver cette propriété
    • Le client web YouTube communique avec InnerTube en JSON, mais cette API JSON est générée à partir de définitions Protocol Buffers

Limite : fiabilité uniquement en warm navigation

  • Cette méthode ne fonctionne qu’en warm navigation, c’est-à-dire lorsqu’on navigue en interne après avoir déjà chargé l’application monopage YouTube
  • Lors d’un cold load, quand on arrive directement sur une page watch, le backend YouTube injecte directement la réponse player dans la page sous la forme de ytInitialPlayerResponse
    • Comme la requête player est alors construite côté backend, il est impossible d’y définir isInlinePlaybackNoAd
  • Une façon de contourner le cold load consiste à supprimer les données initiales pour forcer la création d’une requête player contrôlable
www.youtube.com##+js(set, ytInitialData, undefined) ! mauvaise idée, voir ci-dessous
www.youtube.com##+js(set, ytInitialPlayerResponse, undefined) ! mauvaise idée !
  • Cette approche a trop d’effets de bord pour être réellement exploitable
    • Elle casse complètement les livestreams
    • D’autres fonctions non testées peuvent aussi être cassées
    • Le lecteur vidéo clignote brièvement
    • Le chargement de la page devient plus lent

Locker script et contournement sur Chromium

  • Le filtre fonctionnait dans une certaine mesure, mais il arrivait parfois que uBlock Origin ne puisse pas hooker JSON.stringify
  • Dans certains tests A/B, YouTube ajoute un locker script tout en haut de la balise <head> du HTML frontend
    • Ce script utilise Object.defineProperty pour rendre non modifiables fetch, JSON, JSON.stringify, JSON.parse, Array, Array.prototype.push, Array.prototype.forEach, etc.
    • Il empêche ensuite du code ultérieur d’en modifier le comportement via Proxy
  • uBlock Origin ne peut proxifier JSON.stringify que s’il s’exécute avant le locker script
  • Sur Firefox, un filtre HTML permet de supprimer cette balise <script> du HTML source avant le parsing de la page
  • Chromium ne prend pas en charge les API d’extension nécessaires, ce qui empêche d’utiliser la même méthode
  • Le contournement actuel consiste à hooker Object.assign au lieu de JSON.stringify
    • Object.assign est lui aussi une fonction qui traite le corps de la requête avant fetch
    • Il ne s’agit pas d’une neutralisation du locker script lui-même, mais plutôt d’un détour
    • Comme le scriptlet uBO n’autorise pas le remplacement textuel des clés d’objet, la liste de filtres injecte du JavaScript
(() => {
    const e = {
        apply: (e, n, arguments) => {
            let t = Reflect.apply(e, n, arguments);
            return 3 === arguments.length && t?.body && "string" == typeof t.body && !t.body.includes(`"isInlinePlaybackNoAd":true`) && (t.body = t.body.replace(`"contentPlaybackContext":{`, `"contentPlaybackContext":{"isInlinePlaybackNoAd":true,`)), t
        }
    };
    window.Object.assign = new Proxy(window.Object.assign, e)
})();
  • Les mainteneurs de uAssets ont aidé à ce travail de contournement

Références

Aucun commentaire pour le moment.

Aucun commentaire pour le moment.