|
@@ -0,0 +1,415 @@
|
|
|
+<!DOCTYPE html>
|
|
|
+<html lang="en">
|
|
|
+<head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <meta name="description" content="jMuxer - a simple javascript mp4 muxer for non-standard streaming communications protocol">
|
|
|
+ <meta name="keywords" content="h264 player, mp4 player, mse, mp4 muxing, jmuxer, aac player">
|
|
|
+ <title>JMuxer demo</title>
|
|
|
+ <script async defer src="https://buttons.github.io/buttons.js"></script>
|
|
|
+ <style type="text/css">
|
|
|
+ .github-tools {
|
|
|
+ position: absolute;
|
|
|
+ top: 15px;
|
|
|
+ right: 15px;
|
|
|
+ }
|
|
|
+ a.h264-player {
|
|
|
+ font-size: 20px;
|
|
|
+ text-decoration: none;
|
|
|
+ color: #07568e;
|
|
|
+ margin-top: 10px;
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+ .gesture {
|
|
|
+ font-size: 15px;
|
|
|
+ color: #ad4903;
|
|
|
+ margin-top: 10px;
|
|
|
+ }
|
|
|
+</style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+
|
|
|
+<h2>jMuxer Demo</h2>
|
|
|
+<p>Sample demo node server is running on heroku free hosting</p>
|
|
|
+<br /><br />
|
|
|
+<div class="github-tools">
|
|
|
+<!-- Place this tag where you want the button to render. -->
|
|
|
+
|
|
|
+</div>
|
|
|
+
|
|
|
+<div id="container" style="width: 600px; margin: 0 auto;">
|
|
|
+ <video width="70%" disablePictureInPicture ="true" autoplay poster="images/loader-thumb.jpg" id="player"></video>
|
|
|
+ <audio width="50%" preload="auto" autoplay controls poster="images/loader-thumb.jpg" id="audioPlayer" ></audio>
|
|
|
+ <div class="gesture">If it does not play automatically, Click the `video play button` to initiate the video</div>
|
|
|
+</div>
|
|
|
+
|
|
|
+<body oncontextmenu="Back()">
|
|
|
+</body>
|
|
|
+
|
|
|
+<script src="helper.js" >
|
|
|
+</script>
|
|
|
+
|
|
|
+<script>
|
|
|
+ //隐藏控件 controls
|
|
|
+ var fpsCount = 0;
|
|
|
+ var requestCount = 0;
|
|
|
+ var timeCount = 0;
|
|
|
+ var isVisuable = true;
|
|
|
+ var isFeed = true;
|
|
|
+ var isDrag = false;
|
|
|
+ var shoudDrop = false;
|
|
|
+ var isEnough = true;
|
|
|
+ var ifCanPlay = false;
|
|
|
+ var isFinish = false;
|
|
|
+
|
|
|
+ var delayTime = new Date().getTime();
|
|
|
+ var feedTime = new Date().getTime();
|
|
|
+ var readyTime = new Date().getTime();
|
|
|
+ var requestTime = new Date().getTime() ;
|
|
|
+ var curTime = new Date().getTime();
|
|
|
+ var requestTime = new Date().getTime();//记录离开时间
|
|
|
+ var myVideo = document.getElementById("player");
|
|
|
+ var myAudio = document.getElementById("audioPlayer");
|
|
|
+ var audioBuffer = [];
|
|
|
+ var audioBack = [];
|
|
|
+
|
|
|
+ Module = {};
|
|
|
+ Module.onRuntimeInitialized = function()
|
|
|
+ {
|
|
|
+ console.log("Wasm 加载成功!")
|
|
|
+ isFinish = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ document.addEventListener("visibilitychange", () => {
|
|
|
+
|
|
|
+ if (document.visibilityState == "visible")
|
|
|
+ {
|
|
|
+ console.log("页面可见,继续喂视频");
|
|
|
+ //requestTime = new Date().getTime();
|
|
|
+ isVisuable = true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ isVisuable = false;
|
|
|
+ isFeed = false;
|
|
|
+ myVideo.pause();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+});
|
|
|
+
|
|
|
+
|
|
|
+ myVideo.play();
|
|
|
+
|
|
|
+ myVideo.addEventListener('pause',function(){
|
|
|
+ //console.log("视频播放暂停");
|
|
|
+ isFeed = false;
|
|
|
+ });
|
|
|
+
|
|
|
+ myAudio.addEventListener('canplay',function(){
|
|
|
+ console.log("缓冲区大小 %f", myAudio.buffered.end(0) - myAudio.buffered.start(0));
|
|
|
+ });
|
|
|
+
|
|
|
+ /*function decodeAAC(data)
|
|
|
+ {
|
|
|
+ var retPtr = Module._malloc(4 * 5 * 1024);//接收的数据
|
|
|
+ var inputPtr = Module._malloc(4 * data.length);//输入数据
|
|
|
+
|
|
|
+ for( i =0;i < data.length;i++)
|
|
|
+ {
|
|
|
+ Module.HEAPU8[(inputPtr)+i] = data[i];//转换为堆数据
|
|
|
+ }
|
|
|
+
|
|
|
+ var pcmLen = Module._feedData(retPtr, inputPtr, data.length);
|
|
|
+
|
|
|
+ if(pcmLen > 0)
|
|
|
+ {
|
|
|
+ //console.log("%d帧 aac 解码成功, %d", decodeCount, pcmLen);
|
|
|
+ var pcmData = new Uint8Array(pcmLen);
|
|
|
+ for(i = 0;i < pcmLen;i++)
|
|
|
+ {
|
|
|
+ pcmData[i] = Module.HEAPU8[(retPtr)+i]
|
|
|
+ }
|
|
|
+
|
|
|
+ player.feed(pcmData);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ console.log("%d帧 aac 解码失败, %d", decodeCount, pcmLen);
|
|
|
+ }
|
|
|
+
|
|
|
+ decodeCount++;
|
|
|
+ Module._free(inputPtr);
|
|
|
+ Module._free(retPtr);
|
|
|
+ } */
|
|
|
+
|
|
|
+
|
|
|
+ //解协议
|
|
|
+ function ParseProto(data)
|
|
|
+ {
|
|
|
+ var temp = "";
|
|
|
+ var input = new Uint8Array(data),
|
|
|
+ duration,
|
|
|
+ video,
|
|
|
+ audio;
|
|
|
+
|
|
|
+ if(input[0] == 0 && input[1] == 0 && input[2] == 0 && input[3] == 1)
|
|
|
+ {
|
|
|
+ // debugger
|
|
|
+ video = input;
|
|
|
+ duration = 24;
|
|
|
+ var nalType = input[4] &0x1f;//nalType == 0x07|| nalType == 0x08 || nalType == 0x05
|
|
|
+
|
|
|
+ if(!isFeed)
|
|
|
+ {
|
|
|
+ if(nalType == 0x05)
|
|
|
+ {
|
|
|
+ console.log("发现I帧");
|
|
|
+ }
|
|
|
+
|
|
|
+ if(nalType == 0x05 && isVisuable)
|
|
|
+ {
|
|
|
+ console.log("检测到I帧 %d,重新渲染, 耗时 %d ms", nalType , new Date().getTime() - requestTime);
|
|
|
+ isFeed = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ else if(input[0] == 0xff)
|
|
|
+ {
|
|
|
+ if(!isEnough)
|
|
|
+ {
|
|
|
+ requestCount++;
|
|
|
+ //audioBuffer.push(input);
|
|
|
+ }
|
|
|
+ audio = input;
|
|
|
+
|
|
|
+ if(new Date().getTime() - curTime > 100)
|
|
|
+ {
|
|
|
+ delayTime = new Date().getTime();
|
|
|
+ console.log("接收时间 %d ms", new Date().getTime() - curTime);
|
|
|
+ }
|
|
|
+ curTime = new Date().getTime();
|
|
|
+ duration = 24;
|
|
|
+ //console.log("duration %d", duration);
|
|
|
+
|
|
|
+ }
|
|
|
+ else if(input[0] == 0x68)
|
|
|
+ {
|
|
|
+ if(input[23] == 0x05)//横竖屏标识
|
|
|
+ {
|
|
|
+ var state = CheckScreenDirection(input.slice(24, 24 + 8));
|
|
|
+
|
|
|
+ if(state == 1)
|
|
|
+ {
|
|
|
+ console.log("安卓卡此时竖屏");
|
|
|
+ //竖屏处理
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ console.log("安卓卡此时横屏");
|
|
|
+ //横屏处理
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(input[23] == 0x0b)
|
|
|
+ {
|
|
|
+ console.log("多端登陆");
|
|
|
+ }
|
|
|
+ //console.log("屏幕旋转 %s", PrintArry(input));
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ audio: audio,
|
|
|
+ video: video,
|
|
|
+ duration: duration
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ window.onload = function() {
|
|
|
+ // var socketURL = 'wss://jmuxer-demo-server.herokuapp.com';
|
|
|
+ //socketURL = "ws://127.0.0.1:8080"
|
|
|
+ // socketURL = "ws://192.168.11.233:8080"
|
|
|
+ //socketURL = "ws://14.215.128.98:14112";
|
|
|
+ var socketURL = "ws://192.168.11.66:9101";
|
|
|
+ // socketURL = "ws://14.215.128.98:14077";
|
|
|
+ //socketURL = "wss://192.168.11.242:9104";
|
|
|
+
|
|
|
+
|
|
|
+ var jmuxer = new JMuxer({
|
|
|
+ node: 'player',
|
|
|
+ flushingTime:15 ,
|
|
|
+ fps: 30,
|
|
|
+ mode:'video',
|
|
|
+ debug: false
|
|
|
+ });
|
|
|
+
|
|
|
+ var audioMuxer = new JMuxer({
|
|
|
+ node: 'audioPlayer',
|
|
|
+ flushingTime:15,
|
|
|
+ clearBuffer: true,
|
|
|
+ fps: 60,//可以不选,原先43
|
|
|
+ mode:'audio',
|
|
|
+ debug: false
|
|
|
+ });
|
|
|
+
|
|
|
+ curTime = new Date().getTime();
|
|
|
+ var ws = new WebSocket(socketURL);
|
|
|
+ ws.binaryType = 'arraybuffer';
|
|
|
+
|
|
|
+ //断开检测
|
|
|
+ ws.onclose = function (e) {
|
|
|
+ alert("websocket连接断开");
|
|
|
+ console.log('websocket 断开: ' + e.code + ' ' + e.reason + ' ' + e.wasClean);
|
|
|
+ console.log(e);
|
|
|
+ }
|
|
|
+
|
|
|
+ ws.addEventListener('open', function (event) {
|
|
|
+ console.log("发送配置帧");
|
|
|
+ ws.send(ConfigChannel("RK3923C1201900139"));
|
|
|
+ });
|
|
|
+
|
|
|
+ ws.addEventListener('error', function (event) {
|
|
|
+ console.log("连接失败");
|
|
|
+ });
|
|
|
+
|
|
|
+ ws.addEventListener('message',function(event) {
|
|
|
+ var data = ParseProto(event.data);//JAVA服务器转发
|
|
|
+ //console.log("收到数据");
|
|
|
+
|
|
|
+ var audioData = {
|
|
|
+ audio: data.audio,
|
|
|
+ video: null,
|
|
|
+ duration: data.duration
|
|
|
+ };
|
|
|
+
|
|
|
+ var videoData = {
|
|
|
+ audio: null,
|
|
|
+ video: data.video,
|
|
|
+ duration: data.duration
|
|
|
+ };
|
|
|
+
|
|
|
+ if(myAudio.readyState == 2)
|
|
|
+ {
|
|
|
+ requestTime = new Date().getTime();
|
|
|
+ isEnough = false;
|
|
|
+ console.log("数据存储不够,出现声音停止,时间差 %f", myAudio.buffered.end(0));
|
|
|
+ myAudio.pause();
|
|
|
+ //myAudio.playbackRate = 2;
|
|
|
+ }
|
|
|
+ else if(myAudio.readyState == 4 && isEnough== false)
|
|
|
+ {
|
|
|
+ myAudio.play();
|
|
|
+ var time = new Date().getTime();
|
|
|
+ isEnough = true;
|
|
|
+ console.log("填满耗时 %d ms, 填充帧数 %d, 填充延迟 %d ms", time - requestTime, requestCount, requestCount * 23);
|
|
|
+
|
|
|
+ console.log("----接收到启动 %d ms, 缓冲区 %f---", time - delayTime, myAudio.buffered.end(0) - myAudio.played.end(0));
|
|
|
+ }
|
|
|
+
|
|
|
+ if(data.audio != null)//喂音频
|
|
|
+ {
|
|
|
+ if(myAudio.buffered.length > 0 && myAudio.played.length > 0)
|
|
|
+ {
|
|
|
+ var bufferTime = myAudio.buffered.end(0) - myAudio.played.end(0);
|
|
|
+ //console.log(" bufferTime %d", bufferTime);
|
|
|
+
|
|
|
+ if(bufferTime > 1)
|
|
|
+ {
|
|
|
+ //console.log("丢掉一些包");
|
|
|
+ //return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ audioMuxer.feed(audioData);
|
|
|
+ }
|
|
|
+
|
|
|
+ if(data.video != null)//喂视频
|
|
|
+ {
|
|
|
+ if(isFeed)
|
|
|
+ {
|
|
|
+ jmuxer.feed(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ //jmuxer.feed(videoData);
|
|
|
+ }
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
+ myVideo.onmousedown = function(event)
|
|
|
+ {
|
|
|
+ //放在此处只是为了方便演示,实际使用中查找横竖屏只要刚连接上时调用一次就好。
|
|
|
+ //var checkBuffer = GetScreenState();
|
|
|
+ //ws.send(checkBuffer);
|
|
|
+
|
|
|
+ if(!isFeed)
|
|
|
+ {
|
|
|
+ console.log("重新申请I帧");
|
|
|
+ requestTime = new Date().getTime();
|
|
|
+ var buffer = RequestIFrame();
|
|
|
+ //var buffer = new Uint8Array([0x01]);
|
|
|
+ ws.send(buffer);
|
|
|
+ }
|
|
|
+
|
|
|
+ //console.log("报文 %s", PrintArry(buffer));
|
|
|
+
|
|
|
+
|
|
|
+ if(event.button == 0)
|
|
|
+ {
|
|
|
+ var posX = event.offsetX * 1080 *1.0/myVideo.clientWidth;
|
|
|
+ var posY = event.offsetY * 1920 *1.0/myVideo.clientHeight;
|
|
|
+ var buffer = ExexuteMouseDown(posX.toString(), posY.toString());
|
|
|
+ ws.send(buffer);
|
|
|
+ isDrag = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ myVideo.onmousemove = function(event)
|
|
|
+ {
|
|
|
+ if(isDrag && event.button == 0)
|
|
|
+ {
|
|
|
+ var posX = event.offsetX * 1080 *1.0/myVideo.clientWidth;
|
|
|
+ var posY = event.offsetY * 1920 *1.0/myVideo.clientHeight;
|
|
|
+ var buffer = ExexuteMouseMove(posX.toString(), posY.toString());
|
|
|
+ ws.send(buffer);
|
|
|
+ //console.log("移动位置 %d, %d", posX, posY);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ myVideo.onmouseup = function(event)
|
|
|
+ {
|
|
|
+ isDrag = false;
|
|
|
+ var posX = event.offsetX * 1080 *1.0/myVideo.clientWidth;
|
|
|
+ var posY = event.offsetY * 1920 *1.0/myVideo.clientHeight;
|
|
|
+ var buffer = ExexuteMouseUp(posX.toString(), posY.toString());
|
|
|
+ ws.send(buffer);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ myVideo.onkeydown = function(event)
|
|
|
+ {
|
|
|
+ ExexuteKeyDown(e.keyCode);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+function Back()
|
|
|
+{
|
|
|
+ if(event.button == 2)
|
|
|
+ {
|
|
|
+ //ExexuteKeyDown(4);
|
|
|
+ }
|
|
|
+ ExexuteKeyDown(4);
|
|
|
+ window.event.returnValue=false;
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+</script>
|
|
|
+<script type="text/javascript" src="jmuxer.js"></script>
|
|
|
+<!--<script type="text/javascript" src="aac.js"></script>-->
|
|
|
+
|
|
|
+</body>
|
|
|
+</html>
|