پسوندهای منبع رسانه

فرانسوا بوفور
François Beaufort
جو مدلی
Joe Medley

افزونه های منبع رسانه (MSE) یک API جاوا اسکریپت است که به شما امکان می دهد جریان هایی را برای پخش از بخش های صوتی یا تصویری بسازید. اگرچه در این مقاله پوشش داده نشده است، اما اگر می‌خواهید ویدیوهایی را در سایت خود جاسازی کنید که کارهایی مانند زیر را انجام می‌دهند، به درک MSE نیاز است:

  • جریان تطبیقی، که راه دیگری برای گفتن سازگاری با قابلیت های دستگاه و شرایط شبکه است
  • پیوند تطبیقی، مانند درج آگهی
  • جابجایی زمان
  • کنترل عملکرد و اندازه دانلود
جریان داده های پایه MSE
شکل 1 : جریان داده های پایه MSE

تقریباً می توانید MSE را به عنوان یک زنجیره در نظر بگیرید. همانطور که در شکل نشان داده شده است، بین فایل دانلود شده و عناصر رسانه چندین لایه وجود دارد.

  • یک عنصر <audio> یا <video> برای پخش رسانه.
  • یک نمونه MediaSource با یک SourceBuffer برای تغذیه عنصر رسانه.
  • فراخوانی fetch() یا XHR برای بازیابی داده های رسانه در یک شی Response .
  • فراخوانی به Response.arrayBuffer() برای تغذیه MediaSource.SourceBuffer .

در عمل، زنجیره به صورت زیر است:

var vidElement = document.querySelector('video');

if (window.MediaSource) {
  var mediaSource = new MediaSource();
  vidElement.src = URL.createObjectURL(mediaSource);
  mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
  console.log('The Media Source Extensions API is not supported.');
}

function sourceOpen(e) {
  URL.revokeObjectURL(vidElement.src);
  var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
  var mediaSource = e.target;
  var sourceBuffer = mediaSource.addSourceBuffer(mime);
  var videoUrl = 'droid.webm';
  fetch(videoUrl)
    .then(function (response) {
      return response.arrayBuffer();
    })
    .then(function (arrayBuffer) {
      sourceBuffer.addEventListener('updateend', function (e) {
        if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
          mediaSource.endOfStream();
        }
      });
      sourceBuffer.appendBuffer(arrayBuffer);
    });
}

اگر می‌توانید موارد را از توضیحات تا به اینجا جدا کنید، همین الان خواندن را متوقف کنید. اگر توضیح دقیق تری می خواهید، لطفا به خواندن ادامه دهید. من قصد دارم با ساختن یک مثال اولیه MSE از این زنجیره عبور کنم. هر یک از مراحل ساخت کد را به مرحله قبل اضافه می کند.

نکته ای در مورد وضوح

آیا این مقاله همه آنچه را که باید در مورد پخش رسانه در یک صفحه وب بدانید به شما می گوید؟ نه، این فقط برای کمک به درک کدهای پیچیده تری است که ممکن است در جاهای دیگر پیدا کنید. برای وضوح، این سند بسیاری از چیزها را ساده و حذف می کند. ما فکر می‌کنیم که می‌توانیم از این موضوع خلاص شویم زیرا استفاده از کتابخانه‌ای مانند Shaka Player Google را نیز توصیه می‌کنیم. جاهایی را که عمداً ساده‌سازی می‌کنم، در سرتاسر یادداشت می‌کنم.

چند مورد که پوشش داده نشده است

در اینجا، بدون ترتیب خاصی، چند مورد وجود دارد که نمی‌پردازم.

  • کنترل های پخش با استفاده از عناصر <audio> و <video> HTML5 آنها را به صورت رایگان دریافت می کنیم.
  • رسیدگی به خطا.

برای استفاده در محیط های تولید

