ویدیو را با استفاده از تصویر در تصویر تماشا کنید

فرانسوا بوفور
François Beaufort

Picture-in-Picture (PiP) به کاربران این امکان را می‌دهد که ویدیوها را در یک پنجره شناور (همیشه بالای پنجره‌های دیگر) تماشا کنند تا بتوانند در حین تعامل با سایت‌ها یا برنامه‌های کاربردی، به آنچه که تماشا می‌کنند، توجه داشته باشند.

با استفاده از Picture-in-Picture Web API ، می توانید Picture-in-Picture را برای عناصر ویدئویی در وب سایت خود راه اندازی و کنترل کنید. آن را در نمونه رسمی تصویر در تصویر ما امتحان کنید.

پس زمینه

در سپتامبر 2016، سافاری پشتیبانی Picture-in-Picture را از طریق WebKit API در macOS Sierra اضافه کرد. شش ماه بعد، Chrome به طور خودکار ویدیوی تصویر در تصویر را با انتشار Android O با استفاده از یک API بومی Android در تلفن همراه پخش کرد. شش ماه بعد، ما قصد خود را برای ساخت و استاندارد کردن یک Web API، ویژگی سازگار با Safari اعلام کردیم ، که به توسعه دهندگان وب اجازه می‌دهد تا تجربه کامل را در اطراف Picture-in-Picture ایجاد و کنترل کنند. و ما اینجا هستیم!

وارد کد شوید

Picture-in-Picture را وارد کنید

بیایید به سادگی با یک عنصر ویدیویی و روشی برای تعامل کاربر با آن، مانند عنصر دکمه، شروع کنیم.

<video id="videoElement" src="https://example.com/file.mp4"></video>
<button id="pipButtonElement"></button>

فقط درخواست Picture-in-Picture را در پاسخ به ژست کاربر بدهید، و هرگز به قولی که توسط videoElement.play() برگردانده شده است. این به این دلیل است که وعده‌ها هنوز ژست‌های کاربر را منتشر نمی‌کنند. در عوض، requestPictureInPicture() را در یک کنترل کننده کلیک روی pipButtonElement مانند شکل زیر فراخوانی کنید. مسئولیت رسیدگی به اتفاقاتی که در صورت دوبار کلیک کردن کاربر روی می‌دهد، با شماست.

pipButtonElement.addEventListener('click', async function () {
  pipButtonElement.disabled = true;

  await videoElement.requestPictureInPicture();

  pipButtonElement.disabled = false;
});

وقتی وعده حل شد، Chrome ویدیو را به یک پنجره کوچک کوچک می‌کند که کاربر می‌تواند آن را جابجا کند و روی پنجره‌های دیگر قرار دهد.

شما تمام شده اید. کار عالی! شما می توانید خواندن را متوقف کنید و به تعطیلات شایسته خود بروید. متأسفانه همیشه اینطور نیست. قول ممکن است به هر یک از دلایل زیر رد شود :

  • تصویر در تصویر توسط سیستم پشتیبانی نمی شود.
  • به دلیل یک خط مشی مجوزهای محدود، سند مجاز به استفاده از تصویر در تصویر نیست.
  • ابرداده ویدیو هنوز بارگیری نشده است ( videoElement.readyState === 0 ).
  • فایل ویدیویی فقط صوتی است.
  • ویژگی جدید disablePictureInPicture در عنصر ویدیو وجود دارد.
  • تماس در کنترل کننده رویداد ژست کاربر (به عنوان مثال با کلیک روی دکمه) انجام نشده است. از Chrome 74، این فقط در صورتی قابل اجرا است که از قبل عنصری در Picture-in-Picture وجود نداشته باشد.

بخش پشتیبانی از ویژگی های زیر نحوه فعال/غیرفعال کردن یک دکمه را بر اساس این محدودیت ها نشان می دهد.

بیایید یک بلوک try...catch اضافه کنیم تا این خطاهای احتمالی را ضبط کنیم و به کاربر اطلاع دهیم که چه اتفاقی می افتد.

pipButtonElement.addEventListener('click', async function () {
  pipButtonElement.disabled = true;

  try {
    await videoElement.requestPictureInPicture();
  } catch (error) {
    // TODO: Show error message to user.
  } finally {
    pipButtonElement.disabled = false;
  }
});

عنصر ویدیو چه در تصویر در تصویر باشد چه نباشد یکسان عمل می کند: رویدادها فعال می شوند و روش های فراخوانی کار می کنند. تغییرات حالت را در پنجره Picture-in-Picture منعکس می کند (مانند پخش، مکث، جستجو و غیره) و همچنین امکان تغییر حالت به صورت برنامه نویسی در جاوا اسکریپت وجود دارد.

از Picture-in-Picture خارج شوید

