البث المباشر والوسائط
يقدم البث المباشر محتوى صوتي ومرئي في الوقت الفعلي عبر الإنترنت. في هذا الدرس، سنستكشف التقنيات والتقنيات لتنفيذ بث الوسائط المباشرة في تطبيقات الويب.
مفاهيم بث الفيديو المباشر
يتضمن بث الفيديو المباشر التقاط المحتوى المرئي والصوتي وتشفيره ونقله وتشغيله بأقل تأخير. فهم خط أنابيب البث ضروري لبناء تطبيقات قوية.
خط أنابيب البث:
- الالتقاط: الحصول على الفيديو/الصوت من الكاميرا/الميكروفون
- الترميز: ضغط الوسائط باستخدام برامج الترميز (H.264، VP8، Opus)
- التعبئة: التقسيم إلى أجزاء (HLS، DASH)
- النقل: الإرسال عبر الشبكة (HTTP، WebRTC، WebSocket)
- فك الترميز: فك الضغط على جانب العميل
- التشغيل: عرض الفيديو/الصوت في المتصفح
// الوصول إلى وسائط المستخدم (الكاميرا/الميكروفون)
async function startMediaCapture() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1920 },
height: { ideal: 1080 },
frameRate: { ideal: 30 }
},
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 48000
}
});
// عرض المعاينة المحلية
const videoElement = document.getElementById('localVideo');
videoElement.srcObject = stream;
return stream;
} catch (error) {
console.error('فشل الوصول إلى أجهزة الوسائط:', error);
throw error;
}
}
// عرض الأجهزة المتاحة
async function listMediaDevices() {
const devices = await navigator.mediaDevices.enumerateDevices();
const cameras = devices.filter(d => d.kind === 'videoinput');
const microphones = devices.filter(d => d.kind === 'audioinput');
console.log('الكاميرات:', cameras);
console.log('الميكروفونات:', microphones);
return { cameras, microphones };
}
// تبديل الكاميرا
async function switchCamera(deviceId) {
const stream = await navigator.mediaDevices.getUserMedia({
video: { deviceId: { exact: deviceId } },
audio: true
});
return stream;
}
HLS (البث المباشر عبر HTTP)
HLS هو بروتوكول بث مدعوم على نطاق واسع يقسم الفيديو إلى أجزاء صغيرة قابلة للتنزيل عبر HTTP.
// بث HLS مع مكتبة hls.js
import Hls from 'hls.js';
class HLSPlayer {
constructor(videoElement) {
this.video = videoElement;
this.hls = null;
}
loadStream(streamUrl) {
if (Hls.isSupported()) {
this.hls = new Hls({
enableWorker: true,
lowLatencyMode: true,
backBufferLength: 90
});
this.hls.loadSource(streamUrl);
this.hls.attachMedia(this.video);
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
console.log('البث جاهز للتشغيل');
this.video.play();
});
this.hls.on(Hls.Events.ERROR, (event, data) => {
console.error('خطأ HLS:', data);
if (data.fatal) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
console.log('خطأ في الشبكة، محاولة الاستعادة');
this.hls.startLoad();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.log('خطأ في الوسائط، محاولة الاستعادة');
this.hls.recoverMediaError();
break;
default:
console.error('خطأ فادح، إتلاف المشغل');
this.destroy();
break;
}
}
});
} else if (this.video.canPlayType('application/vnd.apple.mpegurl')) {
// دعم HLS الأصلي (iOS Safari)
this.video.src = streamUrl;
this.video.play();
}
}
destroy() {
if (this.hls) {
this.hls.destroy();
this.hls = null;
}
}
getStats() {
if (!this.hls) return null;
return {
currentLevel: this.hls.currentLevel,
autoLevelEnabled: this.hls.autoLevelEnabled,
bandwidth: this.hls.bandwidthEstimate,
bufferLength: this.hls.media?.buffered.length,
dropped: this.hls.dropped
};
}
}
// الاستخدام
const video = document.getElementById('video');
const player = new HLSPlayer(video);
player.loadStream('https://example.com/stream.m3u8');
مزايا HLS: يعمل عبر HTTP القياسي، متوافق مع CDN، بث معدل بت تكيفي، دعم واسع للأجهزة. الكمون النموذجي: 10-30 ثانية.
DASH (البث التكيفي الديناميكي عبر HTTP)
DASH هو معيار بث معدل بت تكيفي مشابه لـ HLS، ويوفر المزيد من المرونة وبث محايد للترميز.
// بث DASH مع dash.js
import dashjs from 'dashjs';
class DASHPlayer {
constructor(videoElement) {
this.video = videoElement;
this.player = dashjs.MediaPlayer().create();
}
loadStream(streamUrl) {
this.player.initialize(this.video, streamUrl, true);
// تكوين المشغل
this.player.updateSettings({
streaming: {
lowLatencyEnabled: true,
liveDelay: 3, // الكمون المستهدف بالثواني
bufferTimeDefault: 12,
bufferTimeMax: 30
}
});
// مستمعي الأحداث
this.player.on('streamInitialized', () => {
console.log('تم تهيئة البث');
const tracks = this.player.getTracksFor('video');
console.log('مستويات الجودة المتاحة:', tracks);
});
this.player.on('qualityChangeRendered', (e) => {
console.log('تغيرت الجودة:', e.newQuality);
});
this.player.on('error', (e) => {
console.error('خطأ DASH:', e.error);
});
}
setQuality(qualityIndex) {
this.player.setQualityFor('video', qualityIndex);
}
enableAutoQuality() {
this.player.updateSettings({
streaming: {
abr: {
autoSwitchBitrate: { video: true }
}
}
});
}
getMetrics() {
return {
currentBitrate: this.player.getBitrateInfoListFor('video'),
bufferLevel: this.player.getBufferLength('video'),
droppedFrames: this.player.getMetricsFor('video')?.DroppedFrames
};
}
destroy() {
this.player.reset();
}
}
// الاستخدام
const video = document.getElementById('video');
const player = new DASHPlayer(video);
player.loadStream('https://example.com/stream.mpd');
بث الوسائط مع WebRTC
يوفر WebRTC بثًا بزمن انتقال منخفض للغاية (أقل من ثانية) مثالي للاتصال في الوقت الفعلي والتطبيقات التفاعلية.
// بث WebRTC من نظير إلى نظير
class WebRTCStreamer {
constructor(socket) {
this.socket = socket;
this.peerConnection = null;
this.localStream = null;
}
async startStreaming() {
// الحصول على الوسائط المحلية
this.localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// إنشاء اتصال نظير
this.peerConnection = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:turn.example.com:3478',
username: 'user',
credential: 'pass'
}
]
});
// إضافة مسارات البث المحلي
this.localStream.getTracks().forEach(track => {
this.peerConnection.addTrack(track, this.localStream);
});
// معالجة مرشحي ICE
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
this.socket.emit('ice-candidate', {
candidate: event.candidate
});
}
};
// إنشاء وإرسال العرض
const offer = await this.peerConnection.createOffer();
await this.peerConnection.setLocalDescription(offer);
this.socket.emit('offer', {
sdp: this.peerConnection.localDescription
});
// معالجة الرد
this.socket.on('answer', async (data) => {
await this.peerConnection.setRemoteDescription(
new RTCSessionDescription(data.sdp)
);
});
// معالجة مرشحي ICE البعيدة
this.socket.on('ice-candidate', async (data) => {
await this.peerConnection.addIceCandidate(
new RTCIceCandidate(data.candidate)
);
});
}
async receiveStream() {
this.peerConnection = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }
]
});
// معالجة المسارات الواردة
this.peerConnection.ontrack = (event) => {
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = event.streams[0];
};
// معالجة مرشحي ICE
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
this.socket.emit('ice-candidate', {
candidate: event.candidate
});
}
};
// معالجة العرض
this.socket.on('offer', async (data) => {
await this.peerConnection.setRemoteDescription(
new RTCSessionDescription(data.sdp)
);
const answer = await this.peerConnection.createAnswer();
await this.peerConnection.setLocalDescription(answer);
this.socket.emit('answer', {
sdp: this.peerConnection.localDescription
});
});
// معالجة مرشحي ICE البعيدة
this.socket.on('ice-candidate', async (data) => {
await this.peerConnection.addIceCandidate(
new RTCIceCandidate(data.candidate)
);
});
}
stopStreaming() {
if (this.localStream) {
this.localStream.getTracks().forEach(track => track.stop());
}
if (this.peerConnection) {
this.peerConnection.close();
}
}
}
// الاستخدام
const socket = io();
const streamer = new WebRTCStreamer(socket);
// بدء البث
document.getElementById('startBtn').addEventListener('click', () => {
streamer.startStreaming();
});
// مشاهدة البث
document.getElementById('watchBtn').addEventListener('click', () => {
streamer.receiveStream();
});
اعتبارات WebRTC: بينما يوفر WebRTC كمونًا منخفضًا للغاية (<500 مللي ثانية)، فإنه يتطلب بنية تحتية لخادم الإشارات، وخوادم TURN لاجتياز NAT، ولا يتوسع بشكل جيد مثل البث المستند إلى HTTP للجماهير الكبيرة.
بث الصوت
يتبع بث الصوت مبادئ مماثلة ولكن مع متطلبات نطاق ترددي أقل ومعالجة أبسط.
// بث صوتي في الوقت الفعلي مع Web Audio API
class AudioStreamer {
constructor(socket) {
this.socket = socket;
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
this.mediaStream = null;
this.processor = null;
}
async startStreaming() {
// الحصول على الوصول إلى الميكروفون
this.mediaStream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
});
// إنشاء خط أنابيب معالجة الصوت
const source = this.audioContext.createMediaStreamSource(this.mediaStream);
this.processor = this.audioContext.createScriptProcessor(4096, 1, 1);
this.processor.onaudioprocess = (e) => {
const audioData = e.inputBuffer.getChannelData(0);
// التحويل إلى 16 بت PCM
const pcmData = this.float32To16BitPCM(audioData);
// الإرسال إلى الخادم
this.socket.emit('audio-data', pcmData);
};
source.connect(this.processor);
this.processor.connect(this.audioContext.destination);
}
float32To16BitPCM(float32Array) {
const buffer = new ArrayBuffer(float32Array.length * 2);
const view = new DataView(buffer);
for (let i = 0; i < float32Array.length; i++) {
const s = Math.max(-1, Math.min(1, float32Array[i]));
view.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
}
return buffer;
}
async playReceivedAudio() {
const audioQueue = [];
let isPlaying = false;
this.socket.on('audio-data', (arrayBuffer) => {
audioQueue.push(arrayBuffer);
if (!isPlaying) {
this.playNextChunk(audioQueue);
isPlaying = true;
}
});
}
playNextChunk(queue) {
if (queue.length === 0) return;
const arrayBuffer = queue.shift();
this.audioContext.decodeAudioData(arrayBuffer, (audioBuffer) => {
const source = this.audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(this.audioContext.destination);
source.start();
source.onended = () => {
this.playNextChunk(queue);
};
});
}
stop() {
if (this.mediaStream) {
this.mediaStream.getTracks().forEach(track => track.stop());
}
if (this.processor) {
this.processor.disconnect();
}
}
}
MediaSource API
تسمح MediaSource API لـ JavaScript بإنشاء تدفقات وسائط للتشغيل، مما يتيح حلول بث مخصصة.
// بث مخصص مع MediaSource API
class CustomStreamer {
constructor(videoElement) {
this.video = videoElement;
this.mediaSource = new MediaSource();
this.sourceBuffer = null;
this.queue = [];
}
initialize() {
this.video.src = URL.createObjectURL(this.mediaSource);
this.mediaSource.addEventListener('sourceopen', () => {
const mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
if (!MediaSource.isTypeSupported(mimeCodec)) {
console.error('برنامج الترميز غير مدعوم');
return;
}
this.sourceBuffer = this.mediaSource.addSourceBuffer(mimeCodec);
this.sourceBuffer.addEventListener('updateend', () => {
if (this.queue.length > 0 && !this.sourceBuffer.updating) {
this.sourceBuffer.appendBuffer(this.queue.shift());
}
});
});
}
appendSegment(arrayBuffer) {
if (this.sourceBuffer.updating || this.queue.length > 0) {
this.queue.push(arrayBuffer);
} else {
this.sourceBuffer.appendBuffer(arrayBuffer);
}
}
endStream() {
if (this.mediaSource.readyState === 'open') {
this.mediaSource.endOfStream();
}
}
}
// جلب وبث الفيديو
async function streamVideo(url) {
const video = document.getElementById('video');
const streamer = new CustomStreamer(video);
streamer.initialize();
const response = await fetch(url);
const reader = response.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) {
streamer.endStream();
break;
}
streamer.appendSegment(value);
}
}
بث معدل البت التكيفي
يضبط بث معدل البت التكيفي جودة الفيديو تلقائيًا بناءً على ظروف الشبكة وقدرات الجهاز.
// تنفيذ منطق معدل البت التكيفي
class AdaptiveBitrateController {
constructor(player) {
this.player = player;
this.bandwidthHistory = [];
this.qualityLevels = [
{ bitrate: 500000, width: 640, height: 360, label: '360p' },
{ bitrate: 1000000, width: 854, height: 480, label: '480p' },
{ bitrate: 2500000, width: 1280, height: 720, label: '720p' },
{ bitrate: 5000000, width: 1920, height: 1080, label: '1080p' }
];
this.currentQuality = 1;
}
measureBandwidth() {
// تقدير النطاق الترددي بناءً على سرعة التنزيل
const downloadSize = this.player.getLastSegmentSize();
const downloadTime = this.player.getLastSegmentDownloadTime();
const bandwidth = (downloadSize * 8) / downloadTime; // بت في الثانية
this.bandwidthHistory.push(bandwidth);
// احتفظ بآخر 5 قياسات
if (this.bandwidthHistory.length > 5) {
this.bandwidthHistory.shift();
}
return this.getAverageBandwidth();
}
getAverageBandwidth() {
const sum = this.bandwidthHistory.reduce((a, b) => a + b, 0);
return sum / this.bandwidthHistory.length;
}
selectOptimalQuality() {
const bandwidth = this.measureBandwidth();
const bufferLevel = this.player.getBufferLevel();
// نهج متحفظ: استخدم 80٪ من النطاق الترددي
const targetBitrate = bandwidth * 0.8;
// النظر في مستوى المخزن المؤقت
let selectedQuality = 0;
for (let i = 0; i < this.qualityLevels.length; i++) {
if (this.qualityLevels[i].bitrate <= targetBitrate) {
selectedQuality = i;
}
}
// لا تتحول إلى أعلى إذا كان المخزن المؤقت منخفضًا
if (bufferLevel < 5 && selectedQuality > this.currentQuality) {
return this.currentQuality;
}
// التحول إلى أسفل فورًا إذا كان المخزن المؤقت حرجًا
if (bufferLevel < 2 && this.currentQuality > 0) {
return this.currentQuality - 1;
}
this.currentQuality = selectedQuality;
return selectedQuality;
}
adjustQuality() {
const optimalQuality = this.selectOptimalQuality();
if (optimalQuality !== this.currentQuality) {
console.log(
`تبديل الجودة: ${this.qualityLevels[this.currentQuality].label} -> ${this.qualityLevels[optimalQuality].label}`
);
this.player.setQuality(optimalQuality);
}
}
start() {
setInterval(() => {
this.adjustQuality();
}, 5000); // التحقق كل 5 ثوانٍ
}
}
// الاستخدام
const controller = new AdaptiveBitrateController(player);
controller.start();
مقارنة البث:
- HLS: كمون 10-30 ثانية، قابلية توسع ممتازة، نظام Apple البيئي
- DASH: كمون 10-30 ثانية، محايد للترميز، معيار صناعي
- HLS/DASH منخفض الكمون: كمون 3-5 ثوانٍ، بروتوكولات أحدث
- WebRTC: كمون <500 مللي ثانية، من نظير إلى نظير، نطاق محدود
- WebSocket: كمون <2 ثانية، تنفيذ مخصص، نطاق متوسط
تمرين تطبيقي:
- ابنِ مشغل فيديو HLS مع اختيار الجودة وضوابط التشغيل
- نفذ تطبيق دردشة فيديو WebRTC مع مشاركة الشاشة
- أنشئ تطبيق بث صوتي مباشر مع تأثيرات في الوقت الفعلي
- ابنِ متحكم معدل بت تكيفي يبدل الجودة بناءً على ظروف الشبكة
- نفذ حل بث فيديو مخصص باستخدام MediaSource API و WebSockets