إضافات مصدر الوسائط للصوت

Dale Curtis
Dale Curtis

مقدمة

توفّر إضافات مصدر الوسائط (MSE) إمكانية التخزين المؤقت الموسّع والتحكّم في التشغيل لعنصرَي HTML5 <audio> و<video>. على الرغم من أنّه تم تطوير هذه الميزة في الأصل لتسهيل استخدام مشغّلات الفيديو المستندة إلى البث الديناميكي التكيُّفي عبر HTTP (DASH)، سنوضّح أدناه كيفية استخدامها للمحتوى الصوتي، وتحديدًا لتشغيل المحتوى بدون انقطاع.

من المرجّح أنّك استمعت إلى ألبوم موسيقي تتدفق فيه الأغاني بسلاسة بين الأغاني، وقد تكون تستمع إلى ألبوم الآن. يقدّم الفنّانون تجارب تشغيل بدون فواصل هذه كخيار فني وطريقة لعرض محتوى أسطوانات الفينيل والأقراص المدمجة التي تم فيها تسجيل الصوت في بثّ واحد مستمر. ولكن بسبب طريقة عمل برامج ترميز الصوت الحديثة، مثل MP3 وAAC، غالبًا ما لا تتوفر هذه التجربة الصوتية السلسة.

سنوضّح سبب ذلك أدناه، ولكن لنبدأ الآن بعرض توضيحي. في ما يلي أوّل ثلاثين ثانية من فيلم Sintel الرائع الذي تم تقطيعه إلى خمسة ملفات MP3 منفصلة وتم تجميعها باستخدام MSE. تشير الخطوط الحمراء إلى الفجوات التي تمّت أثناء إنشاء (ترميز) كلّ ملف MP3، وستسمع مشاكل في هذه النقاط.

الإصدار التجريبي

يا للعجب! هذه تجربة غير رائعة، ويمكننا تحسينها. باستخدام ملفات MP3 نفسها في العرض الترويجي أعلاه، يمكننا استخدام MSE لإزالة هذه الفجوات المزعجة. تشير الخطوط الخضراء في العرض الترويجي التالي إلى مواضع دمج الملفات وإزالة الفجوات. سيتم تشغيل المحتوى بسلاسة على الإصدار 38 من Chrome والإصدارات الأحدث.

الإصدار التجريبي

هناك طرق متنوعة لإنشاء محتوى بدون انقطاع. لأغراض هذا العرض التوضيحي، سنركّز على نوع الملفات التي قد يمتلكها المستخدم العادي. حيث تم ترميز كل ملف بشكل منفصل بدون مراعاة المقاطع الصوتية قبله أو بعده.

الإعداد الأساسي

أولاً، لنلِعِد إلى الوراء ونتناول الإعداد الأساسي مثيل MediaSource. كما يوحي الاسم، فإنّ "إضافات مصادر الوسائط" هي مجرد إضافات إلى عناصر الوسائط الحالية. في ما يلي، سنحدّد Object URL، الذي يمثّل مثيل MediaSource، لسمة source في عنصر صوت، تمامًا كما يمكنك ضبط عنوان 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].

نهاية sintel_0.mp3

ما هو سبب توفّر كل هذه العيّنات الصفرية (الصامتة)؟ ويعود سبب ظهورها إلى العناصر الناتجة عن الضغط التي تمّ إدخالها أثناء عملية التشفير. يُضيف كلّ برنامج ترميز تقريبًا نوعًا من الحشو. في هذه الحالة، أضاف LAME 576 عيّنة من المحتوى المُضاف في نهاية الملف.

بالإضافة إلى الحشو في النهاية، تمت إضافة حشو إلى بداية كل ملف. إذا ألقينا نظرة على المقطع الصوتي sintel_1.mp3، سنرى 576 عيّنة أخرى من الحشو في المقدّمة. يختلف مقدار الحشو حسب برنامج الترميز والمحتوى، ولكننا نعرف القيم الدقيقة استنادًا إلى metadata المضمّنة في كل ملف.

بداية sintel_1.mp3

بداية sintel_1.mp3

إنّ أقسام الصمت في بداية كل ملف ونهايته هي السبب في حدوث الأعطال بين المقاطع في العرض التجريبي السابق. لتحقيق تشغيل بدون انقطاع، علينا إزالة هذه الأقسام من الصمت. لحسن الحظ، يمكن إجراء ذلك بسهولة باستخدام 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 (باللون الأزرق)، ما أدّى إلى إجراء انتقال سلس بين المقاطع.

دمج sintel_0.mp3 وsintel_1.mp3

الخاتمة

بعد ذلك، تم دمج جميع المقاطع الخمسة بسلاسة في مقطع واحد، ووصلنا إلى نهاية العرض التوضيحي. قبل إنهاء المحادثة، نريد أن نشير إلى أنّ طريقة onAudioLoaded() لا تراعي الحاويات أو برامج الترميز. وهذا يعني أنّ جميع هذه الأساليب ستعمل بغض النظر عن نوع الحاوية أو برنامج الترميز. يمكنك أدناه إعادة تشغيل الإصدار التجريبي الأصلي من ملف MP4 المجزّأ والمتوافق مع DASH بدلاً من MP3.

الإصدار التجريبي

إذا أردت معرفة المزيد من المعلومات، يمكنك الاطّلاع على الملحقات أدناه للحصول على نظرة أعمق على إنشاء المحتوى بدون انقطاع وتحليل البيانات الوصفية. يمكنك أيضًا استكشاف gapless.js للاطّلاع عن كثب على الرمز البرمجي الذي يشغّل هذا العرض التقديمي.

شكرًا على القراءة.

الملحق أ: إنشاء محتوى بدون انقطاع

قد يكون من الصعب إنشاء محتوى بدون انقطاع. في ما يلي خطوات إنشاء الوسائط Sintel المستخدَمة في هذا العرض الترويجي. أولاً، ستحتاج إلى نسخة من الأغنية الصوتية بتنسيق FLAC بدون فقدان البيانات لفيلم Sintel. وإليك أدناه دالة SHA1 لحفظها للأجيال القادمة. بالنسبة إلى الأدوات، ستحتاج إلى FFmpeg وMP4Box وLAME ونظام التشغيل OSX مع afconvert.

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 ملفات wave مدّتها 6.5 ثانية تقريبًا، إذ إنّه من الأسهل استخدام wave لأنّ كلّ برامج الترميز تقريبًا تتيح نقلها. مرة أخرى، يمكننا إجراء ذلك بدقة باستخدام 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 عاديًا وعالي الجودة لملفات wave.

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. في ما يلي، سنحوّل ملفات wave إلى ملفات 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 (وafconvert) قسمًا قصيرًا بترميز ASCII على النحو التالي:

iTunSMPB[ 26 bytes ]0000000 00000840 000001C0 0000000000046E00

ويتم كتابة هذه البيانات داخل علامة ID3 ضمن حاوية MP3 وداخل عنصر بيانات وصفية ضمن حاوية 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. بعد 4 بايت بالضبط من هذه العلامة، هناك 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. يمكنك أيضًا إعادة إلحاق البيانات نفسها في الموضع نفسه إذا لزم الأمر.