حالا بیایید دکمه ضامن خود را برای ورود و خروج از Picture-in-Picture ایجاد کنیم. ابتدا باید بررسی کنیم که آیا شیء فقط خواندنی document.pictureInPictureElement عنصر ویدیوی ما است یا خیر. اگر اینطور نیست، ما درخواستی برای وارد کردن تصویر در تصویر مانند بالا ارسال می کنیم. در غیر این صورت، با فراخوانی document.exitPictureInPicture() درخواست خروج می کنیم، که به این معنی است که ویدیو در برگه اصلی ظاهر می شود. توجه داشته باشید که این روش یک وعده را نیز برمی گرداند.

    ...
    try {
      if (videoElement !== document.pictureInPictureElement) {
        await videoElement.requestPictureInPicture();
      } else {
        await document.exitPictureInPicture();
      }
    }
    ...

به رویدادهای تصویر در تصویر گوش دهید

سیستم عامل ها معمولاً تصویر در تصویر را به یک پنجره محدود می کنند، بنابراین پیاده سازی کروم از این الگو پیروی می کند. این بدان معناست که کاربران می‌توانند هر بار فقط یک ویدیوی تصویر در تصویر را پخش کنند. باید انتظار داشته باشید که کاربران از Picture-in-Picture خارج شوند، حتی زمانی که شما آن را نخواستید.

کنترل‌کننده‌های رویداد جدید enterpictureinpicture و leavepictureinpicture به ما امکان می‌دهند این تجربه را برای کاربران طراحی کنیم. این می تواند هر چیزی باشد، از مرور کاتالوگ ویدیوها گرفته تا پخش جریانی زنده چت.

videoElement.addEventListener('enterpictureinpicture', function (event) {
  // Video entered Picture-in-Picture.
});

videoElement.addEventListener('leavepictureinpicture', function (event) {
  // Video left Picture-in-Picture.
  // User may have played a Picture-in-Picture video from a different page.
});

پنجره Picture-in-Picture را سفارشی کنید

Chrome 74 از دکمه‌های پخش/مکث، آهنگ قبلی و آهنگ بعدی در پنجره Picture-in-Picture پشتیبانی می‌کند که می‌توانید با استفاده از Media Session API آن را کنترل کنید.

کنترل‌های پخش رسانه در یک پنجره تصویر در تصویر
شکل 1. کنترل های پخش رسانه در یک پنجره تصویر در تصویر

به‌طور پیش‌فرض، یک دکمه پخش/مکث همیشه در پنجره Picture-in-Picture نشان داده می‌شود، مگر اینکه ویدیو در حال پخش اشیاء MediaStream باشد (به عنوان مثال getUserMedia() , getDisplayMedia() , canvas.captureStream() ) یا ویدیو دارای یک تنظیم مدت زمان MediaSource باشد. به +Infinity (مثلا فید زنده). برای اطمینان از اینکه دکمه پخش/مکث همیشه قابل مشاهده است، برخی از کنترل‌کننده‌های عملکرد جلسه رسانه را برای رویدادهای رسانه «پخش» و «مکث» به صورت زیر تنظیم کنید.

// Show a play/pause button in the Picture-in-Picture window
navigator.mediaSession.setActionHandler('play', function () {
  // User clicked "Play" button.
});
navigator.mediaSession.setActionHandler('pause', function () {
  // User clicked "Pause" button.
});

نمایش کنترل‌های پنجره «تراک قبلی» و «آهنگ بعدی» مشابه است. تنظیم کنشگرهای جلسه رسانه برای آن‌ها آنها را در پنجره تصویر در تصویر نشان می‌دهد و شما می‌توانید این اقدامات را انجام دهید.

navigator.mediaSession.setActionHandler('previoustrack', function () {
  // User clicked "Previous Track" button.
});

navigator.mediaSession.setActionHandler('nexttrack', function () {
  // User clicked "Next Track" button.
});

برای مشاهده عملی آن، نمونه رسمی جلسه رسانه را امتحان کنید.

اندازه پنجره Picture-in-Picture را دریافت کنید

اگر می‌خواهید کیفیت ویدیو را هنگام ورود و خروج ویدیو از Picture-in-Picture تنظیم کنید، باید اندازه پنجره Picture-in-Picture را بدانید و اگر کاربر به صورت دستی اندازه پنجره را تغییر داد به شما اطلاع داده شود.

مثال زیر نحوه بدست آوردن عرض و ارتفاع پنجره Picture-in-Picture را هنگام ایجاد یا تغییر اندازه نشان می دهد.

let pipWindow;

videoElement.addEventListener('enterpictureinpicture', function (event) {
  pipWindow = event.pictureInPictureWindow;
  console.log(`> Window size is ${pipWindow.width}x${pipWindow.height}`);
  pipWindow.addEventListener('resize', onPipWindowResize);
});

videoElement.addEventListener('leavepictureinpicture', function (event) {
  pipWindow.removeEventListener('resize', onPipWindowResize);
});

