Press ESC to close

Rahasia di Balik Susahnya Melacak Bandwidth Twitch: WASM, Worker, dan IVS

Kali ini saya ingin berbagi sedikit cerita di balik layar pengembangan salah satu fitur di ekstensi NetMeter Web yang sedang saya kerjakan. Khususnya, bagian yang hampir membuat saya putus asa: melacak konsumsi bandwidth video streaming, terutama di platform seperti Twitch, YouTube, dan kawan-kawannya.

Mungkin kalian berpikir,

  • “Helooo! Kan tinggal listen webRequest terus baca contentLength-nya”
  • “Ah, tinggal patch appendBuffer(), kan beres!”

Sayangnya, sekarang mayoritas website video streaming sudah menggunakan HLS (HTTP Live Streaming) atau DASH. Artinya, video akan dipecah menjadi potongan-potongan kecil (segment) yang dikirim dalam format tertentu sebelum akhirnya masuk ke buffer media player di browser.

Masalahnya, segmentasi HLS/DASH sering tidak menyertakan contentLength seperti response HTTP biasa. Karena itu kita harus menemukan “resep” yang ciamik dan pas untuk melakukan monitoring bandwidth di website berbasis video seperti YouTube, TikTok, Twitch, dan layanan streaming lainnya.

Twitch Menggunakan AWS IVS Player

Model streaming Twitch tidak berjalan di media player standar browser. Mereka memakai library Amazon Interactive Video Service (IVS) yang menjadi inti dari player Twitch.

Awalnya, saya mengira IVS tetap bisa di-patch dengan appendBuffer(), sebagaimana teknik yang biasa berhasil di platform lain seperti YouTube atau TikTok. Contohnya seperti patch sederhana berikut:

const originalAppendBuffer = SourceBuffer.prototype.appendBuffer;

SourceBuffer.prototype.appendBuffer = function(this: SourceBuffer, chunk: BufferSource): void {
  try {
    if (chunk && (chunk as ArrayBuffer).byteLength > 0) {
      // Send the byte length to the content script (isolated world)
      console.log("Buffer length found:", (chunk as ArrayBuffer).byteLength);
    }
  } catch (e) {}

  return originalAppendBuffer.apply(this, arguments as any);
};

Tapi ternyata script tersebut tidak berfungsi sama sekali di Twitch, dan uniknya hanya gagal di Chrome dan Brave. Browser lain seperti Firefox dan Microsoft Edge dapat melakukan patch bandwidth Twitch dengan normal. Kepala langsung cenat-cenut! πŸ˜‚

Setelah menelusuri beberapa source .js, saya menemukan file player-core-variant-xxxxx.js milik Twitch yang berisi perlakuan khusus untuk browser dengan teknologi terbaru. Dan saat ini, browser yang masuk kategori itu adalah Chromium dan turunannya: Chrome, Brave dan Edge.

Yang menarik, Edge meski berbasis Chromium tetap diperlakukan berbeda, sehingga patch masih bisa berjalan normal di sana. Benar-benar membuat kepala cenat-cenut lagi! πŸ˜‚

Media Player Berbasis WASM

Setelah tracing berjam-jam (dan berdiskusi dengan hampir semua AI yang ada πŸ˜†), akhirnya saya menemukan kelanjutannya: IVS Player milik Twitch ternyata juga menggunakan WebAssembly (WASM) untuk memproses dan men-deliver data video ke player.

AWS IVS Wasm Worker di Twitch

Jadi intinya, IVS Player dirancang untuk latensi sangat rendah, sehingga ia memanfaatkan teknologi browser paling baru: Web Worker dan WebAssembly.

Nah, di sinilah muncul masalah baru. Untuk membaca bandwidth dari segment HLS/DASH yang difetch oleh worker tersebut, kita tidak bisa lagi memakai teknik patch appendBuffer(). Kita harus melakukan patching Web Worker itu sendiri. Kedengarannya ngeri? Betul. πŸ˜…

Secara teknis, saya harus menginjeksi kode ke dalam Worker (yang dimiliki Twitch). Satu-satunya cara adalah mem-patch konstruktor window.Worker() saat worker dibuat.

Dan tentu saja, patch ini tidak hanya bermanfaat untuk Twitch saja, cara ini bisa dipakai untuk website apa pun yang memanfaatkan WASM untuk melakukan fetch internal. Karena pada akhirnya, tugas kita adalah monitoring bandwidth, bukan khusus Twitch. 🫢

Sebuah Patch Di Dalam Patch 😬

Sebenarnya, melakukan patch pada konstruktor Worker sebenarnya tidak terlalu rumit. Kurang lebih seperti berikut:

const OriginalWorker = window.Worker;

class PatchedWorker extends OriginalWorker {
  constructor(scriptURL: string | URL, options?: WorkerOptions) {
    const urlString = String(scriptURL);
      
      // Skrip patch utama
      const innerPatchScript = `importScripts('${urlString}');`;
      const blob = new Blob([innerPatchScript], { type: 'text/javascript' });
      const blobUrl = URL.createObjectURL(blob);

      // Harus kita ubah ke blob
      super(blobUrl, options); 
      
      URL.revokeObjectURL(blobUrl);
  }
}

window.Worker = PatchedWorker; 

Namun untuk menangkap contentLength dari segment yang difetch oleh Worker, kita juga harus mem-patch self.fetch di dalam worker tersebut. Karena di sinilah segment video HLS/DASH dipanggil, bukan melalui window.fetch. Benar-benar membuat perut mules!

Segment-segment HLS yang di Call oleh WASM

Kurang lebih innerPatchScript-nya seperti ini:

const innerPatchScript = `
  const originalFetch = self.fetch;
  self.fetch = function(input, init) {
    return originalFetch.apply(this, arguments).then(response => {
      const clone = response.clone();
      clone.arrayBuffer().then(buffer => {
        console.log(buffer.byteLength); // INILAH YANG KITA CARI BRO!!!
      }).catch(e => ());
      return response;
    });
  };
  importScripts('${urlString}');
`;

Jadi intinya:

  1. Patch konstruktor window.Worker().
  2. Di dalamnya, lakukan patch juga pada self.fetch.

Sebuah patch yang di dalamnya ada patch lain. Wkwkwk.

Dan akhirnya… berhasil! Semua segment HLS yang melewati worker bisa dibaca arrayBuffer length-nya dan dikirim ke background service worker milik NetMeter Web untuk dihitung total bandwidth-nya.

Perjuangan penuh drama, tapi ilmu yang didapat sangat berharga.

Thanks, Twitch! for today’s unexpected tweak! πŸ‘

Muhammad K Huda

A non exhausted blogger person within fullstack engineer (spicy food), open source religion, self-taught driver and maybe you know or don't like it. Simply says, Hello from Me!

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *

Cek untuk notifikasi e-mail jika komentar dibalas.

This site uses Akismet to reduce spam. Learn how your comment data is processed.