[WebAPIs] Media Streams, Streams API and WebRTC
keywords: MediaStream
, MediaStreamTrack
, MediaDevices
, MediaRecorder
, MediaSource
, track
, channel
, HTML Video Element
, WebRTC
var chunks = [];
navigator.mediaDevices
.getUserMedia(constraints)
.then(function (stream) {
/* Get Tracks */
var videoTracks = stream.getVideoTracks();
/* Add MediaRecorder */
var mediaRecorder = new MediaRecorder(stream);
/* Close all tracks */
stream.getTracks().forEach(function (track) {
track.stop();
});
/* Set stream to <video> */
videoElement.srcObject = stream;
})
.catch(function (err) {
/* handle the error */
});
RecordRTC (library for WebRTC) @ Github
W ebRTC 影音串流概念
WebRTC API @ MDN
Media Capture 和 Streams API 又被稱作 Media Stream API 或 Stream API ,主要用來處理和網頁即時通訊(WebRTC, Web Real-Time Communications)有關的影音資料傳流,以及讓瀏覽器在不需要任何外掛的情況下直接進行 P2P 的溝通。
- 編解碼器(codec):WebRTC 使用的聲音編解碼器包含
Opus
,iSac
,iLBC
;影片編碼器包含VP8
,VP9
。 - 通訊協定:WebRTC 使用的通訊協定為 RTP/RTCP
- Signaling Server:雖然 WebRTC 可以讓使用者直接透過瀏覽器進行 P2P 連線,但在雙方建立連線前仍需要有一個伺服器讓他們知道雙方的位址,才能進行溝通,這個伺服器稱作 Signaling Server,這並沒有規範在 WebRTC 內。
目前主要包含有三種 API 分別是:
getUserMedia
:取得使用者的影音串流(影音採集)RTCPeerConnection
:建立與管理兩個瀏覽器間的直接通訊(P2P)RTCDataChannel
:用來傳送 P2P 間的資料
更多編解碼器(codec)可以參考 [影音傳輸-基礎知識](/Users/pjchen/Projects/Notes/source/_posts/WebDevelopment/[Note] 影音傳輸-基礎知識.md) @ 筆記。 更多通訊協定(protocol)可以參考 [影音傳輸-傳輸方式與通訊協定](/Users/pjchen/Projects/Notes/source/_posts/WebDevelopment/[Note] 影音傳輸-傳輸方式與通訊協定.md) @ 筆記。 關於如何透過 Signaling Server 建立 WebRTC 連線可以參考 30-26 之 WebRTC 的 P2P 即時通信與小範例 by 我是小馬克 @ iThome
了解更多
MediaDevices API - getUserMedia
MediaDevices 這個 API 讓網頁能夠存取與系統連接的媒體裝置,例如相機、麥克風、甚至分享螢幕。
MediaDevices @ MDN - Web APIs
MediaDevices.getUserMedia()
- 透過
getUserMedia
API 可以取得 MediaStream 物件,而一個 MediaStream 通常包含 0 個以上的MediaStreamTrack
物件,這個物件則是用來表徵各種影音軌(tracks),每一個 MediaStreamTrack 則包含一個以上的頻道(channels),每個頻道則是表徵影音串流(media stream)的最小單位,例如某一個講者的聲音訊號,或者是左右聲道。 - 透過
getUserMedia()
可以產生 local 的 MediaStream,通常輸入源來自於使用者的攝影機或麥克風;而 non-local 的 MediaStream 通常來自 media 元素,像是<video>
和<audio>
,或是透過網絡、WebRTC RTCPeerConnection API、或者 Web Audio API MediaStreamAudioSourceNode 產生的串流。 - MediaStream 物件的輸出則是和 consumer 間相連結,它可以是 media 元素,像是
<video>
和<audio>
、WebRTC RTCPeerConnection API 、或 Web Audio API MediaStreamAudioDestinationNode。
MediaDevices.getUserMedia --> Streams --> Tracks
getUserMedia()
會回傳一個 Promise ,當狀態為 fulfillment 的時候,會帶有 MediaStream
物件。
const constraints = { audio: true, video: true };
navigator.mediaDevices
.getUserMedia(constraints)
.then(function (stream) {
/* use the stream */
})
.catch(function (err) {
/* handle the error */
});
Constraints
在 getUserMedia
的 constraints
物件中可以設定影像採集時的幀數、像素大小;聲音採集時的採樣率和採樣大小:
// 取得該瀏覽器支援的 constraints
const supported = navigator.mediaDevices.getSupportedConstraints();
// 常用的 constraints
const constraints = {
video: {
width: { min: 1024, ideal: 1280, max: 1920 }, // ideal 瀏覽器會試著去找到最接近的情況
height: { min: 776, ideal: 720, max: 1080 },
frameRate: 10,
facingMode: "user",
deviceId: myPreferredCameraDeviceId
},
audio: true,
sampleSize: 8(bit),
sampleRate: 40000(40 kHz)
}
錯誤類型
常見的錯誤類型包含:
# 資料來源 https://ithelp.ithome.com.tw/articles/10204097
AbortError:硬件有問題,例如麥克風 G 了。
NotAllowedError:用戶拒絕使用麥克風。
NotFoundError:找不到你的 constraints 裡設置的媒體類型。
NotReadableError:用戶允許使用媒體設備,但是去讀取時發現無法使用媒體設備。
OverConstrainedError:表示無法滿足你所設置的 constraints。
SecurityError:偏好設定中可能有禁止使用媒體設備。
TypeError:constraints 怪怪的。
MediaDevices.enumerateDevices()
enumerateDevices()
會列出可存取的輸入和輸出裝置,像是麥克風、耳機、攝影機等等。它會回傳一個 Promise,當狀態為 fullfillment 時,會帶有 MediaDeviceInfo
陣列來描述該裝置。
navigator.mediaDevices
.enumerateDevices()
.then(function (devices) {
devices.forEach(function (device) {
console.log(`${device.kind}: ${device.label}, id = ${device.deviceId}`);
});
})
.catch(function (err) {
console.log(err.name + ': ' + err.message);
});
MediaStreams API
- Media Streams API @ MDN
- MediaStreams @ MDN - WebAPIs
MediaStream API 代表影音媒體內容的串流(stream fo media content)。一個串流(Stream)包含許多的軌(tracks),像是 video tracks 或 audio tracks。每一個軌(tracks) 都是 MediaStreamTrack
的實例,你可以透過建構式或者呼叫 MediaDevices.getUserMedia()
來取的 MediaStream 物件。
// MediaStream Object
{
"id": "nqVHF7q8QydGQvRJmzNPKm39pjJOhWJRezjo",
"active": true,
"onaddtrack": null,
"onremovetrack": null,
"onactive": null
}
MediaStream() 建構式
透過 MediaStream()
建構式,可以產生一個新的 MediaStream 物件:
stream = new MediaStream();
stream = new MediaStream(stream);
stream = new MediaStream(tracks[]);
MediaStream() @ MDN - Web APIS
透過 MediaStream 取得 Track
MediaStream.getVideoTracks() @ MDN - Web APIs
MediaStream.getVideoTracks();
MediaStream.getAudioTracks();
回傳在 MediaStream 物件中,kind 被設為 video
的 MediaStreamTrack
物件,其中回傳的順序不會是固定的。
若有需要對取得的音軌進行額外的操作,則可以透過 AudioContext API。
MediaStreamTrack
MediaStreamTrack 表示的是在串流(stream)單一的媒體軌(media track),通常會是 audio tracks 或 video tracks。
// MediaStreamTack Object
{
kind: "video",
id: "3026b0eb-f18e-4e2e-8d5d-3ccb53e1494b",
label: "FaceTime HD Camera",
enabled: true,
muted: false,
onended: null,
onmute: null,
onunmute: null,
readyState: "live"
}
- readyState:
- live:表示此 input 已經連結並提供最好的即時資料。在這個情況下,可以透過改變
MediaStreamTrack.enabled
屬性來切換 output data 的開關。 - ended:表示 input 沒有給任何資料,並且也將不會提供任何新的資料。
- live:表示此 input 已經連結並提供最好的即時資料。在這個情況下,可以透過改變
MediaStreamTrack @ MDN - Web APIs
方法
MediaStreamTrack.getCapabilities()
MediaStreamTrack.getConstraints()
MediaStreamTrack.getSettings()
MediaStreamTrack.stop()
:關閉該裝置,該裝置的 state 會變成 ended。
MediaRecorder
透過 MediaRecorder 這個 MediaStream Recording API 可以輕易的錄製影音。它可以透過 MediaRecorder()
建構式來建立。
Media Recorder @ MDN - Web APIs
MediaRecorder() 建構式
透過 MediaRecorder() 建構式,給它要錄製的 MediaStream
,即可以建立 MediaRecorder 物件。同時可以給它參數包含 MIME type(例如, video/webm 或 video/mp4)、影音檔的 bit rates 等等。
var mediaRecorder = new MediaRecorder(stream[, options]);
// 如果沒有指定 mimeType,錄下來的 webm 影片在 Firefox 上可能不能看(Firefox 也不支援 h264)
var mediaRecorder = new MediaRecorder(stream, {
mimeType : 'video/webm\;codecs=vp9'
})
- stream:放入要被記錄的
MediaStream
,它可以是來自navigator.mediaDevices.getUserMedia()
、<audio>
、<video>
或<canvas>
元素。
MediaRecorder 建構式 @ MDN
方法(Methods)
透過 MediaRecorder 的方法針對錄製的串流執行
MediaRecorder.start()
:開始錄製MediaRecorder.stop()
:結束錄製MediaRecorder.requestData()
:用來觸發 dataavailalbe 事件,回傳的 Blob 物件中,會包含當前已錄製影音串流。
// Request Data: mediaRecorder.requestData()
requestDataBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
mediaRecorder.requestData();
console.log('mediaRecorder.requestData()');
console.log('mediaRecorder.state: ', mediaRecorder.state);
});
事件(Events)
MediaRecorder.ondataavailable
/* dataavailable event handler */
MediaRecorder.ondataavailable = function(event) { ... }
MediaRecorder.addEventListener('dataavailable', function(event) { ... })
MediaRecorder.ondataavailable 的事件處理器用來處理 dataavailable 事件,讓你可以針對可存取的 Blob 資料加以操作。這個事件會在 MediaRecorder 傳送媒體資料到應用程式以供使用時促發,data 會是包含媒體資料的 Blob 物件。以下情況將會促發此事件:
- 當媒體串流結束時,還沒傳送到
ondataavailable
事件處理器的媒體檔案會以一個 Blob 傳送。 - 當
MediaRecorder.stop()
被呼叫時:從開始錄影(或者最後一次 dataavailable 事件發生後)所截取的所有媒體檔案會傳送到一個 Blob。此後,擷取結束。 - 當
MediaRecorder.requestData()
被呼叫時:從開始錄影(或者最後一次 dataavailable 事件發生時)所截取的所有媒體檔案會被傳送,接著一個新的 Blob 會被建立,媒體的截取會繼續進入該 blob 中。 - 如果
timeslice
屬性被帶入MediaRecorder.start()
這個方法,則 dataavailable 事件會在每一個 timeslice milliseconds 被促發。也就是說,每一個 blob 會有一個特定的時間長度(除了最後一個 blob 可能會較短)。- 因此,如果這個方法是透過
recorder.start(1000);
這樣的方式呼叫,dataavailable 事件會在每擷取一秒影音時即觸發一次,每一次的事件處理器中都會帶有包含一秒鐘長度的 blob 媒體資料檔。你也可以將 timeslice 搭配MediaRecorder.stop()
或MediaRecorder.requestData()
來產生許多相同長度的 blobs。
- 因此,如果這個方法是透過
Media Source Extension API(MSE)
透過 Media Source Extensions API (MSE) 可以使用網路基礎的影音串流,並且使用 <video>
和 <audio>
加以播放。
過去幾年已經可以不需要透過外掛來播放影音檔,但通常只限於播放單一軌(track),卻仍無法結合或拆分 arrayBuffers。透過 MSE 則可以取代過去使用 src
作為單一軌的影音元素,參照到新的 MediaSource 物件。MediaSource 物件包含了許多媒體狀態的資訊,並可參照到多種 SourceBuffer 物件(包含許多不同影音的 chunks),以此組成完整的串流(stream)。
[Media Source Extension API](Media Source Extensions API) @ MDN - WEB APIs
MediaSource
Media Source Extensions API 中的 MediaSource 用來表徵 HTMLMediaElement
物件的媒體資料來源。
- 範例:透過 AJAX 和 Buffer 取得影音串流
- MediaSource @ MDN - WEB APIs
建立下載或檢視用的 URL
keywords: createObjectURL
, revokeObjectURL
透過 URL.createObjectURL(object)
這個方法可以建立代表參數物件的 URL。參數類型可以是 File
, Blob
, MediaStream
, 或 MediaSource
物件。
需要留意的是,當你每次呼叫 createObjectURL()
時,都會建立一個新的 URL 物件,即使你是對相同的物件建立的 URL 也會產生新的 URL 物件。因此,當你不在需要使用這個 URL 物件時,若你希望釋放這個物件,可以呼叫 URL.revokeObjectURL()
。瀏覽器雖然會在文件 unloaded 時自動釋放他們,但為了效能和記憶體的使用,最好能夠主動 unload 它們。
範例:Using object URLs to display images @ MDN
/**
* 將 blob 產生可使用的 URL
* https://developer.mozilla.org/en-US/docs/Web/API/Blob
**/
var typedArray = GetTheTypedArraySomehow();
// pass a useful mime type here
var blob = new Blob([typedArray], {type: 'application/octet-binary'});
// url will be something like: blob:d3958f5c-0777-0845-9dcf-2cb28783acaf
var url = URL.createObjectURL(blob);
// now you can use the url in any context that regular URLs can be used in, for example img.src, etc.
/**
* 將 media 帶入連結中
* HTMLMediaElement.srcObject
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject
*/
const mediaSource = new MediaSource();
const video = document.createElement('video');
try {
// 透過這個方法 <video> 元素不會有改變
video.srcObject = mediaSource;
} catch (error) {
// 透過這個方法 <video> 會多一個帶有 blob 的 src 屬性
video.src = URL.createObjectURL(mediaSource);
}
URL.createObjectURL() @ MDN -Web APIs URL.revokeObjectURL() @ MDN - Web APIs HTMLMediaElement.srcObject @ MDN - Web APIs
參考
- 30 天之即時網路影音開發攻略(小白本) @ iThome
- [JS30] Day11: Custom HTML Video Player @ Notes
- HTML Video Element @ MDN
- 30-26 之 WebRTC 的 P2P 即時通信與小範例 by 我是小馬克 @ iThome