WEB-API WEB-DEVELOPMENT MEDIA-PROCESSING PERFORMANCE BROWSER REAL-TIME JAVASCRIPT VIDEO AUDIO MODERN-WEB DEEP-DIVE

Menggali Web Codecs API: Membangun Aplikasi Media Interaktif dan Berkinerja Tinggi di Browser

⏱️ 13 menit baca
👨‍💻

Menggali Web Codecs API: Membangun Aplikasi Media Interaktif dan Berkinerja Tinggi di Browser

1. Pendahuluan

Pernahkah Anda membayangkan membangun aplikasi web yang bisa memanipulasi video secara real-time, melakukan efek audio kustom, atau bahkan membuat transcoder media langsung di browser? Dulu, hal ini mungkin terdengar seperti fiksi ilmiah atau membutuhkan plugin browser yang berat. Namun, dengan hadirnya Web Codecs API, semua itu kini menjadi kenyataan.

Sebagai web developer, kita sering berinteraksi dengan media melalui elemen <video> atau <audio> di HTML. Elemen-elemen ini sangat powerful untuk memutar media, tapi kontrol yang kita miliki sangat terbatas. Kita tidak bisa dengan mudah mengakses raw frame video, memodifikasi sample audio, atau mengendalikan proses encoding/decoding dengan granularitas tinggi. Inilah masalah yang dipecahkan oleh Web Codecs API.

💡 Web Codecs API adalah antarmuka tingkat rendah yang memberikan akses langsung ke encoder dan decoder media yang ada di browser. Ini berarti Anda bisa:

Bayangkan potensi yang terbuka: editor video berbasis web yang canggih, aplikasi streaming dengan efek kustom, game yang memproses audio secara dinamis, atau bahkan video conferencing dengan pre-processing video di sisi klien. Web Codecs API adalah kunci untuk membuka performa dan fleksibilitas baru dalam aplikasi media di web. Mari kita selami lebih dalam!

2. Memahami Konsep Dasar: Encoder, Decoder, dan Chunk

Untuk bekerja dengan Web Codecs API, ada beberapa konsep inti yang perlu Anda pahami:

a. Codec

Codec adalah singkatan dari coder-decoder. Ini adalah algoritma yang digunakan untuk mengompresi dan mendekompresi data media. Contoh codec video populer adalah H.264, VP8, VP9, dan AV1. Untuk audio, ada AAC, Opus, dan Vorbis. Web Codecs API memungkinkan kita berinteraksi dengan codec ini secara programatik.

b. VideoDecoder dan AudioDecoder

Ini adalah kelas-kelas utama untuk mendekode media.

c. VideoEncoder dan AudioEncoder

Ini adalah kelas-kelas untuk mengenkode media.

d. EncodedVideoChunk dan EncodedAudioChunk

Ini adalah objek yang merepresentasikan data media yang sudah terkompresi (hasil encoding atau input untuk decoding). Mereka berisi byte data dan metadata seperti waktu presentasi (timestamp) dan jenis chunk (key frame atau delta frame).

e. VideoFrame dan AudioData

Ini adalah representasi data media yang tidak terkompresi.

f. Konfigurasi

Sebelum menggunakan encoder atau decoder, Anda harus mengonfigurasinya dengan parameter yang sesuai, seperti codec, lebar/tinggi (untuk video), sample rate (untuk audio), dan bitrate. Browser akan memeriksa apakah konfigurasi tersebut didukung oleh perangkat keras atau perangkat lunak.

// Contoh konfigurasi VideoDecoder
const videoDecoder = new VideoDecoder({
  output: frame => {
    // Lakukan sesuatu dengan VideoFrame yang sudah didekode
    console.log("Video Frame Didekode:", frame);
    frame.close(); // Penting untuk melepaskan memori
  },
  error: error => console.error("VideoDecoder Error:", error)
});

const config = {
  codec: "vp8", // Contoh codec VP8
  codedWidth: 640,
  codedHeight: 480,
  // ... konfigurasi lain
};

videoDecoder.configure(config);

// Contoh penggunaan
// const encodedChunk = new EncodedVideoChunk({ ... });
// videoDecoder.decode(encodedChunk);

⚠️ Penting: Objek VideoFrame dan AudioData menggunakan memori yang cukup besar. Setelah selesai menggunakannya, selalu panggil .close() untuk melepaskan sumber daya memori. Jika tidak, Anda berisiko mengalami memory leak!

