PJCHENder 未整理筆記

[Media] Media Streams, Streams API and WebRTC

2018-01-27

[Media] Media Streams, Streams API and WebRTC

@(JavaScript)[JavaScript, WebAPIs, WebRTC]

keywords: MediaStream, MediaStreamTrack, MediaDevices, MediaRecorder, MediaSource, track, channel, HTML Video Element, WebRTC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

WebRTC 影音串流概念

WebRTC API @ MDN

Media Capture 和 Streams API 又被稱作 Media Stream APIStream 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

了解更多

  • 包含 WebRTC 完成的說明步驟:WebRTC @ WebRTC
  • 包含完整的 WebRTC 說明文件和 Sample Code:WebRTC @ Github
  • WebRTC 官網

MediaDevices API - getUserMedia

MediaDevices 這個 API 讓網頁能夠存取與系統連接的媒體裝置,例如相機、麥克風、甚至分享螢幕。

MediaDevices @ MDN - Web APIs

MediaDevices.getUserMedia()

MediaDevices.getUserMedia() @ MDN

  • 透過 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
1
MediaDevices.getUserMedia --> Streams --> Tracks

getUserMedia() 會回傳一個 Promise ,當狀態為 fulfillment 的時候,會帶有 MediaStream 物件。

1
2
3
4
5
6
7
8
const constraints = { audio: true, video: true };
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
/* use the stream */
})
.catch(function(err) {
/* handle the error */
});

Constraints

getUserMediaconstraints 物件中可以設定影像採集時的幀數、像素大小;聲音採集時的採樣率和採樣大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 取得該瀏覽器支援的 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)
}

錯誤類型

常見的錯誤類型包含:

1
2
3
4
5
6
7
8
# 資料來源 https://ithelp.ithome.com.tw/articles/10204097
AbortError:硬件有問題,例如麥克風 G 了。
NotAllowedError:用戶拒絕使用麥克風。
NotFoundError:找不到你的 constraints 裡設置的媒體類型。
NotReadableError:用戶允許使用媒體設備,但是去讀取時發現無法使用媒體設備。
OverConstrainedError:表示無法滿足你所設置的 constraints。
SecurityError:偏好設定中可能有禁止使用媒體設備。
TypeError:constraints 怪怪的。

MediaDevices.enumerateDevices()

enumerateDevices() 會列出可存取的輸入和輸出裝置,像是麥克風、耳機、攝影機等等。它會回傳一個 Promise,當狀態為 fullfillment 時,會帶有 MediaDeviceInfo 陣列來描述該裝置。

1
2
3
4
5
6
7
8
9
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)
})

MediaDevices.enumerateDevices() @ MDN

MediaStreams API

MediaStream API 代表影音媒體內容的串流(stream fo media content)。一個串流(Stream)包含許多的軌(tracks),像是 video tracks 或 audio tracks。每一個軌(tracks) 都是 MediaStreamTrack 的實例,你可以透過建構式或者呼叫 MediaDevices.getUserMedia() 來取的 MediaStream 物件

1
2
3
4
5
6
7
8
// MediaStream Object
{
"id":"nqVHF7q8QydGQvRJmzNPKm39pjJOhWJRezjo",
"active":true,
"onaddtrack":null,
"onremovetrack":null,
"onactive":null
}

MediaStream() 建構式

透過 MediaStream() 建構式,可以產生一個新的 MediaStream 物件

1
2
3
stream = new MediaStream();
stream = new MediaStream(stream);
stream = new MediaStream(tracks[]);

MediaStream() @ MDN - Web APIS

透過 MediaStream 取得 Track

MediaStream.getVideoTracks() @ MDN - Web APIs

1
2
MediaStream.getVideoTracks()
MediaStream.getAudioTracks()

回傳在 MediaStream 物件中,kind 被設為 videoMediaStreamTrack 物件,其中回傳的順序不會是固定的

若有需要對取得的音軌進行額外的操作,則可以透過 AudioContext API。

MediaStreamTrack

MediaStreamTrack 表示的是在串流(stream)單一的媒體軌(media track),通常會是 audio tracks 或 video tracks。

1
2
3
4
5
6
7
8
9
10
11
12
// 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 沒有給任何資料,並且也將不會提供任何新的資料。

MediaStreamTrack @ MDN - Web APIs

方法

MediaRecorder

透過 MediaRecorder 這個 MediaStream Recording API 可以輕易的錄製影音。它可以透過 MediaRecorder() 建構式來建立。

Media Recorder @ MDN - Web APIs

MediaRecorder() 建構式

透過 MediaRecorder() 建構式,給它要錄製的 MediaStream ,即可以建立 MediaRecorder 物件。同時可以給它參數包含 MIME type(例如, video/webmvideo/mp4)、影音檔的 bit rates 等等。

1
2
3
4
5
6
var mediaRecorder = new MediaRecorder(stream[, options]);

// 如果沒有指定 mimeType,錄下來的 webm 影片在 Firefox 上可能不能看(Firefox 也不支援 h264)
var mediaRecorder = new MediaRecorder(stream, {
mimeType : 'video/webm\;codecs=vp9'
})

MediaRecorder 建構式 @ MDN

方法(Methods)

透過 MediaRecorder 的方法針對錄製的串流執行

1
2
3
4
5
6
7
8
// 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

1
2
3
/* 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。

MediaRecorder.ondataavailable @ MDN

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 物件的媒體資料來源。

建立下載或檢視用的 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 將 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 將 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

參考

掃描二維條碼,分享此文章