Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. Media elementi React'da
- 2.1 Holat qayerda?
- 2.2 JSX atributlari va muted tuzog'i
- 2.3 useMediaState: holatni hodisalardan olish
- 3. Subtitr: <track> va VTT
- 3.1 VTT fayl koddan
- 3.2 React'da <track>
- 4. HLS: uzun video oqimi
- 4.1 Bitta katta fayl emas — bo'laklar
- 4.2 Kim o'ynaydi: brauzermi, hls.js mi
- 4.3 hls.js React effektida
- 5. YouTube va fasad naqshi
- 5.1 Iframe narxi
- 5.2 O'z fasadimiz
- 5.3 react-lite-youtube-embed
- 6. Media Session: qulf ekranidagi tugmalar
- 7. Tayyor pleyerlar va biz sinab ko'rgan narsa
- 7.1 Uchta kutubxona
- 7.2 Hajm
- 7.3 Nega vazifalar da media yo'q
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): bo'sh joylarni to'ldiring
- 2-mashq (o'rta): bosqichlar ro'yxati
- 3-mashq (qiyin): VTT'ni tekshiruvchi
- 4-mashq: tajriba qadami (ixtiyoriy)
- 10. Real ishda
- Xulosa
- Manbalar
Video va audio React'da: pleyer, subtitr, HLS oqim va YouTube embed
Qisqacha: React'da media elementining "haqiqiy" holati —
<video>/<audio>ning o'zida. Komponent uni ref orqali boshqaradi (play(),pause(),playbackRate) va holatni hodisalardan o'qiydi (play,pause,timeupdate). Subtitr —<track>va VTT fayl. Uzun video — HLS oqimi: Safari o'zi o'ynaydi, boshqalarda hls.js (+172.6 KB gzip, CSP'damedia-src blob:). YouTube — "fasad": avval rasm va tugma, iframe faqat bosilganda (+2.6 KB). Tayyor pleyerlar (react-player +579.1 KB, Vidstack +112.9 KB)vazifalarning Trusted Types siyosati ostida ochilmadi.
Bu darsda
- Media elementini ref bilan boshqarasiz va holatni hodisalardan oladigan hook yozasiz.
- React'dagi media atributlari tuzog'ini (
muted) va<track>subtitrini bilasiz, VTT faylni koddan yasaysiz. - HLS oqimi qanday ishlashini tushuntirasiz va hls.js'ni effekt ichida to'g'ri ulaysiz.
- YouTube'ni fasad bilan joylaysiz va Media Session API bilan telefon qulf ekranidagi tugmalarni ulaysiz.
- Tayyor pleyerlarni hajm va xavfsizlik siyosati natijasi bo'yicha solishtirasiz.
Oldin bilishingiz kerak: Audio va videoni JS'dan boshqarish, video va audio, Subtitrlar va media accessibility, useRef II: DOM elementlari, useEffect I.
1. Nega bu kerak?
Oldingi darsda rasmlar bilan ishladik. «Bahor» endi oshpazlik maktabi ochmoqchi: Jasur aka «Osh damlash» kabi retsept videolarini yozdi, Malika uchun esa audio-ma'ruzalar ("Bahor hikoyalari") ham bo'ladi. Talablar:
- o'z tugmalarimiz, tezlik (1.5× — ma'ruzani tezroq tinglash), subtitr;
- 40 daqiqalik video sekin internetda ham to'xtab qolmasin;
- telefonni qulflasa ham audio davom etsin va qulf ekranida «Pauza» ishlasin;
- ba'zi videolar YouTube'da — sahifani og'irlashtirmasdan qo'yish.
Audio va videoni JS'dan boshqarish darsida bularning ko'pini oddiy JavaScript bilan yozgansiz: play() Promise qaytaradi, timeupdate, autoplay siyosati, <track>. Bu dars ularni React komponentiga o'tkazadi va uchta yangi narsa qo'shadi: HLS oqimi, YouTube fasadi va Media Session.
Hayotiy o'xshatish: televizor va pult. Kanal, ovoz, "hozir nima ko'rsatilyapti" — televizorning o'zida. Pult faqat buyruq beradi va ekrandagi holatni ko'radi. React komponenti — pult. Media elementi — televizor.
2. Media elementi React'da
2.1 Holat qayerda?
React'da odatda "haqiqat state'da" bo'ladi: useState → JSX → ekran. Media bilan bunday emas. Video to'xtashining sabablari ko'p: foydalanuvchi bosdi, internet uzildi, video tugadi, telefonga qo'ng'iroq keldi, boshqa tab ovoz chiqardi. Ularning ko'pchiligi bizning kodimizsiz yuz beradi.
Shuning uchun qoida:
- Buyruq — ref orqali:
ref.current.play(),ref.current.pause(),ref.current.playbackRate = 1.5. - Holat — media hodisalaridan:
play,pause,timeupdate,ratechange,ended. Ular kelganda elementning o'zidan o'qib, state'ga yozamiz.
Bu — useEffect I darsidagi g'oya: media elementi — tashqi tizim, effekt unga obuna bo'ladi. "Bosganda setPlaying(true)" deb yozish xato: play() rad etilishi mumkin (autoplay siyosati), tugma esa "Pauza" deb yolg'on gapiradi. Audio va videoni JS'dan boshqarish darsidagi "Tugma holatini qo'lda yuritish" xatosi React'da ham xuddi shunday.
2.2 JSX atributlari va muted tuzog'i
Media atributlari JSX'da camelCase: autoPlay, playsInline, crossOrigin, srcLang (<track> da). Bitta tuzoq bor — buni tekshiruvchimiz ham, Chrome ham bir xil ko'rsatadi:
export default function App() {
return (
<video
muted
autoPlay
playsInline
controls
ref={(el) => el && console.log(el.outerHTML, el.muted)}
/>
);
}Konsolda:
<video autoplay="" playsinline="" controls=""></video> trueHTML tegida muted atributi yo'q, lekin xususiyat (el.muted) — true. React muted ni atribut qilib yozmaydi, faqat DOM xususiyatini o'rnatadi. Brauzerda ishlayotgan ilovada farqi yo'q: autoplay siyosati xususiyatni tekshiradi va ovozsiz video avtomatik boshlanadi. Lekin HTML'ni serverda tayyorlaydigan freymvorklarda (23-qismda — Next.js) bu muhim: React yuklanguncha brauzer muted siz tegni ko'radi va autoplay'ni to'sadi. Shuning uchun ba'zi loyihalarda autoplay video ref orqali, effektda ishga tushiriladi.
Tekshirib ko'ring: Nega
ref={(el) => el && …}dael &&bor?
Javob
Callback ref ikki marta chaqiriladi. Element DOM'ga qo'shilganda el — element, olib tashlanganda — null. null.outerHTML — TypeError. el && ikkinchi chaqiruvni o'tkazib yuboradi. useRef II darsida React 19 ning callback ref'dan tozalash funksiyasi qaytarish imkoniyatini ko'rgansiz — u ham shu muammoni hal qiladi.
2.3 useMediaState: holatni hodisalardan olish
Endi qoidani hook'ka aylantiramiz va kichik audio pleyer yasaymiz. Natija oynasida tashqi fayl yo'q — makeTone yordamchisi 2 soniyalik tovushni WAV Blob qilib yasaydi (13-qismdagi makeWav kabi; ichini tushunish shart emas). Bu blok faqat saytda ishlaydi: tekshiruvchimiz (jsdom) media o'ynatmaydi. Biz uni Chrome 155 da sinadik — «Ijro», bir soniyadan keyin «1.5×», keyin «Pauza»:
import { useEffect, useRef, useState } from "react";
// Qora quti: sinus tovush — WAV Blob (8 kHz, 8 bit)
function makeTone(seconds, freq) {
const rate = 8000;
const n = rate * seconds;
const v = new DataView(new ArrayBuffer(44 + n));
const text = (o, s) =>
[...s].forEach((c, i) => v.setUint8(o + i, c.charCodeAt(0)));
text(0, "RIFF");
v.setUint32(4, 36 + n, true);
text(8, "WAVEfmt ");
v.setUint32(16, 16, true);
v.setUint16(20, 1, true);
v.setUint16(22, 1, true);
v.setUint32(24, rate, true);
v.setUint32(28, rate, true);
v.setUint16(32, 1, true);
v.setUint16(34, 8, true);
text(36, "data");
v.setUint32(40, n, true);
for (let i = 0; i < n; i++) {
const wave = Math.sin((2 * Math.PI * freq * i) / rate);
v.setUint8(44 + i, 128 + Math.round(60 * wave));
}
return new Blob([v.buffer], { type: "audio/wav" });
}
const EVENTS = [
"loadedmetadata",
"play",
"pause",
"timeupdate",
"ratechange",
"ended",
];
function useMediaState(ref) {
const [state, setState] = useState({
paused: true,
time: 0,
duration: 0,
rate: 1,
});
useEffect(() => {
const media = ref.current;
const read = (event) => {
if (event.type !== "timeupdate") console.log(event.type);
setState({
paused: media.paused,
time: media.currentTime,
duration: media.duration || 0,
rate: media.playbackRate,
});
};
for (const e of EVENTS) media.addEventListener(e, read);
return () => {
for (const e of EVENTS) media.removeEventListener(e, read);
};
}, [ref]);
return state;
}
const clock = (s) => `0:${String(Math.floor(s)).padStart(2, "0")}`;
export default function App() {
const audio = useRef(null);
const [src] = useState(() => URL.createObjectURL(makeTone(2, 440)));
const { paused, time, duration, rate } = useMediaState(audio);
function toggle() {
if (paused) audio.current.play();
else audio.current.pause();
}
return (
<div>
<audio ref={audio} src={src} preload="metadata" />
<button onClick={toggle}>
{paused ? "Ijro" : "Pauza"}
</button>{" "}
<button
onClick={() => {
audio.current.playbackRate = rate === 1 ? 1.5 : 1;
}}
>
{rate === 1 ? "1.5×" : "1×"}
</button>
<p>
{clock(time)} / {clock(duration)}
</p>
</div>
);
}Chrome 155 da (saytdagi natija oynasi hujjati — o'sha React, headless) konsol:
loadedmetadata
play
ratechange
pauseEkranda pauzadan keyin «0:01 / 0:02» chiqdi (soniya — taxminiy, vaqtga bog'liq). Har qadamda nima bo'lganini jadvalda ko'ring — tugma matni bosishdan emas, keyingi hodisadan o'zgaradi:
| Qadam | Kelgan hodisa | paused / rate |
Ekranda |
|---|---|---|---|
| sahifa ochildi | loadedmetadata |
true / 1 |
«Ijro», «1.5×», «0:00 / 0:02» |
| «Ijro» bosildi | play |
false / 1 |
«Pauza», vaqt yura boshlaydi |
| «1.5×» bosildi | ratechange |
false / 1.5 |
«1×» |
| «Pauza» bosildi | pause |
true / 1.5 |
«Ijro», «0:01 / 0:02» |
Kodni tahlil qilamiz:
- Tugma matni —
pauseddan,pausedesaplay/pausehodisasidan. Biz hech qayerdasetPausedyozmadik. Agarplay()rad etilsa,playhodisasi kelmaydi va tugma «Ijro» bo'lib qoladi — haqiqatga mos. - Tezlik —
playbackRatega yozamiz, tugma matni esaratechangedan keyin o'zgaradi. Konsoldagi uchinchi qator — shu hodisa. duration || 0— metama'lumot kelgunchadurationNaNbo'ladi.NaNni chiqarish o'rniga 0.- Tozalash — effekt qaytargan funksiya hamma tinglovchilarni olib tashlaydi. Komponent yopilsa yoki ref boshqa elementga o'tsa, eski elementga obuna qolmaydi.
timeupdate sekundiga 4–66 marta keladi (brauzerga bog'liq). Har safar butun komponent qayta chiziladi. Kichik pleyerda bu muammo emas. Murakkab sahifada vaqtni alohida kichik komponentga ajrating (Kompozitsiya orqali optimallashtirish) — qolgan qism har soniya qayta chizilmaydi.
Maslahat:
useSyncExternalStore(useSyncExternalStore) ham shu ishni qila oladi: media elementi — tashqi manba,subscribe— hodisalarga obuna,getSnapshot—media.paused. Lekin snapshot oddiy qiymat bo'lishi kerak: bir nechta maydon uchun har biriga alohida hook yoki keshlangan obyekt kerak bo'ladi.useEffect+useStatevarianti soddaroq.
3. Subtitr: <track> va VTT
3.1 VTT fayl koddan
Subtitr — Malika va ovozsiz tomosha qiladiganlar uchun (Subtitrlar va media accessibility). Format — WebVTT: WEBVTT sarlavhasi, keyin har bir cue (bitta subtitr yozuvi): vaqt oralig'i va matn. Ko'pincha subtitrlar bazada ma'lumot bo'lib saqlanadi va fayl koddan yasaladi:
function vttTime(sec) {
const ms = Math.round(sec * 1000);
const pad = (n, len = 2) => String(n).padStart(len, "0");
const h = pad(Math.floor(ms / 3600000));
const m = pad(Math.floor(ms / 60000) % 60);
const s = pad(Math.floor(ms / 1000) % 60);
return `${h}:${m}:${s}.${pad(ms % 1000, 3)}`;
}
function toVtt(cues) {
const blocks = cues.map(
(c) => `${vttTime(c.start)} --> ${vttTime(c.end)}\n${c.text}`,
);
return ["WEBVTT", ...blocks].join("\n\n") + "\n";
}
console.log(
toVtt([
{ start: 0, end: 3.5, text: "Avval sabzini to'g'raymiz." },
{ start: 3.5, end: 65, text: "Go'shtni qizartiramiz." },
]),
);Konsolda:
WEBVTT
00:00:00.000 --> 00:00:03.500
Avval sabzini to'g'raymiz.
00:00:03.500 --> 00:01:05.000
Go'shtni qizartiramiz.
Vaqt formati qat'iy: soat, daqiqa, soniya va nuqtadan keyin millisekund (00:01:05.000). SRT formatida esa vergul (00:01:05,000) — boshqa formatdan ko'chirganda eng ko'p uchraydigan xato. Cue'lar orasida bo'sh qator shart.
3.2 React'da <track>
Yasalgan matn — Blob → obyekt URL → <track src>:
const [trackUrl] = useState(() =>
URL.createObjectURL(new Blob([toVtt(cues)], { type: "text/vtt" })),
);
useEffect(() => () => URL.revokeObjectURL(trackUrl), [trackUrl]);
<video src={video} controls crossOrigin="anonymous">
<track
kind="subtitles"
src={trackUrl}
srcLang="uz"
label="O'zbekcha"
default
/>
</video>;Ikki tuzoq. Birinchisi — srcLang (camelCase), srclang deb yozsangiz React ogohlantiradi. Ikkinchisi — CORS: video boshqa domendan (CDN) kelsa, VTT fayl ham boshqa domendan bo'lishi mumkin. Brauzer subtitrni CORS qoidasi bilan yuklaydi: <video crossOrigin="anonymous"> va server Access-Control-Allow-Origin javobi bo'lmasa, subtitr jim ko'rinmaydi.
4. HLS: uzun video oqimi
4.1 Bitta katta fayl emas — bo'laklar
40 daqiqalik videoni bitta osh.mp4 qilib bersangiz, ikki muammo: sekin internetda brauzer kerakli qismni yuklay olmay to'xtaydi, tez internetda esa past sifat ko'rinadi. HLS (HTTP Live Streaming) buni boshqacha hal qiladi: video bir necha sifatda tayyorlanadi va har biri 2–6 soniyalik bo'laklarga (segment) bo'linadi. Ro'yxat fayli — .m3u8 pleylist:
flowchart TB
P["Pleyer"] --> M["master.m3u8 — sifatlar ro'yxati"]
M --> L["360p.m3u8"]
M --> H["1080p.m3u8"]
L --> S1["bo'lak 1, 2, 3 … (.ts / .m4s)"]
H --> S2["bo'lak 1, 2, 3 …"]
P -. "internet tezligini o'lchaydi" .-> Q{"Qaysi sifat?"}
Q -- sekin --> L
Q -- tez --> HDiagrammaga qarang: pleyer avval sifatlar ro'yxatini oladi, keyin bo'laklarni birin-ketin yuklaydi. Har bo'lakdan keyin tezlikni o'lchab, keyingisini qaysi sifatda olishni tanlaydi. Bu — moslashuvchan bitreyt (adaptive bitrate): internet sekinlashsa, video to'xtamaydi, sifati pasayadi. YouTube, kurs platformalari, onlayn TV — hammasi shunday (HLS yoki unga o'xshash DASH).
4.2 Kim o'ynaydi: brauzermi, hls.js mi
Safari (macOS va iOS) HLS'ni o'zi o'ynaydi: <video src="master.m3u8"> yetadi. Boshqa brauzerlarda tarixan hls.js kerak edi. U pleylistni o'qiydi, bo'laklarni fetch qiladi va Media Source Extensions (MSE) orqali <video> ga beradi. MSE — JavaScript'ga video oqimini bo'lak-bo'lak "ovqatlantirish" imkonini beradigan brauzer API'si.
Qaysi biri kerakligini imkoniyatni tekshirish (feature detection) bilan aniqlaymiz: video.canPlayType("application/vnd.apple.mpegurl"). Bu tekshiruv bizda qiziq natija berdi: jsdom'da "" (yo'q), headless Chrome 155 da esa "maybe". Biz Chrome'da haqiqiy HLS oqimini o'ynatib ko'rmadik — shuning uchun hls.js hujjatidagi tartibni saqlang: avval canPlayType, bo'lmasa Hls.isSupported(), va ikkala yo'lni ham haqiqiy brauzerlarda sinang.
4.3 hls.js React effektida
hls.js obyekt yaratadi, tarmoqqa ulanadi va xotira oladi. Demak, u — tashqi tizim, ulash effektda, tozalash shart (kod hujjat bo'yicha, natija oynasida hls.js yo'q):
import { useEffect, useRef } from "react";
import Hls from "hls.js";
export function HlsVideo({ src }) {
const ref = useRef(null);
useEffect(() => {
const video = ref.current;
if (video.canPlayType("application/vnd.apple.mpegurl")) {
video.src = src; // Safari: o'zi o'ynaydi
return;
}
if (!Hls.isSupported()) return;
const hls = new Hls();
hls.loadSource(src);
hls.attachMedia(video);
hls.on(Hls.Events.ERROR, (_, data) => {
if (data.fatal) console.error("HLS:", data.type);
});
// tozalash: tarmoq, bufer, MSE — hammasi bo'shaydi
return () => hls.destroy();
}, [src]);
return <video ref={ref} controls playsInline />;
}Uch nuqta:
destroy()— tozalash funksiyasida. Dev rejimidagi StrictMode effektni ikki marta ishga tushiradi: tozalash bo'lmasa, ikkita hls.js bitta videoni yuklab, bir-birini buzadi.srco'zgarganda ham eski nusxa yopiladi.- Kech yuklash — hls.js kanon tajribasida +172.6 KB gzip. Uni hamma sahifaga qo'shmang: video sahifasida
lazybilan. - CSP — MSE videoga
blob:manzil beradi.vazifalarCSP'sidamedia-srcyo'q (udefault-src 'self'dan oladi) va tajribada haqiqiy buzilish chiqdi:Loading media from 'blob:http://127.0.0.1:5870/b3f5…' violates the fo…(xom faylda shunday kesilgan). Tarjimasi: "blob:dan media yuklash siyosatni buzadi". Davosi —media-src 'self' blob:.
Tekshirib ko'ring:
useEffectga[src]o'rniga[]yozildi. Foydalanuvchi boshqa darsni tanladi —srco'zgardi. Nima bo'ladi?
Javob
Effekt qayta ishlamaydi — hls.js eski pleylistda qoladi. Ekranda yangi dars sarlavhasi, videoda esa eski dars. Bundan tashqari, ESLint'ning react-hooks/exhaustive-deps qoidasi src yetishmayotganini aytadi (useEffect II). [src] bilan: avval eski nusxaning destroy(), keyin yangi Hls yangi manzil bilan.
5. YouTube va fasad naqshi
5.1 Iframe narxi
YouTube videosini qo'yishning eng oddiy yo'li — <iframe src="https://www.youtube.com/embed/…">. Lekin iframe sahifa ochilishi bilan YouTube pleyerining o'z JavaScript'ini, shriftlarini va kuzatuv skriptlarini yuklaydi — foydalanuvchi videoni bosmasa ham. Galereyada 10 ta video bo'lsa — 10 ta pleyer.
Fasad (facade) naqshi: avval faqat videoning rasmi va ▶ tugmasi. Haqiqiy iframe — faqat bosilganda. Do'kon vitrinasidagi televizor maketi kabi: haqiqiy televizorni faqat xaridor so'rasa yoqasiz.
5.2 O'z fasadimiz
import { useState } from "react";
const thumb = (id) => `https://i.ytimg.com/vi/${id}/hqdefault.jpg`;
function YouTubeFacade({ id, title }) {
const [active, setActive] = useState(false);
if (active) {
const host = "https://www.youtube-nocookie.com";
const src = `${host}/embed/${id}?autoplay=1`;
console.log("iframe:", src);
return (
<iframe
src={src}
title={title}
width={320}
height={180}
allow="autoplay; encrypted-media; picture-in-picture"
allowFullScreen
/>
);
}
return (
<button
aria-label={`Videoni ko'rish: ${title}`}
onClick={() => setActive(true)}
style={{
width: 320,
height: 180,
background: `#000 url(${thumb(id)})`,
backgroundSize: "cover",
color: "white",
}}
>
▶ {title}
</button>
);
}
export default function App() {
return <YouTubeFacade id="VIDEO_ID" title="Osh damlash" />;
}Konsolda:
iframe: https://www.youtube-nocookie.com/embed/VIDEO_ID?autoplay=1VIDEO_ID o'rniga videoning 11 belgili identifikatori qo'yiladi. Natija oynasida tugma bosilgach, iframe ochilmaydi — va bu darsning foydali qismi. Saytimizning xavfsizlik siyosatida (CSP) YouTube'ga ruxsat yo'q, natija oynasi esa uni meros oladi. Sizning loyihangizda ham xuddi shunday bo'ladi. Fasad ishlashi uchun CSP'ga ikki joy qo'shiladi:
img-src https://i.ytimg.com— oldindan ko'rish rasmi;frame-src https://www.youtube-nocookie.com— bosilgandan keyingi pleyer.
youtube-nocookie.com — YouTube'ning "kengaytirilgan maxfiylik" domeni: video ijro etilmaguncha kuzatuv cookie'larini yozmaslikka va'da beradi. ?autoplay=1 — foydalanuvchi allaqachon bosdi, ikkinchi bosish kerak emas. aria-label — tugmada faqat ▶ belgisi va nom, Malikaga esa "nima bo'ladi" ham kerak.
5.3 react-lite-youtube-embed
Tayyor fasad — react-lite-youtube-embed: <LiteYouTubeEmbed id="…" title="…" />. Kanon tajribasida (3.7.0) +2.6 KB JS va +1.3 KB CSS. vazifalar CSP'si ostidagi haqiqiy natija:
embed-youtube-lite JS 70426 (+ 2.6 KB) CSS 1308 | CSP img-src×4,frame-src×1 | xato: Loading the image 'https://i.ytimg.com/vi/aqz-KE-bpKQ/hqdefault.jpg' violates the following Content ; Loading the image 'https://i.ytimg.com/vi/aqz-KE-bpKQ/hqdefault.jpg' violates the following Content | ochiq: tashqi fonts.gstatic.com,i.ytimg.com,www.youtube-nocookie.comTo'rtta img-src (rasm va kutubxonaning data: ikonkalari) va bitta frame-src buzilishi — yuqoridagi ro'yxat bilan bir xil. CSP'siz ochilganda brauzer uchta tashqi hostga bordi. Har biri — foydalanuvchining IP manzili va brauzer ma'lumoti begona serverga ketishi. Bu mavzu Uchinchi tomon skriptlar va embedlar darsida davom etadi.
6. Media Session: qulf ekranidagi tugmalar
Telefon qulflanganda yoki quloqchin tugmasi bosilganda brauzer kimga murojaat qiladi? Media Session API shu savolga javob: navigator.mediaSession ga nom, muallif va rasm, hamda "ijro", "pauza", "keyingi" bosilganda nima qilish kerakligi beriladi. Kod hujjat bo'yicha (natija oynasida tizim pleyeri yo'q):
useEffect(() => {
if (!("mediaSession" in navigator)) return;
const media = audio.current;
navigator.mediaSession.metadata = new MediaMetadata({
title: "Osh damlash",
artist: "«Bahor» oshpazlik maktabi",
artwork: [{ src: "/rasm/osh-512.png", sizes: "512x512" }],
});
const actions = {
play: () => media.play(),
pause: () => media.pause(),
seekbackward: () => (media.currentTime -= 10),
seekforward: () => (media.currentTime += 10),
};
for (const [name, fn] of Object.entries(actions)) {
navigator.mediaSession.setActionHandler(name, fn);
}
return () => {
for (const name of Object.keys(actions)) {
navigator.mediaSession.setActionHandler(name, null);
}
};
}, []);metadata— qulf ekranida va bildirishnomada ko'rinadigan nom, muallif, rasm.setActionHandler— tizim tugmalari. Berilmagan amal tugmasi ko'rinmaydi. Tozalashdanull— boshqa sahifaga o'tilganda eski pleyer tugmalari ishlamay qolsin."mediaSession" in navigator— imkoniyat tekshiruvi. Chrome 155 datrue(tekshirdik), jsdom'da — yo'q.
Bu API ishlashi uchun media haqiqatan ijro etilayotgan bo'lishi kerak: tizim "hozir ovoz chiqarayotgan" tabni tanlaydi.
7. Tayyor pleyerlar va biz sinab ko'rgan narsa
7.1 Uchta kutubxona
| Kutubxona | Nima uchun | Kanon tajribasida |
|---|---|---|
| react-player 3.4.0 | YouTube, Vimeo, fayl — bitta <ReactPlayer src> |
+579.1 KB, Trusted Types: to'sildi |
| Vidstack 1.15.7 | to'liq pleyer UI, HLS, subtitr | +112.9 KB (+10.3 CSS), Trusted Types: to'sildi |
| hls.js 1.7.3 | faqat HLS → <video> |
+172.6 KB, media-src blob: kerak |
react-player'ning v3 dagi asosiy prop'i src (eski versiyalarda — url):
import ReactPlayer from "react-player";
<ReactPlayer
src="https://www.youtube.com/watch?v=VIDEO_ID"
controls
/>;Qulay — bitta komponent ko'p manbani o'ynaydi. Hujjatga ko'ra uning light prop'i fasadni ham beradi: avval faqat rasm, pleyer — bosilganda. Lekin narxi og'ir. Tajribadagi haqiqiy xato:
This document requires 'TrustedHTML' assignment. The action has been blocked.
Failed to set the 'innerHTML' property on 'ShadowRoot': This document requires 'TrustedHTML' assignmTarjimasi: "Hujjat TrustedHTML talab qiladi, amal to'sildi"; "ShadowRoot'ning innerHTML iga yozib bo'lmadi". Pleyer o'z ichki qismlarini Shadow DOM ga satr sifatida yozadi — Trusted Types buni to'sadi. Vidstack ham xuddi shu sababli to'sildi.
Vidstack haqida yana bir halol fakt: o'rnatishda npm shu ogohlantirishni chiqardi (kutubxonalar-ornatish.txt):
npm warn deprecated @vidstack/react@1.15.7: Vidstack is deprecated and gets security fixes only until January 2028. Use Video.js 10: https://videojs.org/docs/framework/react/guides/migrate-from-vidstack?utm_source=vidstack (coding agents: run npx @videojs/cli agents skills, then follow the guide's AI Quickstart)Tarjimasi: "Vidstack eskirgan, xavfsizlik tuzatishlari faqat 2028-yil yanvarigacha. Video.js 10 ga o'ting". Ya'ni yangi loyihada Vidstack emas, Video.js 10. Uni biz o'lchamadik. Bu — kutubxona tanlashdan oldin npm view va ogohlantirishlarni o'qishning sababi (npm bilan ishlash).
7.2 Hajm
- react-lite-youtube-embed 3.7.0+1.3 KB CSS2,6 KB
- Vidstack 1.15.7+10.3 KB CSS, TT to'sdi112,9 KB
- hls.js 1.7.3media-src blob: kerak172,6 KB
- react-player 3.4.0TT to'sdi579,1 KB
Manba: O'lchandi: kanon tajribasi, Vite 8.3.3, gzip -9, Chrome 155, natijalar/tajriba/kutubxonalar-21.txt, 2026-10-17
Taqqoslash uchun: React va react-dom'ning o'zi — 66.1 KB. react-player React'dan 8 baravar og'ir, chunki u ko'p xizmatning pleyerlarini o'z ichiga oladi. Bitta YouTube video uchun fasad — 2.6 KB.
7.3 Nega vazifalar da media yo'q
vazifalar — vazifalar ro'yxati: unda video ham, audio ham yo'q. Shuning uchun bu darsda kanon kodi o'zgarmaydi. Kanon kutubxonalarni alohida tajribada o'lchadi. Media kerak bo'lgan loyihada tanlov tayyor: o'z <video> + useMediaState, uzun video — hls.js (kech yuklanadi, CSP'da media-src blob:), YouTube — fasad.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| tugma «Pauza», video esa to'xtagan | holat bosishda qo'lda yozilgan | holat — play/pause hodisalaridan |
vaqt NaN:NaN |
duration metama'lumotdan oldin |
loadedmetadata dan keyin, ` |
| ikkita ovoz, video "sakraydi" | hls.js effektida destroy() yo'q |
tozalash funksiyasi |
| subtitr ko'rinmaydi, xato yo'q | VTT boshqa domendan, CORS yo'q | crossOrigin + server sarlavhasi |
| subtitr fayli ochilmaydi | SRT vaqti 00:00:05,000 |
VTT'da nuqta: 00:00:05.000 |
violates … media-src |
MSE blob: manzili |
media-src 'self' blob: |
| YouTube oynasi bo'sh | CSP'da frame-src yo'q |
frame-src https://www.youtube-nocookie.com |
requires 'TrustedHTML' |
pleyer Shadow DOM'ga innerHTML |
boshqa pleyer yoki o'z <video> |
9. Mashqlar
Mashqlar kurs/mashqlar/21/13-video-audio/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.
1-mashq (oson): bo'sh joylarni to'ldiring
- Media holatini React state'ga yozish uchun tinglanadigan hodisa (ijro boshlandi):
- VTT'da millisekund oldidagi belgi:
- hls.js effektining tozalash funksiyasida chaqiriladigan metod:
hls.() - YouTube'ning maxfiyroq embed domeni:
www..com - MSE ishlatadigan pleyer uchun CSP direktivasi:
media-src 'self'
Yechim
- play —
play()chaqirilgani emas, ijro haqiqatan boshlangani haqidagi hodisa. - nuqta —
00:00:03.500; vergul — SRT formati. - destroy — tarmoq so'rovlari, bufer va MSE bo'shaydi; StrictMode'da ikki nusxa bo'lmaydi.
- youtube-nocookie — ijrodan oldin kuzatuv cookie'lari yozilmaydi.
- blob: — MSE videoga
blob:manzil beradi.
2-mashq (o'rta): bosqichlar ro'yxati
Retsept videosining bosqichlari bor. chapters massividan ro'yxat chizing, hozirgi bosqich aria-current="step" va «▶» belgisi bilan ajralsin. Hozirgi vaqt — time state'i (haqiqiy pleyerda u timeupdate dan keladi). Sinov uchun «+30 s» tugmasi vaqtni 30 soniya oldinga suradi. Uch marta bosilgach (90 s) — «Go'sht» bosqichi faol.
Ishora: faol bosqich — start qiymati time dan kichik yoki teng bo'lganlarning oxirgisi: chapters.findLast((c) => c.start <= time) (Massivda qidirish va tekshirish).
Yechim
import { useState } from "react";
const chapters = [
{ start: 0, title: "Sabzi" },
{ start: 45, title: "Go'sht" },
{ start: 120, title: "Guruch" },
];
export default function App() {
const [time, setTime] = useState(0);
const current = chapters.findLast((c) => c.start <= time);
return (
<>
<button onClick={() => setTime((t) => t + 30)}>+30 s</button>
<p>Vaqt: {time} s</p>
<ol>
{chapters.map((c) => (
<li
key={c.start}
aria-current={c === current ? "step" : undefined}
>
{c === current ? "▶ " : ""}
{c.title}
</li>
))}
</ol>
</>
);
}Ekranda:
+30 s
Vaqt: 90 s
Sabzi
▶ Go'sht
GuruchfindLast — oxiridan qidiradi: 90 dan kichik start lar — 0 va 45, oxirgisi — 45. aria-current={undefined} — atribut umuman chiqmaydi. Haqiqiy pleyerda bosqichni bosganda media.currentTime = c.start (seek) — 13-qismdagi pleyer kabi.
3-mashq (qiyin): VTT'ni tekshiruvchi
Foydalanuvchi subtitr faylini yuklaydi. checkVtt(text) funksiyasini yozing — u xatolar massivini qaytarsin (bo'sh massiv — fayl to'g'ri):
- Birinchi qator
WEBVTTbilan boshlanmasa —"WEBVTT sarlavhasi yo'q". - Har vaqt qatori (
-->bor qator)00:00:00.000 --> 00:00:00.000ko'rinishida bo'lsin; vergulli vaqt bo'lsa —"N-qator: vergul emas, nuqta", boshqa xato —"N-qator: vaqt noto'g'ri"(N — 1 dan boshlab). - Boshlanish oxiridan kichik bo'lsin, aks holda —
"N-qator: oxiri boshidan oldin".
Ishora: satrni qatorlarga — text.split("\n"); vaqt — muntazam ifoda /^(\d{2}):(\d{2}):(\d{2})\.(\d{3})$/ (guruhlar); soniyaga aylantirish — h * 3600 + m * 60 + s + ms / 1000.
Yechim
const TIME = /^(\d{2}):(\d{2}):(\d{2})\.(\d{3})$/;
function toSeconds(t) {
const m = t.match(TIME);
if (!m) return null;
const [, h, min, s, ms] = m.map(Number);
return h * 3600 + min * 60 + s + ms / 1000;
}
function checkVtt(text) {
const lines = text.split("\n");
const errors = [];
if (!lines[0].startsWith("WEBVTT")) {
errors.push("WEBVTT sarlavhasi yo'q");
}
lines.forEach((line, i) => {
if (!line.includes("-->")) return;
const n = i + 1;
const [from, to] = line.split("-->").map((p) => p.trim());
if (from.includes(",") || to.includes(",")) {
errors.push(`${n}-qator: vergul emas, nuqta`);
return;
}
const a = toSeconds(from);
const b = toSeconds(to);
if (a === null || b === null) {
errors.push(`${n}-qator: vaqt noto'g'ri`);
} else if (a >= b) {
errors.push(`${n}-qator: oxiri boshidan oldin`);
}
});
return errors;
}
const good = "WEBVTT\n\n00:00:00.000 --> 00:00:03.500\nSabzi";
const bad = [
"1",
"00:00:01,000 --> 00:00:02,000",
"Salom",
"",
"00:00:09.000 --> 00:00:05.000",
"Xayr",
"",
"0:00:10.000 --> 00:00:12.000",
].join("\n");
console.log(checkVtt(good));
console.log(checkVtt(bad));Konsolda:
[]
[
"WEBVTT sarlavhasi yo'q",
'2-qator: vergul emas, nuqta',
'5-qator: oxiri boshidan oldin',
"8-qator: vaqt noto'g'ri"
]Xatolar qator raqami bilan — foydalanuvchi faylni tuzatishi oson (Fayl yuklash UI dagi qoida: xabar aniq va foydali). m.map(Number) birinchi elementni ham (butun moslik) songa aylantiradi — destrukturizatsiyada uni , bilan tashlab ketamiz. Node satrlarni apostrof bo'lsa qo'shtirnoqda, bo'lmasa bittalik tirnoqda chiqaradi — ma'nosi bir xil.
4-mashq: tajriba qadami (ixtiyoriy)
vazifalar ga tegmang. Alohida Vite + React loyihada «Bahor» retsept sahifasini yasang: HlsVideo komponenti lazy bilan, fasadli YouTube video, vazifalar dagi CSP <meta> i. Ochiq test oqimi sifatida hls.js hujjatidagi namunaviy .m3u8 manzillaridan birini oling. Konsolni kuzating: qaysi direktivani qo'shish kerak bo'ldi?
Yechim
Tekshiruv ro'yxati:
npm i -E hls.js@1.7.3— build'da hls.js alohida chunk'da (Vite oxirida gzip hajmini ko'rsatadi; kanonda ~172 KB).- CSP bilan birinchi urinish:
media-srcbuzilishi (blob:). Qo'shish:media-src 'self' blob:. Tashqi oqim uchunconnect-srcga uning hostini ham qo'shasiz — hls.js bo'laklarnifetchbilan oladi. - YouTube fasadi:
img-src https://i.ytimg.com,frame-src https://www.youtube-nocookie.com. - DevTools → Network: bo'laklar (
.tsyoki.m4s) birin-ketin keladi; tarmoqni "Slow 4G" ga qo'ysangiz, sifat pasayadi. - Sahifadan chiqib qayting: konsolda ikkinchi hls.js nusxasi qolmagan (tozalash ishlayapti).
10. Real ishda
- Kurs platformalari va media saytlar — HLS (yoki DASH) oqimi, o'z pleyer tugmalari, tezlik, subtitr, "qayerda to'xtadingiz". Videoni HLS'ga aylantirish — serverda (FFmpeg) yoki bulut xizmatida.
- Marketing sahifalari — YouTube/Vimeo fasad bilan: Lighthouse'dagi "uchinchi tomon kodi" ogohlantirishini kamaytiradi.
- Podkast va musiqa — Media Session: qulf ekrani, quloqchin tugmalari, avtomobil ekrani.
- Intervyu savollari: "React'da pleyer holatini qayerda saqlaysiz?", "HLS nima va adaptive bitrate qanday ishlaydi?", "YouTube embed sahifani qanday sekinlashtiradi?", "hls.js'ni effektda qanday ulaysiz?".
Xulosa
- Media holati elementning o'zida: buyruq — ref orqali, holat — hodisalardan (
useMediaState). - React
mutedni atribut qilib yozmaydi — server HTML'ida autoplay uchun tuzoq. - Subtitr —
<track srcLang>va WebVTT (nuqtali millisekund); boshqa domendan — CORS bilan. - HLS — sifatlar va bo'laklar pleylisti; Safari o'zi, boshqalar — hls.js (MSE), effektda
destroy(), CSP'damedia-src blob:. - YouTube — fasad: rasm + tugma, iframe bosilganda (
youtube-nocookie,img-srcvaframe-src). - react-player (+579.1 KB) va Vidstack (+112.9) Trusted Types ostida ochilmadi; Vidstack eskirgan → Video.js 10.
Keyingi dars: PDF: ko'rsatish va yaratish — brauzerda PDF ko'rsatish (pdf.js), chekni klientda yaratish (jsPDF, @react-pdf/renderer), chop etish CSS'i va qachon PDF serverda yaratilishi kerak.
Manbalar
- React:
<video>/<audio>komponentlari va media hodisalari — react.dev/reference/react-dom/components/common - MDN:
HTMLMediaElement, WebVTT format,<track>, Media Session API, Media Source Extensions - hls.js: "Getting started", API (
isSupported,loadSource,attachMedia,destroy) — github.com/video-dev/hls.js - react-player v3 — npm; Vidstack → Video.js 10 migratsiyasi — videojs.org; react-lite-youtube-embed — npm
- YouTube: "Privacy-enhanced mode" (youtube-nocookie.com) — support.google.com/youtube
- Kanon:
natijalar/tajriba/kutubxonalar-21.txt,kutubxonalar-ornatish.txt,tajriba/kutubxonalar/src/k/media-*.tsx,embed-youtube-lite.tsx(2026-10-17)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!