المقدّمة
توفّر إضافات مصدر الوسائط (MSE) إمكانية ممتدّة للتخزين المؤقت والتحكم في التشغيل لعناصر HTML5 <audio>
و<video>
. لقد طوّرنا في الأصل لتسهيل استخدام مشغّلات الفيديو المستندة إلى البث الديناميكي الديناميكي عبر HTTP (DASH)، غير أنّنا سنرى أدناه كيفية استخدام هذه مشغّلات الصوت، وخاصةً
للتشغيل بدون فجوات.
من المحتمل أنّك استمعت إلى ألبوم موسيقي تضمّنت فيه الأغاني بسلاسة، وقد تستمع حتى إلى ألبوم حالي. يقدّم الفنانون تجارب تشغيل سلسة كخيار فني وكجزء من تسجيلات الفينيل والأقراص المدمجة التي تمت فيها كتابة الصوت كبث متواصل. بسبب طريقة عمل برامج ترميز الصوت الحديثة مثل MP3 وAAC، غالبًا ما يتم فقدان هذه التجربة السمعية السلسة في أيامنا هذه.
سندخل في تفاصيل السبب أدناه، ولكن في الوقت الحالي، لنبدأ بعرض توضيحي. يمكنك العثور أدناه على أول ثلاثين ثانية من ملف Sintel الممتاز الذي تم تقطيعه إلى خمسة ملفات MP3 منفصلة ثم إعادة تجميعه باستخدام الخطأ MSE في مشروع Google. تشير الخطوط الحمراء إلى الفجوات التي حدثت أثناء إنشاء (ترميز) كل MP3؛ ستسمع مواطن خلل في هذه النقاط.
يا للقرف! لم تكن هذه تجربة رائعة، لذا يمكننا تحقيقها بشكل أفضل. مع بذل مجهود أكبر، وباستخدام ملفات MP3 نفسها في العرض التوضيحي أعلاه، يمكننا استخدام الخطأ التربيعي المتوسط لإزالة هذه الثغرات المزعجة. تشير الخطوط الخضراء في العرض التوضيحي التالي إلى مكان ضم الملفات وإزالة الثغرات. في Chrome 38+، سيتم تشغيل هذا بسلاسة!
هناك طرق مختلفة لإنشاء محتوى بلا فجوات. لأغراض هذا العرض التوضيحي، سنركز على نوع الملفات التي قد يكون المستخدم العادي فيها. حيث يتم ترميز كل ملف بشكل منفصل بدون مراعاة مقاطع الصوت قبله أو بعده.
الإعداد الأساسي
أولاً، لنتراجع عن عملية الإعداد الأساسية لمثيل MediaSource
ونتناولها بالتفصيل.
إضافات مصدر الوسائط، كما يشير الاسم، هي مجرد إضافات لعناصر الوسائط الحالية. في ما يلي، سنضيف علامة
Object URL
تمثّل مثيل MediaSource
في سمة المصدر لأحد العناصر
الصوتية، تمامًا كما تضبط عنوان URL عاديًا.
var audio = document.createElement('audio');
var mediaSource = new MediaSource();
var SEGMENTS = 5;
mediaSource.addEventListener('sourceopen', function () {
var sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');
function onAudioLoaded(data, index) {
// Append the ArrayBuffer data into our new SourceBuffer.
sourceBuffer.appendBuffer(data);
}
// Retrieve an audio segment via XHR. For simplicity, we're retrieving the
// entire segment at once, but we could also retrieve it in chunks and append
// each chunk separately. MSE will take care of assembling the pieces.
GET('sintel/sintel_0.mp3', function (data) {
onAudioLoaded(data, 0);
});
});
audio.src = URL.createObjectURL(mediaSource);
بعد ربط الكائن MediaSource
، سيتم بدء الإعداد
وتنشيط حدث sourceopen
في نهاية المطاف، وعندها يمكننا إنشاء
SourceBuffer
. في المثال أعلاه، ننشئ ملف audio/mpeg
يمكنه تحليل وفك ترميز مقاطع MP3، وتتوفر العديد من الأنواع الأخرى.
أشكال موجية شاذة
سنعود إلى التعليمة البرمجية بعد لحظات، ولكن لنلقِ نظرة الآن عن كثب على
الملف الذي ألحقناه للتو، على وجه التحديد في نهايته. يظهر أدناه رسم بياني
لآخر 3, 000 عيّنة تم تقييمها في المتوسط على كِلا القناتين من المقطع الصوتي
sintel_0.mp3
. كل بكسل على الخط الأحمر هو عينة نقطة عائمة
في نطاق [-1.0, 1.0]
.
ما السبب في جميع هذه النماذج الصفرية (الصامتة)؟ إنها في الواقع ترجع إلى عناصر الضغط التي تم تقديمها أثناء عملية الترميز. يقدّم كل برنامج ترميز تقريبًا نوعًا من المساحة المتروكة. في هذه الحالة، أضافت LAME 576 عينة مساحة متروكة بالضبط في نهاية الملف.
بالإضافة إلى المساحة المتروكة في النهاية، تمت أيضًا إضافة مساحة متروكة لكل ملف في البداية. إذا ألقينا نظرة خاطفة على المسار
sintel_1.mp3
،
سنرى 576 عينة أخرى من المساحة المتروكة في المقدمة. ويختلف حجم المساحة المتروكة حسب برنامج الترميز والمحتوى، لكننا نعرف القيم الدقيقة استنادًا إلى metadata
المضمّنة في كل ملف.
أقسام الصمت في بداية ونهاية كل ملف هي ما تسبب في حدوث أعطال بين المقاطع في الإصدار التجريبي السابق. ولتشغيل الفيديو بسلاسة، يجب
إزالة أقسام الصمت هذه. لحسن الحظ، يمكن إجراء ذلك بسهولة باستخدام
MediaSource
. سنُعدّل أدناه طريقة onAudioLoaded()
لاستخدام نافذة إضافة وإزاحة
الطابع الزمني لإزالة كتم الصوت هذا.
مثال التعليمة البرمجية
function onAudioLoaded(data, index) {
// Parsing gapless metadata is unfortunately non trivial and a bit messy, so
// we'll glaze over it here; see the appendix for details.
// ParseGaplessData() will return a dictionary with two elements:
//
// audioDuration: Duration in seconds of all non-padding audio.
// frontPaddingDuration: Duration in seconds of the front padding.
//
var gaplessMetadata = ParseGaplessData(data);
// Each appended segment must be appended relative to the next. To avoid any
// overlaps, we'll use the end timestamp of the last append as the starting
// point for our next append or zero if we haven't appended anything yet.
var appendTime = index > 0 ? sourceBuffer.buffered.end(0) : 0;
// Simply put, an append window allows you to trim off audio (or video) frames
// which fall outside of a specified time range. Here, we'll use the end of
// our last append as the start of our append window and the end of the real
// audio data for this segment as the end of our append window.
sourceBuffer.appendWindowStart = appendTime;
sourceBuffer.appendWindowEnd = appendTime + gaplessMetadata.audioDuration;
// The timestampOffset field essentially tells MediaSource where in the media
// timeline the data given to appendBuffer() should be placed. I.e., if the
// timestampOffset is 1 second, the appended data will start 1 second into
// playback.
//
// MediaSource requires that the media timeline starts from time zero, so we
// need to ensure that the data left after filtering by the append window
// starts at time zero. We'll do this by shifting all of the padding we want
// to discard before our append time (and thus, before our append window).
sourceBuffer.timestampOffset =
appendTime - gaplessMetadata.frontPaddingDuration;
// When appendBuffer() completes, it will fire an updateend event signaling
// that it's okay to append another segment of media. Here, we'll chain the
// append for the next segment to the completion of our current append.
if (index == 0) {
sourceBuffer.addEventListener('updateend', function () {
if (++index < SEGMENTS) {
GET('sintel/sintel_' + index + '.mp3', function (data) {
onAudioLoaded(data, index);
});
} else {
// We've loaded all available segments, so tell MediaSource there are no
// more buffers which will be appended.
mediaSource.endOfStream();
URL.revokeObjectURL(audio.src);
}
});
}
// appendBuffer() will now use the timestamp offset and append window settings
// to filter and timestamp the data we're appending.
//
// Note: While this demo uses very little memory, more complex use cases need
// to be careful about memory usage or garbage collection may remove ranges of
// media in unexpected places.
sourceBuffer.appendBuffer(data);
}
شكل موجي سلس
لنرى ما حققته التعليمة البرمجية الجديدة اللامعة من خلال إلقاء نظرة أخرى على الشكل الموجي بعد تطبيق نوافذ الملحق. نلاحظ أدناه أنّه تمت إزالة
القسم الصامت في نهاية
sintel_0.mp3
(باللون الأحمر) والقسم الصامت في بداية
sintel_1.mp3
(باللون الأزرق)، ما أتاح لنا انتقالاً سلسًا بين
الشرائح.
الخاتمة
وبهذا، انتهينا من ربط جميع الأقسام الخمسة بسلاسة في قسم واحد،
ووصلنا لاحقًا إلى نهاية العرض التوضيحي. قبل الانتهاء، ربما لاحظت أنّ طريقة onAudioLoaded()
لا تأخذ في الاعتبار الحاويات أو برامج الترميز.
هذا يعني أنّ كل هذه التقنيات ستعمل بغض النظر عن الحاوية أو نوع برنامج الترميز. يمكنك أدناه إعادة تشغيل العرض التوضيحي الأصلي بتنسيق DASH بتنسيق MP4
بدلاً من MP3.
إذا كنت تريد معرفة المزيد من المعلومات، يُرجى مراجعة الملحقات أدناه للاطّلاع على معلومات مفصّلة حول
إنشاء المحتوى وتحليل البيانات الوصفية بسلاسة. ويمكنك أيضًا استكشاف
gapless.js
لإلقاء نظرة فاحصة على
الرمز الذي يشغّل هذا العرض التوضيحي.
شكرًا على القراءة.
ملحق أ: إنشاء محتوى خالٍ من الأخطاء
قد يكون من الصعب إنشاء محتوى بلا فجوات. سنتحدّث أدناه عن طريقة إنشاء وسائط Sintel المستخدَمة في هذا العرض التوضيحي. للبدء، ستحتاج إلى نسخة من مقطع صوتي FLAC بدون فقدان بيانات لتقنية Sintel للأجيال القادمة، يُرجى استخدام خوارزمية SHA1 أدناه. للحصول على أدوات، ستحتاج إلى FFmpeg وMP4Box وLAME وتثبيت نظام التشغيل OSX مع afconversion.
unzip Jan_Morgenstern-Sintel-FLAC.zip
sha1sum 1-Snow_Fight.flac
# 0535ca207ccba70d538f7324916a3f1a3d550194 1-Snow_Fight.flac
أولاً، سنقسّم أول 31.5 ثانية من المقطع الصوتي 1-Snow_Fight.flac
. نريد أيضًا إضافة تأثير التلاشي التدريجي لمدة 2.5 ثانية بدءًا من 28 ثانية لتجنب تلقي أي نقرات بعد انتهاء عملية التشغيل. باستخدام سطر أوامر FFmpeg أدناه، يمكننا
تحقيق كل ذلك ووضع النتائج في sintel.flac
.
ffmpeg -i 1-Snow_Fight.flac -t 31.5 -af "afade=t=out:st=28:d=2.5" sintel.flac
بعد ذلك، سنقسّم الملف إلى 5 ملفات موجة
تبلغ مدة كل ملف منها 6.5 ثانية، ومن الأسهل استخدام الموجة لأنّ كل برنامج ترميز تقريبًا
يتيح عرض هذه الملفات. مرة أخرى، يمكننا إجراء ذلك تحديدًا باستخدام FFmpeg، وبعد ذلك سنحصل على: sintel_0.wav
وsintel_1.wav
وsintel_2.wav
وsintel_3.wav
وsintel_4.wav
.
ffmpeg -i sintel.flac -acodec pcm_f32le -map 0 -f segment \
-segment_list out.list -segment_time 6.5 sintel_%d.wav
بعد ذلك، لنقم بإنشاء ملفات MP3. يضم LAME عدة خيارات
لإنشاء محتوى خالٍ من الثقوب. إذا كنت تتحكّم في المحتوى، يمكنك استخدام --nogap
مع ترميز مُجمَّع لجميع الملفات لتجنُّب المساحة المتروكة بين المقاطع تمامًا.
لأغراض هذا العرض التوضيحي، نريد هذه المساحة المتروكة، ولذلك سنستخدم ترميز VBR قياسيًا وعالي الجودة لملفات الموجات.
lame -V=2 sintel_0.wav sintel_0.mp3
lame -V=2 sintel_1.wav sintel_1.mp3
lame -V=2 sintel_2.wav sintel_2.mp3
lame -V=2 sintel_3.wav sintel_3.mp3
lame -V=2 sintel_4.wav sintel_4.mp3
هذا كل ما هو ضروري لإنشاء ملفات MP3. الآن دعونا نتناول إنشاء ملفات MP4 المجزأة. وسنتّبع توجيهات Apple لإنشاء وسائط تم إتقانها لـ iTunes. في ما يلي، سنحول ملفات الموجات إلى ملفات CAF متوسطة، وفقًا للتعليمات، قبل ترميزها كملفات AAC في حاوية MP4 باستخدام المعلَمات المقترَحة.
afconvert sintel_0.wav sintel_0_intermediate.caf -d 0 -f caff \
--soundcheck-generate
afconvert sintel_1.wav sintel_1_intermediate.caf -d 0 -f caff \
--soundcheck-generate
afconvert sintel_2.wav sintel_2_intermediate.caf -d 0 -f caff \
--soundcheck-generate
afconvert sintel_3.wav sintel_3_intermediate.caf -d 0 -f caff \
--soundcheck-generate
afconvert sintel_4.wav sintel_4_intermediate.caf -d 0 -f caff \
--soundcheck-generate
afconvert sintel_0_intermediate.caf -d aac -f m4af -u pgcm 2 --soundcheck-read \
-b 256000 -q 127 -s 2 sintel_0.m4a
afconvert sintel_1_intermediate.caf -d aac -f m4af -u pgcm 2 --soundcheck-read \
-b 256000 -q 127 -s 2 sintel_1.m4a
afconvert sintel_2_intermediate.caf -d aac -f m4af -u pgcm 2 --soundcheck-read \
-b 256000 -q 127 -s 2 sintel_2.m4a
afconvert sintel_3_intermediate.caf -d aac -f m4af -u pgcm 2 --soundcheck-read \
-b 256000 -q 127 -s 2 sintel_3.m4a
afconvert sintel_4_intermediate.caf -d aac -f m4af -u pgcm 2 --soundcheck-read \
-b 256000 -q 127 -s 2 sintel_4.m4a
لدينا الآن العديد من ملفات M4A التي نحتاج إلى
تقسيمها
بشكل مناسب قبل استخدامها مع
MediaSource
. لأغراضنا، سنستخدم حجم جزء يبلغ ثانية واحدة. سيكتب MP4Box
كل ملف MP4 مجزأ على هيئة sintel_#_dashinit.mp4
بالإضافة إلى
بيان MPEG-DASH (sintel_#_dash.mpd
) الذي يمكن تجاهله.
MP4Box -dash 1000 sintel_0.m4a && mv sintel_0_dashinit.mp4 sintel_0.mp4
MP4Box -dash 1000 sintel_1.m4a && mv sintel_1_dashinit.mp4 sintel_1.mp4
MP4Box -dash 1000 sintel_2.m4a && mv sintel_2_dashinit.mp4 sintel_2.mp4
MP4Box -dash 1000 sintel_3.m4a && mv sintel_3_dashinit.mp4 sintel_3.mp4
MP4Box -dash 1000 sintel_4.m4a && mv sintel_4_dashinit.mp4 sintel_4.mp4
rm sintel_{0,1,2,3,4}_dash.mpd
أكملت هذه الخطوة. لدينا الآن ملفات MP4 وMP3 مجزّأة تتضمن البيانات الوصفية الصحيحة واللازمة للتشغيل بدون أي فجوات. راجع الملحق "ب" للحصول على مزيد من التفاصيل حول شكل بيانات التعريف هذه.
ملحق ب: تحليل بيانات التعريف الناقصة
تمامًا مثل إنشاء محتوى خالٍ من الثقوب، قد يكون تحليل البيانات الوصفية الخالية من الأخطاء أمرًا صعبًا نظرًا لعدم وجود طريقة قياسية للتخزين. سنتعرف في ما يلي على كيفية تخزين برنامجي التشفير الأكثر
شيوعًا، وهما LAME وiTunes، بياناتهما الوصفية الخالية من الأخطاء. لنبدأ بإعداد بعض طرق المساعدة
ومخطط تفصيلي لـ ParseGaplessData()
المستخدمة
أعلاه.
// Since most MP3 encoders store the gapless metadata in binary, we'll need a
// method for turning bytes into integers. Note: This doesn't work for values
// larger than 2^30 since we'll overflow the signed integer type when shifting.
function ReadInt(buffer) {
var result = buffer.charCodeAt(0);
for (var i = 1; i < buffer.length; ++i) {
result <<= 8;
result += buffer.charCodeAt(i);
}
return result;
}
function ParseGaplessData(arrayBuffer) {
// Gapless data is generally within the first 512 bytes, so limit parsing.
var byteStr = new TextDecoder().decode(arrayBuffer.slice(0, 512));
var frontPadding = 0, endPadding = 0, realSamples = 0;
// ... we'll fill this in as we go below.
سنتناول أولاً تنسيق البيانات الوصفية على iTunes من Apple لأنها أسهل من حيث التحليل والشرح. في ملفات MP3 وM4A على iTunes (وafconversion)، اكتب قسمًا قصيرًا بترميز ASCII على النحو التالي:
iTunSMPB[ 26 bytes ]0000000 00000840 000001C0 0000000000046E00
وتتم كتابة هذا المحتوى داخل علامة ID3 ضمن حاوية MP3 وفي ملف Atom
في البيانات الوصفية داخل حاوية MP4. لأغراضنا، يمكننا تجاهل أول
رمز مميّز لـ 0000000
. الرموز المميزة الثلاثة التالية هي المساحة المتروكة الأمامية والمساحة المتروكة النهائية وإجمالي عدد العينات غير المتروكة. تقسيم كل من هذه على معدل عينة الصوت
يعطينا المدة لكل منها.
// iTunes encodes the gapless data as hex strings like so:
//
// 'iTunSMPB[ 26 bytes ]0000000 00000840 000001C0 0000000000046E00'
// 'iTunSMPB[ 26 bytes ]####### frontpad endpad real samples'
//
// The approach here elides the complexity of actually parsing MP4 atoms. It
// may not work for all files without some tweaks.
var iTunesDataIndex = byteStr.indexOf('iTunSMPB');
if (iTunesDataIndex != -1) {
var frontPaddingIndex = iTunesDataIndex + 34;
frontPadding = parseInt(byteStr.substr(frontPaddingIndex, 8), 16);
var endPaddingIndex = frontPaddingIndex + 9;
endPadding = parseInt(byteStr.substr(endPaddingIndex, 8), 16);
var sampleCountIndex = endPaddingIndex + 9;
realSamples = parseInt(byteStr.substr(sampleCountIndex, 16), 16);
}
في المقابل، ستعمل معظم برامج تشفير MP3 مفتوحة المصدر على تخزين البيانات الوصفية الخالية من الأخطاء في عنوان Xing خاص موضوع داخل إطار MPEG صامت (يكون الصوت صامتًا بحيث تعمل برامج فك الترميز التي لا تفهم رأس Xing على كتم الصوت). للأسف، هذه العلامة غير موجودة دائمًا وتحتوي على عدد من الحقول الاختيارية. لأغراض هذا العرض التوضيحي، يمكننا التحكم في الوسائط، ولكن من الناحية العملية، سيُطلب منكِ إجراء بعض عمليات التحقق من الحساسية لمعرفة متى تكون بيانات التعريف خالية من الأخطاء.
أولاً سنقوم بتحليل العدد الإجمالي للعينات. للتبسيط، سنقرأ هذا من عنوان Xing، ولكن يمكن إنشاؤه من رأس صوت MPEG العادي.
يمكن وضع علامة Xing
أو Info
على رؤوس Xing. بعد هذه العلامة، بعد هذه العلامة، هناك 32 بت تمثل إجمالي عدد الإطارات في الملف، ويؤدي ضرب هذه القيمة في عدد العينات لكل إطار إلى الحصول على إجمالي العينات في الملف.
// Xing padding is encoded as 24bits within the header. Note: This code will
// only work for Layer3 Version 1 and Layer2 MP3 files with XING frame counts
// and gapless information. See the following document for more details:
// http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header
var xingDataIndex = byteStr.indexOf('Xing');
if (xingDataIndex == -1) xingDataIndex = byteStr.indexOf('Info');
if (xingDataIndex != -1) {
// See section 2.3.1 in the link above for the specifics on parsing the Xing
// frame count.
var frameCountIndex = xingDataIndex + 8;
var frameCount = ReadInt(byteStr.substr(frameCountIndex, 4));
// For Layer3 Version 1 and Layer2 there are 1152 samples per frame. See
// section 2.1.5 in the link above for more details.
var paddedSamples = frameCount * 1152;
// ... we'll cover this below.
الآن بعد أن أصبح لدينا العدد الإجمالي للعينات، يمكننا الانتقال لقراءة عدد عينات المساحة المتروكة. واعتمادًا على برنامج الترميز الذي تستخدمه، قد تتم كتابة ذلك ضمن علامة LAME أو Lavf مضمّنة في عنوان Xing. بعد 17 بايت بالضبط من هذا الرأس، هناك 3 بايت تمثل المساحة المتروكة الأمامية والنهاية في 12 بت لكل منهما على التوالي.
xingDataIndex = byteStr.indexOf('LAME');
if (xingDataIndex == -1) xingDataIndex = byteStr.indexOf('Lavf');
if (xingDataIndex != -1) {
// See http://gabriel.mp3-tech.org/mp3infotag.html#delays for details of
// how this information is encoded and parsed.
var gaplessDataIndex = xingDataIndex + 21;
var gaplessBits = ReadInt(byteStr.substr(gaplessDataIndex, 3));
// Upper 12 bits are the front padding, lower are the end padding.
frontPadding = gaplessBits >> 12;
endPadding = gaplessBits & 0xFFF;
}
realSamples = paddedSamples - (frontPadding + endPadding);
}
return {
audioDuration: realSamples * SECONDS_PER_SAMPLE,
frontPaddingDuration: frontPadding * SECONDS_PER_SAMPLE
};
}
وبذلك، لدينا وظيفة كاملة لتحليل الغالبية العظمى من المحتوى الخالي من الأخطاء. ومع ذلك، هناك عدد كبير من الحالات الحدّية، لذلك ننصح بتوخي الحذر قبل استخدام رمز مشابه في مرحلة الإنتاج.
ملحق "ج": عند جمع القمامة
إنّ الذاكرة التي تنتمي إلى SourceBuffer
يتم جمع بيانات غير مرغوب فيها بشكل نشط وفقًا لنوع المحتوى والحدود القصوى للنظام الأساسي وموضع التشغيل الحالي. في Chrome، سيتم استعادة الذاكرة أولاً من الموارد الاحتياطية التي سبق تشغيلها.
مع ذلك، إذا تجاوز استخدام الذاكرة الحدود الخاصة بالنظام الأساسي، سيؤدي ذلك إلى إزالة الذاكرة من المخازن المؤقتة التي لم يتم تشغيلها.
عندما يصل التشغيل إلى فجوة في المخطط الزمني بسبب الذاكرة التي تم استردادها، قد يحدث خلل في إذا كانت الفجوة صغيرة بما يكفي أو قد تتوقف تمامًا إذا كانت الفجوة كبيرة جدًا. ولا تقدم أيًّا منهما تجربة مستخدم رائعة، لذلك من المهم تجنّب إلحاق الكثير من البيانات في وقت واحد وإزالة النطاقات يدويًا من المخطط الزمني للوسائط التي لم تعد ضرورية.
يمكن إزالة النطاقات باستخدام الطريقة
remove()
في كل SourceBuffer
، ويستغرق ذلك نطاق [start, end]
بالثواني.
على غرار appendBuffer()
، سينشط كل remove()
حدث updateend
بعد اكتماله. ويجب عدم إصدار عمليات إزالة أو ملحقات أخرى قبل انطلاق الحدث.
على أجهزة سطح المكتب في Chrome، يمكن الاحتفاظ بحوالي 12 ميغابايت من المحتوى الصوتي و150 ميغابايت من محتوى الفيديو في الذاكرة في وقت واحد. ينبغي ألا تعتمد على هذه القيم عبر المتصفحات أو الأنظمة الأساسية؛ فعلى سبيل المثال، إنها بالتأكيد لا تمثل الأجهزة المحمولة.
لا يؤثّر جمع البيانات غير المرغوب فيها إلا في البيانات التي تتم إضافتها إلى SourceBuffers
، وما مِن حدود لمقدار البيانات التي يمكنك الاحتفاظ بها مؤقتًا في متغيّرات JavaScript. يمكنك أيضًا إعادة إلحاق نفس البيانات في نفس الموضع إذا لزم الأمر.