Mundarija (37)
- Bu darsda
- 1. Nega bu kerak?
- 2. Web Audio: koddan tovush
- 2.1 Ovoz grafi
- 2.2 AudioContext va birinchi nota
- 2.3 Ikki qoida: autoplay va bir martalik manba
- 2.4 Balandlik va "konvert"
- 2.5 «Bahor»: buyurtma tayyor signali
- 3. Kamera va mikrofon: getUserMedia
- 3.1 Bitta chaqiruv — ruxsat va oqim
- 3.2 Faqat xavfsiz kontekstda
- 3.3 Nega natija oynasida emas
- 4. Kamera: ko'rsatish, surat olish, o'chirish
- 4.1 To'liq sahifa
- 4.2 Sahifani tahlil qilamiz
- 4.3 Ruxsat xatolari
- 4.4 Qurilmalar ro'yxati
- 5. MediaRecorder: yozib olish
- 5.1 Qanday ishlaydi
- 5.2 Natija oynasida: Web Audio'ni yozib olish
- 5.3 Malikaning ovozli broni
- 6. Hujumchi nigohi
- 7. Ko'p uchraydigan xatolar
- 7.1 http:// da sinash
- 7.2 Trekni to'xtatmaslik
- 7.3 AudioContext dan ovoz chiqmaydi
- 7.4 Ossillyatorni qayta ishlatish
- 7.5 Yozuv turini qattiq yozish
- 7.6 Har kadrda yangi AudioContext
- 8. Mashqlar
- 1-mashq (oson): Taymer signali
- 2-mashq (o'rta): Old va orqa kamera
- 3-mashq (qiyin): 30 soniyalik cheklov va taymer
- 4-mashq: Amaliy tajriba — fokus taymeri
- 9. Real ishda
- Xulosa
- Manbalar
Web Audio, kamera va mikrofon: AudioContext, getUserMedia va MediaRecorder
Qisqacha: Brauzer tovushni fayldan emas, koddan ham yarata oladi:
AudioContext— ovoz "studiyasi",OscillatorNode— tovush manbai,GainNode— balandlik, ularconnectbilan karnaygacha ulanadi. Kamera va mikrofon —navigator.mediaDevices.getUserMedia({ video, audio }): u foydalanuvchidan ruxsat so'raydi vaMediaStreamqaytaradi; faqat HTTPS yokilocalhostda ishlaydi. OqimniMediaRecorderyozib oladi vaBlobberadi. Ishingiz tugagach, har treknitrack.stop()bilan o'chiring.
Bu darsda
AudioContext, ossillyator vaGainNodebilan fayl'siz signal ovozini yaratasiz.getUserMediabilan kamera va mikrofonni yoqib, oqimni<video>da ko'rsatasiz va o'chirasiz.- Kameradan surat olib, uni canvas orqali faylga aylantirasiz.
MediaRecorderbilan ovoz yozib olib, uni tinglash va yuklab olish imkonini berasiz.- Ruxsat xatolarini, HTTPS talabini va ruxsatlar siyosatini tushuntira olasiz.
Oldin bilishingiz kerak: Audio va videoni JS'dan boshqarish, Permissions, Notifications va Geolocation, Canvas amaliyotda, File va Blob.
1. Nega bu kerak?
O'tgan darsda tayyor fayllarni chaldik. «Bahor» da esa yana uch vazifa paydo bo'ldi:
- Fayl'siz signal. Oshxona ekranidagi taymer tugaganda "pip-pip-pip" chalinsin. Buning uchun MP3 yuklash shart emasmi? Shart emas — brauzer tovushni o'zi "sintez" qila oladi.
- Kunning taomi surati. Sardor har kuni menyuga yangi taom suratini qo'shadi. Telefonini olib, faylni topib yurmasdan, sayt ichidagi «Suratga olish» tugmasi bilan.
- Ovozli bron. Malika — ko'zi ojiz mehmon. Unga bron formasini to'ldirishdan ko'ra ovozli xabar qoldirish qulayroq: "Shanba kuni soat 7 ga, to'rt kishi".
Uchalasi ham brauzerning "eshitish va ko'rish" API'lari. Ikkitasi — kamera va mikrofon — shaxsiy ma'lumotga tegadi. Shuning uchun bu darsda texnik qism bilan birga ruxsat va xavfsizlik qoidalari ham bor.
2. Web Audio: koddan tovush
2.1 Ovoz grafi
<audio> elementi tayyor faylni chaladi. Web Audio API esa tovush bilan "ichkaridan" ishlaydi: yaratadi, aralashtiradi, filtrlaydi. U musiqa studiyasidagi mikshir pultiga o'xshaydi: manbalar (mikrofon, sintezator), ular orasida "ruchkalar" (balandlik, filtr) va oxirida karnay. Hammasi kabel bilan ulanadi.
Web Audio'da bu kabellar — connect, qurilmalar esa tugunlar (node). Tugunlar zanjiri ovoz grafi (audio graph) deyiladi:
flowchart LR
O["OscillatorNode<br/>manba"] --> G["GainNode<br/>balandlik"]
G --> D["destination<br/>karnay"]
M["Mikrofon oqimi"] -.-> G
G -.-> R["MediaStream<br/>yozib olish uchun"]Nimaga qarang: tovush chapdan o'ngga oqadi. Manba o'rnida mikrofon ham bo'lishi mumkin (uzuq chiziq), oxirida esa karnay o'rniga yozib olish oqimi. Grafni qanday yig'sangiz, tovush shunday yo'l bosadi.
2.2 AudioContext va birinchi nota
Hamma narsa bitta obyektdan boshlanadi — AudioContext. U ovoz grafini va uning soatini boshqaradi. Sahifada odatda bitta yetadi:
const ctx = new AudioContext();
const osc = new OscillatorNode(ctx, { type: "sine", frequency: 440 });
osc.connect(ctx.destination); // to'g'ridan-to'g'ri karnayga
osc.start();
osc.stop(ctx.currentTime + 0.5); // yarim soniyadan keyinOscillatorNode— ossillyator (oscillator): bir xil tebranadigan tovush manbai.frequency— chastota, gersda (Hz): soniyasiga necha tebranish. 440 Hz — musiqadagi "lya" notasi. Son katta — ovoz ingichka.type— to'lqin shakli:"sine"(yumshoq, "toza"),"square"(eski o'yinlar ovozi),"triangle","sawtooth"(keskin).ctx.destination— karnay (yoki quloqchin).ctx.currentTime— ovoz soati, soniyada.start(vaqt)vastop(vaqt)shu soat bo'yicha rejalashtiriladi:setTimeoutdan farqli o'laroq, aniqligi milisekunddan ham yuqori. Musiqa ritmi uchun aynan shu kerak.
Tugunni konstruktor bilan (new OscillatorNode(ctx, …)) yoki eski uslubdagi metod bilan (ctx.createOscillator() — "menga yangi ossillyator yasab ber") yaratish mumkin. Ikkalasi bir xil — eski kodda ikkinchisini ko'p uchratasiz.
2.3 Ikki qoida: autoplay va bir martalik manba
Autoplay bu yerda ham bor. Audio va video darsidagi siyosat Web Audio'ga ham tegishli. Foydalanuvchi sahifa bilan o'zaro ta'sir qilmaguncha AudioContext suspended (to'xtatilgan) holatda yaratiladi va tovush chiqmaydi. Tugma bosilganda await ctx.resume() — holat running bo'ladi.
Ossillyator — bir martalik. Uni ikkinchi marta start() qilib bo'lmaydi. Biz Chrome 154 da sinadik:
InvalidStateError: Failed to execute 'start' on 'AudioScheduledSourceNode': cannot call start more than once.Tarjimasi: "start ni bajarib bo'lmadi: uni bir martadan ko'p chaqirib bo'lmaydi". Bu xato emas, dizayn: tugunlar arzon, har nota uchun yangisi yaratiladi. stop dan keyin u o'zi o'chadi va axlat yig'uvchi uni tozalaydi.
2.4 Balandlik va "konvert"
Ossillyatorni to'satdan yoqib-o'chirsangiz, boshida va oxirida "chirt" degan tovush eshitiladi. Sababi — to'lqin keskin uziladi. Yechim — ovozni silliq ko'tarib, silliq so'ndirish. Musiqada bu konvert (envelope) deyiladi. Uni GainNode bajaradi:
gain— ko'paytiruvchi: 1 — asl balandlik, 0.3 — past, 0 — jim.gainoddiy son emas, AudioParam — vaqt bo'yicha o'zgartirish mumkin bo'lgan parametr.setValueAtTime(qiymat, vaqt)— shu paytda shu qiymat.linearRampToValueAtTime(qiymat, vaqt)— shu paytgacha tekis o'zgarish: har lahzada bir xil miqdorga kamayadi.exponentialRampToValueAtTime(qiymat, vaqt)— eksponensial o'zgarish: har lahzada bir xil marta kamayadi (masalan, har safar yarmiga). Boshida tez, oxirida sekin so'nadi — qo'ng'iroq tovushi tabiatda aynan shunday o'chadi.
Bitta tuzoq: eksponensial o'zgarish nolga yetolmaydi. Sonni qayta-qayta yarmiga bo'lsangiz, u kichrayaveradi, lekin hech qachon 0 bo'lmaydi. Chrome xatosi:
RangeError: Failed to execute 'exponentialRampToValueAtTime' on 'AudioParam': The float target value provided (0) should not be in the range (-1.40130e-45, 1.40130e-45).Tarjimasi: "berilgan qiymat (0) nolga juda yaqin oraliqda bo'lmasligi kerak". Shuning uchun so'nish 0.001 gacha qilinadi — quloq uchun bu baribir jimlik.
2.5 «Bahor»: buyurtma tayyor signali
Endi hammasi birga. «Buyurtma tayyor» tugmasi ikki notali signal chaladi, to'lqin shaklini tanlash mumkin:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
button { font: inherit; padding: 0.4rem 0.8rem; }
</style>
<form id="form">
<fieldset>
<legend>To'lqin shakli</legend>
<label><input type="radio" name="wave" value="sine" checked>
sine</label>
<label><input type="radio" name="wave" value="square">
square</label>
<label><input type="radio" name="wave" value="triangle">
triangle</label>
</fieldset>
</form>
<p><button id="ready" type="button">Buyurtma tayyor</button></p>
<script>
const ctx = new AudioContext();
const form = document.getElementById("form");
console.log(`AudioContext: ${ctx.state}`);
function playNote(frequency, start, duration, type) {
const osc = new OscillatorNode(ctx, { type, frequency });
const gain = new GainNode(ctx, { gain: 0 });
osc.connect(gain).connect(ctx.destination);
// konvert: 10 ms da ko'tariladi, oxirigacha so'nadi
gain.gain.setValueAtTime(0, start);
gain.gain.linearRampToValueAtTime(0.3, start + 0.01);
gain.gain.exponentialRampToValueAtTime(0.001, start + duration);
osc.start(start);
osc.stop(start + duration);
return osc;
}
document.getElementById("ready").addEventListener("click",
async () => {
if (ctx.state === "suspended") {
await ctx.resume();
console.log(`AudioContext: ${ctx.state}`);
}
const type = form.elements.wave.value;
const now = ctx.currentTime;
playNote(880, now, 0.3, type);
const last = playNote(660, now + 0.3, 0.5, type);
last.addEventListener("ended", () => {
console.log("Signal tugadi");
});
});
</script>Konsolda:
AudioContext: suspendedSahifa ochilganda kontekst to'xtatilgan — hali hech narsa bosilmagan. «Buyurtma tayyor» bosilgach konsolga AudioContext: running va 0,8 soniyadan keyin Signal tugadi qo'shiladi. Biz natija oynasida bosib sinadik. Keyingi bosishlarda resume kerak emas — kontekst allaqachon ishlayapti.
Bir nechta tafsilot:
osc.connect(gain).connect(ctx.destination)—connecto'zi ulagan tugunni qaytaradi, shuning uchun zanjir qilib yoziladi.- Ikkinchi nota
now + 0.3da boshlanadi — oldindan rejalashtirildi. Ikki nota orasidasetTimeoutyo'q, shuning uchun ritm aniq. form.elements.wave.value— tanlangan radio tugmaning qiymati.endedhodisasi —<audio>dagi kabi: manba tugaganda keladi.
Tekshirib ko'ring: Tugma tez-tez uch marta bosildi. Nechta ossillyator yaratiladi va signallar bir-birini to'xtatadimi?
Javob
Oltita — har bosishda ikkitadan. Ular bir-birini to'xtatmaydi: Web Audio'da tovushlar aralashadi — uch signal ustma-ust chalinadi. <audio> elementida esa bitta element bir vaqtda bitta joyni chaladi. Bir vaqtda ko'p ovoz kerak bo'lsa (o'yin, bir nechta buyurtma) — Web Audio qulayroq.
3. Kamera va mikrofon: getUserMedia
3.1 Bitta chaqiruv — ruxsat va oqim
Kamera va mikrofonga kirish eshigi — bitta metod:
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});- Argument — cheklovlar (constraints): nima kerak.
video: true— istalgan kamera,audio: true— istalgan mikrofon. Kamida bittasi bo'lishi shart. Ikkalasi ham berilmasa —TypeError: ... At least one of audio and video must be requested("kamida audio yoki video so'ralishi kerak"). - Natija — Promise. Birinchi marta brauzer foydalanuvchiga ruxsat oynasini ko'rsatadi: "ilmhamroh.uz kamera va mikrofondan foydalanmoqchi". Foydalanuvchi ruxsat bermaguncha Promise kutib turadi. Bu Permissions darsidagi
prompt→granted/deniedyo'li. - Ruxsat berilsa —
MediaStream(media oqimi): kameradan va mikrofondan uzluksiz kelayotgan tasvir va tovush.
Oqim treklardan (track) iborat: har qurilma — bitta trek. stream.getVideoTracks() — kamera treklari, stream.getAudioTracks() — mikrofon treklari, stream.getTracks() — hammasi. Har trekda: kind ("video" yoki "audio"), label (qurilma nomi), enabled (vaqtincha o'chirish), stop() (butunlay o'chirish).
sequenceDiagram
participant S as Sahifa
participant B as Brauzer
participant F as Foydalanuvchi
S->>B: getUserMedia({ video: true })
B->>F: Ruxsat oynasi
F-->>B: "Ruxsat berish"
B-->>S: MediaStream (treklar)
S->>S: video.srcObject = stream
S->>B: track.stop()
Note over B: Kamera chirog'i o'chadi3.2 Faqat xavfsiz kontekstda
getUserMedia faqat xavfsiz kontekstda ishlaydi: https:// sahifa yoki localhost. Oddiy http:// sahifada navigator.mediaDevices ning o'zi yo'q. Biz tekshirdik: sahifani http://localhost da emas, kompyuterning tarmoq manzili (http://172.31.224.1:5510) orqali ochdik. Natija:
TypeError: Cannot read properties of undefined (reading 'getUserMedia')Tarjimasi: "undefined ning getUserMedia xususiyatini o'qib bo'lmaydi". navigator.mediaDevices — undefined. Bu ko'p uchraydi: kompyuterdagi Live Server'ni telefondan Wi-Fi orqali ochsangiz, aynan shu xato. Telefonda sinash uchun sayt HTTPS bilan kerak — masalan, GitHub Pages'ga joylab.
Shuning uchun kod avval imkoniyatni tekshiradi — feature detection:
if (!navigator.mediaDevices?.getUserMedia) {
status.textContent = "Kamera faqat HTTPS yoki localhost'da.";
}3.3 Nega natija oynasida emas
Natija oynasida sinab ko'ring:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
button { font: inherit; padding: 0.4rem 0.8rem; }
</style>
<button id="camera" type="button">Kamerani yoqish</button>
<script>
document.getElementById("camera").addEventListener("click",
async () => {
try {
await navigator.mediaDevices.getUserMedia({ video: true });
console.log("Kamera yoqildi");
} catch (error) {
console.log(`${error.name}: ${error.message}`);
}
});
</script>Bosing. Biz natija oynasida sinadik — konsolga shu chiqdi: SecurityError: Invalid security origin. Tarjimasi: "xavfsizlik origin'i yaroqsiz". Natija oynasi — saytning ichidagi alohida, ajratilgan sahifa: uning origin'i null. Brauzer kamerani "kimligi noma'lum" sahifaga bermaydi. Ustiga-ustak sayt ruxsatlar siyosati (permissions policy) bilan oynaga kamera va mikrofonni yopib qo'ygan. Bu to'g'ri: begona kod (reklama, vidjet) sizning kamerangizni so'ray olmasligi kerak.
Shuning uchun bu bo'limdagi misollarni Live Server da (http://localhost:5500) sinaysiz. Biz ularni Chrome 154 da soxta kamera va mikrofon bilan sinadik (avtomatik sinovda, Chrome'ning --use-fake-device-for-media-stream bayrog'i bilan): Chrome kamera o'rniga yashil sinov tasvirini, mikrofon o'rniga "pip" tovushini beradi. Darsdagi label va o'lchamlar o'sha sinovdan. Sizning kompyuteringizda — haqiqiy kamera nomi, haqiqiy brauzerda esa ruxsat oynasi chiqadi.
Tekshirib ko'ring: Sardor kamera sahifasini kompyuterida Live Server'da ochdi — ishladi. Telefonida xuddi shu sahifani
http://192.168.1.5:5500orqali ochdi — tugma hech narsa qilmayapti. Nega?
Javob
Telefonda manzil localhost emas, oddiy http:// — xavfsiz kontekst emas. U yerda navigator.mediaDevices umuman yo'q, getUserMedia ni chaqirish TypeError beradi. Kompyuterda localhost istisno sifatida xavfsiz hisoblanadi. Telefonda sinash uchun HTTPS kerak.
4. Kamera: ko'rsatish, surat olish, o'chirish
4.1 To'liq sahifa
«Kunning taomi» uchun kamera sahifasi. kurs/mashqlar/13/18-kamera/index.html faylini yarating va Live Server bilan oching:
<!doctype html>
<html lang="uz">
<head>
<meta charset="utf-8">
<title>Kunning taomi — kamera</title>
</head>
<body>
<button id="start" type="button">Kamerani yoqish</button>
<button id="shot" type="button" disabled>Suratga olish</button>
<button id="stop" type="button" disabled>O'chirish</button>
<p id="status" role="status"></p>
<video id="preview" autoplay muted playsinline width="320"></video>
<img id="photo" alt="Kunning taomi surati" width="320" hidden>
<script>
const video = document.getElementById("preview");
const photo = document.getElementById("photo");
const status = document.getElementById("status");
const buttons = {
start: document.getElementById("start"),
shot: document.getElementById("shot"),
stop: document.getElementById("stop"),
};
let stream = null;
function explainError(error) {
const messages = {
NotAllowedError: "Kameraga ruxsat berilmadi.",
NotFoundError: "Kamera topilmadi.",
NotReadableError: "Kamera boshqa dasturda band.",
};
return messages[error.name] ?? "Kamerani yoqib bo'lmadi.";
}
async function startCamera() {
if (!navigator.mediaDevices?.getUserMedia) {
status.textContent = "Kamera faqat HTTPS yoki localhost'da.";
return;
}
try {
stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
facingMode: "environment",
},
});
} catch (error) {
status.textContent = explainError(error);
console.log(`${error.name}: ${error.message}`);
return;
}
video.srcObject = stream;
const [track] = stream.getVideoTracks();
const { width, height } = track.getSettings();
console.log(`Kamera: ${track.label}, ${width} × ${height}`);
buttons.start.disabled = true;
buttons.shot.disabled = false;
buttons.stop.disabled = false;
}
function takePhoto() {
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext("2d").drawImage(video, 0, 0);
canvas.toBlob((blob) => {
if (photo.src) URL.revokeObjectURL(photo.src);
photo.src = URL.createObjectURL(blob);
photo.hidden = false;
console.log(`Surat: ${canvas.width} × ${canvas.height}, `
+ blob.type);
}, "image/jpeg", 0.85);
}
function stopCamera() {
for (const track of stream.getTracks()) track.stop();
video.srcObject = null;
stream = null;
console.log("Kamera o'chirildi");
buttons.start.disabled = false;
buttons.shot.disabled = true;
buttons.stop.disabled = true;
}
buttons.start.addEventListener("click", startCamera);
buttons.shot.addEventListener("click", takePhoto);
buttons.stop.addEventListener("click", stopCamera);
</script>
</body>
</html>Biz uchala tugmani ketma-ket bosdik. Konsolda:
Kamera: fake_device_0, 1280 × 720
Surat: 1280 × 720, image/jpeg
Kamera o'chirildiHaqiqiy kompyuterda birinchi qatorda kamerangiz nomi chiqadi (masalan, "HD Webcam"), o'lcham esa kamera imkoniyatiga qarab.
4.2 Sahifani tahlil qilamiz
video.srcObject = stream. O'tgan darsda src ga fayl manzilini berdik. Jonli oqim fayl emas — unda manzil yo'q. Shuning uchun boshqa xususiyat: srcObject ga oqimning o'zi beriladi. autoplay muted playsinline — oqim darhol ko'rinsin: ovozsiz bo'lgani uchun autoplay ruxsat etiladi, playsinline esa iPhone'da to'liq ekranga o'tib ketmasligi uchun.
Cheklovlar. width: { ideal: 1280 } — "imkon bo'lsa 1280". Kamera qila olmasa, brauzer eng yaqinini beradi. exact esa qat'iy talab — bajarilmasa, Promise rad etiladi. facingMode: "environment" — telefonda orqa kamera (taomni suratga olish uchun), "user" — old kamera (selfi). Kompyuterda kamera bitta — bu tanlov e'tiborsiz qoladi.
track.getSettings() — haqiqatda nima berilganini aytadi. So'ragan narsangiz bilan bir xil bo'lishi shart emas. Biz ideal siz ham sinadik: soxta kamera standartda 640 × 480 berdi.
Surat olish — Canvas amaliyotda darsidagi asboblar. drawImage <video> ning joriy kadrini chizadi. Canvas o'lchami — video.videoWidth × videoHeight, ya'ni kameraning haqiqiy o'lchami, sahifadagi 320 px emas. Keyin toBlob — tayyor JPEG fayl: uni <img> da ko'rsatish, yuklab berish yoki serverga yuborish mumkin.
O'chirish — eng muhim qism. track.stop() kamerani haqiqatan o'chiradi va kamera yonidagi chiroq so'nadi. video.srcObject = null bilan faqat ko'rsatishni to'xtatsangiz, kamera ishlashda davom etadi. Foydalanuvchi chiroqni ko'rib, "sayt meni kuzatyapti" deb o'ylaydi — va haq bo'ladi. Biz sinadik: stop() dan keyin har trekning readyState i "ended".
Tekshirib ko'ring: Sardor «O'chirish» tugmasiga faqat
video.srcObject = nullyozdi. Video oynasi qoraydi. Kamera o'chdimi?
Javob
Yo'q. srcObject = null faqat ko'rsatishni to'xtatadi — oqim va uning treklari tirik qoladi, kamera chirog'i yonib turadi. Kamerani haqiqatan o'chirish uchun har trekda track.stop() kerak.
4.3 Ruxsat xatolari
getUserMedia rad etilsa, xato nomi sababni aytadi:
error.name |
Sabab |
|---|---|
NotAllowedError |
Foydalanuvchi yoki siyosat ruxsat bermadi |
NotFoundError |
Bunday qurilma yo'q (yoki talab bajarib bo'lmas) |
NotReadableError |
Qurilma band (Zoom'da ochiq) yoki ishlamayapti |
OverconstrainedError |
exact talabni bajarib bo'lmaydi |
Ruxsat rad etilganda Chrome 154 dagi matn (biz sinovda ruxsat so'rovlarini avtomatik rad etdik):
NotAllowedError: Permission deniedTarjimasi: "Ruxsat berilmadi". Ruxsat bir marta rad etilsa, sayt uni qayta so'ray olmaydi: keyingi getUserMedia darhol shu xato bilan tugaydi. Foydalanuvchi faqat brauzer sozlamasida (manzil satridagi qulf belgisi) o'zgartira oladi. Shuning uchun xabarda yo'l-yo'riq bering: "Manzil satridagi qulf belgisini bosib, kameraga ruxsat bering".
Ruxsat holatini so'ramasdan bilish mumkin — navigator.permissions.query({ name: "camera" }) (Permissions darsi). Sinovimizda kamera ishlagandan keyin u granted qaytardi.
4.4 Qurilmalar ro'yxati
Kompyuterda bir nechta kamera yoki mikrofon bo'lishi mumkin. Ro'yxat — navigator.mediaDevices.enumerateDevices(): har biri kind (videoinput, audioinput, audiooutput), label va deviceId bilan. Kerakli qurilmani tanlash: getUserMedia({ video: { deviceId: { exact: id } } }).
Bitta nozik joy: ruxsat berilmaguncha label lar bo'sh satr bo'ladi. Bu ham maxfiylik uchun — sayt ruxsatsiz "foydalanuvchida qanaqa qurilmalar bor" deb bilib olmasin. Shuning uchun qurilma tanlash menyusi odatda birinchi ruxsatdan keyin ko'rsatiladi.
5. MediaRecorder: yozib olish
5.1 Qanday ishlaydi
MediaStream — jonli oqim, u hech qayerda saqlanmaydi. Uni faylga yozish uchun MediaRecorder bor:
new MediaRecorder(stream)— yozuvchi yaratiladi.recorder.start()— yozish boshlanadi, holat"recording".- Ma'lumot bo'laklari
dataavailablehodisasida keladi —event.data(Blob). Ularni massivga yig'amiz. recorder.stop()— oxirgi bo'lak keladi, keyinstophodisasi.stopda bo'laklardan bittaBlobyig'iladi — tayyor fayl.
start() ga son bersangiz (start(1000)), bo'laklar har soniyada keladi. Uzun yozuvlar uchun foydali: bo'laklarni serverga yozish davomida yuborish mumkin. Pauza ham bor: pause() va resume().
5.2 Natija oynasida: Web Audio'ni yozib olish
Mikrofon natija oynasida yo'q. Lekin MediaRecorder ga istalgan oqim yaraydi — masalan, Web Audio grafidan chiqqan oqim. ctx.createMediaStreamDestination() — "karnay" o'rniga oqim beradigan tugun. Ovoz grafi chizmasidagi pastki uzuq chiziq aynan shu. «Bahor» kuyini yozib olamiz:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
button { font: inherit; padding: 0.4rem 0.8rem; }
</style>
<p><button id="record" type="button">Kuyni yozib olish</button></p>
<audio id="player" controls hidden></audio>
<script>
const ctx = new AudioContext();
const player = document.getElementById("player");
function scheduleTune(destination) {
const notes = [523, 659, 784, 1047]; // do-mi-sol-do
const start = ctx.currentTime;
notes.forEach((frequency, i) => {
const osc = new OscillatorNode(ctx, { frequency });
const gain = new GainNode(ctx, { gain: 0.2 });
osc.connect(gain).connect(destination);
osc.start(start + i * 0.25);
osc.stop(start + i * 0.25 + 0.2);
});
return notes.length * 0.25; // soniya
}
document.getElementById("record").addEventListener("click",
async () => {
await ctx.resume();
const destination = ctx.createMediaStreamDestination();
const recorder = new MediaRecorder(destination.stream);
const chunks = [];
recorder.addEventListener("dataavailable", (event) => {
chunks.push(event.data);
});
recorder.addEventListener("stop", () => {
const blob = new Blob(chunks, { type: recorder.mimeType });
player.src = URL.createObjectURL(blob);
player.hidden = false;
console.log(`Yozildi: ${blob.type}`);
});
recorder.start();
console.log(`Holat: ${recorder.state}`);
const seconds = scheduleTune(destination);
setTimeout(() => recorder.stop(), seconds * 1000 + 200);
});
</script>Bosing: konsolga Holat: recording, bir soniyadan sal ko'proq vaqtdan keyin Yozildi: audio/webm;codecs=opus chiqadi va pleyer paydo bo'ladi. Biz natija oynasida sinadik. Yozuv jarayonida tovush eshitilmaydi — graf karnayga emas, faqat yozuvchiga ulangan. Pleyerdagi «Ijro» esa yozib olingan faylni chaladi.
E'tibor bering: tur oldindan yozilmadi, recorder.mimeType dan olindi. Brauzerlar har xil format tanlashi mumkin: Chrome — WebM (ichida Opus kodek), boshqa brauzer — MP4. Aniq format kerak bo'lsa, oldin tekshiring: MediaRecorder.isTypeSupported("audio/webm") → true/false. Chrome 154 da audio/webm, audio/mp4, video/webm va video/mp4 — true, audio/ogg — false chiqdi.
Tekshirib ko'ring: Yozib olingan bo'laklardan fayl yig'ishda turini qayerdan olish to'g'ri:
"audio/webm"deb qo'lda yozishmi yokirecorder.?
Javob
recorder.mimeType. Brauzer o'zi tanlagan formatni shu yerda aytadi. Qo'lda "audio/webm" yozsangiz, MP4 yozadigan brauzerda fayl noto'g'ri turda saqlanadi va pleyer uni ocha olmasligi mumkin.
5.3 Malikaning ovozli broni
Endi haqiqiy mikrofon. kurs/mashqlar/13/18-ovozli-bron/index.html — Live Server bilan:
<!doctype html>
<html lang="uz">
<head><meta charset="utf-8"><title>Ovozli bron</title></head>
<body>
<button id="record" type="button" aria-pressed="false">
Yozishni boshlash
</button>
<p id="status" role="status"></p>
<audio id="player" controls hidden></audio>
<script>
const button = document.getElementById("record");
const status = document.getElementById("status");
const player = document.getElementById("player");
let recorder = null;
async function startRecording() {
let stream;
try {
stream = await navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: true, noiseSuppression: true },
});
} catch (error) {
status.textContent = `Mikrofon yoqilmadi: ${error.name}`;
return;
}
const chunks = [];
recorder = new MediaRecorder(stream);
recorder.addEventListener("dataavailable", (event) => {
chunks.push(event.data);
});
recorder.addEventListener("stop", () => {
for (const track of stream.getTracks()) track.stop();
const blob = new Blob(chunks, { type: recorder.mimeType });
player.src = URL.createObjectURL(blob);
player.hidden = false;
status.textContent = "Yozildi. Tinglab ko'ring.";
console.log(`Yozildi: ${blob.type}`);
});
recorder.start();
button.textContent = "To'xtatish";
button.setAttribute("aria-pressed", "true");
status.textContent = "Yozilmoqda…";
}
function stopRecording() {
recorder.stop();
button.textContent = "Yozishni boshlash";
button.setAttribute("aria-pressed", "false");
}
button.addEventListener("click", () => {
if (recorder?.state === "recording") stopRecording();
else startRecording();
});
</script>
</body>
</html>Biz sinadik: ikki soniyalik yozuvdan keyin konsolda Yozildi: audio/webm;codecs=opus, pleyerda duration — taxminan 1,9 soniya.
Bir nechta qaror:
- Bitta tugma — «Yozishni boshlash» / «To'xtatish», holati
aria-pressedda. Malika ekran o'quvchida "Yozishni boshlash, tugma, bosilmagan" eshitadi. Holat matnirole="status"maydonida — u o'zgarganda ekran o'quvchi o'qiydi. echoCancellationvanoiseSuppression— aks-sado va shovqinni kamaytirish. Ovozli xabar uchun to'g'ri tanlov; musiqa yozishda esa o'chiriladi.stophodisasida treklar to'xtatiladi — brauzer yorlig'idagi "mikrofon ishlayapti" belgisi so'nadi. Yozuv tugadi — mikrofon ham kerak emas.
Bu Blob ni bron bilan birga serverga yuborish — FormData bilan fayl kabi. Fayl yuborish va progress — Fayl yuklash darsida.
6. Hujumchi nigohi
Kamera va mikrofon — brauzerdagi eng shaxsiy ma'lumot. Hujumchi (yoki shunchaki beparvo sayt) qayerdan foydalanishga urinadi va himoya qanday ishlaydi:
- Reklama yoki begona vidjet kamerani so'raydi. Sahifangizga joylangan begona
<iframe>(reklama, chat vidjeti) o'zi kamera so'ray olmaydi. Buni ruxsatlar siyosati hal qiladi: iframe'gaallow="camera; microphone"yozilmagan bo'lsa — yopiq. Natija oynasidagiSecurityErroraynan shu himoyaning ishlashi. Qoida: begona iframe'ga kamera ruxsatini faqat kerak bo'lsa va faqat ishonchli manbaga bering. - Sahifa ochilishi bilan ruxsat so'rash. Foydalanuvchi nima uchunligini bilmay "Ruxsat berish" ni bosib yuborishi mumkin — yoki aksincha, "Bloklash" ni bosib, keyin kerak bo'lganda qaytara olmaydi. Ikkala holatda ham sayt foydalanuvchi ishonchini yo'qotadi. Himoya:
getUserMediafaqat foydalanuvchi aniq tugmani bosganda va nima uchunligi yozilgan holda. - Kamerani o'chirmaslik. "Surat olindi — tamom" deb trekni to'xtatmaslik — kamera fonda ishlayveradi. Himoya: ish tugashi bilan
track.stop(); sahifadan chiqilganda ham (pagehide). - Yozuvni yashirincha yuborish. Ruxsat — "yozib olish va serverga yuborish" uchun emas, "foydalanish" uchun. Yuborishdan oldin foydalanuvchiga eshittiring va tasdiqlating («Yuborish» tugmasi).
- HTTP orqali. Shifrlanmagan sahifaga tarmoqdagi hujumchi o'z skriptini qo'sha oladi. Shuning uchun brauzer kamerani faqat HTTPS'da beradi — bu talab siz uchun emas, foydalanuvchi uchun.
7. Ko'p uchraydigan xatolar
7.1 http:// da sinash
Telefonda yoki boshqa kompyuterda http://IP orqali — Cannot read properties of undefined (reading 'getUserMedia'). Tuzatish: localhost yoki HTTPS; kodda navigator.mediaDevices?.getUserMedia tekshiruvi.
7.2 Trekni to'xtatmaslik
video.srcObject = null — kamera baribir yoniq. Tuzatish: stream.getTracks().forEach((t) => t.stop()).
7.3 AudioContext dan ovoz chiqmaydi
Kontekst sahifa ochilganda yaratilgan va suspended holatda qolgan. Tuzatish: tugma bosilganda await ctx.resume().
7.4 Ossillyatorni qayta ishlatish
Ikkinchi start() — InvalidStateError. Tuzatish: har nota uchun yangi OscillatorNode.
7.5 Yozuv turini qattiq yozish
new Blob(chunks, { type: "audio/webm" }) — boshqa brauzerda MP4 yozilgan bo'lsa, fayl noto'g'ri turda saqlanadi. Tuzatish: recorder.mimeType.
7.6 Har kadrda yangi AudioContext
Har signal uchun new AudioContext() — har kontekst o'z ovoz soati va tizim ovoz qurilmasi bilan aloqani ochadi; ular yopilmasa (ctx.close()), resurs bekorga band bo'ladi. Tuzatish: sahifada bitta kontekst, har nota uchun yangi tugun.
8. Mashqlar
1-mashq (oson): Taymer signali
«Taymer tugadi» tugmasi uch marta qisqa "pip" chalsin: har biri 0.15 soniya, oralig'i 0.25 soniya, chastotasi 1000 Hz, "square" to'lqin. Notalarni ctx.currentTime bo'yicha oldindan rejalashtiring (setTimeout siz). Oxirgi pip tugaganda konsolga Pip-pip-pip.
Ishora: «Buyurtma tayyor» misolidagi playNote ni uch marta, now + i * 0.25 vaqtida chaqiring.
Yechim
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
button { font: inherit; padding: 0.4rem 0.8rem; }
</style>
<button id="timer" type="button">Taymer tugadi</button>
<script>
const ctx = new AudioContext();
function playNote(frequency, start, duration, type) {
const osc = new OscillatorNode(ctx, { type, frequency });
const gain = new GainNode(ctx, { gain: 0 });
osc.connect(gain).connect(ctx.destination);
gain.gain.setValueAtTime(0, start);
gain.gain.linearRampToValueAtTime(0.2, start + 0.01);
gain.gain.exponentialRampToValueAtTime(0.001, start + duration);
osc.start(start);
osc.stop(start + duration);
return osc;
}
document.getElementById("timer").addEventListener("click",
async () => {
await ctx.resume();
const now = ctx.currentTime;
let last;
for (let i = 0; i < 3; i++) {
last = playNote(1000, now + i * 0.25, 0.15, "square");
}
last.addEventListener("ended", () => {
console.log("Pip-pip-pip");
});
});
</script>Uchala nota bitta siklda, bir zumda rejalashtiriladi — keyin ular ovoz soati bo'yicha aniq vaqtida chalinadi. square to'lqin "elektron" eshitiladi — taymer uchun mos. Bosing: 0,65 soniyadan keyin konsolda Pip-pip-pip (biz natija oynasida sinadik).
2-mashq (o'rta): Old va orqa kamera
Kamera sahifasiga «Kamerani almashtirish» tugmasini qo'shing: bosilganda facingMode "environment" va "user" orasida almashsin. Yangi oqim olishdan oldin eski treklarni to'xtating. Kompyuterda kamera bitta bo'lsa ham kod xatosiz ishlashi kerak.
Ishora: ideal (yoki shunchaki facingMode: "user") — "imkon bo'lsa". exact ishlatsangiz, kompyuterda OverconstrainedError yoki NotFoundError olasiz.
Yechim
let facing = "environment";
async function switchCamera() {
facing = facing === "environment" ? "user" : "environment";
if (stream) {
for (const track of stream.getTracks()) track.stop();
}
try {
stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: facing }, // ideal — qat'iy emas
});
video.srcObject = stream;
console.log(`Kamera: ${stream.getVideoTracks()[0].label}`);
} catch (error) {
status.textContent = explainError(error);
}
}Eski treklar avval to'xtatiladi: ba'zi telefonlar ikki kamerani bir vaqtda ocha olmaydi va yangi so'rov NotReadableError bilan tugaydi. facingMode qat'iy emas — kompyuterda bitta kamera qaytadi, xato yo'q.
3-mashq (qiyin): 30 soniyalik cheklov va taymer
Ovozli bronni yaxshilang: yozuv paytida 0:07 / 0:30 ko'rinishidagi taymer (har soniyada yangilansin), 30 soniyada yozuv o'zi to'xtasin. To'xtagandan keyin taymer ham to'xtasin. Yozuv hajmini ham ko'rsating: Yozildi: 1,9 soniya, 31 KB.
Ishora: setInterval (start paytida) va clearInterval (stop hodisasida). Avtomatik to'xtatish — setTimeout(stopRecording, 30_000), uni ham stop da clearTimeout qiling — foydalanuvchi o'zi oldinroq to'xtatgan bo'lishi mumkin. Davomiylik — stop dagi vaqtdan start dagi vaqtni ayirib (performance.now()).
Yechim
const LIMIT = 30; // soniya
let tickId = null;
let limitId = null;
let startedAt = 0;
function formatTime(sec) {
const minutes = Math.floor(sec / 60);
return `${minutes}:${String(sec % 60).padStart(2, "0")}`;
}
function onRecordingStart() {
startedAt = performance.now();
status.textContent = `0:00 / ${formatTime(LIMIT)}`;
tickId = setInterval(() => {
const sec = Math.floor((performance.now() - startedAt) / 1000);
status.textContent = `${formatTime(sec)} / ${formatTime(LIMIT)}`;
}, 1000);
limitId = setTimeout(stopRecording, LIMIT * 1000);
}
function onRecordingStop(blob) {
clearInterval(tickId);
clearTimeout(limitId);
const seconds = (performance.now() - startedAt) / 1000;
const shown = seconds.toFixed(1).replace(".", ",");
status.textContent = `Yozildi: ${shown} soniya, `
+ `${Math.round(blob.size / 1024)} KB`;
}onRecordingStart ni recorder.start() dan keyin, onRecordingStop(blob) ni stop hodisasida Blob yig'ilgach chaqiring. Ikki taymer ikki xil ish qiladi: setInterval — ko'rsatish, setTimeout — cheklov. Ikkalasini ham tozalash shart: aks holda 30 soniyadan keyin allaqachon to'xtagan yozuvchida stop() chaqiriladi va InvalidStateError chiqadi. Davomiylikni setInterval sanog'idan emas, performance.now() farqidan olamiz — taymer kechikishi mumkin (Taymerlar).
4-mashq: Amaliy tajriba — fokus taymeri
vazifalar bilan ishlash uchun fokus taymeri: kurs/mashqlar/13/18-fokus/index.html. «25 daqiqa» tugmasi taymerni boshlaydi (sinash uchun 5 soniya qiling). Qolgan vaqt sahifada ko'rinadi. Tugaganda Web Audio bilan ikki notali signal chalinadi va sahifa sarlavhasi (document.title) "Vaqt tugadi!" bo'ladi — boshqa tabda ishlayotgan foydalanuvchi ham ko'rsin. Live Server bilan oching.
Yechim
<button id="boshla" type="button">25 daqiqa</button>
<p id="qoldi" role="timer">25:00</p>
<script>
const DURATION_SEC = 5; // sinash uchun 5 s; haqiqiysi 25 * 60
const ctx = new AudioContext();
const remaining = document.getElementById("qoldi");
function playSignal() {
const now = ctx.currentTime;
[660, 880].forEach((frequency, i) => {
const osc = new OscillatorNode(ctx, { frequency });
const gain = new GainNode(ctx, { gain: 0 });
osc.connect(gain).connect(ctx.destination);
const t = now + i * 0.3;
gain.gain.setValueAtTime(0, t);
gain.gain.linearRampToValueAtTime(0.3, t + 0.01);
gain.gain.exponentialRampToValueAtTime(0.001, t + 0.3);
osc.start(t);
osc.stop(t + 0.3);
});
}
document.getElementById("boshla").addEventListener("click",
async () => {
await ctx.resume(); // bosish — ruxsat shu yerda olinadi
const endTime = performance.now() + DURATION_SEC * 1000;
const id = setInterval(() => {
const s = Math.max(0,
Math.ceil((endTime - performance.now()) / 1000));
remaining.textContent = `${Math.floor(s / 60)}:`
+ String(s % 60).padStart(2, "0");
if (s === 0) {
clearInterval(id);
document.title = "Vaqt tugadi!";
playSignal();
}
}, 250);
});
</script>Asosiy hiyla — ctx.resume() tugma bosilganda. 25 daqiqadan keyin signal chalinadigan paytda foydalanuvchi hech narsa bosmaydi, lekin kontekst allaqachon running — autoplay siyosati to'sqinlik qilmaydi. Vaqt performance.now() bilan hisoblanadi: yashirin tabda setInterval sekinlashsa ham (requestAnimationFrame darsidagi tajriba), qolgan vaqt to'g'ri ko'rinadi.
git add 13/18-kamera 13/18-ovozli-bron 13/18-fokus
git commit -m "13/18: kamera, ovozli bron va fokus taymeri"9. Real ishda
- Video qo'ng'iroqlar. Google Meet, Zoom'ning brauzer versiyasi va Telegram Web qo'ng'iroqlari —
getUserMedia+ WebRTC (oqimni boshqa kompyuterga to'g'ridan-to'g'ri yuborish texnologiyasi). WebRTC — kursdan tashqarida, lekin uning birinchi qadami shu darsda. - Ovozli xabarlar va diktovka. Telegram Web'dagi ovozli xabar —
MediaRecorder. Nutqni matnga aylantiruvchi xizmatlar ham avval shu yo'l bilan ovozni yozib oladi. - QR va hujjat skanerlari. Bank ilovalari, yetkazib berish xizmatlari kameradan kadr olib, canvas'da QR kodni o'qiydi.
- Ovoz effektlari va musiqa. Brauzer o'yinlari va onlayn musiqa asboblari Web Audio grafida ishlaydi; Tone.js kabi kutubxonalar uni qulay qiladi.
- Intervyu. "
getUserMedianegahttpda ishlamaydi?", "Kamerani qanday to'g'ri o'chirasiz?", "AudioContextnegasuspended?" — uchraydigan savollar.
Xulosa
- Web Audio — ovoz grafi: manba (
OscillatorNode) →GainNode→destination; vaqtctx.currentTimebo'yicha rejalashtiriladi. AudioContexttugma bosilgunchasuspended—await ctx.resume(). Ossillyator bir martalik; so'nish0.001gacha.getUserMedia(constraints)→ ruxsat oynasi →MediaStream; faqat HTTPS yokilocalhost; natija oynasida yopiq.- Oqim —
video.srcObject; surat — canvas'gadrawImage(video); tugagach — har trekdastop(). MediaRecorder:start→dataavailablebo'laklari →stop→Blob; turi —recorder.mimeType.- Ruxsatni faqat aniq harakatda so'rang, rad etilsa — tushunarli yo'l-yo'riq bering.
Keyingi dars: Web Workers — og'ir hisobni alohida thread'ga chiqarib, sahifani qotirmaslik: postMessage, structured clone, transferable va module worker.
Manbalar
- W3C: "Web Audio API", "Media Capture and Streams" (
getUserMedia), "MediaStream Recording" — w3.org - MDN: "Using the Web Audio API",
MediaDevices.getUserMedia(),MediaRecorder— developer.mozilla.org - web.dev: "Capturing audio and video in HTML5" — web.dev
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!