直播架構
業務服務器:負責協調直播類應用的業務邏輯
創建直播房間
返回直播房間播放地址列表
關閉直播房間
LiveNet 實時流網絡:負責流媒體的分發、直播流的創建、查詢等相關操作
采集端:負責采集和推送流媒體
播放端:負責拉取並播放流媒體
直播雲API:https://developer.qiniu.com/pili
服務端
SDK:https://developer.qiniu.com/pili/sdk/1220/server-sdk
關鍵代碼:
// 配置企業開發者密鑰,密鑰使用七牛賬號登錄https://portal.qiniu.com/user/key 獲取 String accessKey = "xxx"; String secretKey = "xxx"; // 直播空間名稱 // 直播空間必須事先存在,可以在 portal.qiniu.com 上創建 String hubName = "xxx"; Config.APIHost = "pili.qiniuapi.com"; //初始化client Client cli = new Client(accessKey,secretKey); //初始化Hub Hub hub = cli.newHub(hubName);
//列出所有流
try{
Hub.ListRet listRet = hub.list("liu", 0, "");
System.out.printf("hub=%s 列出流: keys=%s marker=%s\n", hubName,printArrary(listRet.keys) , listRet.omarker);
}catch (PiliException e){
e.printStackTrace();
return;
}
//列出正在直播的流
try{
Hub.ListRet listRet = hub.listLive("liu", 0, "");
System.out.printf("hub=%s 列出正在直播的流: keys=%s marker=%s\n", hubName, printArrary(listRet.keys), listRet.omarker);
}catch (PiliException e){
e.printStackTrace();
return;
}
// RTMP推流地址
String url = cli.RTMPPublishURL("pili-publish.www.gs369.cn", hubName, keyA, 3600);
System.out.printf("keyA=%s RTMP推流地址=%s\n", keyA, url);
//RTMP直播地址
String RTMPUrl = cli.RTMPPlayURL("pili-live-rtmp.www.gs369.cn", hubName, keyA);
System.out.printf("keyA=%s RTMP直播地址=%s\n", keyA, RTMPUrl);
request.setAttribute("RTMPUrl",urlTimestamp(RTMPUrl));
//HLS直播地址
String HLSUrl = cli.HLSPlayURL("pili-live-hls.www.gs369.cn", hubName, keyA);
System.out.printf("keyA=%s HLS直播地址=%s\n", keyA, HLSUrl);
request.setAttribute("HLSUrl",urlTimestamp(HLSUrl));
//HDL直播地址
String FLVUrl = cli.HDLPlayURL("pili-live-hdl.www.gs369.cn", hubName, keyA);
System.out.printf("keyA=%s HDL直播地址=%s\n", keyA, FLVUrl);
request.setAttribute("FLVUrl",urlTimestamp(FLVUrl));
// 截圖直播地址
url = cli.SnapshotPlayURL("pili-live-snapshot.www.gs369.cn", hubName, keyA);
System.out.printf("keyA=%s 截圖直播地址=%s\n", keyA, url);
將URL添加時間戳代碼:
1 private static String urlTimestamp(String url,String key){ 2 String t = Long.toHexString(new Long(new Date().getTime() + 60)); 3 //String key = "yuanda"; 4 int index = url.indexOf("/",url.indexOf("//")+3); 5 String s = key+url.substring(index)+t; 6 System.out.println("s:"+s); 7 String sign=""; 8 try { 9 MessageDigest md5 = MessageDigest.getInstance("md5"); 10 md5.update(s.getBytes()); 11 byte[] tmp = md5.digest(); 12 StringBuilder sb = new StringBuilder(); 13 for (byte b : tmp) { 14 if (b > 0 && b < 16) 15 sb.append("0"); 16 sb.append(Integer.toHexString(b & 0xff)); 17 } 18 sign=sb.toString().toLowerCase(); 19 } catch (NoSuchAlgorithmException e) { 20 e.printStackTrace(); 21 } 22 //System.out.println("sign:"+sign); 23 String resultUrl = url + "?sign=" + sign + "&t=" + t ; 24 return resultUrl; 25 }
播放端
web播放端,m3u8格式,此處JSP
1 <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> 2 <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 3 <!DOCTYPE html PUBLIC "-//W3C//DTD//XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 4 <html xmlns="http://www.w3.org/1999/xhtml"> 5 <head><title>Qiniu Web Player in HTML5</title> 6 7 </head> 8 <body> 9 10 HLSUrl: ${HLSUrl } <br/> 11 RTMPUrl: ${RTMPUrl } <br/> 12 FLVUrl: ${FLVUrl } <br/> 13 <hr/> 14 HLS格式播放: 15 <link href="http://sdk-release.qnsdk.com/qiniuplayer-0.3.9.min.css" rel="stylesheet"> 16 <script src="http://sdk-release.qnsdk.com/qiniuplayer-0.3.9.min.js"></script> 17 <video id="demo-video" class="video-js vjs-big-play-centered"></video> 18 <p>播放器狀態: 0: 視頻尚未開始加載,當前沒有可用媒體信息; 19 1: 視頻元數據已經可用; 20 2: 視頻可以播放; 21 3: 視頻可以播放,並且可以被快進; 22 4: 視頻可以無終止的播放。</p> 23 <p id="ddd"></p> 24 25 <script> 26 var options = { 27 controls: true, 28 url: '${HLSUrl }', 29 type: 'MP4', //視頻播放類型hls或MP4 30 preload: true, 31 autoplay: false, // 如為 true,則視頻將會自動播放 32 poster: '', //視頻封面 33 }; 34 var player = new QiniuPlayer('demo-video', options); 35 //實時顯示播放狀態 36 setInterval(function () { 37 var d=document.getElementById("ddd").innerHTML=player.state(); 38 },2000); 39 </script> 40 </body> 41 </html>
-------------------或者videojs播放器:
源碼:https://github.com/saysmy/videojs-hls
<video style="height:300px;width:400px" id="roomVideo" class="video-js vjs-default-skin vjs-big-play-centered" x-webkit-airplay="allow" poster="" webkit-playsinline playsinline x5-video-player-type="h5" x5-video-player-fullscreen="true" preload="auto"> <source src="${HLSUrl }" type="application/x-mpegURL"> </video> <button id="btn">play</button> <script src="./videojs/video.js?v=fc5104a2ab23"></script> <script src="./videojs/videojs-contrib-hls.js?v=c726b94b9923"></script> <script type="text/javascript"> var myPlayer = videojs('roomVideo',{ bigPlayButton : false, textTrackDisplay : false, posterImage: true, errorDisplay : false, controlBar : false },function(){ console.log(this) this.on('loadedmetadata',function(){ console.log('loadedmetadata'); //加載到元數據后開始播放視頻 //startVideo(); }) this.on('ended',function(){ console.log('ended') }) }); document.getElementById('btn').addEventListener('click', function(){ myPlayer.play(); }) var isVideoBreak; function startVideo() { myPlayer.play(); //微信內全屏支持 document.getElementById('roomVideo').style.width = window.screen.width + "px"; document.getElementById('roomVideo').style.height = window.screen.height + "px"; //判斷開始播放視頻,移除高斯模糊等待層 var isVideoPlaying = setInterval(function(){ var currentTime = myPlayer.currentTime(); if(currentTime > 0){ $('.vjs-poster').remove(); clearInterval(isVideoPlaying); } },200) //判斷視頻是否卡住,卡主3s重新load視頻 var lastTime = -1, tryTimes = 0; clearInterval(isVideoBreak); isVideoBreak = setInterval(function(){ var currentTime = myPlayer.currentTime(); console.log('currentTime'+currentTime+'lastTime'+lastTime); if(currentTime == lastTime){ //此時視頻已卡主3s //設置當前播放時間為超時時間,此時videojs會在play()后把currentTime設置為0 myPlayer.currentTime(currentTime+10000); myPlayer.play(); //嘗試5次播放后,如仍未播放成功提示刷新 if(++tryTimes > 5){ alert('您的網速有點慢,刷新下試試'); tryTimes = 0; } }else{ lastTime = currentTime; tryTimes = 0; } },3000) } </script>