此处使用到的WebRTC皆为H5的API,实际上调用的是封装在浏览器的WebRTC的库,用于获取实时视频数据,传输数据则是使用WebSocket实现。
其中的实例语法只用到原生JS,版本为ES6,可能需要较高版本的浏览器支持(IE一般不支持)。
1.获取音视频数据
方法:navigator.mediaDevices.getUserMedia
1.1前置条件
基于浏览器的安全策略,通过WebRTC(具体为getUserMedia)调用摄像头和麦克风获取音视频数据,只能是在HTTPS下的网页,或者是本地localhost下才能调用,需要先校验。
function validate(){
var isSecureOrigin = location.protocol === 'https:' ||
location.hostname === 'localhost';
if (!isSecureOrigin) {
alert('getUserMedia() must be run from a secure origin: HTTPS or localhost.' +'\n\nChanging protocol to HTTPS');
location.protocol = 'HTTPS';
}
}
1.2初始化
直接获取音视频,一般默认是前置摄像头。
var constraints = {
audio: true,
video: true //设置为false则只获取音频
};
1
2
3
4
还可以设置视频的其他参数,此处设置视频的分辨率,min和max为分辨率的最值,ideal为理想值,浏览器会先尝试找到最接近指定的理想值的设定或者摄像头(如果设备拥有不止一个摄像头),但不保证。下面是获取分辨率为640*480的视频。
var constraints = {
audio: true,
video: { width: { min: 640, ideal: 640, max: 640 }, height: { min: 480, ideal: 480, max: 480 }}
};
初始化
//链式操作,then和catch是并列关系
navigator.mediaDevices.getUserMedia(constraints)
.then(handleSuccess)
.catch(handleError);
1.3处理音视频数据
此时返回的只是一个流对象,包括了原始的音视频数据,不能直接操作,需要后续的MediaRecorder封装才能处理。
function handleSuccess(stream) {
log('getUserMedia() got stream: ', stream);
//stream即为返回成功的结果,设置为全局便于的后续上传操作
window.stream = stream;
//显示在页面上
var recordedVideo = document.querySelector('video#recorded');
recordedVideo.srcObject = stream;
recordedVideo.onloadedmetadata = function(e) {
log("Label: " + stream.label);
//音轨,PCM
log("AudioTracks" , stream.getAudioTracks());
//视频图像
log("VideoTracks" , stream.getVideoTracks());
};
}
function handleError(error) {
console.log('navigator.getUserMedia error: ', error);
alert('navigator.getUserMedia error: ', error);
}
在当前页面显示需要通过\
<video id="recorded" autoplay muted></video>
1
2.处理音视频数据
2.1前置条件
使用MediaRecorder(录制视频的API),需要检测支持编码的格式。
var mimeType = 'video/webm;codecs=vp9';
if (!MediaRecorder.isTypeSupported(mimeType)) {
console.log(mimeType + ' is not Supported');
mimeType = 'video/webm;codecs=vp8';
if (!MediaRecorder.isTypeSupported(mimeType)) {
console.log(mimeType + ' is not Supported');
mimeType = 'video/webm';
}
}
2.2初始化
设置音视频的编码格式,同时也可以设置音视频码率,目前只支持WebM格式,支持的编码格式具体看浏览器。
var mineType = 'video/webm; codecs=vp9';
var options = {mimeType:mimeType};
//音频码率
options.audioBitsPerSecond = 10000;
//视频码率
options.videoBitsPerSecond = 25000;
初始化MediaRecorder,添加音视频的编码格式。
try {
var options = {mimeType:mimeType};
mediaRecorder = new MediaRecorder(window.stream, options);
} catch (e) {
console.error('Exception while creating MediaRecorder: ' + e);
alert('Exception while creating MediaRecorder: '
+ e + '. mimeType: ' + options.mimeType);
return;
}
2.3处理音视频数据
开始录制,最好设置录制的间隔参数,1000代表的是每1000毫秒调用handleDataAvailable处理音视频数据,我们可以通过handleDataAvailable间接上传数据。
//1000毫秒上传一次数据,过小(如10ms)会导致接收端浏览器处理失败
mediaRecorder.start(1000);
1
2
时间设置必须合理,不然接收端会处理不来,报错。
Uncaught TypeError: Failed to execute ‘appendBuffer’ on ‘SourceBuffer’: No function was found that matched the signature provided.
at DataConnection.dataConnectionMessage (chatRoom.html:272)
停止录制。
mediaRecorder.stop();
1
处理音视频数据,此时可以上传到服务器。
mediaRecorder.ondataavailable = handleDataAvailable;
//根据mediaRecorder.start(1000)设置的时间会触发一次
function handleDataAvailable(event) {
if (event.data && event.data.size > 0) {
console.log('正在发送数据...');
//recordedBlobs.push(event.data);
//通过WebSocket发送到后台
socket.send( event.data );//实际数据,音视频同帧
socket.send( event.timeStamp );
}
}
3.显示(服务器推送的)音视频数据
3.1前置条件
使用MediaSource(直播流MSE API),检测支持的解码格式。
var mimeType = 'video/webm;codecs=vp9';
if (!MediaSource.isTypeSupported(mimeType)) {
console.log(mimeType + ' is not Supported');
mimeType = 'video/webm;codecs=vp8';
if (!MediaSource.isTypeSupported(mimeType)) {
console.log(mimeType + ' is not Supported');
mimeType = 'video/webm';
}
}
3.2初始化
var vidElement = document.getElementById('vid');
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
因为URL.createObjectURL(mediaSource)这一步在MediaSource是异步的,需要监听sourceopen等其创建完成后进行下一步。
mediaSource.addEventListener('sourceopen', sourceOpen);
1
添加sourceBuffer,实际上数据的交互在此对象中。
var sourceBuffer;
//当加载完成后
function sourceOpen(e) {
console.log('sourceOpen');
//节省内存,可不用这句
URL.revokeObjectURL(vidElement.src);
var mime = mineType;
var mediaSource = e.target;
//添加解码格式
sourceBuffer = mediaSource.addSourceBuffer(mime);
}
3.3处理数据
接收数据(只支持ArrayBuffer,可以在WebSocket上设置binaryType实现)。
sourceBuffer.appendBuffer(data);
1
在当前页面显示需要通过\
<video id="vid" autoplay muted></video>
1
注意:首包包含了格式,接收方一定要确定收到,不然<video>无法解析。
4.WebSocket
4.1客户端
浏览器已实现具体细节,使用WebSocket调用,注意HTTP协议下WebSocket使用WS协议,HTTPS下使用WWS。
var wsHeader = 'ws://';
if(location.protocol === 'https:') wsHeader = 'wss://';
// URL形如 ws://localhost:8080/user,其中/user代表服务端自定义的实现类,可带参数
var ws = new WebSocket( wsHeader+window.location.host+'/user'+'?chatId=123');
通过websocket.binaryType设置传输的格式,此处设置为arraybuffer,后端的对象自动转换。发送端的类型为Blob。
ws.binaryType = 'arraybuffer';
1
这一步相当于显式的
FileReader.addEventListener("load", function(){sourceBuffer.appendBuffer(this.result)});
this.reader.readAsArrayBuffer(event.data);
1
2
接收数据
ws.onmessage = function (event){
//实际数据在event.data
console.log('正在接收数据...',event.data);
//sourceBuffer.appendBuffer(event.data);
};
发送数据
var data = new Blob();
ws.send(data);
1
2
4.2服务端
基于tomcat的实现类WebSocketServlet和MessageInbound实现。
package org.rtc.chat;
import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;
import org.apache.commons.lang.StringUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 管理用户活动和实时音视频数据,用户前端获取UserConnection实例
*
*/
//具体路径
@WebServlet(urlPatterns = { "/user"})
public class UserWebSocketServlet extends WebSocketServlet {
private static final long serialVersionUID = 1L;
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("websocket chat by "+request.getParameter("uid")+" "+request.getParameter("rid"));
super.doGet(request, response);
}
@Override
protected StreamInbound createWebSocketInbound(String s, HttpServletRequest request) {
String userId = request.getParameter("uid");
String roomId = request.getParameter("rid");
roomId = StringUtils.isNotEmpty(roomId)? roomId: "testRoom";
return new UserConnection(roomId, userId);
}
}
1
具体业务实现MessageInbound,比如UserConnection,里面主要是要接收浏览器通过WebSocket接收数据。
Java WebSocket接收数据的方法
//主要是接收前端的Blob二进制对象
protected void onBinaryMessage(ByteBuffer message){}
//接收文本
protected void onTextMessage(CharBuffer message) {}
1
2
3
4
发送数据
//发送二进制数据到前端
MessageInbound.getWsOutbound().writeBinaryMessage(ByteBuffer)
//发送字符数据到前端
MessageInbound.getWsOutbound().writeTextMessage(CharBuffer)
注意:org.apache.catalina.websocket.MessageInbound的ByteBuffer是复用的,需要读取实际数据,建议将实际数据提取出来,如保存到字节数组中。
//ByteBuffer message
System.out.println(message.mark());
byte[] b = new byte[message.limit()];
message.get(b, 0, message.limit());
5.WebView
设置可访问不安全的https
wv.setWebViewClient(new WebViewClient(){
//访问https
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error){
//handler.cancel(); 默认的处理方式,WebView变成空白页
handler.proceed(); //接受证书
//handleMessage(Message msg); 其他处理
}
});
设置可调用WebRTC
wv.setWebChromeClient(new WebChromeClient(){
//---WebRTC:
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onPermissionRequest(PermissionRequest request) {
request.grant(request.getResources());
}
});
WebSettings setting = wv.getSettings();
setting.setJavaScriptEnabled(true);
6.总结
上述方法只是取巧地实现了视频聊天功能,其中遇到的问题:
和内置浏览器的表现一样,移动端浏览器需要设置才能自动播放。但是火狐可设置为自动播放,谷歌浏览器只能自动播放静音视频。
MediaRecorder 、SourceBuffer、Blob都需要编码,浏览器支持程度不一样。Chrome支持采集视频编码格式为H264和VPx的WebM,但只支持播放H264的MP4和VPx的WebM。
华为P10不支持播放vp9编码的,坚果支持。安卓5,6不支持。webM迅雷播放webm不行,会认为是直播,转为MP4可以,直接在网页上播放也行,腾讯视频也可正常播放。
接收端需要存够足够的时间才能播放流。append()函数时需要一定的执行时间的,如果在很短的时间内接收到两片及以上的数据,就会引发两次append()函数,在前次append()函数未执行结束的情况下再次调用append()函数会产生错误从而导致该sourcebuffer被移除从而出现错误
Uncaught DOMException: Failed to execute ‘appendBuffer’ on ‘SourceBuffer’: This SourceBuffer is still processing an ‘appendBuffer’ or ‘remove’ operation.
facingMode: "environment"属性对安卓系统无效。
WebRTC基于C++编写的去中心化项目,在浏览器上封装了H5的API,涉及音视频的采集,编解码,优化等,其中的数据传输部分只有点对点的实现,如果想实现视频的录制、回放等功能,需要另行编码实现。而市面上也有成熟的流服务器(MCU),但是基于C++编写的,或者是NodeJS的,或者只有SDK供调用。
————————————————
版权声明:本文为CSDN博主「Cceking」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Cceking/java/article/details/80297249