Sebuah situs Shopify klien punya satu video loop produk yang di-embed inline, cuma beberapa detik, tanpa suara, dimaksudkan berputar mulus di bawah gambar hero. Di mesin saya bagus. Di Android klien bagus. Lalu klien buka di iPhone-nya dan mengirim video layar: alih-alih memutar sendiri, video itu diam dengan kontrol play native muncul di tengahnya, dan seluruh halaman terasa berat saat scroll. Dia harus mengetuk play secara manual di video itu setiap kali.
Refleks pertama saya adalah menyalahkan file. Saya cek codec-nya, dan memang H.264, format yang paling universal. Saya pikir kalau sudah H.264 pasti aman di iOS. Ternyata tidak sesederhana itu. Setelah saya buka DevTools yang tersambung ke iPhone, dua masalah terpisah muncul, dan keduanya menumpuk jadi satu gejala yang sama.
Kenapa iOS menolak
Masalah pertama ada di profil encoding. File saya di-encode sebagai H.264 High profile pada 1080p sekitar 3.37 Mbps. Di desktop dan Android itu tidak masalah. Tapi Safari di iOS sering menolak High profile untuk autoplay inline; WebKit lebih suka Main atau Baseline. Jadi meskipun ekstensinya .mp4 dan codec-nya H.264, decoder iOS memperlakukannya sebagai kandidat autoplay yang tidak layak dan menolak memutarnya otomatis. Bitrate dan resolusi 1080p itu juga yang bikin scroll terasa berat di perangkat.
Masalah kedua lebih halus dan menyangkut cara autoplay dipicu. Saya mengandalkan atribut HTML autoplay. Masalahnya, atribut itu mencoba memutar pada saat parse, sebelum ada gesture user apa pun. iOS memblokir autoplay yang datang tanpa konteks gesture, dan ketika pemblokiran itu terjadi, Safari diam-diam jatuh ke menampilkan kontrol play native. Jadi bahkan seandainya profilnya benar, cara saya memicu playback tetap salah menurut aturan WebKit.
Begitu dua hal ini terpisah di kepala saya, gejalanya masuk akal. Android memutar karena lebih permisif soal profil maupun autoplay. iOS menolak di dua titik sekaligus, lalu menampilkan kontrol sebagai fallback, seolah videonya rusak padahal file-nya baik-baik saja.
Perbaikannya
Saya perbaiki dari dua sisi: file-nya dulu, lalu cara memicunya.
Untuk file, saya re-encode ke H.264 Main profile di 720p. Itu langsung menyelesaikan penolakan profil sekaligus meringankan beban decode yang bikin scroll berat:
ffmpeg -i input.mp4 -profile:v main -level 4.0 -crf 26 -vf scale=1280:-2 -an -movflags +faststart output.mp4-profile:v main menukar High jadi Main, scale=1280:-2 menurunkan ke 720p dengan tinggi genap otomatis, -an membuang audio track yang memang tidak dipakai, dan -movflags +faststart memindah metadata ke depan supaya playback bisa mulai sebelum file terunduh penuh.
Untuk markup, saya buang atribut autoplay dan menggantinya dengan kombinasi yang benar-benar diizinkan iOS untuk inline muted playback:
<video muted loop playsinline preload="metadata" data-lazy-video>
<source src="output.mp4" type="video/mp4" />
</video>Kunci di sini playsinline (supaya iOS tidak paksa fullscreen) plus muted dan loop, tanpa autoplay. Video muted diperlakukan lebih longgar oleh WebKit, tapi tetap tidak boleh dipicu dari parse time.
Yang mengendalikan playback adalah IntersectionObserver. Alih-alih memutar saat parse, saya panggil video.play() begitu elemen masuk viewport, karena tindakan scroll dihitung sebagai konteks gesture user yang cukup untuk iOS:
const io = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const video = entry.target;
video.muted = true;
video.play().catch(() => {
video.controls = true;
});
});
}, { threshold: 0.15, rootMargin: '0px 0px 200px 0px' });
document.querySelectorAll('[data-lazy-video]').forEach((v) => io.observe(v));Saya set video.muted = true lagi tepat sebelum play() untuk memastikan, lalu .catch() menangkap kasus di mana iOS tetap menolak. Kalau itu terjadi, saya set video.controls = true sebagai fallback yang anggun: user melihat tombol play alih-alih video mati yang misterius. rootMargin 200px memberi video sedikit waktu buffer sebelum benar-benar terlihat.
Pelajaran
H.264 tidak cukup sebagai jaminan; profilnya penting. iOS Safari pilih-pilih dan lebih suka Main atau Baseline daripada High untuk autoplay inline, jadi re-encode dengan -profile:v main dan turunkan ke 720p supaya decode ringan. Dan jangan mengandalkan atribut autoplay di iOS; atribut itu menembak sebelum ada gesture dan diam-diam diblokir. Picu playback dari IntersectionObserver supaya konteks scroll menghitungnya sebagai gesture, selalu set muted sebelum play(), dan pasang .catch() yang menyalakan controls sebagai fallback. Sejak itu, kalau ada yang bilang "jalan di Android tapi tidak di iPhone", saya tidak lagi menyalahkan file secara buta, saya cek dulu profil encoding dan cara autoplay-nya dipicu.
