Làm cách nào để tạo trải nghiệm nội dung nghe nhìn tốt nhất trên thiết bị di động trên web? Thật dễ dàng! Tất cả đều phụ thuộc vào mức độ tương tác của người dùng và tầm quan trọng mà bạn dành cho nội dung nghe nhìn trên một trang web. Tôi nghĩ chúng ta đều đồng ý rằng nếu video là lý do CHÍNH khiến người dùng truy cập, thì trải nghiệm của người dùng phải thật hấp dẫn và lôi cuốn.
Trong bài viết này, tôi sẽ hướng dẫn bạn cách cải thiện trải nghiệm nghe nhìn theo cách tăng dần và khiến trải nghiệm đó trở nên sống động hơn nhờ có nhiều API web. Đó là lý do chúng ta sẽ xây dựng một trải nghiệm trình phát đơn giản trên thiết bị di động với các chế độ điều khiển tuỳ chỉnh, chế độ toàn màn hình và chế độ phát trong nền. Bạn có thể dùng thử mẫu ngay bây giờ và tìm mã trong kho lưu trữ GitHub của chúng tôi.
Chế độ điều khiển tuỳ chỉnh
Như bạn có thể thấy, bố cục HTML mà chúng ta sẽ sử dụng cho trình phát nội dung nghe nhìn khá đơn giản: một phần tử gốc <div> chứa một phần tử nội dung nghe nhìn <video> và một phần tử con <div> dành riêng cho các nút điều khiển video.
Các nút điều khiển video mà chúng ta sẽ đề cập sau bao gồm: nút phát/tạm dừng, nút toàn màn hình, nút tua đi và tua lại, cũng như một số thành phần cho thời gian hiện tại, thời lượng và theo dõi thời gian.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls"></div>
</div>
Đọc siêu dữ liệu video
Trước tiên, hãy đợi siêu dữ liệu video được tải để đặt thời lượng video, thời gian hiện tại và khởi chạy thanh tiến trình. Xin lưu ý rằng hàm secondsToTimeCode() là một hàm hiệu dụng tuỳ chỉnh mà tôi đã viết để chuyển đổi số giây thành chuỗi ở định dạng "hh:mm:ss", phù hợp hơn trong trường hợp của chúng ta.
<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
})`;
});
Phát/tạm dừng video
Bây giờ, khi siêu dữ liệu video đã được tải, hãy thêm nút đầu tiên cho phép người dùng phát và tạm dừng video bằng video.play() và video.pause() tuỳ thuộc vào trạng thái phát của video.
<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();
}
});
Thay vì điều chỉnh các chế độ điều khiển video trong trình nghe sự kiện click, chúng ta sử dụng các sự kiện video play và pause. Việc tạo các sự kiện dựa trên chế độ điều khiển sẽ giúp tăng tính linh hoạt (như chúng ta sẽ thấy sau này với API Phiên phát nội dung nghe nhìn) và cho phép chúng ta đồng bộ hoá các chế độ điều khiển nếu trình duyệt can thiệp vào quá trình phát.
Khi video bắt đầu phát, chúng ta sẽ thay đổi trạng thái nút thành "tạm dừng" và ẩn các nút điều khiển video. Khi video tạm dừng, chúng ta chỉ cần thay đổi trạng thái nút thành "phát" và hiển thị các nút điều khiển video.
video.addEventListener('play', function () {
playPauseButton.classList.add('playing');
});
video.addEventListener('pause', function () {
playPauseButton.classList.remove('playing');
});
Khi thời gian được thuộc tính currentTime của video chỉ định thay đổi thông qua sự kiện video timeupdate, chúng tôi cũng cập nhật các chế độ điều khiển tuỳ chỉnh nếu các chế độ này hiển thị.
video.addEventListener('timeupdate', function () {
if (videoControls.classList.contains('visible')) {
videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
videoProgressBar.style.transform = `scaleX(${
video.currentTime / video.duration
})`;
}
});
Khi video kết thúc, chúng ta chỉ cần thay đổi trạng thái nút thành "play" (phát), đặt video
currentTime về 0 và hiện các nút điều khiển video. Xin lưu ý rằng chúng ta cũng có thể chọn tự động tải một video khác nếu người dùng đã bật một số loại tính năng "Tự động phát".
video.addEventListener('ended', function () {
playPauseButton.classList.remove('playing');
video.currentTime = 0;
});
Tua lại và tua đi
Hãy tiếp tục và thêm các nút "tua lùi" và "tua đi" để người dùng có thể dễ dàng bỏ qua một số nội dung.
<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);
});
Như trước, thay vì điều chỉnh kiểu video trong trình nghe sự kiện click của các nút này, chúng ta sẽ sử dụng các sự kiện video seeking và seeked được kích hoạt để điều chỉnh độ sáng của video. Lớp CSS seeking tuỳ chỉnh của tôi đơn giản như filter: brightness(0);.
video.addEventListener('seeking', function () {
video.classList.add('seeking');
});
video.addEventListener('seeked', function () {
video.classList.remove('seeking');
});
Dưới đây là những gì chúng tôi đã tạo cho đến nay. Trong phần tiếp theo, chúng ta sẽ triển khai nút toàn màn hình.
Toàn màn hình
Tại đây, chúng ta sẽ tận dụng một số API web để tạo ra trải nghiệm toàn màn hình hoàn hảo và liền mạch. Để xem cách hoạt động, hãy xem mẫu.
Rõ ràng là bạn không cần phải sử dụng tất cả các thành phần này. Bạn chỉ cần chọn những thành phần phù hợp với mình và kết hợp chúng để tạo quy trình tuỳ chỉnh.
Ngăn tự động chuyển sang chế độ toàn màn hình
Trên iOS, các phần tử video sẽ tự động chuyển sang chế độ toàn màn hình khi quá trình phát nội dung nghe nhìn bắt đầu. Vì chúng tôi đang cố gắng điều chỉnh và kiểm soát nhiều nhất có thể trải nghiệm nội dung nghe nhìn trên các trình duyệt di động, nên bạn nên đặt thuộc tính playsinline của phần tử video để buộc phần tử này phát cùng dòng trên iPhone và không chuyển sang chế độ toàn màn hình khi bắt đầu phát. Xin lưu ý rằng việc này không có tác dụng phụ
trên các trình duyệt khác.
<div id="videoContainer"></div>
<video id="video" src="file.mp4"></video><strong>playsinline</strong></video>
<div id="videoControls">...</div>
</div>
Bật/tắt chế độ toàn màn hình khi nhấp vào nút
Bây giờ, khi đã ngăn chặn chế độ toàn màn hình tự động, chúng ta cần tự xử lý chế độ toàn màn hình cho video bằng Fullscreen API. Khi người dùng
nhấp vào "nút toàn màn hình", hãy thoát khỏi chế độ toàn màn hình bằng
document.exitFullscreen() nếu tài liệu hiện đang sử dụng chế độ toàn màn hình. Nếu không, hãy yêu cầu chế độ toàn màn hình trên vùng chứa video bằng phương thức requestFullscreen() nếu có hoặc quay lại webkitEnterFullscreen() trên phần tử video chỉ trên 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);
});
Bật/tắt chế độ toàn màn hình khi thay đổi hướng màn hình
Khi người dùng xoay thiết bị sang chế độ ngang, hãy xử lý thông minh và tự động yêu cầu chế độ toàn màn hình để tạo trải nghiệm sống động. Để làm việc này, chúng ta cần có Screen Orientation API (API hướng màn hình) chưa được hỗ trợ ở mọi nơi và vẫn có tiền tố trong một số trình duyệt tại thời điểm đó. Do đó, đây sẽ là bản cải tiến tăng dần đầu tiên của chúng ta.
Cơ chế này hoạt động như thế nào? Ngay khi phát hiện thấy hướng màn hình thay đổi, hãy yêu cầu chế độ toàn màn hình nếu cửa sổ trình duyệt ở chế độ ngang (tức là chiều rộng lớn hơn chiều cao). Nếu không, hãy thoát khỏi chế độ toàn màn hình. Chỉ có vậy thôi.
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();
}
});
}
Khoá màn hình khoá ở chế độ ngang khi nhấp vào nút
Vì video có thể xem tốt hơn ở chế độ ngang, nên chúng ta có thể khoá màn hình ở chế độ ngang khi người dùng nhấp vào "nút toàn màn hình". Chúng ta sẽ kết hợp Screen Orientation API đã dùng trước đó và một số truy vấn phương tiện để đảm bảo trải nghiệm này là tốt nhất.
Việc khoá màn hình ở chế độ ngang cũng dễ dàng như việc gọi screen.orientation.lock('landscape'). Tuy nhiên, chúng ta chỉ nên làm việc này khi
thiết bị ở chế độ chân dung với matchMedia('(orientation: portrait)') và có thể
được cầm bằng một tay với matchMedia('(max-device-width: 768px)') vì điều này
sẽ không mang lại trải nghiệm tuyệt vời cho người dùng trên máy tính bảng.
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');
}
}
Mở khoá màn hình khi hướng thiết bị thay đổi
Có thể bạn đã nhận thấy trải nghiệm màn hình khoá mà chúng ta vừa tạo chưa hoàn hảo vì chúng ta không nhận được thông tin thay đổi về hướng màn hình khi màn hình bị khoá.
Để khắc phục vấn đề này, hãy sử dụng Device Orientation API (API Hướng thiết bị) nếu có. API này cung cấp thông tin từ phần cứng đo lường vị trí và chuyển động của thiết bị trong không gian: con quay hồi chuyển và la bàn kỹ thuật số cho hướng của thiết bị, còn gia tốc kế cho vận tốc của thiết bị. Khi phát hiện thấy sự thay đổi về hướng của thiết bị, hãy mở khoá màn hình bằng screen.orientation.unlock() nếu người dùng cầm thiết bị ở chế độ dọc và màn hình bị khoá ở chế độ ngang.
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,
);
}
}
},
);
}
Như bạn có thể thấy, đây là trải nghiệm toàn màn hình liền mạch mà chúng ta đang tìm kiếm. Để xem ví dụ thực tế, hãy xem mẫu.
Phát trong nền
Khi phát hiện thấy một trang web hoặc video trong trang web không còn xuất hiện nữa, bạn nên cập nhật dữ liệu phân tích để phản ánh điều này. Điều này cũng có thể ảnh hưởng đến quá trình phát hiện tại, chẳng hạn như chọn một bản nhạc khác, tạm dừng hoặc thậm chí là hiển thị các nút tuỳ chỉnh cho người dùng.
Tạm dừng video khi chế độ hiển thị trang thay đổi
Với API Chế độ hiển thị của trang, chúng ta có thể xác định chế độ hiển thị hiện tại của một trang và nhận thông báo về các thay đổi đối với chế độ hiển thị. Đoạn mã dưới đây tạm dừng video khi trang bị ẩn. Điều này xảy ra khi phương thức khoá màn hình đang hoạt động hoặc khi bạn chuyển đổi giữa các thẻ.
Vì hầu hết các trình duyệt di động hiện đều cung cấp các chế độ điều khiển bên ngoài trình duyệt cho phép tiếp tục phát video bị tạm dừng, nên bạn chỉ nên đặt hành vi này nếu người dùng được phép phát trong nền.
document.addEventListener('visibilitychange', function () {
// Pause video when page is hidden.
if (document.hidden) {
video.pause();
}
});
Hiện/ẩn nút tắt micrô khi chế độ hiển thị video thay đổi
Nếu sử dụng Intersection Observer API mới, bạn có thể nhận được thông tin chi tiết hơn mà không mất phí. API này cho bạn biết thời điểm một phần tử được quan sát xuất hiện hoặc thoát khỏi khung nhìn của trình duyệt.
Hãy hiện/ẩn nút tắt micrô dựa trên chế độ hiển thị video trong trang. Nếu video đang phát nhưng hiện không hiển thị, thì một nút tắt tiếng nhỏ sẽ xuất hiện ở góc dưới cùng bên phải của trang để người dùng điều khiển âm thanh video. Sự kiện video volumechange được dùng để cập nhật kiểu nút tắt tiếng.
<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);
});
Mỗi lần chỉ phát một video
Nếu có nhiều video trên một trang, bạn chỉ nên phát một video và tự động tạm dừng các video khác để người dùng không phải nghe nhiều bản âm thanh phát cùng lúc.
// 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();
});
}
Tuỳ chỉnh thông báo về nội dung nghe nhìn
Với Media Session API, bạn cũng có thể tuỳ chỉnh thông báo về nội dung nghe nhìn bằng cách cung cấp siêu dữ liệu cho video đang phát. Lớp này cũng cho phép bạn xử lý các sự kiện liên quan đến nội dung nghe nhìn, chẳng hạn như tua hoặc thay đổi bản nhạc có thể đến từ thông báo hoặc phím nội dung nghe nhìn. Để xem ví dụ thực tế, hãy xem mẫu.
Khi ứng dụng web của bạn đang phát âm thanh hoặc video, bạn có thể thấy một thông báo về nội dung nghe nhìn trong khay thông báo. Trên Android, Chrome cố gắng hết sức để hiển thị thông tin phù hợp bằng cách sử dụng tiêu đề của tài liệu và hình ảnh biểu tượng lớn nhất mà Chrome có thể tìm thấy.
Hãy cùng tìm hiểu cách tuỳ chỉnh thông báo nội dung nghe nhìn này bằng cách đặt một số siêu dữ liệu của phiên phát nội dung nghe nhìn, chẳng hạn như tiêu đề, nghệ sĩ, tên đĩa nhạc và hình minh hoạ bằng 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',
},
],
});
}
Sau khi phát xong, bạn không cần phải "phát hành" phiên phát nội dung nghe nhìn vì thông báo sẽ tự động biến mất. Xin lưu ý rằng navigator.mediaSession.metadata hiện tại sẽ được dùng khi bắt đầu phát. Đây là lý do bạn cần cập nhật để đảm bảo luôn hiển thị thông tin liên quan trong thông báo về nội dung nghe nhìn.
Nếu ứng dụng web của bạn cung cấp danh sách phát, bạn có thể cho phép người dùng di chuyển qua danh sách phát ngay từ thông báo nội dung nghe nhìn bằng một số biểu tượng "Bài hát trước" và "Bài hát tiếp theo".
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
});
}
Xin lưu ý rằng trình xử lý hành động đa phương tiện sẽ vẫn tồn tại. Điều này rất giống với mẫu trình nghe sự kiện, ngoại trừ việc xử lý một sự kiện có nghĩa là trình duyệt ngừng thực hiện bất kỳ hành vi mặc định nào và sử dụng hành vi này làm tín hiệu cho thấy ứng dụng web của bạn hỗ trợ thao tác đa phương tiện. Do đó, các nút điều khiển hành động nội dung nghe nhìn sẽ không xuất hiện trừ phi bạn đặt trình xử lý hành động thích hợp.
Nhân tiện, việc huỷ đặt trình xử lý hành động đa phương tiện cũng dễ dàng như việc gán trình xử lý đó cho null.
Media Session API cho phép bạn hiển thị các biểu tượng thông báo nội dung nghe nhìn "Tua lùi" và "Tua đi" nếu bạn muốn kiểm soát khoảng thời gian tua.
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);
});
}
Biểu tượng "Phát/Tạm dừng" luôn xuất hiện trong thông báo về nội dung nghe nhìn và các sự kiện liên quan sẽ được trình duyệt tự động xử lý. Nếu vì lý do nào đó mà hành vi mặc định không hoạt động, bạn vẫn có thể xử lý các sự kiện đa phương tiện "Phát" và "Tạm dừng".
Điều thú vị về API Phiên phát nội dung nghe nhìn là khay thông báo không phải là nơi duy nhất hiển thị siêu dữ liệu và các nút điều khiển nội dung nghe nhìn. Thông báo về nội dung nghe nhìn sẽ tự động được đồng bộ hoá với mọi thiết bị đeo đã ghép nối. Và thông tin này cũng xuất hiện trên màn hình khoá.