1.無(wú)意中接觸到webrtc,了解到的就是直播過(guò)程中延遲很短哄芜,控制在1-2s左右貌亭,開(kāi)發(fā)過(guò)程中發(fā)現(xiàn)關(guān)于webrtc的參考案例很少,就把demo貼出來(lái)认臊,也順便把遇到的問(wèn)題和如何解決的也貼出來(lái)圃庭。
<!DOCTYPE html>
<html>
<head>
<title>CDN Demo</title>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
<link rel="stylesheet">
<link rel='stylesheet'>
<style>
#remote-video-wrap div{max-width:800px; display: inline-block; padding:10px;}
#remote-video-wrap video{max-width:800px; display: inline-block; padding:10px}
</style>
</head>
<body>
<div id="input-container" class="container">
<div class="login-box">
<div class="form-group">
<label for="streamurl">拉流地址:</label>
<input class="form-control" id="streamurl" type="text" value="webrtc://xxxxxx" /> //webrtc類型的流地址
</div>
<button style="float: left;margin:10px;" type="button" onclick="pullStream()" id="pullFun">拉流</button>
<button style="float: left;margin:10px;" type="button" onclick="stopStream()">停止拉流</button>
<!-- <button style="float: left;margin:10px;" type="button" onclick="refreshStream()">刷新</button>
<button style="float: left;margin:10px;" type="button" onclick="opennewpage()">打開(kāi)新的頁(yè)面</button> -->
<!-- <a target="_top">打開(kāi)新的頁(yè)面</a> -->
</div>
</div>
<div id="video-section" style="float:left;margin:195px 0px 0px 75px;">
<div id="remote-video-wrap">
</div>
</div>
</div>
<script>
window._tx_webrtc_enable = true;
// delete window.RTCPeerConnection;
// delete window.webkitRTCPeerConnection;
// delete window.RTCIceGatherer;
</script>
<script src="https://unpkg.com/flyio/dist/fly.min.js"></script>
<script src="https://cloud.webrtc.qq.com/httpdemo/js/jquery.min.js"></script>
<script src="https://cloud.webrtc.qq.com/httpdemo/js/adapter.js"></script> //很有必要 兼容瀏覽器
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script> //用于ios端視頻不能自動(dòng)播放
<script>
function getStreamId(url){
//var url = "webrtc://domain/path/stream_id[?txSecret=xxx&txTime=xxx]";
var parseStreamid = /^(?:webrtc:\/\/)(?:[0-9.\-A-Za-z_]+)(?:\/)(?:[0-9.\-A-Za-z_]+)(?:\/)([^?#]*)(?:\?*)(?:[^?#]*)/;
var result = parseStreamid.exec(url);
if(result)
return result[1];
return null;
}
function createVideoElement(id) {
var videoDiv = document.createElement("div");
videoDiv.innerHTML = `<video id="${id}" autoplay unmuted playsinline controls></video>`; //width="480" height="320"
document.querySelector("#remote-video-wrap").appendChild(videoDiv);
return document.getElementById(id);
}
function onAddStream(e){
console.log(`onAddStream, streamId:${e.streamId}`, e.stream);
var streamId = e.streamId;
var video = document.getElementById(streamId);
if(!video){
video = createVideoElement(streamId);
}
video.srcObject = e.stream;
//video.muted = true
video.autoplay = true
video.playsinline = true
var playPromise = video.play();
if(playPromise){
playPromise.then(() => {
//此處監(jiān)聽(tīng)到webrtc流地址可以播放
console.log(`play ok!`);
}).catch(() => {
//此處可做其他類型的流地址切換
//webrtc地址不可播放
console.log(`play failed!`);
});
}
}
var streamUrl;
var svrSig;
var peerConnection;
function pullStream(){
var clientInfo = "clientinfo_test";
var sessionId = "sessionId_Test";
streamUrl = $("#streamurl").val();
var streamId = getStreamId(streamUrl);
var video = document.getElementById(streamId);
if(!video){
video = createVideoElement(streamId);
}
var config = {
iceServers: [],
bundlePolicy: "max-bundle",
rtcpMuxPolicy: "require",
tcpCandidatePolicy: "disable",
IceTransportsType: "nohost",
sdpSemantics: 'unified-plan'
};
var optional = {
optional: [{
DtlsSrtpKeyAgreement: true
}]
};
var offerSdpOption = {
offerToReceiveAudio: true,
offerToReceiveVideo: true,
voiceActivityDetection: false
};
peerConnection = new RTCPeerConnection(config, optional);
peerConnection.onicecandidate = function(e) {
console.log("peerConnection.onicecandidate:", e);
};
peerConnection.onaddstream = function(e) {
console.log("peerConnection.onaddstream");
};
peerConnection.onremovestream = function(e) {
console.log("peerConnection.onremovestream");
};
peerConnection.ontrack = function(e) {
console.log("peerConnection.ontrack, kind:" + e.track.kind + ",track.id:" + e.track.id);
var track = e.track;
if(!peerConnection.stream){
peerConnection.streamId = streamId;
peerConnection.stream = new MediaStream();
peerConnection.stream.addTrack(track);
onAddStream(peerConnection);
} else{
peerConnection.stream.addTrack(track);
console.log("track.lenght:" + peerConnection.stream.getTracks().length);
}
};
peerConnection.onconnectionstatechange = function(e) {
console.log("onconnectionstatechange", e);
}
peerConnection.oniceconnectionstatechange = function(e) {
console.log("peerConnection.oniceconnectionstatechange: " + JSON.stringify(e), e);
};
peerConnection.onicegatheringstatechange = function(e) {
console.log("peerConnection.onicegatheringstatechange : " + e.target.iceGatheringState, e);
};
peerConnection.onsignalingstatechange = function(e) {
console.log("peerConnection.onsignalingstatechange : " + peerConnection.signalingState, e);
};
peerConnection.onnegotiationneeded = function(e) {
console.log("peerConnection.onnegotiationneeded", e);
};
peerConnection.createOffer(offerSdpOption).then(function(offer) {
peerConnection.setLocalDescription(offer);
var url = "https://webrtc.liveplay.myqcloud.com/webrtc/v1/pullstream";
var reqBody = {
streamurl: streamUrl,
sessionid: sessionId,
clientinfo: clientInfo,
localsdp: offer
};
$.ajax({
type: "post",
url: url,
data: JSON.stringify(reqBody),
//contentType: "application/json; charset=utf-8",
dataType: "json",
success: pullStreamRes,
crossDomain: true
});
//fly.interceptors.request.use((request) => {
// request.headers['Content-Type'] ='application/json';
// request.headers['Access-Control-Allow-Origin'] ='*';
// request.headers['Access-Control-Allow-Credentials'] ='true';
//})
//fly.post(url, JSON.stringify(reqBody)).then(pullStreamRes).catch(function(error){
// console.log("fly post err:", error);
//});
function pullStreamRes(data){
console.log("pullStreamRes:", data);
// data = data.data;
var errCode = data.errcode;
var errMsg = data.errmsg;
if(errCode != 0){
console.log(`pull stream failed!errCode:${errCode}, errmsg:${errMsg}`);
return;
}
var remoteSdp = data.remotesdp;
svrSig = data.svrsig;
console.log(`svrSig:${svrSig}`);
peerConnection.setRemoteDescription(
new RTCSessionDescription(remoteSdp),
function(){
console.log("setRemoteSdp succ!");
},
function(e){
console.log("setRemoteSdp failed, exception = " + e.message);
}
);
}
}).catch(function(reason) {
console.log('create offer failed : reason = ' + reason);
});
}
function stopStream(){
var url = "https://webrtc.liveplay.myqcloud.com/webrtc/v1/stopstream";
var reqBody = {
streamurl: streamUrl,
svrsig: svrSig
};
$.ajax({
type: "post",
url: url,
data: JSON.stringify(reqBody),
//contentType: "application/json; charset=utf-8",
dataType: "json",
success: stopStreamRes,
crossDomain: true
});
//fly.interceptors.request.use((request) => {
// request.headers['Content-Type'] ='application/json';
// request.headers['Access-Control-Allow-Origin'] ='*';
// request.headers['Access-Control-Allow-Credentials'] ='true';
//})
// fly.post(url, JSON.stringify(reqBody)).then(stopStreamRes).catch(function(error){
// console.log("fly post err:", error);
//});
function stopStreamRes(data){
console.log(`stopStreamRes:`, data);
//data = data.data;
var errCode = data.errcode;
var errMsg = data.errmsg;
if(errCode != 0){
console.log(`pull stream failed!errCode:${errCode}, errmsg:${errMsg}`);
}
peerConnection.close();
var streamId = getStreamId(streamUrl);
var videoNode = document.getElementById(streamId);
if (videoNode) {
document.getElementById(streamId).parentElement.removeChild(videoNode);
console.log(`RemoveStream, streamUrl:${streamUrl}`);
} else{
console.log(`RemoveStream, streamUrl:${streamUrl} not exist`);
}
}
}
//此處用來(lái)調(diào)取微信console面板 用于開(kāi)發(fā)審查(很有必要)
function createVConsole() {
var scriptEle = document.createElement("script");
scriptEle.src = '//sqimg.qq.com/expert_qq/vConsole/vconsole.min.js';
document.body.appendChild(scriptEle)
scriptEle.onload = function(){
window.vConsole = new VConsole();
}
};
createVConsole();
//此處作用:因ios端視頻禁止自動(dòng)播放 故寫此代碼用來(lái)調(diào)取視頻播放
let video = document.querySelectorAll("video")[0];
document.addEventListener("WeixinJSBridgeReady", function () {
pullStream(); //拉流(相當(dāng)于手動(dòng)操作拉流按鈕,因?qū)嶋H項(xiàng)目中沒(méi)有此按鈕來(lái)手動(dòng)拉流)
video.play();
}, false);
</script>
</body>
</html>
2.遇到的問(wèn)題
2.1 問(wèn)題:ios微信瀏覽器播放器不能自動(dòng)播放 導(dǎo)致stream監(jiān)聽(tīng)事件提示play failed美尸,因業(yè)務(wù)中需區(qū)分webrtc能不能播放冤议,不能播放則切換至其他流地址進(jìn)行播放
解決方案:引入jweixin-1.0.0.js 監(jiān)聽(tīng)WeixinJSBridgeReady觸發(fā)事件即可(參考網(wǎng)絡(luò)案例,還有其他方式师坎,此處只提供一種恕酸,如不能解決,歡迎留言胯陋。)
2.2 ios微信瀏覽器一開(kāi)始不能播放webrtc蕊温,但是在微信開(kāi)發(fā)者工具則正常播放
解決方案:引入adapter.js即可解決瀏覽器兼容性問(wèn)題