كيف يمكنك توفير أفضل تجربة لعرض الوسائط على الأجهزة الجوّالة على الويب؟ سهل جدًا. يعتمد ذلك كله على تفاعل المستخدمين والأهمية التي تمنحها للوسائط على صفحات الويب. أعتقد أنّنا نتفق جميعًا على أنّه إذا كان الفيديو هو السبب الرئيسي لزيارة المستخدم، يجب أن تكون تجربته غامرة وإعادة جذبه.
في هذه المقالة، سأوضّح لك كيفية تحسين تجربة الوسائط بشكل تدريجي وجعلها أكثر تشويقًا بفضل مجموعة كبيرة من واجهات برمجة التطبيقات على الويب. لهذا السبب، سنوفّر تجربة بسيطة لمشغّل الوسائط على الأجهزة الجوّالة تتضمّن عناصر تحكّم مخصّصة وتشغيلًا بالحجم الكامل وفي الخلفية. يمكنك تجربة النموذج الآن والعثور على الرمز في مستودع جيت هب.
عناصر التحكّم المخصّصة
كما ترى، تنسيق HTML الذي سنستخدمه لمشغّل الوسائط هو
بسيط جدًا: عنصر الجذر <div>
يحتوي على عنصر وسائط <video>
و<div>
عنصر فرعي مخصّص لأداة التحكّم في الفيديو.
تشمل عناصر التحكم في الفيديو التي سنتناولها لاحقًا زر التشغيل/الإيقاف المؤقت، وزر ملء الشاشة، وأزرار الانتقال للخلف وللأمام، وبعض العناصر لتتبع الوقت الحالي والمدة الزمنية والوقت.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls"></div>
</div>
قراءة البيانات الوصفية للفيديو
أولاً، لننتظر تحميل البيانات الوصفية للفيديو لضبط مدّة
الفيديو والوقت الحالي وبدء شريط التقدم. يُرجى العِلم أنّ دالة
secondsToTimeCode()
هي دالة مساعدة مخصّصة كتبتها والتي
تحوّل عددًا من الثواني إلى سلسلة بتنسيق "hh:mm:ss" وهو التنسيق المناسب
في حالتنا.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</strong>
</div>
</div>
video.addEventListener('loadedmetadata', function () {
videoDuration.textContent = secondsToTimeCode(video.duration);
videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
videoProgressBar.style.transform = `scaleX(${
video.currentTime / video.duration
})`;
});
تشغيل/إيقاف تشغيل الفيديو
بعد تحميل البيانات الوصفية للفيديو، لنضيف الزر الأول الذي يتيح للمستخدمين تشغيل الفيديو وإيقافه مؤقتًا باستخدام video.play()
وvideo.pause()
استنادًا إلى حالة تشغيله.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<strong><button id="playPauseButton"></button></strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</div>
</div>
playPauseButton.addEventListener('click', function (event) {
event.stopPropagation();
if (video.paused) {
video.play();
} else {
video.pause();
}
});
بدلاً من تعديل عناصر التحكّم في الفيديو في أداة معالجة الحدث click
، نستخدم
حدثَي الفيديو play
وpause
. ويساعد إنشاء أحداث عناصر التحكم في تحقيق المرونة (كما سنرى لاحقًا في واجهة برمجة التطبيقات لجلسة الوسائط)، كما سيتيح لنا ذلك الحفاظ على مزامنة عناصر التحكم في حال تدخل المتصفح في التشغيل.
عندما يبدأ تشغيل الفيديو، نغيّر حالة الزر
إلى "إيقاف مؤقت" ونخفي عناصر التحكم في الفيديو. عند إيقاف الفيديو مؤقتًا،
نغيّر حالة الزر إلى "تشغيل" ونعرض عناصر التحكّم في الفيديو.
video.addEventListener('play', function () {
playPauseButton.classList.add('playing');
});
video.addEventListener('pause', function () {
playPauseButton.classList.remove('playing');
});
عندما يتغيّر الوقت الذي تشير إليه سمة currentTime
للفيديو من خلال حدث
timeupdate
للفيديو، نعدّل أيضًا عناصر التحكّم المخصّصة إذا كانت
مرئية.
video.addEventListener('timeupdate', function () {
if (videoControls.classList.contains('visible')) {
videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
videoProgressBar.style.transform = `scaleX(${
video.currentTime / video.duration
})`;
}
});
عند انتهاء الفيديو، سنغيّر حالة الزر إلى "تشغيل"، ونعيد ضبط قيمة
currentTime
للفيديو إلى 0، ونعرض عناصر التحكّم في الفيديو في الوقت الحالي. تجدر الإشارة إلى أنّه يمكننا أيضًا اختيار تحميل فيديو آخر تلقائيًا إذا فعّل المستخدم نوعًا ما من ميزات "التشغيل التلقائي".
video.addEventListener('ended', function () {
playPauseButton.classList.remove('playing');
video.currentTime = 0;
});
الترجيع للأمام وللخلف
لنواصل إضافة زرَّي "تقديم/ترجيع" لكي يتمكّن المستخدِم من تخطّي بعض المحتوى بسهولة.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<button id="playPauseButton"></button>
<strong
><button id="seekForwardButton"></button>
<button id="seekBackwardButton"></button
></strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</div>
</div>
var skipTime = 10; // Time to skip in seconds
seekForwardButton.addEventListener('click', function (event) {
event.stopPropagation();
video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
});
seekBackwardButton.addEventListener('click', function (event) {
event.stopPropagation();
video.currentTime = Math.max(video.currentTime - skipTime, 0);
});
كما في السابق، بدلاً من تعديل تصميم الفيديو في مستمعي أحداث click
لهذه الأزرار، سنستخدم أحداث الفيديو seeking
وseeked
التي يتم تشغيلها
لضبط سطوع الفيديو. فئة CSS المخصّصة seeking
هي بسيطة مثل filter: brightness(0);
.
video.addEventListener('seeking', function () {
video.classList.add('seeking');
});
video.addEventListener('seeked', function () {
video.classList.remove('seeking');
});
في ما يلي ما أنشأناه حتى الآن. في القسم التالي، سننفّذ زرّ ملء الشاشة.
ملء الشاشة
سنستفيد هنا من العديد من واجهات برمجة التطبيقات لإنشاء تجربة مثالية وسلسة بملء الشاشة. للاطّلاع على كيفية استخدام هذه الميزة، يمكنك الاطّلاع على العيّنة.
بالطبع، لا يتعين عليك استخدامها كلها. ما عليك سوى اختيار الخطوات التي تبدو منطقية بالنسبة إليك ودمجها لإنشاء مسار مخصّص.
منع وضع ملء الشاشة التلقائي
على نظام التشغيل iOS، تدخل عناصر video
تلقائيًا في وضع ملء الشاشة عند بدء تشغيل الوسائط. بما أنّنا نحاول تخصيص تجربة
الوسائط والتحكم فيها قدر الإمكان على متصفّحات الأجهزة الجوّالة، ننصحك بضبط سمة playsinline
لعنصر video
لإجباره على التشغيل مضمّنًا على iPhone وعدم
دخول وضع ملء الشاشة عند بدء التشغيل. يُرجى العِلم أنّ هذا الإجراء ليس له أيّ تأثيرات جانبية
على المتصفّحات الأخرى.
<div id="videoContainer"></div>
<video id="video" src="file.mp4"></video><strong>playsinline</strong></video>
<div id="videoControls">...</div>
</div>
تبديل وضع ملء الشاشة عند النقر على الزر
والآن بعد أن منعنا وضع ملء الشاشة التلقائي، نحتاج إلى ضبط أنفسنا في وضع ملء الشاشة للفيديو باستخدام واجهة برمجة تطبيقات ملء الشاشة. عندما يُقرِّر المستخدِم
النقر على "زرّ ملء الشاشة"، لنخرج من وضع ملء الشاشة باستخدام
document.exitFullscreen()
إذا كان وضع ملء الشاشة قيد الاستخدام حاليًا في
المستند. في الحالات الأخرى، يمكنك طلب وضع ملء الشاشة على حاوية الفيديو باستخدام الطريقة
requestFullscreen()
إذا كانت متاحة أو يمكنك استخدام webkitEnterFullscreen()
في عنصر الفيديو فقط على نظام التشغيل iOS.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<button id="playPauseButton"></button>
<button id="seekForwardButton"></button>
<button id="seekBackwardButton"></button>
<strong><button id="fullscreenButton"></button></strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</div>
</div>
fullscreenButton.addEventListener('click', function (event) {
event.stopPropagation();
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
requestFullscreenVideo();
}
});
function requestFullscreenVideo() {
if (videoContainer.requestFullscreen) {
videoContainer.requestFullscreen();
} else {
video.webkitEnterFullscreen();
}
}
document.addEventListener('fullscreenchange', function () {
fullscreenButton.classList.toggle('active', document.fullscreenElement);
});
تفعيل وضع ملء الشاشة أو إيقافه عند تغيير اتجاه الشاشة
عندما يقوم المستخدم بتدوير الجهاز في الوضع الأفقي، دعنا نكون ذكيين بشأن ذلك ونطلب تلقائيًا وضع ملء الشاشة لإنشاء تجربة غامرة. لهذا الغرض، سنحتاج إلى واجهة برمجة التطبيقات Screen Orientation API التي لا تتوفّر بعد في كل مكان ولا تزال مضمّنة في بعض المتصفّحات في ذلك الوقت. وبالتالي، سيكون هذا هو أول تحسين تدريجي لنا.
كيف يتم ذلك؟ بعد رصد تغييرات اتجاه الشاشة، لنطلب عرض المحتوى بملء الشاشة إذا كانت نافذة المتصفّح في الوضع الأفقي (أي أنّ عرضها أكبر من ارتفاعها). إذا لم يكن الأمر كذلك، لنخرج من وضع ملء الشاشة. هذا كلّ ما في الأمر.
if ('orientation' in screen) {
screen.orientation.addEventListener('change', function () {
// Let's request fullscreen if user switches device in landscape mode.
if (screen.orientation.type.startsWith('landscape')) {
requestFullscreenVideo();
} else if (document.fullscreenElement) {
document.exitFullscreen();
}
});
}
قفل شاشة العرض في الوضع الأفقي عند النقر على الزر
بما أنّه قد يكون من الأفضل مشاهدة الفيديو في الوضع الأفقي، قد نريد قفل الشاشة في الوضع الأفقي عندما ينقر المستخدم على "زر ملء الشاشة". سنجمع بين Screen Orientation API التي سبق استخدامها وبعض طلبات البحث عن الوسائط للتأكّد من أنّ هذه التجربة هي الأفضل.
يمكنك قفل الشاشة في الوضع الأفقي بسهولة مثل الاتصال
screen.orientation.lock('landscape')
. ويجب عدم إجراء ذلك إلا عندما يكون الجهاز في الوضع العمودي باستخدام matchMedia('(orientation: portrait)')
ويمكن
حمله بيد واحدة باستخدام matchMedia('(max-device-width: 768px)')
لأنّ ذلك
لن يكون تجربة رائعة لمستخدمي الأجهزة اللوحية.
fullscreenButton.addEventListener('click', function (event) {
event.stopPropagation();
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
requestFullscreenVideo();
<strong>lockScreenInLandscape();</strong>;
}
});
function lockScreenInLandscape() {
if (!('orientation' in screen)) {
return;
}
// Let's force landscape mode only if device is in portrait mode and can be held in one hand.
if (
matchMedia('(orientation: portrait) and (max-device-width: 768px)').matches
) {
screen.orientation.lock('landscape');
}
}
فتح قفل الشاشة عند تغيير اتجاه الجهاز
ربما لاحظت أن تجربة شاشة القفل التي أنشأناها للتو ليست مثالية بالرغم من أننا لا نتلقّى تغييرات في اتجاه الشاشة عندما تكون الشاشة مقفلة.
لحلّ هذه المشكلة، لنستخدم واجهة برمجة التطبيقات Device Orientation API إذا كانت متوفرة. توفّر واجهة برمجة التطبيقات هذه معلومات من الأجهزة التي تقيس موضع الجهاز وحركته في الفضاء، وتشمل هذه الأجهزة الجيروسكوب والبوصلة الرقمية لتحديد اتجاهه ومقياس التسارع لسرعته. عند رصد تغيير في اتجاه الجهاز، لنتمكّن من فتح شاشة الجهاز باستخدام screen.orientation.unlock()
إذا كان المستخدم يمسك بالجهاز في الوضع العمودي وكانت الشاشة مقفلة في الوضع الأفقي.
function lockScreenInLandscape() {
if (!('orientation' in screen)) {
return;
}
// Let's force landscape mode only if device is in portrait mode and can be held in one hand.
if (matchMedia('(orientation: portrait) and (max-device-width: 768px)').matches) {
screen.orientation.lock('landscape')
<strong>.then(function() {
listenToDeviceOrientationChanges();
})</strong>;
}
}
function listenToDeviceOrientationChanges() {
if (!('DeviceOrientationEvent' in window)) {
return;
}
var previousDeviceOrientation, currentDeviceOrientation;
window.addEventListener(
'deviceorientation',
function onDeviceOrientationChange(event) {
// event.beta represents a front to back motion of the device and
// event.gamma a left to right motion.
if (Math.abs(event.gamma) > 10 || Math.abs(event.beta) < 10) {
previousDeviceOrientation = currentDeviceOrientation;
currentDeviceOrientation = 'landscape';
return;
}
if (Math.abs(event.gamma) < 10 || Math.abs(event.beta) > 10) {
previousDeviceOrientation = currentDeviceOrientation;
// When device is rotated back to portrait, let's unlock screen orientation.
if (previousDeviceOrientation == 'landscape') {
screen.orientation.unlock();
window.removeEventListener(
'deviceorientation',
onDeviceOrientationChange,
);
}
}
},
);
}
كما ترى، هذه هي التجربة السلسة التي أردناها في وضع ملء الشاشة. للاطّلاع على مثال على ذلك، يمكنك الاطّلاع على النموذج.
التشغيل في الخلفية
عندما ترصد أنّ صفحة ويب أو فيديو في صفحة الويب لم يعُد مرئيًا، يمكنك تعديل الإحصاءات لتعكس ذلك. وقد يؤثّر ذلك أيضًا في عملية التشغيل الحالية، مثل اختيار مسار مختلف أو إيقافه مؤقّتًا أو حتى عرض أزرار مخصَّصة للمستخدم مثلاً.
إيقاف الفيديو مؤقتًا عند تغيير مستوى رؤية الصفحة
باستخدام واجهة برمجة التطبيقات Page Visibility API، يمكننا تحديد مستوى الظهور الحالي لإحدى الصفحات والحصول على إشعارات بشأن تغييرات مستوى الظهور. يؤدي الرمز أدناه إلى إيقاف الفيديو مؤقتًا عند إخفاء الصفحة. ويحدث ذلك عندما يكون قفل الشاشة نشطًا أو عند تبديل علامات التبويب على سبيل المثال.
بما أنّ معظم متصفّحات الأجهزة الجوّالة تقدّم الآن عناصر تحكّم خارج المتصفّح تتيح مجددًا تشغيل فيديو تم إيقافه مؤقتًا، أنصحك بعدم ضبط هذا السلوك إلا إذا كان مسموحًا للمستخدم بالتشغيل في الخلفية.
document.addEventListener('visibilitychange', function () {
// Pause video when page is hidden.
if (document.hidden) {
video.pause();
}
});
عرض/إخفاء زر كتم الصوت عند تغيير مستوى عرض الفيديو
إذا كنت تستخدم واجهة برمجة التطبيقات الجديدة Intersection Observer API، يمكنك إجراء عمليات أكثر دقة بدون أي تكلفة. تتيح لك واجهة برمجة التطبيقات هذه معرفة وقت دخول عنصر تمت ملاحظته إلى إطار عرض المتصفّح أو خروجه منه.
لِنعرض أو نخفي زر كتم الميكروفون استنادًا إلى مستوى عرض الفيديو على الصفحة. إذا كان الفيديو قيد التشغيل ولكنه غير مرئي حاليًا، سيظهر زر صغير لكتم الصوت في أسفل يسار الصفحة لمنح المستخدم إمكانية التحكّم في صوت الفيديو. يُستخدَم
حدث الفيديو "volumechange
" لتعديل تصميم زر كتم الميكروفون.
<button id="muteButton"></button>
if ('IntersectionObserver' in window) {
// Show/hide mute button based on video visibility in the page.
function onIntersection(entries) {
entries.forEach(function (entry) {
muteButton.hidden = video.paused || entry.isIntersecting;
});
}
var observer = new IntersectionObserver(onIntersection);
observer.observe(video);
}
muteButton.addEventListener('click', function () {
// Mute/unmute video on button click.
video.muted = !video.muted;
});
video.addEventListener('volumechange', function () {
muteButton.classList.toggle('active', video.muted);
});
تشغيل فيديو واحد فقط في كل مرة
إذا كان هناك أكثر من فيديو واحد في الصفحة، نقترح عليك تشغيل فيديو واحد فقط وتوقيف الفيديوهات الأخرى مؤقتًا تلقائيًا كي لا يضطر المستخدم إلى سماع مقاطع صوتية متعددة يتم تشغيلها في الوقت نفسه.
// This array should be initialized once all videos have been added.
var videos = Array.from(document.querySelectorAll('video'));
videos.forEach(function (video) {
video.addEventListener('play', pauseOtherVideosPlaying);
});
function pauseOtherVideosPlaying(event) {
var videosToPause = videos.filter(function (video) {
return !video.paused && video != event.target;
});
// Pause all other videos currently playing.
videosToPause.forEach(function (video) {
video.pause();
});
}
تخصيص "إشعارات الوسائط"
باستخدام Media Session API، يمكنك أيضًا تخصيص إعلامات الوسائط من خلال تقديم بيانات وصفية للفيديو الذي يتم تشغيله حاليًا. ويسمح لك أيضًا بمعالجة الأحداث المتعلّقة بالوسائط، مثل تغيير مسار التقديم أو الإيقاف أو التتبع، التي قد تأتي من الإشعارات أو مفاتيح الوسائط. للاطلاع على ذلك عمليًا، يمكنك الاطلاع على النموذج.
عندما يشغّل تطبيق الويب الصوت أو الفيديو، يمكنك رؤية إشعار إعلام بالوسائط في علبة الإشعارات. على أجهزة Android، يبذل Chrome قصارى جهده لعرض المعلومات المناسبة باستخدام عنوان المستند وأكبر صورة رمز يمكنه العثور عليها.
لنطّلِع على كيفية تخصيص إشعار الوسائط هذا من خلال ضبط بعض البيانات الوصفية لجلسة الوسائط، مثل العنوان والفنان واسم الألبوم والعمل الفني باستخدام Media Session API.
playPauseButton.addEventListener('click', function(event) {
event.stopPropagation();
if (video.paused) {
video.play()
<strong>.then(function() {
setMediaSession();
});</strong>
} else {
video.pause();
}
});
function setMediaSession() {
if (!('mediaSession' in navigator)) {
return;
}
navigator.mediaSession.metadata = new MediaMetadata({
title: 'Never Gonna Give You Up',
artist: 'Rick Astley',
album: 'Whenever You Need Somebody',
artwork: [
{src: 'https://dummyimage.com/96x96', sizes: '96x96', type: 'image/png'},
{
src: 'https://dummyimage.com/128x128',
sizes: '128x128',
type: 'image/png',
},
{
src: 'https://dummyimage.com/192x192',
sizes: '192x192',
type: 'image/png',
},
{
src: 'https://dummyimage.com/256x256',
sizes: '256x256',
type: 'image/png',
},
{
src: 'https://dummyimage.com/384x384',
sizes: '384x384',
type: 'image/png',
},
{
src: 'https://dummyimage.com/512x512',
sizes: '512x512',
type: 'image/png',
},
],
});
}
بعد انتهاء التشغيل، ليس عليك "إغلاق" جلسة الوسائط لأنّه
سيختفي الإشعار تلقائيًا. يُرجى العِلم أنّه سيتم استخدام قيمة
navigator.mediaSession.metadata
الحالية عند بدء أي تشغيل. لهذا السبب، يجب تعديلها للتأكّد من عرض معلومات مفيدة دائمًا في إشعار الوسائط.
إذا كان تطبيق الويب الخاص بك يقدّم قائمة تشغيل، ننصحك بالسماح للمستخدم بالتنقّل في قائمة التشغيل مباشرةً من إشعار الوسائط باستخدام رمزَي "الأغنية السابقة" و "الأغنية التالية".
if ('mediaSession' in navigator) {
navigator.mediaSession.setActionHandler('previoustrack', function () {
// User clicked "Previous Track" media notification icon.
playPreviousVideo(); // load and play previous video
});
navigator.mediaSession.setActionHandler('nexttrack', function () {
// User clicked "Next Track" media notification icon.
playNextVideo(); // load and play next video
});
}
يُرجى العلم أنّ معالجات إجراءات الوسائط ستظلّ موجودة. وهذا مشابه جدًا لنمط مستمع الأحداث، باستثناء أنّ معالجة حدث ما تعني أنّ المتصفّح يتوقف عن تنفيذ أي سلوك تلقائي ويستخدم ذلك كإشارة إلى أنّ تطبيق الويب يتوافق مع إجراء الوسائط. وبالتالي، لن يتم عرض عناصر التحكم في إجراءات الوسائط ما لم يتم ضبط معالج الإجراء المناسب.
يُرجى العِلم أنّ إلغاء ضبط معالِج إجراء على الوسائط سهل مثل تعيينه على null
.
تتيح لك Media Session API عرض رمزَي إعلام الوسائط "تقديم/ترجيع" إذا كنت تريد التحكّم في مقدار الوقت الذي يتم تخطّيه.
if ('mediaSession' in navigator) {
let skipTime = 10; // Time to skip in seconds
navigator.mediaSession.setActionHandler('seekbackward', function () {
// User clicked "Seek Backward" media notification icon.
video.currentTime = Math.max(video.currentTime - skipTime, 0);
});
navigator.mediaSession.setActionHandler('seekforward', function () {
// User clicked "Seek Forward" media notification icon.
video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
});
}
يتم عرض رمز "التشغيل/الإيقاف المؤقت" دائمًا في إشعار الوسائط، ويعالج المتصفح تلقائيًا المحتوى المرتبط بالأحداث. إذا لم يعمل السلوك التلقائي بشكلٍ سليم لسبب ما، سيظل بإمكانك معالجة أحداث إعلام "تشغيل" و"إيقاف مؤقت" .
من المزايا الرائعة لواجهة برمجة التطبيقات Media Session API أنّ علبة الإشعارات ليست المكان الوحيد الذي تظهر فيه البيانات الوصفية للوسائط وعناصر التحكّم فيها. تتم مزامنة إعلام الوسائط تلقائيًا مع أي جهاز قابل للارتداء مقترن. ويظهر أيضًا على شاشات القفل.