Bladeren bron

change:
1. 新增播放器
2. 修复语音对讲点播异常问题

kindring 1 jaar geleden
bovenliggende
commit
b280a9aabb

+ 4 - 2
package/startDockerImage.sh

@@ -76,7 +76,8 @@ if [ "$enable_sql" == "1" ];then
       -v "$SCRIPT_DIR/mysql":/var/lib/mysql \
       -v "$SCRIPT_DIR/sqlBack":/data/sqlBack \
       --mount type=bind,src="$SCRIPT_DIR/mysqlConf/my.cnf",dst=/etc/mysql/my.cnf \
-      -e MYSQL_ROOT_PASSWORD="$sql_root_passwd" \
+      -e MYSQL_ROOT_PASSWORD="$sql_root_passwd"mysql -u root -p12345678
+       \
       hfysql:latest
     else
       docker run -d --name hfysql \
@@ -170,7 +171,8 @@ if [ "$enable_sql" == "1" ];then
     # 刷新权限
     docker exec -it hfysql mysql \
       -uroot -p"$sql_root_passwd" \
-      -e "FLUSH PRIVILEGES;"
+      -e "
+      FLUSH PRIVILEGES;"
     echo "等待sql重启,等待15秒"
     sleep 15
     reConnect=0

+ 28 - 29
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java

@@ -446,35 +446,34 @@ public class PlayServiceImpl implements IPlayService {
                               Device device,
                               int waitTime,
                               BroadcastCallback callback){
-        logger.warn("[语音广播] 开语音广播 新");
-        JSONObject errJson = new JSONObject();
-        try {
-            cmder.audioBroadcastCmd(device);
-        }catch (InvalidArgumentException | SipException | ParseException e) {
-            logger.error("[命令发送失败] 发送broadcast中 errorMsg: {}", e.getMessage());
-            errJson.put("msg","[命令发送失败] 无法发送broadcast消息");
-            callback.run(2,errJson,null);
-        }
-        logger.warn("等待设备返回invite");
-        HookSubscribeForKey broadcastForInviteHook =  GBHookSubscribeFactory.on_broadcast_invite(device.getDeviceId());
-        // 创建计时器,计时结束未收到invite则自动进行失败处理
-        String timeOutTaskKey = UUID.randomUUID().toString();
-        dynamicTask.startDelay(timeOutTaskKey,()->{
-            // todo 发送 bye 通知给设备?
-            logger.warn("invite超时");
-            errJson.put("msg","等待设备语音invite信息超时");
-            callback.run(1,errJson,null);
-        },waitTime + (1000 * 7));
-
-        GBHookSubscribe.addInviteSubscribe(broadcastForInviteHook,
-        (int code, JSONObject json, SIPRequest request)->{
-            // invite信息返回
-            logger.info("[语音广播] 接收到设备invite信息___订阅事件触发 JSONDATA: {}",json.toJSONString());
-            // 取消计时器
-            dynamicTask.stop(timeOutTaskKey);
-            callback.run(0,json,request);
-        });
-
+            logger.warn("[语音广播] 开语音广播 新");
+            JSONObject errJson = new JSONObject();
+            try {
+                cmder.audioBroadcastCmd(device);
+            } catch (InvalidArgumentException | SipException | ParseException e) {
+                logger.error("[命令发送失败] 发送broadcast中 errorMsg: {}", e.getMessage());
+                errJson.put("msg", "[命令发送失败] 无法发送broadcast消息");
+                callback.run(2, errJson, null);
+            }
+            logger.warn("等待设备返回invite");
+            HookSubscribeForKey broadcastForInviteHook = GBHookSubscribeFactory.on_broadcast_invite(device.getDeviceId());
+            // 创建计时器,计时结束未收到invite则自动进行失败处理
+            String timeOutTaskKey = UUID.randomUUID().toString();
+            dynamicTask.startDelay(timeOutTaskKey, () -> {
+                // todo 发送 bye 通知给设备?
+                logger.warn("invite超时");
+                errJson.put("msg", "等待设备语音invite信息超时");
+                callback.run(1, errJson, null);
+            }, waitTime);
+
+            GBHookSubscribe.addInviteSubscribe(broadcastForInviteHook,
+                    (int code, JSONObject json, SIPRequest request) -> {
+                        // invite信息返回
+                        logger.info("[语音广播] 接收到设备invite信息___订阅事件触发 JSONDATA: {}", json.toJSONString());
+                        // 取消计时器
+                        dynamicTask.stop(timeOutTaskKey);
+                        callback.run(0, json, request);
+                    });
     };
     @Override
     public void onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId) {

+ 104 - 194
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java

@@ -276,95 +276,6 @@ public class PlayController {
 		}
 	}
 