3. Use Case Praktis 1: Manipulasi Video Real-time di Browser

Mari kita bayangkan skenario di mana kita ingin mengambil stream video dari kamera pengguna, menerapkan efek grayscale secara real-time, dan menampilkannya di elemen <canvas>.

Langkah-langkah:

  1. Dapatkan stream dari kamera: Gunakan navigator.mediaDevices.getUserMedia().
  2. Buat VideoDecoder: Untuk mendekode stream kamera menjadi VideoFrame.
  3. Buat VideoEncoder: Untuk mengenkode kembali VideoFrame yang sudah dimanipulasi.
  4. Proses frame: Ambil setiap VideoFrame, manipulasi pixel (misal, grayscale), lalu render ke <canvas>.
  5. Tampilkan di <canvas>: Gunakan CanvasRenderingContext2D.drawImage(VideoFrame).
<video id="webcamVideo" autoplay muted></video>
<canvas id="outputCanvas"></canvas>
const webcamVideo = document.getElementById('webcamVideo');
const outputCanvas = document.getElementById('outputCanvas');
const ctx = outputCanvas.getContext('2d');

let videoProcessor; // Untuk mengelola decoder dan encoder

async function startVideoProcessing() {
  const stream = await navigator.mediaDevices.getUserMedia({ video: true });
  webcamVideo.srcObject = stream;

  await webcamVideo.play();

  outputCanvas.width = webcamVideo.videoWidth;
  outputCanvas.height = webcamVideo.videoHeight;

  // Mendapatkan track video dari stream
  const videoTrack = stream.getVideoTracks()[0];
  const processor = new MediaStreamTrackProcessor(videoTrack);
  const generator = new MediaStreamTrackGenerator({ kind: 'video' });

  const writable = generator.writable;
  const reader = processor.readable.getReader();
  const writer = writable.getWriter();

  let videoDecoder = new VideoDecoder({
    output: async frame => {
      // ✅ Frame berhasil didekode
      // Lakukan manipulasi: Grayscale
      const bitmap = await createImageBitmap(frame);
      ctx.drawImage(bitmap, 0, 0, outputCanvas.width, outputCanvas.height);

      const imageData = ctx.getImageData(0, 0, outputCanvas.width, outputCanvas.height);
      const data = imageData.data;
      for (let i = 0; i < data.length; i += 4) {
        const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
        data[i] = avg;     // Red
        data[i + 1] = avg; // Green
        data[i + 2] = avg; // Blue
      }
      ctx.putImageData(imageData, 0, 0);

      // Jika ingin mengenkode kembali, buat VideoFrame baru dari canvas
      // const newFrame = new VideoFrame(outputCanvas, { timestamp: frame.timestamp });
      // videoEncoder.encode(newFrame);
      // newFrame.close(); // Penting!

      frame.close(); // Penting untuk melepaskan memori
      bitmap.close(); // Penting!
    },
    error: e => console.error('VideoDecoder error:', e),
  });

  // Konfigurasi decoder (mungkin perlu disesuaikan dengan codec yang digunakan kamera)
  // Untuk stream dari getUserMedia, umumnya tidak perlu konfigurasi codec eksplisit
  // karena browser akan menangani decoding secara internal.
  // Namun, jika Anda mendapatkan EncodedVideoChunk dari sumber lain, Anda perlu mengonfigurasinya.
  // Untuk contoh ini, kita akan langsung menggunakan VideoFrame dari MediaStreamTrackProcessor
  // dan hanya mengandalkan output callback.

  // Memulai loop membaca chunk dari stream
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    // Di sini 'value' adalah VideoFrame dari MediaStreamTrackProcessor
    // Kita langsung memprosesnya di callback output decoder
    // Untuk Web Codecs API, kita akan mendapatkan EncodedVideoChunk, bukan VideoFrame,
    // dari MediaStreamTrackProcessor jika kita ingin mendekode secara manual.
    // Tapi untuk demonstrasi langsung manipulasi frame, kita bisa melewatkan tahap encoding/decoding
    // jika hanya ingin menampilkan ke canvas.
    // Untuk benar-benar menggunakan VideoDecoder, Anda butuh EncodedVideoChunk.
    // Contoh ini lebih ke manipulasi MediaStreamTrackProcessor & Canvas.

    // Untuk Web Codecs API yang sebenarnya, kita perlu EncodedVideoChunk.
    // Untuk mendapatkan EncodedVideoChunk dari MediaStreamTrackProcessor,
    // kita perlu codec yang didukung dan konfigurasi. Ini bisa agak kompleks.
    // Sebagai alternatif, kita bisa menggunakan requestVideoFrameCallback() untuk mendapatkan VideoFrame.
    // Contoh sederhana manipulasi frame tanpa decode/encode eksplisit:
    // requestVideoFrameCallback(webcamVideo, processFrame);
  }
}

