WebRTC简介
WebRTC(Web Real-Time Communication)是一种开放的实时通信技术,它使Web浏览器能够在不需要插件或其他软件的情况下进行实时音频、视频和数据传输。WebRTC基于标准的HTML5和JavaScript API,提供了一种简单易用的方式,使开发人员能够在网页上构建实时通信应用程序。
通过WebRTC,可以实现点对点(peer-to-peer)的实时音视频通信,无需服务器的中转。它使用了一些关键技术,如媒体捕获(Media Capture)、媒体传输(Media Transport)、媒体处理(Media Processing)和网络协商(Network Negotiation),以实现高质量、低延迟的音视频传输。
WebRTC不仅适用于视频会议和语音通话,还可以应用于各种实时通信场景,如在线教育、远程医疗、客服和社交媒体等。它支持跨平台和跨浏览器,可以在主流浏览器中使用,并且已经得到了广泛的支持和采用。
实践
框架介绍
Android端
集成WebRTC Android库,实现视频的接收,负责与WebSocket服务之间进行通信。
WebSocket服务
采用WebSocket实现服务端,WebSocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信。
JavaScript
推送端采用JavaScript 调用WebRTC,实现流推送。
Android客户端 主要代码
//初始化PeerConnection
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(context).createInitializationOptions());
// 创建 EglBase
mEglBase = EglBase.create();
// 创建 PeerConnectionFactory
mPeerConnectionFactory = createPeerConnectionFactory(mEglBase);
private PeerConnectionFactory createPeerConnectionFactory(EglBase eglBase) {
VideoEncoderFactory videoEncoderFactory = new DefaultVideoEncoderFactory(eglBase.getEglBaseContext(), true, true);
VideoDecoderFactory videoDecoderFactory = new DefaultVideoDecoderFactory(eglBase.getEglBaseContext());
return PeerConnectionFactory.builder().setVideoEncoderFactory(videoEncoderFactory).setVideoDecoderFactory(videoDecoderFactory).createPeerConnectionFactory();
}
//接收连接请求
private void receivedOffer(SessionDescription offer) {
// 创建 PeerConnection
mPeerConnection = createPeerConnection();
// 为 PeerConnection 添加音轨、视轨
//mPeerConnection.addTrack(mAudioTrack, STREAM_IDS);
//mPeerConnection.addTrack(mVideoTrack, STREAM_IDS);
// 将 offer sdp 作为参数 setRemoteDescription
mPeerConnection.setRemoteDescription(new MySdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
}
@Override
public void onSetSuccess() {
ShowLogUtil.verbose("set remote sdp success.");
// 通过 PeerConnection 创建 answer,获取 sdp
MediaConstraints mediaConstraints = new MediaConstraints();
mPeerConnection.createAnswer(new MySdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
ShowLogUtil.verbose("create answer success.");
// 将 answer sdp 作为参数 setLocalDescription
mPeerConnection.setLocalDescription(new MySdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
}
@Override
public void onSetSuccess() {
ShowLogUtil.verbose("set local sdp success.");
// 发送 answer sdp
sendAnswer(sessionDescription);
}
}, sessionDescription);
}
@Override
public void onSetSuccess() {
}
}, mediaConstraints);
}
}, offer);
}
//创建配对
private PeerConnection createPeerConnection() {
PeerConnection.RTCConfiguration rtcConfiguration = new PeerConnection.RTCConfiguration(new ArrayList<>());
PeerConnection peerConnection = mPeerConnectionFactory.createPeerConnection(rtcConfiguration, new PeerConnection.Observer() {
@Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED) {
runOnUiThread(new Runnable() {
@Override
public void run() {
hangUp();
}
});
}
}
@Override
public void onIceConnectionReceivingChange(boolean b) {
}
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
}
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
ShowLogUtil.verbose("onIceCandidate--->" + iceCandidate);
sendIceCandidate(iceCandidate);
}
@Override
public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
}
@Override
public void onAddStream(MediaStream mediaStream) {
ShowLogUtil.verbose("onAddStream--->" + mediaStream);
if (mediaStream == null || mediaStream.videoTracks == null || mediaStream.videoTracks.isEmpty()) {
return;
}
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.d("mars","setVideo");
//svrRemote = findViewById(R.id.svr_remote);
Log.d("mars",mediaStream.videoTracks.size()+"");
mediaStream.videoTracks.get(0).addSink(svrRemote);
}
});
}
@Override
public void onRemoveStream(MediaStream mediaStream) {
}
@Override
public void onDataChannel(DataChannel dataChannel) {
}
@Override
public void onRenegotiationNeeded() {
}
@Override
public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
}
});
return peerConnection;
}
WebSocket 服务器主要代码
public class WebSocketTest {
private WebSocketServer mWebSocketServer;
private final List<WebSocket> mWebSockets = new ArrayList<>();
private static final String HOST_NAME = "192.168.1.162";
private static final int PORT = 8888;
public void start() {
InetSocketAddress inetSocketAddress = new InetSocketAddress(HOST_NAME, PORT);
mWebSocketServer = new WebSocketServer(inetSocketAddress) {
@Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {
System.out.println("onOpen--->" + conn);
// 客户端连接时保存到集合中
mWebSockets.add(conn);
}
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
System.out.println("onClose--->" + conn);
// 客户端断开时从集合中移除
mWebSockets.remove(conn);
}
@Override
public void onMessage(WebSocket conn, String message) {
// System.out.println("onMessage--->" + message);
// 消息直接透传给除发送方以外的连接
for (WebSocket webSocket : mWebSockets) {
if (webSocket != conn) {
webSocket.send(message);
}
}
}
@Override
public void onError(WebSocket conn, Exception ex) {
System.out.println("onError--->" + conn + ", ex--->" + ex);
// 客户端连接异常时从集合中移除
mWebSockets.remove(conn);
}
@Override
public void onStart() {
System.out.println("onStart");
}
};
mWebSocketServer.start();
}
public void stop() {
if (mWebSocketServer == null) {
return;
}
for (WebSocket webSocket : mWebSocketServer.getConnections()) {
webSocket.close();
}
try {
mWebSocketServer.stop();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
mWebSocketServer = null;
}
public static void main(String[] args) {
new WebSocketTest().start();
}
JavaScript 推送端主要代码
function createPeerConnection() {
let rtcPeerConnection = new RTCPeerConnection();
//rtcPeerConnection.channel=rtcPeerConnection.createDataChannel('msgdatachannel');
rtcPeerConnection.oniceconnectionstatechange = function (event) {
if ("disconnected" == event.target.iceConnectionState) {
hangUp();
}
}
rtcPeerConnection.onicecandidate = function (event) {
console.log("onicecandidate--->" + event.candidate);
let iceCandidate = event.candidate;
if (iceCandidate == null) {
return;
}
sendIceCandidate(iceCandidate);
}
rtcPeerConnection.ontrack = function (event) {
console.log("remote ontrack--->" + event.streams);
let streams = event.streams;
if (streams && streams.length > 0) {
remoteView.srcObject = streams[0];
}
}
return rtcPeerConnection
}
var displayMediaStreamConstraints = {
video: true,
audio: true// or pass HINTS
};
function share(){
if (navigator.mediaDevices.getDisplayMedia) {
navigator.mediaDevices.getDisplayMedia(displayMediaStreamConstraints).then(handleSuccess).catch(error);
} else {
navigator.getDisplayMedia(displayMediaStreamConstraints).then(handleSuccess).catch(error);
}
}
function call() {
// 创建 PeerConnection
peerConnection = createPeerConnection();
// 为 PeerConnection 添加音轨、视轨
for (let i = 0; localStream != null && i < localStream.getTracks().length; i++) {
const track = localStream.getTracks()[i];
peerConnection.addTrack(track, localStream);
}
// 通过 PeerConnection 创建 offer,获取 sdp
peerConnection.createOffer().then(function (sessionDescription) {
console.log("create offer success.");
// 将 offer sdp 作为参数 setLocalDescription;
peerConnection.setLocalDescription(sessionDescription).then(function () {
console.log("set local sdp success.");
// 发送 offer sdp
sendOffer(sessionDescription)
})
});
const obj = JSON.stringify({
'command': 'offer',
'desc': 'desc'
});
peerConnection.sendChannel.send(obj);
}
function sendOffer(offer) {
var jsonObject = {
"msgType": "sdp",
"type": offer.type,
"sdp": offer.sdp
};
send(JSON.stringify(jsonObject));
}
function receivedOffer(offer) {
// 创建 PeerConnection
peerConnection = createPeerConnection();
// 为 PeerConnection 添加音轨、视轨
for (let i = 0; localStream != null && i < localStream.getTracks().length; i++) {
const track = localStream.getTracks()[i];
peerConnection.addTrack(track, localStream);
}
// 将 offer sdp 作为参数 setRemoteDescription
peerConnection.setRemoteDescription(offer).then(function () {
console.log("set remote sdp success.");
// 通过 PeerConnection 创建 answer,获取 sdp
peerConnection.createAnswer().then(function (sessionDescription) {
console.log("create answer success.");
// 将 answer sdp 作为参数 setLocalDescription
peerConnection.setLocalDescription(sessionDescription).then(function () {
console.log("set local sdp success.");
// 发送 answer sdp
sendAnswer(sessionDescription);
})
})
})
}