MediaStream 是連接 WebRTC API 和底層物理流的中間層渣刷,webRTC將音視頻經(jīng)過Vocie / Video engine進(jìn)行處理后徐勃,再通過MediaStream API給暴露給上層使用笤喳。
概念
- MediaStreamTrack
- MediaStream
- source
- sink
- MediaTrackConstraints
1. MediaStreamTrack
MediaStreamTrack是WebRTC中的基本媒體單位弯予,一個MediaStreamTrack包含一種媒體源(媒體設(shè)備或錄制內(nèi)容)返回的單一類型的媒體(如音頻肛跌,視頻)愉舔。單個軌道可包含多個頻道钢猛,如立體聲源盡管由多個音頻軌道構(gòu)成,但也可以看作是一個軌道轩缤。
WebRTC并不能直接訪問或者控制源命迈,對源的一切控制都可以通過軌道控制MediaTrackConstraints
進(jìn)行實施。
MediaStreamTrack MDN文檔
MediaTrackConstraints MDN文檔
2. MediaStream
MediaStream是MediaStreamTrack的合集典奉,可以包含 >=0 個 MediaStreamTrack躺翻。MediaStream能夠確保它所包含的所有軌道都是是同時播放的,以及軌道的單一性卫玖。
3. source 與 sink
再MediaTrack的源碼中公你,MediaTrack都是由對應(yīng)的source和sink組成的。
//src\pc\video_track.cc
void VideoTrack::AddOrUpdateSink(rtc::VideoSinkInterface<VideoFrame>* sink, const rtc::VideoSinkWants& wants) {
RTC_DCHECK(worker_thread_->IsCurrent());
VideoSourceBase::AddOrUpdateSink(sink, wants);
rtc::VideoSinkWants modified_wants = wants;
modified_wants.black_frames = !enabled();
video_source_->AddOrUpdateSink(sink, modified_wants);
}
void VideoTrack::RemoveSink(rtc::VideoSinkInterface<VideoFrame>* sink) {
RTC_DCHECK(worker_thread_->IsCurrent());
VideoSourceBase::RemoveSink(sink);
video_source_->RemoveSink(sink);
}
瀏覽器中存在從source到sink的媒體管道假瞬,其中source負(fù)責(zé)生產(chǎn)媒體資源陕靠,包括多媒體文件,web資源等靜態(tài)資源以及麥克風(fēng)采集的音頻脱茉,攝像頭采集的視頻等動態(tài)資源剪芥。而sink則負(fù)責(zé)消費source生產(chǎn)媒體資源,也就是通過<img>琴许,<video>税肪,<audio>等媒體標(biāo)簽進(jìn)行展示,或者是通過RTCPeerConnection將source通過網(wǎng)絡(luò)傳遞到遠(yuǎn)端。RTCPeerConnection可同時扮演source與sink的角色益兄,作為sink锻梳,可以將獲取的source降低碼率,縮放净捅,調(diào)整幀率等疑枯,然后傳遞到遠(yuǎn)端,作為source蛔六,將獲取的遠(yuǎn)端碼流傳遞到本地渲染荆永。
source 與sink構(gòu)成一個MediaTrack,多個MeidaTrack構(gòu)成MediaStram国章。
4. MediaTrackConstraints
MediaTrackConstraints
描述MediaTrack的功能以及每個功能可以采用的一個或多個值具钥,從而達(dá)到選擇和控制源的目的。 MediaTrackConstraints
可作為參數(shù)傳遞給applyConstraints()
以達(dá)到控制軌道屬性的目的捉腥,同時可以通過調(diào)getConstraints()
用來查看最近應(yīng)用自定義約束氓拼。
const constraints = {
width: {min: 640, ideal: 1280},
height: {min: 480, ideal: 720},
advanced: [
{width: 1920, height: 1280},
{aspectRatio: 1.333}
]
};
//{ video: true }也是一個MediaTrackConstraints對象,用于指定請求的媒體類型和相對應(yīng)的參數(shù)抵碟。
navigator.mediaDevices.getUserMedia({ video: true })
.then(mediaStream => {
const track = mediaStream.getVideoTracks()[0];
track.applyConstraints(constraints)
.then(() => {
// Do something with the track such as using the Image Capture API.
})
.catch(e => {
// The constraints could not be satisfied by the available devices.
});
});
如何播放MediaStream
可將MediaStream對象直接賦值給HTMLMediaElement
接口的 srcObject
屬性桃漾。
video.srcObject = stream;
如何獲取MediaStream
- 本地設(shè)備
可通過調(diào)用MediaDevices.getUserMedia()
來訪問本地媒體,調(diào)用該方法后瀏覽器會提示用戶給予使用媒體輸入的許可拟逮,媒體輸入會產(chǎn)生一個MediaStream
撬统,里面包含了請求的媒體類型的軌道。此流可以包含一個視頻軌道(來自硬件或者虛擬視頻源敦迄,比如相機(jī)恋追、視頻采集設(shè)備和屏幕共享服務(wù)等等)、一個音頻軌道(同樣來自硬件或虛擬音頻源罚屋,比如麥克風(fēng)苦囱、A/D轉(zhuǎn)換器等等),也可能是其它軌道類型脾猛。
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
/* 使用這個stream*/
video.srcObject = stream;
})
.catch(function(err) {
/* 處理error */
});
通過MediaDevices.enumerateDevices()
我們可以得到一個本機(jī)可用的媒體輸入和輸出設(shè)備的列表撕彤,例如麥克風(fēng),攝像機(jī)猛拴,耳機(jī)設(shè)備等羹铅。
//獲取媒體設(shè)備
navigator.mediaDevices.enumerateDevices().then(res => {
console.log(res);
});
列表中的每個媒體輸入都可作為MediaTrackConstraints中對應(yīng)類型的值,如一個音頻設(shè)備輸入audioDeviceInput可設(shè)置為MediaTrackConstraints中audio屬性的值
cosnt constraints = { audio : audioDeviceInput }
將該constraint值作為參數(shù)傳入到MediaDevices.getUserMedia(constraints)
中愉昆,便可獲得該設(shè)備的MediaStream职员。
MediaDevices.enumerateDevices() MDN文檔
MediaDevices.getUserMedia() MDN文檔
- 捕獲屏幕
使用MediaDevices.getDisplayMedia()
方法,可以提示用戶去選擇和授權(quán)捕獲展示的內(nèi)容或部分內(nèi)容(如一個窗口)跛溉,并將錄制內(nèi)容在一個MediaStream
里焊切。
MediaDevices.getDisplayMedia() MDN文檔
- HTMLCanvasElement.captureStream()
使用HTMLCanvasElement.captureStream()
方法返回的 CanvasCaptureMediaStream
是一個實時捕獲的canvas動畫流扮授。
//frameRate設(shè)置雙精準(zhǔn)度浮點值為每個幀的捕獲速率。
//如果未設(shè)置蛛蒙,則每次畫布更改時都會捕獲一個新幀糙箍。
//如果設(shè)置為0,則會捕獲單個幀牵祟。
cosnt canvasStream = canvas.captureStream(frameRate);
video.srcObject = canvasSream;
HTMLCanvasElement.captureStream() MDN文檔
CanvasCaptureMediaStream MDN文檔
RTCPeerConnection
從其他MediaStream中獲取
可通過構(gòu)造函數(shù)MediaStream()
返回新建的空白的 MediaStream
實例
newStream = new MediaStream();
- 傳入
MediaStream
對象,該MediaStream
對象的數(shù)據(jù)軌會被自動添加到新建的流中抖格。且這些數(shù)據(jù)軌不會從原流中移除诺苹,即變成了兩條流共享的數(shù)據(jù)。
newStream = new MediaStream(otherStream);
- 傳入
MediaStreamTrack
對象的Array
類型的成員雹拄,代表了每一個添加到流中的數(shù)據(jù)軌收奔。
newStream = new MediaStream(tracks[]);
-
MediaStream.addTrack()
方法會給流添加一個新軌道。 -
MediaStream.clone()
方法復(fù)制一份副本MediaStream
滓玖。這個新的MediaStream
對象有一個新的id
坪哄。
實現(xiàn)一個簡易的錄屏工具
- 獲取捕獲屏幕的MeidaStream
const screenStream = await navigator.mediaDevices.getDisplayMedia();
- 獲取本地音視頻數(shù)據(jù)
const cameraStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
const audioStream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
- 將屏幕畫面與攝像頭畫面繪制canvas中
function createStreamVideo(stream) {
const video = document.createElement("video");
video.srcObject = stream;
video.autoplay = true;
return video;
}
const cameraVideo = createStreamVideo(cameraStream);
const screenVideo = createStreamVideo(screenStream);
animationFrameHandler() {
if (screenVideo) {
canvas.drawImage(screenVideo, 0, 0, canvasWidth, canvasHeight);
}
if (cameraVideo) {
canvas.drawImage(
cameraVideo,
canvasWidth - CAMERA_VIDEO_WIDTH,
canvasHeight - CAMERA_VIDEO_HEIGHT,
CAMERA_VIDEO_WIDTH,
CAMERA_VIDEO_HEIGHT
)
}
requestAnimationFrame(animationFrameHandler.bind(this));
}
- 獲取canvas的MediaStream
const recorderVideoStream = await canvas.captureStream();
- 合并本地的音頻軌道和canvasStream的視頻軌道,獲得最終畫面的MediaStream
const stream = new MediaStream();
audioStream.getAudioTracks().forEach(track => stream.addTrack(track));
recorderVideoStream.getVideoTracks().forEach(track => stream.addTrack(track));
video.srcObject = stream;
- 通過
MediaRecorder
對畫面進(jìn)行錄制
const recorder = new MediaRecorder(stream, { mineType: "video/webm;codecs=h264" });
recorder.ondataavailable = e => {
recorderVideo.src = URL.createObjectURL(e.data);
};
//開始錄制
recorder.start();
//停止錄制
recorder.stop();