// Alternatif yang lebih sederhana untuk mendapatkan VideoFrame dari elemen video
function processFrame(now, metadata) {
  const frame = new VideoFrame(webcamVideo, { timestamp: webcamVideo.currentTime * 1000000 });
  // Lakukan manipulasi di sini, lalu gambar ke canvas
  ctx.drawImage(frame, 0, 0, outputCanvas.width, outputCanvas.height);

  const imageData = ctx.getImageData(0, 0, outputCanvas.width, outputCanvas.height);
  const data = imageData.data;
  for (let i = 0; i < data.length; i += 4) {
    const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
    data[i] = avg;     // Red
    data[i + 1] = avg; // Green
    data[i + 2] = avg; // Blue
  }
  ctx.putImageData(imageData, 0, 0);

  frame.close(); // Penting!
  webcamVideo.requestVideoFrameCallback(processFrame);
}

async function startSimpleVideoProcessing() {
  const stream = await navigator.mediaDevices.getUserMedia({ video: true });
  webcamVideo.srcObject = stream;
  await webcamVideo.play();

  outputCanvas.width = webcamVideo.videoWidth;
  outputCanvas.height = webcamVideo.videoHeight;

  webcamVideo.requestVideoFrameCallback(processFrame);
}


// Jalankan fungsi
startSimpleVideoProcessing().catch(console.error);

Catatan: Contoh di atas menggunakan requestVideoFrameCallback dan new VideoFrame(videoElement) yang lebih sederhana untuk mendapatkan frame dari elemen <video>. Untuk benar-benar menggunakan VideoDecoder dan VideoEncoder, Anda perlu mengambil EncodedVideoChunk dari sumber (misalnya, file video, stream jaringan, atau MediaStreamTrackProcessor yang dikonfigurasi untuk encoding). Proses ini sedikit lebih kompleks karena melibatkan parsing header codec (misalnya, AVCC untuk H.264) untuk mengonfigurasi decoder dengan benar. Namun, intinya adalah Anda akan mendapatkan VideoFrame yang bisa dimanipulasi.

🎯 Use case sebenarnya dengan VideoDecoder: Anda memiliki file video (misal, mp4) dalam bentuk ArrayBuffer. Anda ingin mendekode chunk demi chunk, memanipulasi setiap frame, lalu menampilkannya. Ini membutuhkan parser format kontainer (misal, MP4 parser) untuk mengekstrak EncodedVideoChunk dan data konfigurasi codec yang benar.

4. Use Case Praktis 2: Pemrosesan Audio Lanjutan

Bagaimana jika kita ingin membuat visualisasi audio spectrum secara real-time dari input mikrofon, atau menerapkan gain kustom?

Langkah-langkah:

  1. Dapatkan stream dari mikrofon: Gunakan navigator.mediaDevices.getUserMedia().
  2. Buat AudioDecoder: Untuk mendekode stream audio menjadi AudioData.
  3. Proses sample audio: Ambil setiap AudioData, lakukan analisis atau modifikasi.
  4. Tampilkan visualisasi: Gunakan <canvas> untuk menggambar spectrum.
  5. Putar kembali (opsional): Jika dimanipulasi, bisa mengenkode kembali dengan AudioEncoder dan memutar melalui AudioContext.
