Forráskód Böngészése

Merge branch '648540858:wvp-28181-2.0' into wvp-28181-2.0

hotcoffie 3 éve
szülő
commit
3aa2665fb2

+ 23 - 1
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java

@@ -6,6 +6,10 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.util.StringUtils;
 
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.regex.Pattern;
+
 
 @Configuration("mediaConfig")
 public class MediaConfig{
@@ -161,7 +165,18 @@ public class MediaConfig{
         if (StringUtils.isEmpty(sdpIp)){
             return ip;
         }else {
-            return sdpIp;
+            if (isValidIPAddress(sdpIp)) {
+                return sdpIp;
+            }else {
+                // 按照域名解析
+                String hostAddress = null;
+                try {
+                    hostAddress = InetAddress.getByName(sdpIp).getHostAddress();
+                } catch (UnknownHostException e) {
+                    throw new RuntimeException(e);
+                }
+                return hostAddress;
+            }
         }
     }
 
@@ -211,4 +226,11 @@ public class MediaConfig{
         return mediaServerItem;
     }
 
+    private boolean isValidIPAddress(String ipAddress) {
+        if ((ipAddress != null) && (!ipAddress.isEmpty())) {
+            return Pattern.matches("^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}$", ipAddress);
+        }
+        return false;
+    }
+
 }

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java

@@ -94,7 +94,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
             String deviceId = uri.getUser();
 
             AuthorizationHeader authHead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
-            if (authHead == null) {
+            if (authHead == null && !StringUtils.isEmpty(sipConfig.getPassword())) {
                 logger.info("[注册请求] 未携带授权头 回复401: {}", requestAddress);
                 response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
                 new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain());

+ 16 - 16
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java

@@ -49,28 +49,28 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
             return;
         }
         try {
+            // 判断RPort是否改变,改变则说明路由nat信息变化,修改设备信息
+            // 获取到通信地址等信息
+            ViaHeader viaHeader = (ViaHeader) evt.getRequest().getHeader(ViaHeader.NAME);
+            String received = viaHeader.getReceived();
+            int rPort = viaHeader.getRPort();
+            // 解析本地地址替代
+            if (StringUtils.isEmpty(received) || rPort == -1) {
+                received = viaHeader.getHost();
+                rPort = viaHeader.getPort();
+            }
+            if (device.getPort() != rPort) {
+                device.setPort(rPort);
+                device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
+            }
+            device.setKeepaliveTime(DateUtil.getNow());
             if (device.getOnline() == 1) {
                 // 回复200 OK
                 responseAck(evt, Response.OK);
+                deviceService.updateDevice(device);
             }else {
                 // 对于已经离线的设备判断他的注册是否已经过期
                 if (!deviceService.expire(device)){
-                    device.setKeepaliveTime(DateUtil.getNow());
-                    // 判断RPort是否改变,改变则说明路由nat信息变化,修改设备信息
-                    // 获取到通信地址等信息
-                    ViaHeader viaHeader = (ViaHeader) evt.getRequest().getHeader(ViaHeader.NAME);
-                    String received = viaHeader.getReceived();
-                    int rPort = viaHeader.getRPort();
-                    // 解析本地地址替代
-                    if (StringUtils.isEmpty(received) || rPort == -1) {
-                        received = viaHeader.getHost();
-                        rPort = viaHeader.getPort();
-                    }
-                    if (device.getPort() != rPort) {
-                        device.setPort(rPort);
-                        device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
-                    }
-                    device.setKeepaliveTime(DateUtil.getNow());
                     deviceService.online(device);
                     // 回复200 OK
                     responseAck(evt, Response.OK);

+ 9 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java

@@ -70,15 +70,20 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
 
             rootElement = getRootElement(evt, device.getCharset());
             String sn = getText(rootElement, "SN");
-
+            RecordInfo recordInfo = new RecordInfo();
+            recordInfo.setDeviceId(device.getDeviceId());
+            recordInfo.setSn(sn);
+            recordInfo.setName(getText(rootElement, "Name"));
             String sumNumStr = getText(rootElement, "SumNum");
             int sumNum = 0;
             if (!StringUtils.isEmpty(sumNumStr)) {
                 sumNum = Integer.parseInt(sumNumStr);
             }
+            recordInfo.setSumNum(sumNum);
             Element recordListElement = rootElement.element("RecordList");
             if (recordListElement == null || sumNum == 0) {
                 logger.info("无录像数据");
+                eventPublisher.recordEndEventPush(recordInfo);
                 recordDataCatch.put(device.getDeviceId(), sn, sumNum, new ArrayList<>());
                 releaseRequest(device.getDeviceId(), sn);
             } else {
@@ -112,6 +117,9 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
                         record.setRecorderId(getText(itemRecord, "RecorderID"));
                         recordList.add(record);
                     }
+                    recordInfo.setRecordList(recordList);
+                    // 发送消息,如果是上级查询此录像,则会通过这里通知给上级
+                    eventPublisher.recordEndEventPush(recordInfo);
                     int count = recordDataCatch.put(device.getDeviceId(), sn, sumNum, recordList);
                     logger.info("[国标录像], {}->{}: {}/{}", device.getDeviceId(), sn, count, sumNum);
                 }

+ 33 - 3
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java

@@ -87,6 +87,9 @@ public class PlayServiceImpl implements IPlayService {
     @Autowired
     private DynamicTask dynamicTask;
 
+    @Autowired
+    private ZLMHttpHookSubscribe subscribe;
+
 
 
 
@@ -256,6 +259,7 @@ public class PlayServiceImpl implements IPlayService {
             }
         }, userSetting.getPlayTimeout()*1000);
         final String ssrc = ssrcInfo.getSsrc();
+        final String stream = ssrcInfo.getStream();
         cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
             logger.info("收到订阅消息: " + response.toJSONString());
             dynamicTask.stop(timeOutTaskKey);
@@ -271,9 +275,13 @@ public class PlayServiceImpl implements IPlayService {
             if (ssrcIndex >= 0) {
                 //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
                 String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
-                if (!ssrc.equals(ssrcInResponse) && device.isSsrcCheck()) { // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
-                    // 查询 ssrcInResponse 是否可用
-                    if (mediaServerItem.isRtpEnable() && !mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
+                // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
+                if (ssrc.equals(ssrcInResponse)) {
+                    return;
+                }
+                logger.info("[SIP 消息] 收到invite 200, 发现下级自定义了ssrc 开启修正");
+                if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
+                    if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
                         // ssrc 不可用
                         // 释放ssrc
                         mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc());
@@ -283,10 +291,32 @@ public class PlayServiceImpl implements IPlayService {
                         errorEvent.response(event);
                         return;
                     }
+
+                    // 单端口模式streamId也有变化,需要重新设置监听
+                    if (!mediaServerItem.isRtpEnable()) {
+                        // 添加订阅
+                        JSONObject subscribeKey = new JSONObject();
+                        subscribeKey.put("app", "rtp");
+                        subscribeKey.put("stream", stream);
+                        subscribeKey.put("regist", true);
+                        subscribeKey.put("schema", "rtmp");
+                        subscribeKey.put("mediaServerId", mediaServerItem.getId());
+                        subscribe.removeSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed,subscribeKey);
+                        subscribeKey.put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
+                        subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
+                                (MediaServerItem mediaServerItemInUse, JSONObject response)->{
+                                    logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
+                                    dynamicTask.stop(timeOutTaskKey);
+                                    // hook响应
+                                    onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId, uuid);
+                                    hookEvent.response(mediaServerItemInUse, response);
+                                });
+                    }
                     // 关闭rtp server
                     mediaServerService.closeRTPServer(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
                     // 重新开启ssrc server
                     mediaServerService.openRTPServer(mediaServerItem, finalSsrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), false);
+
                 }
             }
         }, (event) -> {

+ 3 - 1
web_src/src/components/channelList.vue

@@ -220,6 +220,7 @@ export default {
         method: 'get',
         url: '/api/play/start/' + deviceId + '/' + channelId
       }).then(function (res) {
+        console.log(res)
         that.isLoging = false;
         if (res.data.code === 0) {
 
@@ -241,8 +242,9 @@ export default {
           that.$message.error(res.data.msg);
         }
       }).catch(function (e) {
+        console.error(e)
         that.isLoging = false;
-        that.$message.error("请求超时");
+        // that.$message.error("请求超时");
       });
     },
     queryRecords: function (itemData) {

+ 51 - 35
web_src/src/components/dialog/devicePlayer.vue

@@ -4,10 +4,22 @@
     <el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" @close="close()">
         <!-- <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" :message="videoError" :hasaudio="hasaudio" fluent autoplay live></LivePlayer> -->
       <div style="width: 100%; height: 100%">
-        <player ref="videoPlayer" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></player>
+        <el-tabs type="card" :stretch="true" v-model="activePlayer" @tab-click="changePlayer" v-if="Object.keys(this.player).length > 1">
+          <el-tab-pane label="Jessibuca" name="jessibuca">
+            <jessibucaPlayer v-if="activePlayer === 'jessibuca'" ref="jessibuca" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></jessibucaPlayer>
+          </el-tab-pane>
+          <el-tab-pane label="WebRTC" name="webRTC">
+            <rtc-player v-if="activePlayer === 'webRTC'" ref="webRTC" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></rtc-player>
+          </el-tab-pane>
+          <el-tab-pane label="h265web">h265web敬请期待</el-tab-pane>
+          <el-tab-pane label="wsPlayer">wsPlayer 敬请期待</el-tab-pane>
+        </el-tabs>
+        <jessibucaPlayer v-if="Object.keys(this.player).length == 1 && this.player.jessibuca" ref="jessibuca" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></jessibucaPlayer>
+        <rtc-player v-if="Object.keys(this.player).length == 1 && this.player.webRTC" ref="jessibuca" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></rtc-player>
+
       </div>
         <div id="shared" style="text-align: right; margin-top: 1rem;">
-            <el-tabs v-model="tabActiveName" @tab-click="tabHandleClick">
+            <el-tabs v-model="tabActiveName" @tab-click="tabHandleClick" >
                 <el-tab-pane label="实时视频" name="media">
                     <div style="margin-bottom: 0.5rem;">
                         <!--		<el-button type="primary" size="small" @click="playRecord(true, '')">播放</el-button>-->
@@ -273,16 +285,16 @@
 </template>
 
 <script>
-// import player from '../dialog/rtcPlayer.vue'
+import rtcPlayer from '../dialog/rtcPlayer.vue'
 // import LivePlayer from '@liveqing/liveplayer'
 // import player from '../dialog/easyPlayer.vue'
-import player from '../common/jessibuca.vue'
+import jessibucaPlayer from '../common/jessibuca.vue'
 import recordDownload from '../dialog/recordDownload.vue'
 export default {
     name: 'devicePlayer',
     props: {},
     components: {
-        player,recordDownload,
+        jessibucaPlayer, rtcPlayer, recordDownload,
     },
     computed: {
         getPlayerShared: function () {
@@ -293,11 +305,22 @@ export default {
             };
         }
     },
-    created() {},
+    created() {
+      console.log(this.player)
+      if (Object.keys(this.player).length === 1) {
+        this.activePlayer = Object.keys(this.player)[0]
+      }
+    },
     data() {
         return {
             video: 'http://lndxyj.iqilu.com/public/upload/2019/10/14/8c001ea0c09cdc59a57829dabc8010fa.mp4',
             videoUrl: '',
+            activePlayer: "jessibuca",
+            // 如何你只是用一种播放器,直接注释掉不用的部分即可
+            player: {
+              jessibuca : ["ws_flv", "wss_flv"],
+              webRTC: ["rtc", "rtc"],
+            },
             videoHistory: {
                 date: '',
                 searchHistoryResult: [] //媒体流历史记录搜索结果
@@ -364,6 +387,12 @@ export default {
                 }).catch(function (e) {});
             }
         },
+        changePlayer: function (tab) {
+            console.log(this.player[tab.name][0])
+            this.activePlayer = tab.name;
+            this.videoUrl = this.streamInfo[this.player[tab.name][0]]
+            console.log(this.videoUrl)
+        },
         openDialog: function (tab, deviceId, channelId, param) {
             this.tabActiveName = tab;
             this.channelId = channelId;
@@ -372,8 +401,8 @@ export default {
             this.mediaServerId = "";
             this.app = "";
             this.videoUrl = ""
-            if (!!this.$refs.videoPlayer) {
-                this.$refs.videoPlayer.pause();
+            if (!!this.$refs[this.activePlayer]) {
+              this.$refs[this.activePlayer].pause();
             }
             switch (tab) {
                 case "media":
@@ -402,38 +431,25 @@ export default {
             this.hasAudio = hasAudio;
             this.isLoging = false;
             // this.videoUrl = streamInfo.rtc;
-            this.videoUrl = this.getUrlByStreamInfo(streamInfo);
+            this.videoUrl = this.getUrlByStreamInfo();
             this.streamId = streamInfo.stream;
             this.app = streamInfo.app;
             this.mediaServerId = streamInfo.mediaServerId;
             this.playFromStreamInfo(false, streamInfo)
         },
-        getUrlByStreamInfo(streamInfo){
-            let baseZlmApi = process.env.NODE_ENV === 'development'?`${location.host}/debug/zlm`:`${location.host}/zlm`
-            // return `${baseZlmApi}/${streamInfo.app}/${streamInfo.streamId}.flv`;
-            // return `http://${baseZlmApi}/${streamInfo.app}/${streamInfo.streamId}.flv`;
+        getUrlByStreamInfo(){
             if (location.protocol === "https:") {
-              if (streamInfo.wss_flv === null) {
-                console.error("媒体服务器未配置ssl端口, 使用http端口")
-                // this.$message({
-                //   showClose: true,
-                //   message: '媒体服务器未配置ssl端口, ',
-                //   type: 'error'
-                // });
-                return streamInfo.ws_flv
-              }else {
-                return streamInfo.wss_flv;
-              }
-
+              this.videoUrl = this.streamInfo[this.player[this.activePlayer][1]]
             }else {
-              return streamInfo.ws_flv;
+              this.videoUrl = this.streamInfo[this.player[this.activePlayer][0]]
             }
+            return this.videoUrl;
 
         },
         coverPlay: function () {
             var that = this;
             this.coverPlaying = true;
-            this.$refs.videoPlayer.pause()
+            this.$refs[this.activePlayer].pause()
             that.$axios({
                 method: 'post',
                 url: '/api/gb_record/convert/' + that.streamId
@@ -465,7 +481,7 @@ export default {
         },
         convertStopClick: function() {
             this.convertStop(()=>{
-                this.$refs.videoPlayer.play(this.videoUrl)
+                this.$refs[this.activePlayer].play(this.videoUrl)
             });
         },
         convertStop: function(callback) {
@@ -490,12 +506,12 @@ export default {
         playFromStreamInfo: function (realHasAudio, streamInfo) {
           this.showVideoDialog = true;
           this.hasaudio = realHasAudio && this.hasaudio;
-          this.$refs.videoPlayer.play(this.getUrlByStreamInfo(streamInfo))
+          this.$refs[this.activePlayer].play(this.getUrlByStreamInfo(streamInfo))
         },
         close: function () {
             console.log('关闭视频');
-            if (!!this.$refs.videoPlayer){
-              this.$refs.videoPlayer.pause();
+            if (!!this.$refs[this.activePlayer]){
+              this.$refs[this.activePlayer].pause();
             }
             this.videoUrl = '';
             this.coverPlaying = false;
@@ -600,7 +616,7 @@ export default {
             }
         },
         stopPlayRecord: function (callback) {
-            this.$refs.videoPlayer.pause();
+          this.$refs[this.activePlayer].pause();
             this.videoUrl = '';
             this.$axios({
                 method: 'get',
@@ -646,7 +662,7 @@ export default {
             }
         },
         stopDownloadRecord: function (callback) {
-            this.$refs.videoPlayer.pause();
+            this.$refs[this.activePlayer].pause();
             this.videoUrl = '';
             this.$axios({
                 method: 'get',
@@ -753,7 +769,7 @@ export default {
             method: 'get',
             url: '/api/playback/resume/' + this.streamId
           }).then((res)=> {
-            this.$refs.videoPlayer.play(this.videoUrl)
+            this.$refs[this.activePlayer].play(this.videoUrl)
           });
         },
         gbPause(){
@@ -784,7 +800,7 @@ export default {
             url: `/api/playback/seek/${this.streamId }/` + Math.floor(this.seekTime * val / 100000)
           }).then( (res)=> {
             setTimeout(()=>{
-              this.$refs.videoPlayer.play(this.videoUrl)
+              this.$refs[this.activePlayer].play(this.videoUrl)
             }, 600)
           });
         },

+ 199 - 27
web_src/static/js/ZLMRTCClient.js

@@ -6,11 +6,17 @@ var ZLMRTCClient = (function (exports) {
 	  WEBRTC_ICE_CANDIDATE_ERROR: 'WEBRTC_ICE_CANDIDATE_ERROR',
 	  WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED: 'WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED',
 	  WEBRTC_ON_REMOTE_STREAMS: 'WEBRTC_ON_REMOTE_STREAMS',
-	  WEBRTC_ON_LOCAL_STREAM: 'WEBRTC_ON_LOCAL_STREAM'
+	  WEBRTC_ON_LOCAL_STREAM: 'WEBRTC_ON_LOCAL_STREAM',
+	  WEBRTC_ON_CONNECTION_STATE_CHANGE: 'WEBRTC_ON_CONNECTION_STATE_CHANGE',
+	  WEBRTC_ON_DATA_CHANNEL_OPEN: 'WEBRTC_ON_DATA_CHANNEL_OPEN',
+	  WEBRTC_ON_DATA_CHANNEL_CLOSE: 'WEBRTC_ON_DATA_CHANNEL_CLOSE',
+	  WEBRTC_ON_DATA_CHANNEL_ERR: 'WEBRTC_ON_DATA_CHANNEL_ERR',
+	  WEBRTC_ON_DATA_CHANNEL_MSG: 'WEBRTC_ON_DATA_CHANNEL_MSG',
+	  CAPTURE_STREAM_FAILED: 'CAPTURE_STREAM_FAILED'
 	};
 
 	const VERSION = '1.0.1';
-	const BUILD_DATE = 'Mon Apr 05 2021 10:22:48 GMT+0800 (中国标准时间)';
+	const BUILD_DATE = 'Thu Mar 24 2022 17:42:57 GMT+0800 (China Standard Time)';
 
 	// Copyright (C) <2018> Intel Corporation
 	//
@@ -7284,11 +7290,16 @@ var ZLMRTCClient = (function (exports) {
 	      debug: false,
 	      // if output debug log
 	      zlmsdpUrl: '',
-	      simulecast: false,
+	      simulcast: false,
 	      useCamera: true,
 	      audioEnable: true,
 	      videoEnable: true,
-	      recvOnly: false
+	      recvOnly: false,
+	      resolution: {
+	        w: 0,
+	        h: 0
+	      },
+	      usedatachannel: false
 	    };
 	    this.options = Object.assign({}, defaults, options);
 
@@ -7299,7 +7310,12 @@ var ZLMRTCClient = (function (exports) {
 	    this.e = {
 	      onicecandidate: this._onIceCandidate.bind(this),
 	      ontrack: this._onTrack.bind(this),
-	      onicecandidateerror: this._onIceCandidateError.bind(this)
+	      onicecandidateerror: this._onIceCandidateError.bind(this),
+	      onconnectionstatechange: this._onconnectionstatechange.bind(this),
+	      ondatachannelopen: this._onDataChannelOpen.bind(this),
+	      ondatachannelmsg: this._onDataChannelMsg.bind(this),
+	      ondatachannelerr: this._onDataChannelErr.bind(this),
+	      ondatachannelclose: this._onDataChannelClose.bind(this)
 	    };
 	    this._remoteStream = null;
 	    this._localStream = null;
@@ -7307,6 +7323,17 @@ var ZLMRTCClient = (function (exports) {
 	    this.pc.onicecandidate = this.e.onicecandidate;
 	    this.pc.onicecandidateerror = this.e.onicecandidateerror;
 	    this.pc.ontrack = this.e.ontrack;
+	    this.pc.onconnectionstatechange = this.e.onconnectionstatechange;
+	    this.datachannel = null;
+
+	    if (this.options.usedatachannel) {
+	      this.datachannel = this.pc.createDataChannel('chat');
+	      this.datachannel.onclose = this.e.ondatachannelclose;
+	      this.datachannel.onerror = this.e.ondatachannelerr;
+	      this.datachannel.onmessage = this.e.ondatachannelmsg;
+	      this.datachannel.onopen = this.e.ondatachannelopen;
+	    }
+
 	    if (!this.options.recvOnly && (this.options.audioEnable || this.options.videoEnable)) this.start();else this.receive();
 	  }
 
@@ -7337,7 +7364,7 @@ var ZLMRTCClient = (function (exports) {
 	          let ret = response.data; //JSON.parse(response.data);
 
 	          if (ret.code != 0) {
-	            // mean failed for offer/anwser exchange
+	            // mean failed for offer/anwser exchange 
 	            this.dispatch(Events$1.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, ret);
 	            return;
 	          }
@@ -7347,7 +7374,7 @@ var ZLMRTCClient = (function (exports) {
 	          anwser.type = 'answer';
 	          log(this.TAG, 'answer:', ret.sdp);
 	          this.pc.setRemoteDescription(anwser).then(() => {
-	            log(this.TAG, 'set remote success');
+	            log(this.TAG, 'set remote sucess');
 	          }).catch(e => {
 	            error(this.TAG, e);
 	          });
@@ -7377,6 +7404,10 @@ var ZLMRTCClient = (function (exports) {
 	      }
 	    }
 
+	    if (this.options.resolution.w != 0 && this.options.resolution.h != 0 && typeof videoConstraints == 'object') {
+	      videoConstraints.resolution = new Resolution(this.options.resolution.w, this.options.resolution.h);
+	    }
+
 	    MediaStreamFactory.createMediaStream(new StreamConstraints(audioConstraints, videoConstraints)).then(stream => {
 	      this._localStream = stream;
 	      this.dispatch(Events$1.WEBRTC_ON_LOCAL_STREAM, stream);
@@ -7389,33 +7420,40 @@ var ZLMRTCClient = (function (exports) {
 	        sendEncodings: []
 	      };
 
-	      if (this.options.simulecast && stream.getVideoTracks().length > 0) {
+	      if (this.options.simulcast && stream.getVideoTracks().length > 0) {
 	        VideoTransceiverInit.sendEncodings = [{
-	          rid: 'q',
+	          rid: 'h',
 	          active: true,
-	          scaleResolutionDownBy: 4.0
+	          maxBitrate: 1000000
 	        }, {
-	          rid: 'h',
+	          rid: 'm',
 	          active: true,
-	          scaleResolutionDownBy: 2.0
+	          maxBitrate: 500000,
+	          scaleResolutionDownBy: 2
 	        }, {
-	          rid: 'f',
-	          active: true
+	          rid: 'l',
+	          active: true,
+	          maxBitrate: 200000,
+	          scaleResolutionDownBy: 4
 	        }];
 	      }
 
-	      if (stream.getAudioTracks().length > 0) {
-	        this.pc.addTransceiver(stream.getAudioTracks()[0], AudioTransceiverInit);
-	      } else {
-	        AudioTransceiverInit.direction = 'recvonly';
-	        this.pc.addTransceiver('audio', AudioTransceiverInit);
+	      if (this.options.audioEnable) {
+	        if (stream.getAudioTracks().length > 0) {
+	          this.pc.addTransceiver(stream.getAudioTracks()[0], AudioTransceiverInit);
+	        } else {
+	          AudioTransceiverInit.direction = 'recvonly';
+	          this.pc.addTransceiver('audio', AudioTransceiverInit);
+	        }
 	      }
 
-	      if (stream.getVideoTracks().length > 0) {
-	        this.pc.addTransceiver(stream.getVideoTracks()[0], VideoTransceiverInit);
-	      } else {
-	        VideoTransceiverInit.direction = 'recvonly';
-	        this.pc.addTransceiver('video', VideoTransceiverInit);
+	      if (this.options.videoEnable) {
+	        if (stream.getVideoTracks().length > 0) {
+	          this.pc.addTransceiver(stream.getVideoTracks()[0], VideoTransceiverInit);
+	        } else {
+	          VideoTransceiverInit.direction = 'recvonly';
+	          this.pc.addTransceiver('video', VideoTransceiverInit);
+	        }
 	      }
 	      /*
 	      stream.getTracks().forEach((track,idx)=>{
@@ -7440,7 +7478,7 @@ var ZLMRTCClient = (function (exports) {
 	            let ret = response.data; //JSON.parse(response.data);
 
 	            if (ret.code != 0) {
-	              // mean failed for offer/anwser exchange
+	              // mean failed for offer/anwser exchange 
 	              this.dispatch(Events$1.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, ret);
 	              return;
 	            }
@@ -7450,7 +7488,7 @@ var ZLMRTCClient = (function (exports) {
 	            anwser.type = 'answer';
 	            log(this.TAG, 'answer:', ret.sdp);
 	            this.pc.setRemoteDescription(anwser).then(() => {
-	              log(this.TAG, 'set remote success');
+	              log(this.TAG, 'set remote sucess');
 	            }).catch(e => {
 	              error(this.TAG, e);
 	            });
@@ -7460,7 +7498,7 @@ var ZLMRTCClient = (function (exports) {
 	        error(this.TAG, e);
 	      });
 	    }).catch(e => {
-	      error(this.TAG, e);
+	      this.dispatch(Events$1.CAPTURE_STREAM_FAILED); //debug.error(this.TAG,e);
 	    }); //const offerOptions = {};
 
 	    /*
@@ -7495,7 +7533,48 @@ var ZLMRTCClient = (function (exports) {
 	    this.dispatch(Events$1.WEBRTC_ICE_CANDIDATE_ERROR, event);
 	  }
 
+	  _onconnectionstatechange(event) {
+	    this.dispatch(Events$1.WEBRTC_ON_CONNECTION_STATE_CHANGE, this.pc.connectionState);
+	  }
+
+	  _onDataChannelOpen(event) {
+	    log(this.TAG, 'ondatachannel open:', event);
+	    this.dispatch(Events$1.WEBRTC_ON_DATA_CHANNEL_OPEN, event);
+	  }
+
+	  _onDataChannelMsg(event) {
+	    log(this.TAG, 'ondatachannel msg:', event);
+	    this.dispatch(Events$1.WEBRTC_ON_DATA_CHANNEL_MSG, event);
+	  }
+
+	  _onDataChannelErr(event) {
+	    log(this.TAG, 'ondatachannel err:', event);
+	    this.dispatch(Events$1.WEBRTC_ON_DATA_CHANNEL_ERR, event);
+	  }
+
+	  _onDataChannelClose(event) {
+	    log(this.TAG, 'ondatachannel close:', event);
+	    this.dispatch(Events$1.WEBRTC_ON_DATA_CHANNEL_CLOSE, event);
+	  }
+
+	  sendMsg(data) {
+	    if (this.datachannel != null) {
+	      this.datachannel.send(data);
+	    } else {
+	      error(this.TAG, 'data channel is null');
+	    }
+	  }
+
+	  closeDataChannel() {
+	    if (this.datachannel) {
+	      this.datachannel.close();
+	      this.datachannel = null;
+	    }
+	  }
+
 	  close() {
+	    this.closeDataChannel();
+
 	    if (this.pc) {
 	      this.pc.close();
 	      this.pc = null;
@@ -7528,15 +7607,108 @@ var ZLMRTCClient = (function (exports) {
 
 	}
 
+	const quickScan = [{
+	  'label': '4K(UHD)',
+	  'width': 3840,
+	  'height': 2160
+	}, {
+	  'label': '1080p(FHD)',
+	  'width': 1920,
+	  'height': 1080
+	}, {
+	  'label': 'UXGA',
+	  'width': 1600,
+	  'height': 1200,
+	  'ratio': '4:3'
+	}, {
+	  'label': '720p(HD)',
+	  'width': 1280,
+	  'height': 720
+	}, {
+	  'label': 'SVGA',
+	  'width': 800,
+	  'height': 600
+	}, {
+	  'label': 'VGA',
+	  'width': 640,
+	  'height': 480
+	}, {
+	  'label': '360p(nHD)',
+	  'width': 640,
+	  'height': 360
+	}, {
+	  'label': 'CIF',
+	  'width': 352,
+	  'height': 288
+	}, {
+	  'label': 'QVGA',
+	  'width': 320,
+	  'height': 240
+	}, {
+	  'label': 'QCIF',
+	  'width': 176,
+	  'height': 144
+	}, {
+	  'label': 'QQVGA',
+	  'width': 160,
+	  'height': 120
+	}];
+	function GetSupportCameraResolutions$1() {
+	  return new Promise(function (resolve, reject) {
+	    let resolutions = [];
+	    let ok = 0;
+	    let err = 0;
+
+	    for (let i = 0; i < quickScan.length; ++i) {
+	      let videoConstraints = new VideoTrackConstraints(VideoSourceInfo.CAMERA);
+	      videoConstraints.resolution = new Resolution(quickScan[i].width, quickScan[i].height);
+	      MediaStreamFactory.createMediaStream(new StreamConstraints(false, videoConstraints)).then(stream => {
+	        resolutions.push(quickScan[i]);
+	        ok++;
+
+	        if (ok + err == quickScan.length) {
+	          resolve(resolutions);
+	        }
+	      }).catch(e => {
+	        err++;
+
+	        if (ok + err == quickScan.length) {
+	          resolve(resolutions);
+	        }
+	      });
+	    }
+	  });
+	}
+	function GetAllScanResolution$1() {
+	  return quickScan;
+	}
+	function isSupportResolution$1(w, h) {
+	  return new Promise(function (resolve, reject) {
+	    let videoConstraints = new VideoTrackConstraints(VideoSourceInfo.CAMERA);
+	    videoConstraints.resolution = new Resolution(w, h);
+	    MediaStreamFactory.createMediaStream(new StreamConstraints(false, videoConstraints)).then(stream => {
+	      resolve();
+	    }).catch(e => {
+	      reject(e);
+	    });
+	  });
+	}
+
 	console.log('build date:', BUILD_DATE);
 	console.log('version:', VERSION);
 	const Events = Events$1;
 	const Media = media;
 	const Endpoint = RTCEndpoint;
+	const GetSupportCameraResolutions = GetSupportCameraResolutions$1;
+	const GetAllScanResolution = GetAllScanResolution$1;
+	const isSupportResolution = isSupportResolution$1;
 
 	exports.Endpoint = Endpoint;
 	exports.Events = Events;
+	exports.GetAllScanResolution = GetAllScanResolution;
+	exports.GetSupportCameraResolutions = GetSupportCameraResolutions;
 	exports.Media = Media;
+	exports.isSupportResolution = isSupportResolution;
 
 	Object.defineProperty(exports, '__esModule', { value: true });
 

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
web_src/static/js/ZLMRTCClient.js.map


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
web_src/static/js/jessibuca/decoder.js


BIN
web_src/static/js/jessibuca/decoder.wasm


+ 637 - 0
web_src/static/js/jessibuca/jessibuca.d.ts

@@ -0,0 +1,637 @@
+declare namespace Jessibuca {
+
+    /** 超时信息 */
+    enum TIMEOUT {
+        /** 当play()的时候,如果没有数据返回 */
+        loadingTimeout = 'loadingTimeout',
+        /** 当播放过程中,如果超过timeout之后没有数据渲染 */
+        delayTimeout = 'delayTimeout',
+    }
+
+    /** 错误信息 */
+    enum ERROR {
+        /** 播放错误,url 为空的时候,调用 play 方法 */
+        playError = 'playError',
+        /** http 请求失败 */
+        fetchError = 'fetchError',
+        /** websocket 请求失败 */
+        websocketError = 'websocketError',
+        /** webcodecs 解码 h265 失败 */
+        webcodecsH265NotSupport = 'webcodecsH265NotSupport',
+        /** mediaSource 解码 h265 失败 */
+        mediaSourceH265NotSupport = 'mediaSourceH265NotSupport',
+        /** wasm 解码失败 */
+        wasmDecodeError = 'wasmDecodeError',
+    }
+
+    interface Config {
+        /**
+         * 播放器容器
+         * *  若为 string ,则底层调用的是 document.getElementById('id')
+         * */
+        container: HTMLElement | string;
+        /**
+         * 设置最大缓冲时长,单位秒,播放器会自动消除延迟
+         */
+        videoBuffer?: number;
+        /**
+         * worker地址
+         * *  默认引用的是根目录下面的decoder.js文件 ,decoder.js 与 decoder.wasm文件必须是放在同一个目录下面。 */
+        decoder?: string;
+        /**
+         * 是否不使用离屏模式(提升渲染能力)
+         */
+        forceNoOffscreen?: boolean;
+        /**
+         * 是否开启当页面的'visibilityState'变为'hidden'的时候,自动暂停播放。
+         */
+        hiddenAutoPause?: boolean;
+        /**
+         * 是否有音频,如果设置`false`,则不对音频数据解码,提升性能。
+         */
+        hasAudio?: boolean;
+        /**
+         * 设置旋转角度,只支持,0(默认),180,270 三个值
+         */
+        rotate?: boolean;
+        /**
+         * 1. 当为`true`的时候:视频画面做等比缩放后,高或宽对齐canvas区域,画面不被拉伸,但有黑边。 等同于 `setScaleMode(1)`
+         * 2. 当为`false`的时候:视频画面完全填充canvas区域,画面会被拉伸。等同于 `setScaleMode(0)`
+         */
+        isResize?: boolean;
+        /**
+         * 1. 当为`true`的时候:视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全。等同于 `setScaleMode(2)`
+         */
+        isFullSize?: boolean;
+        /**
+         * 1. 当为`true`的时候:ws协议不检验是否以.flv为依据,进行协议解析。
+         */
+        isFlv?: boolean;
+        /**
+         * 是否开启控制台调试打
+         */
+        debug?: boolean;
+        /**
+         * 1. 设置超时时长, 单位秒
+         * 2. 在连接成功之前(loading)和播放中途(heart),如果超过设定时长无数据返回,则回调timeout事件
+         */
+        timeout?: number;
+        /**
+         * 1. 设置超时时长, 单位秒
+         * 2. 在连接成功之前,如果超过设定时长无数据返回,则回调timeout事件
+         */
+        heartTimeout?: number;
+        /**
+         * 1. 设置超时时长, 单位秒
+         * 2. 在连接成功之前,如果超过设定时长无数据返回,则回调timeout事件
+         */
+        loadingTimeout?: number;
+        /**
+         * 是否支持屏幕的双击事件,触发全屏,取消全屏事件
+         */
+        supportDblclickFullscreen?: boolean;
+        /**
+         * 是否显示网
+         */
+        showBandwidth?: boolean;
+        /**
+         * 配置操作按钮
+         */
+        operateBtns?: {
+            /** 是否显示全屏按钮 */
+            fullscreen?: boolean;
+            /** 是否显示截图按钮 */
+            screenshot?: boolean;
+            /** 是否显示播放暂停按钮 */
+            play?: boolean;
+            /** 是否显示声音按钮 */
+            audio?: boolean;
+            /** 是否显示录制按 */
+            record?: boolean;
+        };
+        /**
+         * 开启屏幕常亮,在手机浏览器上, canvas标签渲染视频并不会像video标签那样保持屏幕常亮
+         */
+        keepScreenOn?: boolean;
+        /**
+         * 是否开启声音,默认是关闭声音播放的
+         */
+        isNotMute?: boolean;
+        /**
+         * 加载过程中文案
+         */
+        loadingText?: boolean;
+        /**
+         * 背景图片
+         */
+        background?: string;
+        /**
+         * 是否开启MediaSource硬解码
+         * * 视频编码只支持H.264视频(Safari on iOS不支持)
+         * * 不支持 forceNoOffscreen 为 false (开启离屏渲染)
+         */
+        useMSE?: boolean;
+        /**
+         * 是否开启Webcodecs硬解码
+         * *  视频编码只支持H.264视频 (需在chrome 94版本以上,需要https或者localhost环境)
+         * *  支持 forceNoOffscreen 为 false (开启离屏渲染)
+         * */
+        useWCS?: boolean;
+        /**
+         * 是否开启键盘快捷键
+         * 目前支持的键盘快捷键有:esc -> 退出全屏;arrowUp -> 声音增加;arrowDown -> 声音减少;
+         */
+        hotKey?: boolean;
+        /**
+         *  在使用MSE或者Webcodecs 播放H265的时候,是否自动降级到wasm模式。
+         *  设置为false 则直接关闭播放,抛出Error 异常,设置为true 则会自动切换成wasm模式播放。
+         */
+        autoWasm?: boolean;
+        /**
+         * heartTimeout 心跳超时之后自动再播放,不再抛出异常,而直接重新播放视频地址。
+         */
+        heartTimeoutReplay?: boolean,
+        /**
+         * heartTimeoutReplay 从试次数,超过之后,不再自动播放
+         */
+        heartTimeoutReplayTimes?: number,
+        /**
+         * loadingTimeout loading之后自动再播放,不再抛出异常,而直接重新播放视频地址。
+         */
+        loadingTimeoutReplay?: boolean,
+        /**
+         * heartTimeoutReplay 从试次数,超过之后,不再自动播放
+         */
+        loadingTimeoutReplayTimes?: number
+        /**
+         * wasm解码报错之后,不再抛出异常,而是直接重新播放视频地址。
+         */
+        wasmDecodeErrorReplay?: boolean,
+        /**
+         * https://github.com/langhuihui/jessibuca/issues/152 解决方案
+         * 例如:WebGL图像预处理默认每次取4字节的数据,但是540x960分辨率下的U、V分量宽度是540/2=270不能被4整除,导致绿屏。
+         */
+        openWebglAlignment?: boolean
+    }
+}
+
+
+declare class Jessibuca {
+
+    constructor(config?: Jessibuca.Config);
+
+    /**
+     * 是否开启控制台调试打印
+     @example
+     // 开启
+     jessibuca.setDebug(true)
+     // 关闭
+     jessibuca.setDebug(false)
+     */
+    setDebug(flag: boolean): void;
+
+    /**
+     * 静音
+     @example
+     jessibuca.mute()
+     */
+    mute(): void;
+
+    /**
+     * 取消静音
+     @example
+     jessibuca.cancelMute()
+     */
+    cancelMute(): void;
+
+    /**
+     * 留给上层用户操作来触发音频恢复的方法。
+     *
+     * iPhone,chrome等要求自动播放时,音频必须静音,需要由一个真实的用户交互操作来恢复,不能使用代码。
+     *
+     * https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
+     */
+    audioResume(): void;
+
+    /**
+     *
+     * 设置超时时长, 单位秒
+     * 在连接成功之前和播放中途,如果超过设定时长无数据返回,则回调timeout事件
+
+     @example
+     jessibuca.setTimeout(10)
+
+     jessibuca.on('timeout',function(){
+        //
+    });
+     */
+    setTimeout(): void;
+
+    /**
+     * @param mode
+     *      0 视频画面完全填充canvas区域,画面会被拉伸  等同于参数 `isResize` 为false
+     *
+     *      1 视频画面做等比缩放后,高或宽对齐canvas区域,画面不被拉伸,但有黑边 等同于参数 `isResize` 为true
+     *
+     *      2 视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全 等同于参数 `isFullResize` 为true
+     @example
+     jessibuca.setScaleMode(0)
+
+     jessibuca.setScaleMode(1)
+
+     jessibuca.setScaleMode(2)
+     */
+    setScaleMode(mode: number): void;
+
+    /**
+     * 暂停播放
+     *
+     * 可以在pause 之后,再调用 `play()`方法就继续播放之前的流。
+     @example
+     jessibuca.pause().then(()=>{
+        console.log('pause success')
+
+        jessibuca.play().then(()=>{
+
+        }).catch((e)=>{
+
+        })
+
+    }).catch((e)=>{
+        console.log('pause error',e);
+    })
+     */
+    pause(): Promise<void>;
+
+    /**
+     * 关闭视频,不释放底层资源
+     @example
+     jessibuca.close();
+     */
+    close(): void;
+
+    /**
+     * 关闭视频,释放底层资源
+     @example
+     jessibuca.destroy()
+     */
+    destroy(): void;
+
+    /**
+     * 清理画布为黑色背景
+     @example
+     jessibuca.clearView()
+     */
+    clearView(): void;
+
+    /**
+     * 播放视频
+     @example
+
+     jessibuca.play('url').then(()=>{
+        console.log('play success')
+    }).catch((e)=>{
+        console.log('play error',e)
+    })
+     //
+     jessibuca.play()
+     */
+    play(url?: string): Promise<void>;
+
+    /**
+     * 重新调整视图大小
+     */
+    resize(): void;
+
+    /**
+     * 设置最大缓冲时长,单位秒,播放器会自动消除延迟。
+     *
+     * 等同于 `videoBuffer` 参数。
+     *
+     @example
+     // 设置 200ms 缓冲
+     jessibuca.setBufferTime(0.2)
+     */
+    setBufferTime(time: number): void;
+
+    /**
+     * 设置旋转角度,只支持,0(默认) ,180,270 三个值。
+     *
+     * > 可用于实现监控画面小窗和全屏效果,由于iOS没有全屏API,此方法可以模拟页面内全屏效果而且多端效果一致。   *
+     @example
+     jessibuca.setRotate(0)
+
+     jessibuca.setRotate(90)
+
+     jessibuca.setRotate(270)
+     */
+    setRotate(deg: number): void;
+
+    /**
+     *
+     * 设置音量大小,取值0 — 1
+     *
+     * > 区别于 mute 和 cancelMute 方法,虽然设置setVolume(0) 也能达到 mute方法,但是mute 方法是不调用底层播放音频的,能提高性能。而setVolume(0)只是把声音设置为0 ,以达到效果。
+     * @param volume 当为0时,完全无声;当为1时,最大音量,默认值
+     @example
+     jessibuca.setVolume(0.2)
+
+     jessibuca.setVolume(0)
+
+     jessibuca.setVolume(1)
+     */
+    setVolume(volume: number): void;
+
+    /**
+     * 返回是否加载完毕
+     @example
+     var result = jessibuca.hasLoaded()
+     console.log(result) // true
+     */
+    hasLoaded(): boolean;
+
+    /**
+     * 开启屏幕常亮,在手机浏览器上, canvas标签渲染视频并不会像video标签那样保持屏幕常亮。
+     * H5目前在chrome\edge 84, android chrome 84及以上有原生亮屏API, 需要是https页面
+     * 其余平台为模拟实现,此时为兼容实现,并不保证所有浏览器都支持
+     @example
+     jessibuca.setKeepScreenOn()
+     */
+    setKeepScreenOn(): boolean;
+
+    /**
+     * 全屏(取消全屏)播放视频
+     @example
+     jessibuca.setFullscreen(true)
+     //
+     jessibuca.setFullscreen(false)
+     */
+    setFullscreen(flag: boolean): void;
+
+    /**
+     *
+     * 截图,调用后弹出下载框保存截图
+     * @param filename 可选参数, 保存的文件名, 默认 `时间戳`
+     * @param format   可选参数, 截图的格式,可选png或jpeg或者webp ,默认 `png`
+     * @param quality  可选参数, 当格式是jpeg或者webp时,压缩质量,取值0 ~ 1 ,默认 `0.92`
+     * @param type 可选参数, 可选download或者base64或者blob,默认`download`
+
+     @example
+
+     jessibuca.screenshot("test","png",0.5)
+
+     const base64 = jessibuca.screenshot("test","png",0.5,'base64')
+
+     const fileBlob = jessibuca.screenshot("test",'blob')
+     */
+    screenshot(filename?: string, format?: string, quality?: number, type?: string): void;
+
+    /**
+     * 开始录制。
+     * @param fileName 可选,默认时间戳
+     * @param fileType 可选,默认webm,支持webm 和mp4 格式
+
+     @example
+     jessibuca.startRecord('xxx','webm')
+     */
+    startRecord(fileName: string, fileType: string): void;
+
+    /**
+     * 暂停录制并下载。
+     @example
+     jessibuca.stopRecordAndSave()
+     */
+    stopRecordAndSave(): void;
+
+    /**
+     * 返回是否正在播放中状态。
+     @example
+     var result = jessibuca.isPlaying()
+     console.log(result) // true
+     */
+    isPlaying(): boolean;
+
+    /**
+     *   返回是否静音。
+     @example
+     var result = jessibuca.isMute()
+     console.log(result) // true
+     */
+    isMute(): boolean;
+
+    /**
+     * 返回是否正在录制。
+     @example
+     var result = jessibuca.isRecording()
+     console.log(result) // true
+     */
+    isRecording(): boolean;
+
+
+    /**
+     * 监听 jessibuca 初始化事件
+     * @example
+     * jessibuca.on("load",function(){console.log('load')})
+     */
+    on(event: 'load', callback: () => void): void;
+
+    /**
+     * 视频播放持续时间,单位ms
+     * @example
+     * jessibuca.on('timeUpdate',function (ts) {console.log('timeUpdate',ts);})
+     */
+    on(event: 'timeUpdate', callback: () => void): void;
+
+    /**
+     * 当解析出视频信息时回调,2个回调参数
+     * @example
+     * jessibuca.on("videoInfo",function(data){console.log('width:',data.width,'height:',data.width)})
+     */
+    on(event: 'videoInfo', callback: (data: {
+        /** 视频宽 */
+        width: number;
+        /** 视频高 */
+        height: number;
+    }) => void): void;
+
+    /**
+     * 当解析出音频信息时回调,2个回调参数
+     * @example
+     * jessibuca.on("audioInfo",function(data){console.log('numOfChannels:',data.numOfChannels,'sampleRate',data.sampleRate)})
+     */
+    on(event: 'audioInfo', callback: (data: {
+        /** 声频通道 */
+        numOfChannels: number;
+        /** 采样率 */
+        sampleRate: number;
+    }) => void): void;
+
+    /**
+     * 信息,包含错误信息
+     * @example
+     * jessibuca.on("log",function(data){console.log('data:',data)})
+     */
+    on(event: 'log', callback: () => void): void;
+
+    /**
+     * 错误信息
+     * @example
+     * jessibuca.on("error",function(error){
+        if(error === Jessibuca.ERROR.fetchError){
+            //
+        }
+        else if(error === Jessibuca.ERROR.webcodecsH265NotSupport){
+            //
+        }
+        console.log('error:',error)
+    })
+     */
+    on(event: 'error', callback: (err: Jessibuca.ERROR) => void): void;
+
+    /**
+     * 当前网速, 单位KB 每秒1次,
+     * @example
+     * jessibuca.on("kBps",function(data){console.log('kBps:',data)})
+     */
+    on(event: 'kBps', callback: (value: number) => void): void;
+
+    /**
+     * 渲染开始
+     * @example
+     * jessibuca.on("start",function(){console.log('start render')})
+     */
+    on(event: 'start', callback: () => void): void;
+
+    /**
+     * 当设定的超时时间内无数据返回,则回调
+     * @example
+     * jessibuca.on("timeout",function(error){console.log('timeout:',error)})
+     */
+    on(event: 'timeout', callback: (error: Jessibuca.TIMEOUT) => void): void;
+
+    /**
+     * 当play()的时候,如果没有数据返回,则回调
+     * @example
+     * jessibuca.on("loadingTimeout",function(){console.log('timeout')})
+     */
+    on(event: 'loadingTimeout', callback: () => void): void;
+
+    /**
+     * 当播放过程中,如果超过timeout之后没有数据渲染,则抛出异常。
+     * @example
+     * jessibuca.on("delayTimeout",function(){console.log('timeout')})
+     */
+    on(event: 'delayTimeout', callback: () => void): void;
+
+    /**
+     * 当前是否全屏
+     * @example
+     * jessibuca.on("fullscreen",function(flag){console.log('is fullscreen',flag)})
+     */
+    on(event: 'fullscreen', callback: () => void): void;
+
+    /**
+     * 触发播放事件
+     * @example
+     * jessibuca.on("play",function(flag){console.log('play')})
+     */
+    on(event: 'play', callback: () => void): void;
+
+    /**
+     * 触发暂停事件
+     * @example
+     * jessibuca.on("pause",function(flag){console.log('pause')})
+     */
+    on(event: 'pause', callback: () => void): void;
+
+    /**
+     * 触发声音事件,返回boolean值
+     * @example
+     * jessibuca.on("mute",function(flag){console.log('is mute',flag)})
+     */
+    on(event: 'mute', callback: () => void): void;
+
+    /**
+     * 流状态统计,流开始播放后回调,每秒1次。
+     * @example
+     * jessibuca.on("stats",function(s){console.log("stats is",s)})
+     */
+    on(event: 'stats', callback: (stats: {
+        /** 当前缓冲区时长,单位毫秒 */
+        buf: number;
+        /** 当前视频帧率 */
+        fps: number;
+        /** 当前音频码率,单位bit */
+        abps: number;
+        /** 当前视频码率,单位bit */
+        vbps: number;
+        /** 当前视频帧pts,单位毫秒 */
+        ts: number;
+    }) => void): void;
+
+    /**
+     * 渲染性能统计,流开始播放后回调,每秒1次。
+     * @param performance 0: 表示卡顿,1: 表示流畅,2: 表示非常流程
+     * @example
+     * jessibuca.on("performance",function(performance){console.log("performance is",performance)})
+     */
+    on(event: 'performance', callback: (performance: 0 | 1 | 2) => void): void;
+
+    /**
+     * 录制开始的事件
+
+     * @example
+     * jessibuca.on("recordStart",function(){console.log("record start")})
+     */
+    on(event: 'recordStart', callback: () => void): void;
+
+    /**
+     * 录制结束的事件
+
+     * @example
+     * jessibuca.on("recordEnd",function(){console.log("record end")})
+     */
+    on(event: 'recordEnd', callback: () => void): void;
+
+    /**
+     * 录制的时候,返回的录制时长,1s一次
+
+     * @example
+     * jessibuca.on("recordingTimestamp",function(timestamp){console.log("recordingTimestamp is",timestamp)})
+     */
+    on(event: 'recordingTimestamp', callback: (timestamp: number) => void): void;
+
+    /**
+     * 监听调用play方法 经过 初始化-> 网络请求-> 解封装 -> 解码 -> 渲染 一系列过程的时间消耗
+     * @param event
+     * @param callback
+     */
+    on(event: 'playToRenderTimes', callback: (times: {
+        playInitStart: number, // 1 初始化
+        playStart: number, // 2 初始化
+        streamStart: number, // 3 网络请求
+        streamResponse: number, // 4 网络请求
+        demuxStart: number, // 5 解封装
+        decodeStart: number, // 6 解码
+        videoStart: number, // 7 渲染
+        playTimestamp: number,// playStart- playInitStart
+        streamTimestamp: number,// streamStart - playStart
+        streamResponseTimestamp: number,// streamResponse - streamStart
+        demuxTimestamp: number, // demuxStart - streamResponse
+        decodeTimestamp: number, // decodeStart - demuxStart
+        videoTimestamp: number,// videoStart - decodeStart
+        allTimestamp: number // videoStart - playInitStart
+    }) => void): void
+
+    /**
+     * 监听方法
+     *
+     @example
+
+     jessibuca.on("load",function(){console.log('load')})
+     */
+    on(event: string, callback: Function): void;
+
+}
+
+export default Jessibuca;

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
web_src/static/js/jessibuca/jessibuca.js


Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott