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? Dễ thôi! Mọi thứ đề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 đặt ra cho nội dung nghe nhìn trên trang web. Tôi nghĩ chúng ta đều đồng ý rằng nếu video là LÝ DO khiến người dùng truy cập, thì trải nghiệm của người dùng phải sống động và thu hút họ quay lại.
Trong bài viết này, tôi sẽ hướng dẫn bạn cách nâng cao dần trải nghiệm nội dung đa phương tiện và làm cho trải nghiệm đó trở nên phong phú hơn nhờ vào các 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 dành cho 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à phát ở chế độ nền. Bạn có thể thử mẫu này ngay 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 đa phương tiện khá đơn giản: phần tử gốc <div>
chứa phần tử nội dung đa phương tiện <video>
và phần tử con <div>
dành riêng cho các chế độ đ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 lại và tua tới, cũng như một số thành phần cho thời gian hiện tại, thời lượng và tính năng 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 chờ siêu dữ liệu video tải xong để đặ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 tiện ích tuỳ chỉnh mà tôi đã viết để chuyển đổi số giây thành một 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
Giờ đây, 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.
<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 điều khiển giúp tăng tính linh hoạt (như chúng ta sẽ thấy sau 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 tôi chỉ cần thay đổi trạng thái của 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 biểu thị bởi thuộc tính currentTime
của video thay đổi thông qua sự kiện video timeupdate
, chúng tôi cũng cập nhật các thành phần điều khiển tuỳ chỉnh nếu các thành phần đó 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 "phát", đặt video currentTime
về 0 và hiện các nút điều khiển video. Lưu ý rằng chúng tôi 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 đây, 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
đã kích hoạt để điều chỉnh độ sáng của video. Lớp CSS seeking
tuỳ chỉnh của tôi cũng đơ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 ta đã tạo được cho đến thời điểm này. 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
Ở đây, chúng ta sẽ tận dụng một số API Web để tạo trải nghiệm toàn màn hình hoàn hảo và liền mạch. Để xem ví dụ thực tế, vui lòng xem mẫu.
Rõ ràng là bạn không phải sử dụng tất cả các tính năng này. Bạn chỉ cần chọn những hành động phù hợp với mình và kết hợp chúng để tạo luồng tuỳ chỉnh.
Ngăn chế độ toàn màn hình tự động
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 ta đang cố gắng điều chỉnh và kiểm soát trải nghiệm nội dung nghe nhìn trên các trình duyệt di động nhiều nhất có thể, 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 nội dung nội tuyến 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
Giờ đây, khi đã ngăn chế độ tự động chuyển sang toàn màn hình, 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 chế độ toàn màn hình bằng document.exitFullscreen()
nếu tài liệu đ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 dự phòng cho webkitEnterFullscreen()
chỉ trên phần tử video 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 tự động yêu cầu chế độ toàn màn hình để tạo trải nghiệm sống động. Để làm được 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à tính năng cải tiến dần đầu tiên của chúng tôi.
Cơ chế này hoạt động như thế nào? Ngay khi phát hiện 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 ở chế độ ngang khi nhấp vào nút
Do video có thể xem tốt hơn ở chế độ ngang, nên chúng ta nên 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 tôi sẽ kết hợp API Hướng màn hình đã sử dụng trước đó và một số truy vấn nội dung nghe nhì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ư gọi screen.orientation.lock('landscape')
. Tuy nhiên, chúng ta chỉ nên thực hiện việc này khi
thiết bị ở chế độ dọc với matchMedia('(orientation: portrait)')
và có thể
được cầm bằng một tay với matchMedia('(max-device-width: 768px)')
vì đây
sẽ không phải là một 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 thay đổi hướng thiết bị
Bạn có thể 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 các 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ị và gia tốc kế cho vận tốc của thiết bị. Khi chúng ta phát hiện sự thay đổi hướng 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 tôi mong đợi. Để xem cách thực hiện, hãy xem mẫu.
Phát trong nền
Khi phát hiện 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 số 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à được thông báo về các thay đổi về chế độ hiển thị. Mã bên dưới sẽ tạm dừng video khi trang bị ẩn. Điều này xảy ra khi tính năng khoá màn hình đang hoạt động hoặc khi bạn chuyển đổi thẻ.
Vì hầu hết trình duyệt dành cho thiết bị di động hiện 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 đã 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 ở chế độ nền.
document.addEventListener('visibilitychange', function () {
// Pause video when page is hidden.
if (document.hidden) {
video.pause();
}
});
Hiển thị/ẩn nút tắt tiếng khi thay đổi chế độ hiển thị video
Nếu sử dụng Intersection Observer API mới, bạn có thể phân tích 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 đi vào hoặc thoát khỏi khung nhìn của trình duyệt.
Hãy hiển thị/ẩn nút tắt tiếng dựa trên chế độ hiển thị video trong trang. Nếu video đang phát nhưng không hiển thị, thì một nút tắt tiếng thu nhỏ sẽ xuất hiện ở góc dưới cùng bên phải của trang để người dùng có thể điều khiển âm thanh của 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);
});
Chỉ phát một video tại một thời điểm
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 đồng thời.
// 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 API phiên nội dung nghe nhìn, 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. Thư viện 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ư tìm kiếm hoặc theo dõi thay đổi có thể phát sinh từ thông báo hoặc phím nội dung nghe nhìn). Để xem ví dụ thực tế, vui lòng 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 thông báo 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 có thể tìm thấy.
Hãy xem cách tuỳ chỉnh thông báo nội dung nghe nhìn này bằng cách thiết lập một số siêu dữ liệu về phiên phát nội dung nghe nhìn, chẳng hạn như tiêu đề, nghệ sĩ, tên album 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 "giải phóng" 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. Đó là lý do bạn cần cập nhật thông tin này để đảm bảo luôn hiển thị thông tin liên quan trong thông báo 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 nên cho phép người dùng điều hướng qua danh sách phát ngay trong thông báo nội dung nghe nhìn bằng một số biểu tượng "Bản nhạc trước" và "Bản nhạc 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. Mẫ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 mẫu này làm tín hiệu cho biết ứng dụng web của bạn hỗ trợ hành động đối với nội dung đa phương tiện. Do đó, các chế độ điều khiển hành động đối với nội dung đa phương tiệ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ỷ thiết lập 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ị biểu tượng thông báo nội dung nghe nhìn "Tìm ngược" và "Tìm tiến" nếu bạn muốn kiểm soát khoảng thời gian bỏ qua.
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 nội dung nghe nhìn và trình duyệt sẽ tự động xử lý các sự kiện liên quan. 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 phát và tạm dừng nội dung nghe nhìn.
Điều thú vị về Media Session API 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 chế độ điều khiển nội dung nghe nhìn. Thông báo nội dung nghe nhìn sẽ tự động đồng bộ hoá với mọi thiết bị đeo được ghép nối. Đồng thời, thông báo này cũng xuất hiện trên màn hình khoá.