در اینجا مواردی وجود دارد که من در استفاده تولیدی از APIهای مرتبط با MSE توصیه می کنم:

  • قبل از برقراری تماس با این APIها، هرگونه رویداد خطا یا استثناهای API را مدیریت کنید و HTMLMediaElement.readyState و MediaSource.readyState را بررسی کنید. این مقادیر می توانند قبل از تحویل رویدادهای مرتبط تغییر کنند.
  • قبل از به‌روزرسانی mode SourceBuffer ، timestampOffset ، appendWindowStart ، appendWindowEnd ، یا فراخوانی appendBuffer() یا remove() در SourceBuffer ، مطمئن شوید که تماس‌های قبلی appendBuffer() و remove() هنوز در حال انجام نیستند، با بررسی مقدار منطقی SourceBuffer.updating .
  • برای همه نمونه‌های SourceBuffer که به MediaSource شما اضافه شده‌اند، قبل از فراخوانی MediaSource.endOfStream() یا به‌روزرسانی MediaSource.duration مطمئن شوید که هیچ یک از مقادیر updating آن‌ها درست نیست.
  • اگر مقدار MediaSource.readyState ended برسد، فراخوانی هایی مانند appendBuffer() و remove() یا تنظیم SourceBuffer.mode یا SourceBuffer.timestampOffset باعث می شود که این مقدار open شود. این بدان معنی است که شما باید برای مدیریت چندین رویداد sourceopen آماده باشید.
  • هنگام مدیریت رویدادهای HTMLMediaElement error ، محتویات MediaError.message می تواند برای تعیین علت اصلی خرابی مفید باشد، به خصوص برای خطاهایی که بازتولید آن ها در محیط های آزمایشی دشوار است.

یک نمونه MediaSource را به یک عنصر رسانه متصل کنید

مانند بسیاری از موارد در توسعه وب این روزها، شما با تشخیص ویژگی شروع می کنید. در مرحله بعد، یک عنصر رسانه، یا عنصر <audio> یا <video> را دریافت کنید. در نهایت یک نمونه از MediaSource ایجاد کنید. به یک URL تبدیل می شود و به ویژگی منبع عنصر رسانه منتقل می شود.

var vidElement = document.querySelector('video');

if (window.MediaSource) {
  var mediaSource = new MediaSource();
  vidElement.src = URL.createObjectURL(mediaSource);
  // Is the MediaSource instance ready?
} else {
  console.log('The Media Source Extensions API is not supported.');
}
یک ویژگی منبع به عنوان یک لکه
شکل 1 : یک ویژگی منبع به عنوان یک لکه

اینکه یک شی MediaSource می تواند به یک ویژگی src ارسال شود ممکن است کمی عجیب به نظر برسد. آنها معمولاً رشته ای هستند، اما می توانند حباب نیز باشند . اگر صفحه ای را با رسانه جاسازی شده بررسی کنید و عنصر رسانه آن را بررسی کنید، منظور من را خواهید دید.

آیا نمونه MediaSource آماده است؟

URL.createObjectURL() خود همزمان است. با این حال، پیوست را به صورت ناهمزمان پردازش می کند. این باعث می شود قبل از اینکه بتوانید کاری با نمونه MediaSource انجام دهید، کمی تاخیر ایجاد می کند. خوشبختانه راه هایی برای آزمایش این موضوع وجود دارد. ساده ترین راه با یک ویژگی MediaSource به نام readyState است. ویژگی readyState رابطه بین یک نمونه MediaSource و یک عنصر رسانه را توصیف می کند. می تواند یکی از مقادیر زیر را داشته باشد:

  • closed - نمونه MediaSource به یک عنصر رسانه متصل نیست.
  • open - نمونه MediaSource به یک عنصر رسانه متصل است و آماده دریافت داده است یا در حال دریافت داده است.
  • ended - نمونه MediaSource به یک عنصر رسانه متصل است و تمام داده های آن به آن عنصر منتقل شده است.

جستجوی مستقیم این گزینه ها می تواند بر عملکرد تأثیر منفی بگذارد. خوشبختانه، MediaSource همچنین هنگام تغییر readyState ، به ویژه sourceopen ، sourceclosed ، sourceended ، رویدادها را فعال می کند. برای مثالی که می‌سازم، از رویداد sourceopen استفاده می‌کنم تا به من بگویم چه زمانی ویدیو را واکشی و بافر کنم.