-	@Operation(summary = "语音广播命令")
-	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
-	@Parameter(name = "app", description = "推流app", required = true)
-	@Parameter(name = "stream", description = "音频推流编号", required = true)
-    @GetMapping("/broadcastOld")
-//    @PostMapping("/broadcast/{deviceId}")
-    public DeferredResult<WVPResult<String>> broadcastApi(
-			HttpServletRequest request,
-			@RequestParam String deviceId,
-			@RequestParam String app,
-			@RequestParam String stream) {
-		logger.info("[语音广播] 开始语音广播交互流程");
-
-		// 添加计时器,待事件结束则自动触发超时回复
-		RequestMessage msg = new RequestMessage();
-		String key  = DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId;
-		boolean exist = resultHolder.exist(key, null);
-		msg.setKey(key);
-		String uuid = UUID.randomUUID().toString();
-		msg.setId(uuid);
-		DeferredResult<WVPResult<String>> result = new DeferredResult<>(13*1000l);
-		DeferredResultEx<WVPResult<String>> deferredResultEx = new DeferredResultEx<>(result);
-		HookSubscribeForKey broadcastForInviteHook =  GBHookSubscribeFactory.on_broadcast_invite(deviceId);
-
-		// 判断流是否存在
-		StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(app, stream);
-		if (streamAuthorityInfo == null) {
-			logger.error("webrtc推流未找到");
-			WVPResult wvpResult = new WVPResult();
-			wvpResult.setCode(ErrorCode.ERR_NOTFOUND_STREAM.getCode());
-			wvpResult.setMsg("[广播失败] 无法获取webrtc流信息");
-			msg.setData(wvpResult);
-			resultHolder.invokeAllResult(msg);
-			return result;
-		}
-
-		result.onTimeout(()->{
-			logger.warn("[广播超时] 与设备交互broadcast流程超时,未收到设备invite信息");
-			WVPResult wvpResult = new WVPResult();
-			wvpResult.setCode(ErrorCode.ERROR100.getCode());
-			wvpResult.setMsg("[广播超时] 与设备交互broadcast流程超时,未收到设备invite信息");
-			msg.setData(wvpResult);
-			resultHolder.invokeAllResult(msg);
-			// 超时移除
-			GBHookSubscribe.removeSubscribe(broadcastForInviteHook);
-		});
-		resultHolder.put(key, uuid, result);
-		Device device = storager.queryVideoDevice(deviceId);
-		MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
-		logger.info("[语言广播] 分配的流媒体服务器为 {}",newMediaServerItem.getId());
-		if (newMediaServerItem == null) {
-			logger.warn("[语音广播] 无法连接至ZLM服务器");
-			WVPResult wvpResult = new WVPResult();
-			wvpResult.setCode(ErrorCode.ERROR100.getCode());
-			wvpResult.setMsg("无法连接至流媒体服务器");
-			msg.setData(wvpResult);
-			resultHolder.invokeAllResult(msg);
-		}else{
-			playService.openBroadcast(
-					newMediaServerItem,
-					deviceId,
-					app,
-					stream,
-					broadcastForInviteHook,
-					(int code,String tipMsg)->{
-						if(code == 1){
-							WVPResult wvpResult = new WVPResult();
-							wvpResult.setCode(ErrorCode.ERROR100.getCode());
-							wvpResult.setMsg(tipMsg);
-							msg.setData(wvpResult);
-							// 回复之前所有的点播请求
-							logger.info("语音广播失败,取消invite等待");
-							resultHolder.invokeAllResult(msg);
-						} else if (code == 0) {
-							resultHolder.invokeAllResult(msg);
-							WVPResult wvpResult = new WVPResult();
-							wvpResult.setCode(ErrorCode.SUCCESS.getCode());
-							wvpResult.setMsg("okokokokooo");
-//							wvpResult.setData(ssrcInfo);
-							msg.setData(wvpResult);
-							resultHolder.invokeAllResult(msg);
-						}
-					}, null);
-		}
-
-		return result;
-
-	}
-
 	/**
 	 * 开始语音广播
 	 * @param deviceId
@@ -380,7 +291,8 @@ public class PlayController {
 								   @RequestParam("channelId") String channelId,
 									@RequestParam(value = "waitTime",
 											required = false,
-											defaultValue = "5000") int waitTimeStr			   ) {
+											defaultValue = "5000") int waitTimeStr			   )
+	{
 
 		WVPResult wvpResult = new WVPResult();
 
@@ -499,112 +411,111 @@ public class PlayController {
 			@RequestParam(value = "stream") String stream
 	){
 		logger.info("[语音对讲] web端已经开启推流");
-		RequestMessage msg = new RequestMessage();
-		// 返回invite信息给设备
-		String key  = DeferredResultHolder.CALLBACK_CMD_BROADCAST_INVITE + deviceId;
-		msg.setKey(key);
-		String uuid = UUID.randomUUID().toString();
-		msg.setId(uuid);
-		DeferredResult<WVPResult<String>> result = new DeferredResult<>(10*1000l);
-		WVPResult wvpResult = new WVPResult();
-		Map<String, Object> resultData = new HashMap<>(16);
-		resultHolder.put(key, uuid, result);
+			RequestMessage msg = new RequestMessage();
+			// 返回invite信息给设备
+			String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST_INVITE + deviceId;
+			msg.setKey(key);
+			String uuid = UUID.randomUUID().toString();
+			msg.setId(uuid);
+			DeferredResult<WVPResult<String>> result = new DeferredResult<>((long) waitTime +  1000L);
+			WVPResult wvpResult = new WVPResult();
+			Map<String, Object> resultData = new HashMap<>(16);
+			resultHolder.put(key, uuid, result);
 
-		// 检查设备是否存在
-		Device device = storager.queryVideoDevice(deviceId);
-		if (device == null){
-			// 无法找到设备
-			wvpResult.setCode(ErrorCode.ERROR404.getCode());
-			wvpResult.setMsg("无法找到设备");
-			msg.setData(wvpResult);
-			resultHolder.invokeAllResult(msg);
-		}
+			// 检查设备是否存在
+			Device device = storager.queryVideoDevice(deviceId);
+			if (device == null) {
+				// 无法找到设备
+				wvpResult.setCode(ErrorCode.ERROR404.getCode());
+				wvpResult.setMsg("无法找到设备");
+				msg.setData(wvpResult);
+				resultHolder.invokeAllResult(msg);
+			}
 
-		// 获取对应的媒体服务
-		MediaServerItem mediaServerItem = playService.getNewMediaServerItem(device);
-		logger.info("[语言广播] 分配的流媒体服务器为 {}",mediaServerItem.getId());
-		if (mediaServerItem == null) {
-			logger.warn("[语音广播] 无法连接至ZLM服务器");
-			wvpResult.setCode(ErrorCode.ERR_NOTFOUND_STREAM.getCode());
-			wvpResult.setMsg("无法连接至流媒体服务器");
-			msg.setData(wvpResult);
-			resultHolder.invokeAllResult(msg);
-		}else{
-			playService.openBroadcast(
-				mediaServerItem,
-				device,
-				waitTime,
-				(int code, JSONObject json, SIPRequest request)->{
-					// 0 ok,1 超时,2 异常
-					// invite sdp , message data
-					// request , null
-					if(code == 1){
-						logger.warn("invite超时");
-						wvpResult.setCode(ErrorCode.ERR_TIMEOUT.getCode());
-						wvpResult.setMsg(ErrorCode.ERR_TIMEOUT.getMsg());
-					} else if (code == 2) {
-						wvpResult.setCode(ErrorCode.ERROR100.getCode());
-						wvpResult.setMsg((String) json.get("msg"));
-					} else if (code == 0) {
-						logger.info("收到设备invite信息: {}",json);
-
-						BroadcastItem broadcastItem = new BroadcastItem();
-						broadcastItem.setMediaId(mediaServerItem.getId());
-						broadcastItem.setDeviceId(deviceId);
-						broadcastItem.setApp(app);
-						broadcastItem.setStream(stream);
-						broadcastItem.setRecv_stream("recv_"+stream);
-						broadcastItem.setIpcIp((String) json.get("addr"));
-						broadcastItem.setIpcAudioPort((Integer) json.get("port"));
-						broadcastItem.setSsrc((String) json.get("ssrc"));
-						broadcastItem.setRequest(request);
-						broadcastItem.setAudioFormats((Vector) json.get("audioFormats"));
-						// 获取id
-						playService.broadcast(
-								mediaServerItem,
-								device,
-								broadcastItem,
-								waitTime,
-								(int _code, JSONObject _json, SIPRequest _request)->{
-									// todo 处理并返回语音广播请求,应该有一个ack
-									if(_code == 1){
-										logger.warn("等待设备ack超时");
-										wvpResult.setCode(ErrorCode.ERR_TIMEOUT.getCode());
-										wvpResult.setMsg(ErrorCode.ERR_TIMEOUT.getMsg());
-									} else if (_code == 2) {
-										wvpResult.setCode(ErrorCode.ERROR100.getCode());
-										wvpResult.setMsg((String) _json.get("msg"));
-									} else if (_code == 0) {
-										logger.info("回复 invite 200 成功: {}",_json);
-
-										// 获取id
-										resultData.put("mediaId",mediaServerItem.getId());
-										wvpResult.setCode(ErrorCode.SUCCESS.getCode());
-										wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
-										wvpResult.setData(resultData);
-									}
-									msg.setData(wvpResult);
-									resultHolder.invokeAllResult(msg);
-								}
-						);
-						//存储invite信息和request信息至redis中
-						gbStore.addBroadcastStore(
-								"broadcast_"+deviceId,
-								broadcastItem
-						);
-
-					}
-					msg.setData(wvpResult);
-					resultHolder.invokeAllResult(msg);
+			// 获取对应的媒体服务
+			MediaServerItem mediaServerItem = playService.getNewMediaServerItem(device);
+			logger.info("[语言广播] 分配的流媒体服务器为 {}", mediaServerItem.getId());
+			if (mediaServerItem == null) {
+				logger.warn("[语音广播] 无法连接至ZLM服务器");
+				wvpResult.setCode(ErrorCode.ERR_NOTFOUND_STREAM.getCode());
+				wvpResult.setMsg("无法连接至流媒体服务器");
+				msg.setData(wvpResult);
+				resultHolder.invokeAllResult(msg);
+			} else {
+				playService.openBroadcast(
+						mediaServerItem,
+						device,
+						waitTime,
+						(int code, JSONObject json, SIPRequest request) -> {
+							// 0 ok,1 超时,2 异常
+							// invite sdp , message data
+							// request , null
+							if (code == 1) {
+								logger.warn("invite超时");
+								wvpResult.setCode(ErrorCode.ERR_TIMEOUT.getCode());
+								wvpResult.setMsg(ErrorCode.ERR_TIMEOUT.getMsg());
+							} else if (code == 2) {
+								wvpResult.setCode(ErrorCode.ERROR100.getCode());
+								wvpResult.setMsg((String) json.get("msg"));
+							} else if (code == 0) {
+								logger.info("收到设备invite信息: {}", json);
+
+								BroadcastItem broadcastItem = new BroadcastItem();
+								broadcastItem.setMediaId(mediaServerItem.getId());
+								broadcastItem.setDeviceId(deviceId);
+								broadcastItem.setApp(app);
+								broadcastItem.setStream(stream);
+								broadcastItem.setRecv_stream("recv_" + stream);
+								broadcastItem.setIpcIp((String) json.get("addr"));
+								broadcastItem.setIpcAudioPort((Integer) json.get("port"));
+								broadcastItem.setSsrc((String) json.get("ssrc"));
+								broadcastItem.setRequest(request);
+								broadcastItem.setAudioFormats((Vector) json.get("audioFormats"));
+								// 获取id
+								playService.broadcast(
+										mediaServerItem,
+										device,
+										broadcastItem,
+										waitTime,
+										(int _code, JSONObject _json, SIPRequest _request) -> {
+											// todo 处理并返回语音广播请求,应该有一个ack
+											if (_code == 1) {
+												logger.warn("等待设备ack超时");
+												wvpResult.setCode(ErrorCode.ERR_TIMEOUT.getCode());
+												wvpResult.setMsg(ErrorCode.ERR_TIMEOUT.getMsg());
+											} else if (_code == 2) {
+												wvpResult.setCode(ErrorCode.ERROR100.getCode());
+												wvpResult.setMsg((String) _json.get("msg"));
+											} else if (_code == 0) {
+												logger.info("回复 invite 200 成功: {}", _json);
+
+												// 获取id
+												resultData.put("mediaId", mediaServerItem.getId());
+												wvpResult.setCode(ErrorCode.SUCCESS.getCode());
+												wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
+												wvpResult.setData(resultData);
+											}
+											msg.setData(wvpResult);
+											resultHolder.invokeAllResult(msg);
+										}
+								);
+								//存储invite信息和request信息至redis中
+								gbStore.addBroadcastStore(
+										"broadcast_" + deviceId,
+										broadcastItem
+								);
+
+							}
+							msg.setData(wvpResult);
+							resultHolder.invokeAllResult(msg);
 //					return result;
-				}
-		);
-
-		}
-		// 获取zlm推流端口
-		// todo 回复 invite 200 给设备
+						}
+				);
 
-		return result;
+			}
+			// 获取zlm推流端口
+			// todo 回复 invite 200 给设备
+			return result;
 	}
 
 	@Operation(summary = "停止语音广播")
@@ -612,8 +523,7 @@ public class PlayController {
 	@Parameter(name = "channelId", description = "设备通道编号", required = true)
 	@GetMapping("/stopBroadcast")
 	public WVPResult stopBroadcast(@RequestParam("deviceId") String deviceId,@RequestParam("channelId") String channelId){
-//
-//
+
 		WVPResult wvpResult = new WVPResult();
 //		Device device = storager.queryVideoDevice(deviceId);
 		// 停止音频流,给设备发送bye

File diff suppressed because it is too large
+ 14766 - 1
web_src/package-lock.json


+ 2 - 0
web_src/package.json

@@ -16,7 +16,9 @@
     "echarts": "^4.9.0",
     "element-ui": "^2.15.6",
     "fingerprintjs2": "^2.1.2",
+    "flv.js": "^1.6.2",
     "moment": "^2.29.1",
+    "mpegts.js": "^1.7.3",
     "ol": "^6.14.1",
     "postcss-pxtorem": "^5.1.1",
     "uuid": "^8.3.2",

+ 129 - 0
web_src/src/components/common/flvVideo.vue

@@ -0,0 +1,129 @@
+<template>
+  <div :class="`relative v-parent ${controller?'v-parent-hide':''}` ">
+    <div class="v-video flex justify-center items-center">
+      <video class="h-full" ref="video" ></video>
+<!--      <div class="w-full h-full flex justify-center items-center" v-show="!isPlay">-->
+
+<!--      </div>-->
+    </div>
+    <div v-if="controller" class="v-control bg-red flex items-center">
+      <div class="mx-2">
+        <a-button  @click="play" v-if="isPlay" :disabled="!isPush">播放</a-button>
+        <a-button @click="pause" v-else :disabled="!isPush">暂停</a-button>
+      </div>
+      <a-button @click="replay" >重新加载</a-button>
+      <div class="mx-1 flex">
+        <a-input v-model="flvUrl" allow-clear>
+          <svg-icon class="text-xl" slot="suffix" name="copy" :icon-class="'copy'"  @click.native="copyUrl"  />
+        </a-input>
+        <a-button @click="setFlvUrl(flvUrl)">手动拉流</a-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import flv from 'flv.js';
+import copy from '@/until/copy'
+export default {
+  name: "flvVideo",
+  props:{
+    isPush:{},
+    controller: {default:false},
+  },
+  data(){
+    return {
+      isOk: true,
+      isPlay: false,
+      loading: false,
+      flvUrl: '',
+      videoPlayer: null,
+      tips: '',
+    }
+  },
+  beforeDestroy() {
+    this.destroyVideo();
+  },
+  methods:{
+    copyUrl(){
+      copy(this.flvUrl).then(()=>{
+        this.$message.success('复制成功');
+      }).catch(err=>{
+        this.$message.error('复制失败,请手动尝试复制');
+      })
+    },
+    setFlvUrl(flvUrl){
+      this.flvUrl = flvUrl
+      this.initVideo()
+    },
+    // 初始化视频播放组件
+    initVideo(){
+      //清除
+      if(!this.flvUrl){
+        return this.$message.warn('请先进行推流')
+      }
+      this.isPlay = false;
+      this.destroyVideo();
+      // 设置直播地址
+      let flvPlayer = flv.createPlayer({
+        url:this.flvUrl,
+        type: 'flv'
+      })
+      let videoEl = this.$refs.video;
+      // console.log(videoEl);
+      flvPlayer.attachMediaElement(videoEl);
+      flvPlayer.load();
+      flvPlayer.play();
+      flvPlayer.on(flv.Events.ERROR,(err)=>{
+        console.log('---------------------------');
+        console.log('------------err---------------');
+        console.log('err');
+        console.log(err);
+        this.$emit('error',err);
+      })
+      this.videoPlayer = flvPlayer;
+    },
+    destroyVideo(){
+      if(this.videoPlayer){
+        this.videoPlayer.destroy();
+      }
+    },
+    play(url){
+      if(url){
+        this.setFlvUrl(url);
+      }else{
+        this.isPlay = true;
+        this.videoPlayer.play();
+      }
+    },
+    pause(){
+      this.videoPlayer.pause();
+    },
+    replay(){
+      this.initVideo();
+    }
+  }
+}
+</script>
+
+<style scoped >
+.v-parent .v-video {
+  width: 100%;
+  height: 100%;
+}
+.v-parent .v-control {
+  width: 100%;
+  height: 0;
+  overflow: hidden;
+  background-color: rgba(0, 0, 0, 0.3);
+}
+.v-parent-hide:hover .v-video {
+  height: calc(100% - 50px);
+}
+.v-parent-hide:hover .v-control {
+  height: 50px;
+}
+
+
+
+</style>

+ 130 - 0
web_src/src/components/common/mpegtsVideo.vue

@@ -0,0 +1,130 @@
+<template>
+  <div :class="`relative v-parent ${controller?'v-parent-hide':''}` ">
+    <div class="v-video flex justify-center items-center">
+      <video class="h-full" ref="video" ></video>
+      <!--      <div class="w-full h-full flex justify-center items-center" v-show="!isPlay">-->
+
+      <!--      </div>-->
+    </div>
+    <div v-if="controller" class="v-control bg-red flex items-center">
+      <div class="mx-2">
+        <a-button  @click="play" v-if="isPlay" :disabled="!isPush">播放</a-button>
+        <a-button @click="pause" v-else :disabled="!isPush">暂停</a-button>
+      </div>
+      <a-button @click="replay" >重新加载</a-button>
+      <div class="mx-1 flex">
+        <a-input v-model="playUrl" allow-clear>
+          <svg-icon class="text-xl" slot="suffix" name="copy" :icon-class="'copy'"  @click.native="copyUrl"  />
+        </a-input>
+        <a-button @click="setplayUrl(playUrl)">手动拉流</a-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import mpegts from 'mpegts.js';
+import copy from '@/until/copy'
+export default {
+  name: "mpegTsVideo",
+  props:{
+    isPush:{},
+    controller: {default:false},
+  },
+  data(){
+    return {
+      isOk: true,
+      isPlay: false,
+      loading: false,
+      playUrl: '',
+      videoPlayer: null,
+      tips: '',
+    }
+  },
+  beforeDestroy() {
+    this.destroyVideo();
+  },
+  methods:{
+    copyUrl(){
+      copy(this.playUrl).then(()=>{
+        this.$message.success('复制成功');
+      }).catch(err=>{
+        this.$message.error('复制失败,请手动尝试复制');
+      })
+    },
+    setPlayUrl(playUrl){
+      this.playUrl = playUrl
+      this.initVideo()
+    },
+    // 初始化视频播放组件
+    initVideo(){
+      //清除
+      if(!this.playUrl){
+        return this.$message.warn('请先进行推流')
+      }
+      this.isPlay = false;
+      this.destroyVideo();
+      // 设置直播地址
+      let player = mpegts.createPlayer({
+        type: 'mse',  // could also be mpegts, m2ts, flv
+        isLive: true,
+        url: this.playUrl
+      });
+      let videoEl = this.$refs.video;
+      // console.log(videoEl);
+      player.attachMediaElement(videoEl);
+      player.load();
+      player.play();
+      player.on(mpegts.Events.ERROR,(err)=>{
+        console.log('---------------------------');
+        console.log('------------err---------------');
+        console.log('err');
+        console.log(err);
+        this.$emit('error',err);
+      })
+      this.videoPlayer = player;
+    },
+    destroyVideo(){
+      if(this.videoPlayer){
+        this.videoPlayer.destroy();
+      }
+    },
+    play(url){
+      if(url){
+        this.setPlayUrl(url);
+      }else{
+        this.isPlay = true;
+        this.videoPlayer.play();
+      }
+    },
+    pause(){
+      this.videoPlayer.pause();
+    },
+    replay(){
+      this.initVideo();
+    }
+  }
+}
+</script>
+
+<style scoped >
+.v-parent .v-video {
+  width: 100%;
+  height: 100%;
+}
+.v-parent .v-control {
+  width: 100%;
+  height: 0;
+  overflow: hidden;
+  background-color: rgba(0, 0, 0, 0.3);
+}
+.v-parent-hide:hover .v-video {
+  height: calc(100% - 50px);
+}
+.v-parent-hide:hover .v-control {
+  height: 50px;
+}
+
+
+
+</style>

+ 119 - 40
web_src/src/components/dialog/customPlayer.vue

@@ -1,8 +1,9 @@
 <template>
-  <div id="customPlayer" v-loading="isLoging">
+  <div id="customPlayer" v-loading="isLoading">
     <el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" @close="closeHandle()">
       <div style="width: 100%; height: 100%">
-        <rtc-player ref="webRTC" @eventCallback="playEventHandle" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></rtc-player>
+        <rtc-player ref="webRTC" v-if="useActivePlayer === 'webRTC'" @eventCallback="playEventHandle"  :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></rtc-player>
+        <mpeg-ts-video ref="flv" v-if="useActivePlayer === 'flv'" ></mpeg-ts-video>
       </div>
       <div id="shared" style="text-align: right; margin-top: 1rem;">
         <el-tabs v-model="tabActiveName" @tab-click="tabHandleClick" >
@@ -36,6 +37,20 @@
             </div>
 
           </el-tab-pane>
+          <el-tab-pane label="更多选项" name="play">
+            <div class="contentLine">
+              <span class="contentLabel">
+                播放器选择:
+              </span>
+              <el-radio-group v-model="activePlayer" size="mini" @change="switchPlayerHandle" >
+                <el-tooltip v-for="player in playerList" :key="player.key" :content="player.description" placement="top">
+                  <el-radio-button  :label="player.key" border >
+                    {{ player.text }}
+                  </el-radio-button>
+                </el-tooltip>
+              </el-radio-group>
+            </div>
+          </el-tab-pane>
         </el-tabs>
       </div>
     </el-dialog>
@@ -46,10 +61,14 @@
 import rtcPlayer from '../dialog/rtcPlayer.vue'
 import PtzControl from "@/components/common/ptzControl";
 import {webrtcEvent} from "@/map/eventMap";
+// import FlvVideo from "@/components/common/flvVideo.vue";
+import mpegTsVideo from "@/components/common/mpegtsVideo.vue"
 export default {
   name: "customPlayer",
   components: {
-    PtzControl, rtcPlayer,
+    mpegTsVideo,
+    PtzControl,
+    rtcPlayer,
   },
   computed: {
     getPlayerShared: function () {
@@ -70,10 +89,28 @@ export default {
     return {
       video: '',
       videoUrl: '',
-      activePlayer: "webRTC",
+
+      // 播放器列表
+      playerList: [
+        {
+          key: "webRTC",
+          text: "webrtc 播放器",
+          description: "延迟较低的播放器,需要浏览器支持",
+          disable: false
+        },
+        {
+          key: "flv",
+          text: "flv 播放器",
+          description: "用于播放flv格式视频,可支持aac音频",
+          disable: false
+        }
+      ],
+      activePlayer: "",
+      useActivePlayer: "",
       // 如何你只是用一种播放器,直接注释掉不用的部分即可
       player: {
         webRTC: ["rtc", "rtcs"],
+        flv: ["flv", "https_flv"],
       },
       videoHistory: {
         date: '',
@@ -90,8 +127,8 @@ export default {
       hasAudio: false,
       loadingRecords: false,
       recordsLoading: false,
-      isLoging: false,
-      controSpeed: 30,
+      isLoading: false,
+      controlSpeed: 30,
       timeVal: 0,
       timeMin: 0,
       timeMax: 1440,
@@ -106,7 +143,7 @@ export default {
       tracksLoading: false,
       recordPlay: "",
       showPtz: true,
-      showRrecord: true,
+      showRecord: true,
       tracksNotLoaded: false,
       sliderTime: 0,
       seekTime: 0,
@@ -114,39 +151,46 @@ export default {
       showTimeText: "00:00:00",
       streamInfo: null,
       enableDebug: true,
-
+      playEventHandleTimer: null,
     };
   },
+  beforeMount() {
+    this.activePlayer = this.playerList[0].key;
+    this.useActivePlayer = this.activePlayer;
+  },
   mounted() {
     // this.showVideoDialog = true;
   },
   methods: {
+    switchPlayerHandle(){
+      let activePlayerKey = this.activePlayer;
+      if(this.useActivePlayer === activePlayerKey){
+        return this.$message.warning("无法切换至同播放器");
+      }
+      console.log('切换播放器');
+      // 暂停旧播放器视频
+      this.$refs[this.useActivePlayer].pause();
+
+      this.$nextTick(()=>{
+        // 使用新播放器打开视频
+        this.useActivePlayer = activePlayerKey;
+        this.play();
+      })
+
+    },
     playEventHandle(type,e){
       // 添加同类型防抖
-
       if(this.enableDebug){
         console.log('playEventHandle');
         console.log(type,e);
       }
-      if(type === webrtcEvent.apiFail.code){
-        this.$notify.error({
-          title: 'ZLM连接失败',
-          dangerouslyUseHTMLString: true,
-          message: `<span>连接zlm服务失败${e.message}</span> <br/>
-                        <a href="${this.videoUrl}" target="_blank"><span>手动访问</span></a>`,
-          duration: 0
-        });
-        this.videoError = true;
-        this.videoUrl = '';
-
-      }else if(type === webrtcEvent.played.code){
-        this.$message.success('播放成功');
-      }else if(type === webrtcEvent.sdpFail.code){
-        this.$notify.error({
-          title: 'sdp 交互失败',
-          duration: 4500
-        })
+      if(this.playEventHandleTimer){
+        clearTimeout(this.playEventHandleTimer);
       }
+      this.playEventHandleTimer = setTimeout(()=>{
+        this.webrtcEventExecute(type);
+      },300);
+
     },
 
     tabHandleClick: function(tab, event) {
@@ -180,11 +224,17 @@ export default {
       console.log("播放器错误:" + JSON.stringify(e));
     },
     play: function (streamInfo, hasAudio) {
+      // 读取缓存
+      if(!streamInfo && this.streamInfo){
+        streamInfo = this.streamInfo;
+        hasAudio = this.hasAudio;
+      }
       this.streamInfo = streamInfo;
       this.hasAudio = hasAudio;
-      this.isLoging = false;
+      this.isLoading = false;
       // this.videoUrl = streamInfo.rtc;
       this.videoUrl = this.getUrlByStreamInfo(streamInfo);
+      console.log(this.videoUrl);
       this.streamId = streamInfo.stream;
       this.app = streamInfo.app;
       this.mediaServerId = streamInfo.mediaServerId;
@@ -196,9 +246,8 @@ export default {
       this.videoUrl = this.getUrlByStreamInfo(streamInfo);
       console.log(this.videoUrl);
       this.$nextTick(()=>{
-        this.$refs[this.activePlayer].play(this.videoUrl,this.enableDebug)
+        this.$refs[this.useActivePlayer].play(this.videoUrl,this.enableDebug)
       });
-
     },
     openDialog: function (tab, deviceId, channelId, param) {
       if (this.showVideoDialog) {
@@ -212,8 +261,8 @@ export default {
       this.app = "";
       this.videoUrl = ""
       this.$nextTick(()=>{
-      if (!!this.$refs[this.activePlayer]) {
-        this.$refs[this.activePlayer].pause();
+      if (!!this.$refs[this.useActivePlayer]) {
+        this.$refs[this.useActivePlayer].pause();
       }
       this.play(param.streamInfo, param.hasAudio)
       switch (tab) {
@@ -227,7 +276,7 @@ export default {
           break;
         case "streamPlay":
           this.tabActiveName = "media";
-          this.showRrecord = false;
+          this.showRecord = false;
           this.showPtz = false;
           this.play(param.streamInfo, param.hasAudio)
           break;
@@ -239,20 +288,20 @@ export default {
     getUrlByStreamInfo(){
       console.log("获取基础拉流地址")
       console.log(this.streamInfo)
+      let playerData = this.player[this.useActivePlayer];
       if (location.protocol === "https:") {
-        this.videoUrl = this.streamInfo[this.player[this.activePlayer][1]]
+        this.videoUrl = this.streamInfo[playerData[1]]
       }else {
-        this.videoUrl = this.streamInfo[this.player[this.activePlayer][0]]
+        this.videoUrl = this.streamInfo[playerData[0]]
       }
       console.log(this.videoUrl)
       return this.videoUrl;
-
     },
     coverPlay: function () {
       let that = this;
       this.coverPlaying = true;
       this.$nextTick(()=>{
-        this.$refs[this.activePlayer].pause()
+        this.$refs[this.useActivePlayer].pause()
         that.$axios({
           method: 'post',
           url: '/api/play/convert/' + that.streamId
@@ -260,11 +309,11 @@ export default {
           if (res.data.code === 0) {
             that.convertKey = res.data.key;
             setTimeout(()=>{
-              that.isLoging = false;
+              that.isLoading = false;
               that.playFromStreamInfo(false, res.data.data);
             }, 2000)
           } else {
-            that.isLoging = false;
+            that.isLoading = false;
             that.coverPlaying = false;
             that.$message({
               showClose: true,
@@ -286,11 +335,33 @@ export default {
     },
     convertStopClick: function() {
       this.convertStop(()=>{
-        this.$refs[this.activePlayer].play(this.videoUrl)
+        this.$refs[this.useActivePlayer].play(this.videoUrl)
       });
     },
     closeHandle(){
       this.$emit('close')
+    },
+    webrtcEventExecute(type){
+      // 防抖,防止多次触发
+      if(type === webrtcEvent.apiFail.code){
+        this.$notify.error({
+          title: 'ZLM连接失败',
+          dangerouslyUseHTMLString: true,
+          message: `<span>连接zlm服务失败${e.message}</span> <br/>
+                        <a href="${this.videoUrl}" target="_blank"><span>手动访问</span></a>`,
+          duration: 0
+        });
+        this.videoError = true;
+        this.videoUrl = '';
+
+      }else if(type === webrtcEvent.played.code){
+        this.$message.success('播放成功');
+      }else if(type === webrtcEvent.sdpFail.code){
+        this.$notify.error({
+          title: 'sdp 交互失败',
+          duration: 4500
+        })
+      }
     }
   }
 }
@@ -482,4 +553,12 @@ export default {
   width: 80%;
   padding: 0 10%;
 }
+.contentLine{
+  display: flex;
+  align-items: center;
+  margin-top: 5px;
+}
+.contentLine .contentLabel{
+  margin-right: 5px;
+}
 </style>

+ 30 - 0
web_src/src/until/copy.js

@@ -0,0 +1,30 @@
+
+
+export default function (content){
+    return new Promise((resolve,reject)=>{
+        // 判断是否支持 clipboard api 复制
+        try {
+            if (navigator.clipboard) {
+                navigator.clipboard.writeText(content).then(resolve).catch(reject);
+            } else {
+                let textarea = document.createElement('textarea');
+                // 隐藏此输入框
+                textarea.style.position = 'fixed';
+                textarea.style.opacity = '0';
+                textarea.style.top = '20px';
+                document.body.appendChild(textarea);
+                // 赋值
+                textarea.value = content;
+                // 选中
+                textarea.select();
+                // 复制
+                document.execCommand('copy', true);
+                // 移除输入框
+                document.body.removeChild(textarea);
+            }
+            resolve('ok');
+        } catch (error) {
+            reject(error);
+        }
+    })
+}

Some files were not shown because too many files changed in this diff