function onPipWindowResize(event) {
  console.log(
    `> Window size changed to ${pipWindow.width}x${pipWindow.height}`
  );
  // TODO: Change video quality based on Picture-in-Picture window size.
}

پیشنهاد می‌کنم مستقیماً به رویداد تغییر اندازه متصل نشوید، زیرا هر تغییر کوچکی که در اندازه پنجره Picture-in-Picture ایجاد می‌شود، یک رویداد جداگانه ایجاد می‌کند که ممکن است باعث مشکلات عملکردی شود، اگر در هر تغییر اندازه یک عملیات گران قیمت انجام دهید. به عبارت دیگر، عملیات تغییر اندازه وقایع را بارها و بارها با سرعت بسیار زیاد انجام می دهد. من توصیه می کنم از تکنیک های رایج مانند throttling و debouncing برای رفع این مشکل استفاده کنید.

پشتیبانی از ویژگی ها

ممکن است API Web Picture-in-Picture پشتیبانی نشود، بنابراین شما باید آن را شناسایی کنید تا پیشرفت تدریجی ارائه دهید. حتی زمانی که پشتیبانی می‌شود، ممکن است توسط کاربر غیرفعال شود یا توسط یک خط‌مشی مجوزها غیرفعال شود . خوشبختانه، برای تعیین این مورد می توانید از document.pictureInPictureEnabled جدید boolean استفاده کنید.

if (!('pictureInPictureEnabled' in document)) {
  console.log('The Picture-in-Picture Web API is not available.');
} else if (!document.pictureInPictureEnabled) {
  console.log('The Picture-in-Picture Web API is disabled.');
}

این روشی است که ممکن است بخواهید روی یک عنصر دکمه خاص برای یک ویدیو اعمال شود.

if ('pictureInPictureEnabled' in document) {
  // Set button ability depending on whether Picture-in-Picture can be used.
  setPipButton();
  videoElement.addEventListener('loadedmetadata', setPipButton);
  videoElement.addEventListener('emptied', setPipButton);
} else {
  // Hide button if Picture-in-Picture is not supported.
  pipButtonElement.hidden = true;
}

function setPipButton() {
  pipButtonElement.disabled =
    videoElement.readyState === 0 ||
    !document.pictureInPictureEnabled ||
    videoElement.disablePictureInPicture;
}

پشتیبانی ویدیویی MediaStream

پخش ویدئو اشیاء MediaStream (به عنوان مثال getUserMedia() , getDisplayMedia() , canvas.captureStream() ) نیز از Picture-in-Picture در Chrome 71 پشتیبانی می کند. نمایش جریان ویدئو، یا حتی یک عنصر بوم. توجه داشته باشید که برای وارد شدن به Picture-in-Picture مطابق شکل زیر، عنصر ویدیو لازم نیست به DOM متصل شود.

نمایش وب کم کاربر در پنجره Picture-in-Picture

const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
video.play();

// Later on, video.requestPictureInPicture();

نمایش نمایش در پنجره تصویر در تصویر

const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getDisplayMedia({video: true});
video.play();

// Later on, video.requestPictureInPicture();

نمایش عنصر بوم در پنجره Picture-in-Picture

const canvas = document.createElement('canvas');
// Draw something to canvas.
canvas.getContext('2d').fillRect(0, 0, canvas.width, canvas.height);

const video = document.createElement('video');
video.muted = true;
video.srcObject = canvas.captureStream();
video.play();

// Later on, video.requestPictureInPicture();

با ترکیب canvas.captureStream() با Media Session API ، می‌توانید برای مثال یک پنجره فهرست پخش صوتی در Chrome 74 ایجاد کنید. نمونه رسمی فهرست پخش صوتی را بررسی کنید.

لیست پخش صوتی در پنجره تصویر در تصویر
شکل 2. لیست پخش صوتی در یک پنجره تصویر در تصویر

نمونه ها، دموها و کدها

نمونه رسمی Picture-in-Picture ما را بررسی کنید تا API Web Picture-in-Picture را امتحان کنید.

دموها و کدها به دنبال خواهند داشت.

بعدش چی

ابتدا صفحه وضعیت پیاده سازی را بررسی کنید تا بدانید کدام بخش از API در حال حاضر در کروم و سایر مرورگرها پیاده سازی شده است.

در اینجا چیزی است که می توانید انتظار داشته باشید در آینده نزدیک ببینید:

پشتیبانی از مرورگر

Picture-in-Picture Web API در Chrome، Edge، Opera و Safari پشتیبانی می‌شود. برای جزئیات بیشتر به MDN مراجعه کنید.

منابع

با تشکر فراوان از مونیر لاموری و جنیفر آپسیبل برای کارشان بر روی تصویر در تصویر و کمک به این مقاله. و از همه کسانی که در تلاش استانداردسازی دخیل بودند تشکر می کنم.