var vidElement = document.querySelector('video');

if (window.MediaSource) {
  var mediaSource = new MediaSource();
  vidElement.src = URL.createObjectURL(mediaSource);
  <strong>mediaSource.addEventListener('sourceopen', sourceOpen);</strong>
} else {
  console.log("The Media Source Extensions API is not supported.")
}

<strong>function sourceOpen(e) {
  URL.revokeObjectURL(vidElement.src);
  // Create a SourceBuffer and get the media file.
}</strong>

توجه داشته باشید که من revokeObjectURL() را نیز فراخوانی کرده ام. می‌دانم که این کار زودرس به نظر می‌رسد، اما می‌توانم هر زمانی که ویژگی src عنصر رسانه به یک نمونه MediaSource متصل شد، این کار را انجام دهم. فراخوانی این متد هیچ شیئی را از بین نمی برد. این به پلت فرم اجازه می دهد تا جمع آوری زباله را در زمان مناسب انجام دهد، به همین دلیل است که من فوراً با آن تماس می گیرم.

یک SourceBuffer ایجاد کنید

اکنون زمان ایجاد SourceBuffer است، که شیئی است که در واقع کار انتقال داده بین منابع رسانه و عناصر رسانه را انجام می دهد. یک SourceBuffer باید مختص نوع فایل رسانه ای باشد که بارگیری می کنید.

در عمل می توانید این کار را با فراخوانی addSourceBuffer() با مقدار مناسب انجام دهید. توجه داشته باشید که در مثال زیر رشته نوع mime شامل یک نوع mime و دو کدک است. این یک رشته mime برای یک فایل ویدیویی است، اما از کدک های جداگانه برای بخش های ویدیویی و صوتی فایل استفاده می کند.

نسخه 1 مشخصات MSE به عوامل کاربر اجازه می دهد تا در مورد نیاز به نوع mime و کدک متفاوت باشند. برخی از عوامل کاربر نیازی ندارند، اما فقط به نوع mime اجازه می دهند. برخی از عوامل کاربر، برای مثال کروم، به یک کدک برای انواع mime نیاز دارند که کدک‌هایشان را خودشان توصیف نمی‌کنند. به جای تلاش برای مرتب کردن همه اینها، بهتر است فقط هر دو را بگنجانید.

var vidElement = document.querySelector('video');

if (window.MediaSource) {
  var mediaSource = new MediaSource();
  vidElement.src = URL.createObjectURL(mediaSource);
  mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
  console.log('The Media Source Extensions API is not supported.');
}

function sourceOpen(e) {
  URL.revokeObjectURL(vidElement.src);
  <strong>
    var mime = 'video/webm; codecs="opus, vp09.00.10.08"'; // e.target refers to
    the mediaSource instance. // Store it in a variable so it can be used in a
    closure. var mediaSource = e.target; var sourceBuffer =
    mediaSource.addSourceBuffer(mime); // Fetch and process the video.
  </strong>;
}

فایل رسانه را دریافت کنید

اگر در اینترنت برای نمونه های MSE جستجو کنید، تعداد زیادی فایل های رسانه ای را با استفاده از XHR بازیابی خواهید کرد. برای پیشرفت بیشتر، من قصد دارم از Fetch API و Promise it استفاده کنم. اگر می‌خواهید این کار را در Safari انجام دهید، بدون fetch() polyfill کار نمی‌کند.

function sourceOpen(e) {
  URL.revokeObjectURL(vidElement.src);
  var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
  var mediaSource = e.target;
  var sourceBuffer = mediaSource.addSourceBuffer(mime);
  var videoUrl = 'droid.webm';
  <strong>
    fetch(videoUrl) .then(function(response){' '}
    {
      // Process the response object.
    }
    );
  </strong>;
}