// Membutuhkan MediaStreamTrackProcessor dan AudioContext
async function startAudioProcessing() {
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  const audioTrack = stream.getAudioTracks()[0];
  const processor = new MediaStreamTrackProcessor(audioTrack);
  const reader = processor.readable.getReader();

  const audioCtx = new AudioContext();
  const source = audioCtx.createBufferSource();

  let audioDecoder = new AudioDecoder({
    output: (audioData) => {
      // ✅ AudioData berhasil didekode
      console.log("Audio Data Didekode:", audioData);

      // Contoh: Dapatkan channel data
      const channelData = new Float32Array(audioData.numberOfFrames * audioData.numberOfChannels);
      audioData.copyTo(channelData, {
        frameCount: audioData.numberOfFrames,
        frameOffset: 0,
        planeIndex: 0 // Ambil channel pertama
      });

      // Lakukan analisis/manipulasi di sini
      // Misalnya, menghitung RMS (Root Mean Square) untuk volume
      let sumOfSquares = 0;
      for (let i = 0; i < channelData.length; i++) {
        sumOfSquares += channelData[i] * channelData[i];
      }
      const rms = Math.sqrt(sumOfSquares / channelData.length);
      console.log("RMS (Volume):", rms);

      // Untuk visualisasi spectrum, Anda akan menggunakan AnalyserNode dari Web Audio API
      // dan mengumpankan AudioData ini ke sana.

      audioData.close(); // Penting!
    },
    error: (e) => console.error('AudioDecoder error:', e),
  });

  // Untuk stream dari getUserMedia, kita perlu mengetahui codec dan sample rate.
  // Ini bisa didapat dari MediaStreamTrack, atau diinfer dari EncodedAudioChunk pertama.
  // Biasanya, browser akan menggunakan Opus atau AAC.
  // Untuk contoh ini, kita asumsikan Opus.
  const config = {
    codec: 'opus',
    sampleRate: audioCtx.sampleRate, // Sesuaikan dengan AudioContext
    numberOfChannels: 1, // Atau 2 jika stereo
  };

  try {
    const support = await AudioDecoder.isConfigSupported(config);
    if (!support.supported) {
      console.error('AudioDecoder config not supported:', support.reason);
      return;
    }
    audioDecoder.configure(config);
  } catch (e) {
    console.error('Failed to configure AudioDecoder:', e);
    return;
  }


  // Memulai loop membaca chunk dari stream
  // Ini adalah bagian yang kompleks, karena MediaStreamTrackProcessor
  // untuk audio tidak langsung menghasilkan EncodedAudioChunk.
  // Anda perlu mengonfigurasi AudioEncoder untuk mengubah AudioData dari mikrofon
  // menjadi EncodedAudioChunk, lalu mendekodenya lagi.
  // Atau, gunakan AudioContext untuk pemrosesan audio mentah.

  // 📌 Alternatif: Web Audio API lebih cocok untuk pemrosesan audio real-time
  // dari mikrofon jika Anda tidak perlu encoding/decoding spesifik.
  // Web Codecs API lebih ke jika Anda ingin memanipulasi *encoded* stream
  // atau membuat *custom codec* pipeline.
}

// startAudioProcessing().catch(console.error);

📌 Catatan: Untuk audio dari mikrofon, Web Audio API dengan AnalyserNode atau AudioWorklet mungkin merupakan pilihan yang lebih langsung dan efisien untuk pemrosesan raw sample audio real-time. Web Codecs API lebih bersinar ketika Anda berurusan dengan encoded audio (misalnya dari file atau stream jaringan) dan perlu mendekode, memanipulasi, lalu mengenkode ulang.

5. Performa dan Batasan

Web Codecs API dirancang untuk performa tinggi dengan memanfaatkan akselerasi hardware di browser sedapat mungkin. Namun, ada beberapa hal yang perlu diperhatikan:

Best Practice: Selalu lakukan validasi konfigurasi dan penanganan error yang robust.

// Contoh memeriksa dukungan konfigurasi
async function checkCodecSupport() {
  const config = {
    codec: "av1",
    width: 1280,
    height: 720,
    bitrate: 10_000_000, // 10 Mbps
    framerate: 30
  };

  const support = await VideoEncoder.isConfigSupported(config);
  if (support.supported) {
    console.log("Konfigurasi AV1 didukung!");
  } else {
    console.warn("Konfigurasi AV1 tidak didukung:", support.reason);
  }
}
checkCodecSupport();

6. Integrasi dengan Teknologi Web Lain

Web Codecs API tidak bekerja sendiri. Kekuatannya akan maksimal ketika diintegrasikan dengan API dan teknologi web lainnya: