2 pontos por GN⁺ 2025-06-21 | Ainda não há comentários. | Compartilhar no WhatsApp
  • O YouTube está fazendo testes A/B de uma abordagem que impõe a usuários com bloqueador de anúncios um atraso antes do início do vídeo que parece buffering falso, em vez de exibir anúncios
  • O atraso é implementado como backoff no streaming SABR, e o InnerTube responde à primeira solicitação de conteúdo /videoplayback instruindo a aguardar 80% da duração do anúncio
  • Usuários que não bloqueiam anúncios carregam o conteúdo enquanto o anúncio é reproduzido, então quase não percebem o atraso mesmo com o mesmo backoff
  • Ao enviar isInlinePlaybackNoAd como true, o InnerTube não fornece anúncios, e o backoff do stream GVS também desaparece; a lista de filtros padrão do uBlock Origin inclui uma evasão relacionada
  • Em alguns experimentos, o YouTube atrapalha hooks de filtros com um locker script que bloqueia objetos globais como JSON.stringify; no Chromium, a evasão é feita com um hook em Object.assign

Onde surge o buffering falso

  • Nos últimos meses, o YouTube vem fazendo testes A/B de novas medidas anti-bloqueio de anúncios
  • Uma das medidas observadas é um buffering falso que aparece apenas no início do vídeo
    • Não ocorre no meio do vídeo
    • O tempo de atraso é 80% da duração do anúncio que teria sido visto originalmente
    • Mesmo usando um bloqueador de anúncios, ainda se economiza tempo em comparação a assistir ao anúncio inteiro
  • Exemplos:
    • Se houver um anúncio de 15 segundos, ocorre um backoff de 12 segundos
    • Se houver um anúncio não pulável de 6 segundos junto com um anúncio de 15 segundos, ocorre um backoff de 16,8 segundos

O papel do InnerTube, GVS e SABR

  • InnerTube é uma API interna usada pelo cliente web e pelos apps móveis do YouTube para obter informações e dados detalhados de vídeos
    • O endpoint tem o formato https://www.youtube.com/youtubei/
    • /youtubei/v1/player é chamado quando o usuário clica em um vídeo, para obter dados de URL e URLs de streams GVS
  • GVS é o Google Video Services, que fornece streams de vídeo do YouTube, Google Drive e Google Photos
    • Para fazer streaming de um vídeo pelo GVS, é necessário receber uma URL /videoplayback do InnerTube ou das APIs internas do Drive/Photos
    • As URLs GVS são assinadas e normalmente expiram em 6 horas, portanto não podem ser criadas diretamente
    • ISPs podem hospedar servidores Google Global Cache em sua própria infraestrutura, fornecendo tráfego do YouTube sem enviá-lo para fora da rede do ISP
    • O GGC fornece apenas vídeos públicos ou parcialmente públicos do YouTube; vídeos privados do YouTube e vídeos do Drive/Photos sempre são fornecidos pelos data centers do Google
  • Originalmente, o cliente web do YouTube fazia streaming especificando o intervalo necessário do vídeo como parâmetros de consulta da URL GVS
  • Depois, o YouTube passou a usar SABR
    • SABR é Server ABR, um protocolo binário próprio do YouTube para bitrate adaptativo do lado do servidor
    • Ele foi projetado para evitar buffering melhor do que formatos abertos como MPEG-DASH
    • Em vez de enviar dados de vídeo e áudio ao cliente, o servidor pode enviar um backoff, instruindo-o a aguardar por um certo tempo e tentar novamente

O backoff é aplicado independentemente da detecção de bloqueio de anúncios

  • O buffering falso ocorre quando o InnerTube fornece um stream GVS que inclui um backoff equivalente a 80% da duração do anúncio na primeira solicitação de conteúdo /videoplayback
    • O alvo não é o stream do anúncio, mas o stream de vídeo do conteúdo
    • Os streams de anúncio e de conteúdo continuam separados
    • Isso não é inserção de anúncios do lado do servidor; os experimentos de inserção de anúncios do lado do servidor do YouTube são conduzidos separadamente
  • A caixa de diálogo “Experiencing interruptions” pode ser disparada por causa do longo backoff do GVS
  • Se o usuário estiver no grupo do teste A/B, esse backoff ocorre sempre, independentemente de o YouTube julgar que ele usa bloqueador de anúncios
    • Se os anúncios não forem bloqueados, o cliente web carrega o vídeo de conteúdo enquanto o anúncio é reproduzido
    • A diferença percebida pelo usuário é que o buffering do vídeo de conteúdo só começa depois que 80% do anúncio já foi exibido
  • Alegações online de que o YouTube aumenta drasticamente o uso de CPU de usuários com bloqueador de anúncios e danifica computadores não são verdadeiras
    • O YouTube não usa CPU enquanto espera o backoff expirar
    • Mesmo que a espera fosse implementada como um spinloop, usar um único núcleo ao máximo por menos de 30 segundos não danifica a CPU

Como evitar a entrega de anúncios com isInlinePlaybackNoAd

  • Para evitar o backoff, em vez de esperar até o fim de um anúncio não pulável, é preciso impedir que o anúncio seja fornecido desde o início
  • Ao definir a propriedade playbackContext.contentPlaybackContext.isInlinePlaybackNoAd da solicitação do player como true, o InnerTube não fornece anúncios
    • Se anúncios não forem fornecidos, o stream GVS também não inclui backoff
  • É possível criar uma regra de filtro que adiciona "isInlinePlaybackNoAd":true quando o cliente web serializa o JSON para a solicitação ao servidor
www.youtube.com##+js(trusted-replace-outbound-text, JSON.stringify, 'contentPlaybackContext":{', 'contentPlaybackContext":{"isInlinePlaybackNoAd":true,', condition, 'contentPlaybackContext')
  • A propriedade isInlinePlaybackNoAd é referenciada no JavaScript do front-end
  • Usando req2proto, é possível extrair a definição protobuf usada nas chamadas a /youtubei/v1/player e encontrar essa propriedade
    • O cliente web do YouTube se comunica com o InnerTube via JSON, mas essa API JSON é gerada a partir de definições de Protocol Buffers

Limitação estável apenas em warm navigation

  • Esse método funciona apenas em warm navigation, quando a página de aplicação de página única do YouTube já foi carregada e a navegação ocorre por clique interno
  • Em um cold load, ao acessar diretamente a página watch, o backend do YouTube insere a resposta do player diretamente na página como ytInitialPlayerResponse
    • Como a solicitação do player é criada no backend, não é possível definir isInlinePlaybackNoAd
  • Uma forma de contornar o cold load é remover os dados iniciais para forçar a criação de uma solicitação do player controlável
www.youtube.com##+js(set, ytInitialData, undefined) ! bad idea, see below
www.youtube.com##+js(set, ytInitialPlayerResponse, undefined) ! bad idea!
  • Essa abordagem tem muitos efeitos colaterais, tornando-a difícil de usar
    • Quebra completamente transmissões ao vivo
    • Outros recursos não testados também podem quebrar
    • O player de vídeo pisca brevemente
    • O carregamento da página fica mais lento

Locker script e evasão no Chromium

  • Embora o filtro tenha funcionado em certa medida, às vezes o uBlock Origin não conseguia fazer hook em JSON.stringify
  • Em alguns testes A/B, o YouTube adiciona um locker script logo no início da tag <head> do HTML de front-end
    • Esse script usa Object.defineProperty para tornar fetch, JSON, JSON.stringify, JSON.parse, Array, Array.prototype.push, Array.prototype.forEach etc. non-writable
    • Isso impede que código posterior altere o comportamento com Proxy
  • O uBlock Origin só consegue aplicar proxy em JSON.stringify quando é executado antes do locker script
  • No Firefox, é possível remover essa tag de script do HTML de origem antes do parsing da página com um HTML filter
  • O Chromium não oferece suporte à API de extensão necessária, então não é possível tratá-lo da mesma forma
  • A evasão atual faz hook em Object.assign em vez de JSON.stringify
    • Object.assign também é uma função que processa o corpo da solicitação antes do fetch
    • Não é uma forma de neutralizar o locker script em si, mas sim uma evasão
    • Como o scriptlet do uBO não permite substituição de texto em chaves de objeto, a lista de filtros injeta 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)
})();
  • Os mantenedores do uAssets ajudaram nesse trabalho de evasão

Referências

Ainda não há comentários.

Ainda não há comentários.