Tiện ích nguồn nội dung nghe nhìn (MSE) là một API JavaScript cho phép bạn tạo luồng để phát từ các phân đoạn âm thanh hoặc video. Mặc dù không được đề cập trong bài viết này, nhưng bạn cần hiểu rõ MSE nếu muốn nhúng video vào trang web để thực hiện những việc như:
- Truyền phát thích ứng, một cách khác để nói về việc thích ứng với khả năng của thiết bị và tình trạng mạng
- Nối thích ứng, chẳng hạn như chèn quảng cáo
- Dịch chuyển thời gian
- Kiểm soát hiệu suất và kích thước tải xuống
Bạn gần như có thể coi MSE là một chuỗi cửa hàng. Như minh hoạ trong hình, giữa tệp đã tải xuống và các phần tử nội dung đa phương tiện có một số lớp.
- Một phần tử
<audio>
hoặc<video>
để phát nội dung nghe nhìn. - Thực thể
MediaSource
cóSourceBuffer
để cấp dữ liệu cho phần tử nội dung nghe nhìn. - Lệnh gọi
fetch()
hoặc XHR để truy xuất dữ liệu nội dung nghe nhìn trong đối tượngResponse
. - Lệnh gọi đến
Response.arrayBuffer()
để cấp dữ liệu choMediaSource.SourceBuffer
.
Trong thực tế, chuỗi sẽ có dạng như sau:
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);
});
}
Nếu bạn có thể hiểu được nội dung giải thích cho đến thời điểm này, hãy ngừng đọc ngay. Nếu bạn muốn được giải thích chi tiết hơn, vui lòng đọc tiếp. Tôi sẽ tìm hiểu về chuỗi này bằng cách xây dựng một ví dụ cơ bản về MSE. Mỗi bước tạo bản dựng sẽ thêm mã vào bước trước đó.
Lưu ý về tính rõ ràng
Bài viết này sẽ cung cấp mọi điều bạn cần phải biết về phát nội dung đa phương tiện trên trang web chứ? Không, công cụ này chỉ nhằm giúp bạn hiểu mã phức tạp hơn mà bạn có thể tìm thấy ở nơi khác. Xin lưu ý rằng tài liệu này sẽ đơn giản hoá và loại trừ nhiều nội dung. Chúng tôi cho rằng mình có thể tránh được việc này vì chúng tôi cũng đề xuất sử dụng một thư viện như shaka Player của Google. Tôi sẽ lưu ý trong suốt quá trình tôi đơn giản hoá một cách có chủ ý.
Một số nội dung không được đề cập
Dưới đây là một số nội dung mà tôi sẽ không đề cập đến, không theo thứ tự cụ thể.
- Điều khiển chế độ phát. Chúng tôi nhận được các quảng cáo đó miễn phí nhờ sử dụng các phần tử HTML5
<audio>
và<video>
. - Xử lý lỗi.
Để sử dụng trong môi trường sản xuất
Sau đây là một số điều tôi đề xuất khi sử dụng API liên quan đến MSE trong phiên bản chính thức:
- Trước khi thực hiện lệnh gọi trên các API này, hãy xử lý mọi sự kiện lỗi hoặc trường hợp ngoại lệ về API, đồng thời kiểm tra
HTMLMediaElement.readyState
vàMediaSource.readyState
. Các giá trị này có thể thay đổi trước khi sự kiện liên kết được phân phối. - Hãy đảm bảo rằng các lệnh gọi
appendBuffer()
vàremove()
trước đó vẫn đang diễn ra bằng cách kiểm tra giá trị booleanSourceBuffer.updating
trước khi cập nhậtmode
,timestampOffset
,appendWindowStart
,appendWindowEnd
củaSourceBuffer
hoặc gọiappendBuffer()
hoặcremove()
trênSourceBuffer
. - Đối với mọi thực thể
SourceBuffer
được thêm vàoMediaSource
, hãy đảm bảo không có giá trịupdating
nào là đúng trước khi gọiMediaSource.endOfStream()
hoặc cập nhậtMediaSource.duration
. - Nếu giá trị
MediaSource.readyState
làended
, các lệnh gọi nhưappendBuffer()
vàremove()
hoặc việc đặtSourceBuffer.mode
haySourceBuffer.timestampOffset
sẽ khiến giá trị này chuyển đổi sangopen
. Điều này có nghĩa là bạn nên chuẩn bị để xử lý nhiều sự kiệnsourceopen
. - Khi xử lý các sự kiện
HTMLMediaElement error
, nội dung củaMediaError.message
có thể hữu ích trong việc xác định nguyên nhân gốc của lỗi, đặc biệt đối với các lỗi khó tái tạo trong môi trường kiểm thử.
Đính kèm một bản sao MediaSource vào phần tử đa phương tiện
Giống như nhiều hoạt động phát triển web hiện nay, bạn cần bắt đầu bằng việc phát hiện tính năng. Tiếp theo, hãy lấy một phần tử nội dung nghe nhìn, đó là phần tử <audio>
hoặc <video>
.
Cuối cùng, hãy tạo một thực thể của MediaSource
. URL này được chuyển thành URL và được truyền đến thuộc tính nguồn của phần tử nội dung đa phương tiện.
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.');
}
Việc chuyển đối tượng MediaSource
đến thuộc tính src
có vẻ hơi lạ. Chúng thường là chuỗi, nhưng cũng có thể là blob.
Nếu kiểm tra một trang có nội dung nghe nhìn được nhúng và kiểm tra thành phần nội dung đa phương tiện của trang đó, thì bạn sẽ thấy ý tôi là gì.
Thực thể MediaSource đã sẵn sàng chưa?
URL.createObjectURL()
có tính chất đồng bộ; tuy nhiên, tệp này sẽ xử lý tệp đính kèm một cách không đồng bộ. Điều này sẽ gây ra sự chậm trễ đôi chút trước khi bạn có thể thực hiện thao tác với thực thể MediaSource
. Thật may là có nhiều cách để kiểm tra điều này.
Cách đơn giản nhất là sử dụng thuộc tính MediaSource
có tên là readyState
. Thuộc tính readyState
mô tả mối quan hệ giữa thực thể MediaSource
và phần tử nội dung đa phương tiện. Loại dữ liệu này có thể có một trong những giá trị sau:
closed
– Thực thểMediaSource
không được đính kèm vào phần tử phương tiện.open
– Thực thểMediaSource
được đính kèm vào một phần tử nội dung nghe nhìn và đã sẵn sàng nhận dữ liệu hoặc đang nhận dữ liệu.ended
– Thực thểMediaSource
được đính kèm vào một thành phần nội dung đa phương tiện và tất cả dữ liệu của thành phần đó đã được truyền đến thành phần đó.
Việc trực tiếp truy vấn những tuỳ chọn này có thể ảnh hưởng tiêu cực đến hiệu suất. May mắn là MediaSource
cũng kích hoạt các sự kiện khi readyState
thay đổi, cụ thể là sourceopen
, sourceclosed
, sourceended
. Trong ví dụ tôi đang xây dựng, tôi sẽ sử dụng sự kiện sourceopen
để cho tôi biết thời điểm tìm nạp và lưu video vào bộ đệm.
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>
Lưu ý rằng tôi cũng đã gọi revokeObjectURL()
. Tôi biết rằng việc này có vẻ quá sớm, nhưng tôi có thể làm điều này bất cứ lúc nào sau khi thuộc tính src
của phần tử đa phương tiện được kết nối với một thực thể MediaSource
. Việc gọi phương thức này không huỷ bất kỳ
đối tượng nào. API này cho phép nền tảng xử lý việc thu gom rác tại một thời điểm thích hợp, đó là lý do tôi gọi ngay tính năng này.
Tạo SourceBuffer
Bây giờ, đã đến lúc tạo SourceBuffer
. Đây là đối tượng thực sự
thực hiện công việc chuyển đổi dữ liệu giữa nguồn nội dung đa phương tiện và phần tử nội dung đa phương tiện. SourceBuffer
phải dành riêng cho loại tệp nội dung nghe nhìn mà bạn đang tải.
Trong thực tế, bạn có thể thực hiện việc này bằng cách gọi addSourceBuffer()
với giá trị thích hợp. Hãy lưu ý rằng trong ví dụ bên dưới, chuỗi loại MIME chứa một loại MIME và 2 codec. Đây là chuỗi MIME cho tệp video, nhưng sử dụng
các bộ mã hoá và giải mã riêng biệt cho các phần video và âm thanh của tệp.
Phiên bản 1 của quy cách MSE cho phép các tác nhân người dùng khác nhau về việc có cần cả loại MIME và codec hay không. Một số tác nhân người dùng không yêu cầu, nhưng chỉ cho phép loại MIME. Ví dụ: một số tác nhân người dùng (Chrome) yêu cầu bộ mã hoá và giải mã cho loại MIME không tự mô tả bộ mã hoá và giải mã của chúng. Thay vì cố gắng sắp xếp tất cả những thứ này, tốt hơn là chỉ nên đưa vào cả hai.
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>;
}
Tải tệp nội dung nghe nhìn
Nếu bạn tìm kiếm các ví dụ về MSE trên Internet, bạn sẽ tìm thấy nhiều yêu cầu truy xuất các tệp nội dung nghe nhìn bằng XHR. Để cải tiến hơn nữa,
tôi sẽ sử dụng API Tìm nạp
và Promise mà API trả về. Nếu bạn đang cố gắng thực hiện việc này trong Safari, thì thao tác này sẽ không hoạt động nếu không có polyfill fetch()
.
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>;
}
Trình phát chất lượng chính thức sẽ có cùng một tệp trong nhiều phiên bản để hỗ trợ các trình duyệt khác nhau. Dịch vụ này có thể sử dụng các tệp riêng biệt cho âm thanh và video để cho phép chọn âm thanh dựa trên chế độ cài đặt ngôn ngữ.
Mã trong thực tế cũng sẽ có nhiều bản sao của các tệp nội dung nghe nhìn ở nhiều độ phân giải để có thể thích ứng với các tính năng khác nhau của thiết bị và điều kiện mạng. Một ứng dụng như vậy có thể tải và phát video theo nhiều đoạn bằng cách sử dụng yêu cầu phạm vi hoặc phân đoạn. Điều này cho phép thích ứng với điều kiện mạng trong khi nội dung nghe nhìn đang phát. Bạn có thể đã nghe nói đến thuật ngữ DASH (Truyền phát thích ứng động qua HTTP) hoặc HLS (Phát trực tuyến dựa trên HTTP), đây là hai phương pháp để thực hiện điều này. Nội dung thảo luận đầy đủ về chủ đề này nằm ngoài phạm vi của phần giới thiệu này.
Xử lý đối tượng phản hồi
Mã này gần như đã hoàn tất, nhưng nội dung nghe nhìn không phát được. Chúng ta cần lấy dữ liệu nội dung nghe nhìn từ đối tượng Response
vào SourceBuffer
.
Cách thông thường để truyền dữ liệu từ đối tượng phản hồi đến thực thể MediaSource
là lấy ArrayBuffer
từ đối tượng phản hồi và truyền dữ liệu đó đến SourceBuffer
. Bắt đầu bằng cách gọi response.arrayBuffer()
để trả về một lời hứa vào vùng đệm. Trong mã của mình, tôi đã truyền lời hứa này vào mệnh đề then()
thứ hai, trong đó tôi thêm lời hứa này vào 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>
}
Gọi endOfStream()
Sau khi thêm tất cả ArrayBuffers
và dự kiến không có thêm dữ liệu nội dung nghe nhìn nào khác, hãy gọi MediaSource.endOfStream()
. Thao tác này sẽ thay đổi MediaSource.readyState
thành
ended
và kích hoạt sự kiện 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);
});
}
Phiên bản cuối cùng
Sau đây là ví dụ về đoạn mã hoàn chỉnh. Tôi hy vọng bạn đã tìm hiểu điều gì đó về Tiện ích nguồn nội dung nghe nhìn.
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);
});
}