یک پخش کننده با کیفیت تولید، یک فایل را در چندین نسخه برای پشتیبانی از مرورگرهای مختلف خواهد داشت. می تواند از فایل های جداگانه برای صدا و تصویر استفاده کند تا امکان انتخاب صدا بر اساس تنظیمات زبان را فراهم کند.

کد دنیای واقعی همچنین دارای چندین نسخه از فایل‌های رسانه‌ای با وضوح‌های مختلف است تا بتواند با قابلیت‌های دستگاه و شرایط شبکه متفاوت سازگار شود. چنین برنامه‌ای می‌تواند ویدیوها را با استفاده از درخواست‌های محدوده یا بخش‌ها بارگیری و پخش کند. این امکان انطباق با شرایط شبکه را در حین پخش رسانه فراهم می کند. ممکن است اصطلاحات DASH یا HLS را شنیده باشید که دو روش برای انجام این کار هستند. بحث کامل درباره این موضوع از حوصله این مقدمه خارج است.

شی پاسخ را پردازش کنید

به نظر می رسد کد تقریباً تمام شده است، اما رسانه پخش نمی شود. ما باید داده های رسانه را از شی Response به SourceBuffer دریافت کنیم.

روش معمولی برای ارسال داده از شی پاسخ به نمونه MediaSource این است که یک ArrayBuffer از شی پاسخ گرفته و آن را به SourceBuffer ارسال کنید. با فراخوانی response.arrayBuffer() شروع کنید که یک وعده را به بافر برمی گرداند. در کدم، این قول را به یک بند ثانوی then() منتقل کرده‌ام که در آنجا آن را به SourceBuffer اضافه می‌کنم.

function sourceOpen(e) {
  URL.revokeObjectURL(vidElement.src);
  var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
  var mediaSource = e.target;
  var sourceBuffer = mediaSource.addSourceBuffer(mime);
  var videoUrl = 'droid.webm';
  fetch(videoUrl)
    .then(function(response) {
      <strong>return response.arrayBuffer();</strong>
    })
    <strong>.then(function(arrayBuffer) {
      sourceBuffer.appendBuffer(arrayBuffer);
    });</strong>
}

فراخوانی endOfStream()

پس از اینکه همه ArrayBuffers اضافه شدند و هیچ داده رسانه دیگری انتظار نمی رود، MediaSource.endOfStream() را فراخوانی کنید. با این کار MediaSource.readyState به ended تغییر می کند و رویداد sourceended فعال می شود.

function sourceOpen(e) {
  URL.revokeObjectURL(vidElement.src);
  var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
  var mediaSource = e.target;
  var sourceBuffer = mediaSource.addSourceBuffer(mime);
  var videoUrl = 'droid.webm';
  fetch(videoUrl)
    .then(function(response) {
      return response.arrayBuffer();
    })
    .then(function(arrayBuffer) {
      <strong>sourceBuffer.addEventListener('updateend', function(e) {
        if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
          mediaSource.endOfStream();
        }
      });</strong>
      sourceBuffer.appendBuffer(arrayBuffer);
    });
}

نسخه نهایی

در اینجا نمونه کد کامل است. امیدوارم در مورد افزونه های رسانه سورس چیزی یاد گرفته باشید.

var vidElement = document.querySelector('video');

if (window.MediaSource) {
  var mediaSource = new MediaSource();
  vidElement.src = URL.createObjectURL(mediaSource);
  mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
  console.log('The Media Source Extensions API is not supported.');
}

function sourceOpen(e) {
  URL.revokeObjectURL(vidElement.src);
  var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
  var mediaSource = e.target;
  var sourceBuffer = mediaSource.addSourceBuffer(mime);
  var videoUrl = 'droid.webm';
  fetch(videoUrl)
    .then(function (response) {
      return response.arrayBuffer();
    })
    .then(function (arrayBuffer) {
      sourceBuffer.addEventListener('updateend', function (e) {
        if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
          mediaSource.endOfStream();
        }
      });
      sourceBuffer.appendBuffer(arrayBuffer);
    });
}

بازخورد