| | |
| | | <meta charset="UTF-8"> |
| | | <title>测试页</title> |
| | | <script th:src="@{/js/jquery-3.6.4.min.js}"></script> |
| | | <script th:src="@{/js/adapter.min.js}"></script> |
| | | <script th:src="@{/js/webrtcstreamer.js}"></script> |
| | | <link rel="stylesheet" th:href="@{/css/bootstrap.css}"/> |
| | | <script th:src="@{/js/bootstrap.js}"></script> |
| | | <style> |
| | | .top-buffer { |
| | | margin-top: 10px; |
| | | } |
| | | .container { |
| | | border: 2px solid #1b6d85; |
| | | padding: 15px; |
| | | } |
| | | </style> |
| | | <body> |
| | | <div class="container"> |
| | | <div class="row "> |
| | | <div class="dropdown"> |
| | | 相机id:<select id="select"> |
| | | </select> |
| | | </div> |
| | | </div> |
| | | <div class="row top-buffer"> |
| | | <div class="col-md-1 col-md-offset-1"> |
| | | <button id="up" type="button" class="btn btn-primary">上</button> |
| | | </div> |
| | | <div class="col-md-4 col-md-offset-3"> |
| | | <div class="btn-group" role="group"> |
| | | <button id="controlZoomIn" type="button" class="btn btn-primary">调焦-</button> |
| | | <button id="controlZoomOut" type="button" class="btn btn-primary">调焦+</button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="row "> |
| | | <div class="col-md-1"> |
| | | <button id="left" type="button" class="btn btn-primary">左</button> |
| | | </div> |
| | | <div class="col-md-1 col-md-offset-1"> |
| | | <button id="right" type="button" class="btn btn-primary">右</button> |
| | | </div> |
| | | <div class="col-md-4 col-md-offset-2"> |
| | | <div class="btn-group" role="group"> |
| | | <button id="controlFocusNear" type="button" class="btn btn-primary">聚焦-</button> |
| | | <button id="controlFocusFar" type="button" class="btn btn-primary">聚焦+</button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="row "> |
| | | <div class="col-md-1 col-md-offset-1"> |
| | | <button id="down" type="button" class="btn btn-primary">下</button> |
| | | </div> |
| | | <div class="col-md-4 col-md-offset-3"> |
| | | <div class="btn-group" role="group"> |
| | | <button id="controlIrisOpen" type="button" class="btn btn-primary">光圈-</button> |
| | | <button id="controlIrisClose" type="button" class="btn btn-primary">光圈+</button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="row"> |
| | | <div class="col-md-6"> |
| | | <div class="row top-buffer"> |
| | | <div class="input-group"> |
| | | <span class="input-group-addon">目的坐标值:</span> |
| | | <input id="targetPostion" class="form-control" placeholder="目的坐标"/> |
| | | <button id="setTargetPostion" type="button" class="btn btn-default">指向坐标</button> |
| | | </div> |
| | | <div class="input-group"> |
| | | <span class="input-group-addon">P值:</span> |
| | | <input id="p" class="form-control" placeholder="请输入P值"/> |
| | | </div> |
| | | <div class="input-group"> |
| | | <span class="input-group-addon">T值:</span> |
| | | <input id="t" class="form-control" placeholder="请输入T值"/> |
| | | </div> |
| | | <div class="input-group"> |
| | | <span class="input-group-addon">Z值:</span> |
| | | <input id="z" class="form-control" placeholder="请输入Z值"/> |
| | | </div> |
| | | </div> |
| | | <div class="row top-buffer"> |
| | | <div class="btn-group" role="group"> |
| | | <button id="getPTZ" type="button" class="btn btn-default">获取ptz</button> |
| | | <button id="setPTZ" type="button" class="btn btn-default">设置ptz</button> |
| | | <button id="setPreset" type="button" class="btn btn-default">设预置点</button> |
| | | <button id="gotoPreset" type="button" class="btn btn-default">调预置点</button> |
| | | <button id="getZeroPTZ" type="button" class="btn btn-default">调用零方位角</button> |
| | | <button id="setZeroPTZ" type="button" class="btn btn-default">设置零方位角</button> |
| | | </div> |
| | | </div> |
| | | <div class="row top-buffer"> |
| | | <div class="btn-group" role="group"> |
| | | <button id="FocusMode" type="button" class="btn btn-default">手动聚焦</button> |
| | | <div id="focusDiv" class="input-group"> |
| | | <span class="input-group-addon">聚焦值:</span> |
| | | <input id="focus" class="form-control" placeholder="聚焦值"/> |
| | | </div> |
| | | <button id="getFocusPos" type="button" class="btn btn-default">获取聚焦值</button> |
| | | <button id="setFocusPos" type="button" class="btn btn-default">设置聚焦值</button> |
| | | </div> |
| | | </div> |
| | | <div class="row top-buffer"> |
| | | <div class="btn-group" role="group"> |
| | | <button id="WiperPwron" type="button" class="btn btn-default">开启雨刷</button> |
| | | <button id="Defogcfg" type="button" class="btn btn-default">开启透雾</button> |
| | | <button id="Infrarecfg" type="button" class="btn btn-default">开启红外</button> |
| | | <button id="HeateRpwron" type="button" class="btn btn-default">开启云台加热</button> |
| | | <button id="CameraDeicing" type="button" class="btn btn-default">开启镜头加热</button> |
| | | </div> |
| | | </div> |
| | | <div class="row top-buffer"> |
| | | <div class="btn-group" role="group"> |
| | | <button id="voice" type="button" class="btn btn-default">开始语音对讲</button> |
| | | <button id="record" type="button" class="btn btn-default">开始录像</button> |
| | | <button id="realCutPic" type="button" class="btn btn-default">实时抓图</button> |
| | | <button id="saveCutPic" type="button" class="btn btn-default">存储抓图</button> |
| | | </div> |
| | | </div> |
| | | <div class="row top-buffer"> |
| | | <div class="col-md-6"> |
| | | <img class="thumbnail" id="imgContainer" style="width: 500px; height: 300px;"/> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="col-md-1"/> |
| | | <div class="col-md-5"> |
| | | <div class="row top-buffer"> |
| | | <video id="video" muted autoplay loop controls style="width: 800px; height: 100%; object-fit: fill;"/> |
| | | 设备:<select id="selectDev" style="width: 330px;"> |
| | | </select> |
| | | </div> |
| | | <div class="row top-buffer"> |
| | | 通道:<select id="selectChn" style="width: 330px;"> |
| | | </select> |
| | | </div> |
| | | <div class="row top-buffer"> |
| | | <div class="col-md-1 col-md-offset-1"> |
| | | <button id="up" type="button" class="btn btn-primary">上</button> |
| | | </div> |
| | | <div class="col-md-6 col-md-offset-2"> |
| | | <div class="btn-group" role="group"> |
| | | <button id="controlZoomIn" type="button" class="btn btn-primary">调焦-</button> |
| | | <button id="controlZoomOut" type="button" class="btn btn-primary">调焦+</button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="row "> |
| | | <div class="col-md-1"> |
| | | <button id="left" type="button" class="btn btn-primary">左</button> |
| | | </div> |
| | | <div class="col-md-1 col-md-offset-1"> |
| | | <button id="right" type="button" class="btn btn-primary">右</button> |
| | | </div> |
| | | <div class="col-md-6 col-md-offset-1"> |
| | | <div class="btn-group" role="group"> |
| | | <button id="controlFocusNear" type="button" class="btn btn-primary">聚焦-</button> |
| | | <button id="controlFocusFar" type="button" class="btn btn-primary">聚焦+</button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="row "> |
| | | <div class="col-md-1 col-md-offset-1"> |
| | | <button id="down" type="button" class="btn btn-primary">下</button> |
| | | </div> |
| | | <div class="col-md-6 col-md-offset-2"> |
| | | <div class="btn-group" role="group"> |
| | | <button id="controlIrisOpen" type="button" class="btn btn-primary">光圈-</button> |
| | | <button id="controlIrisClose" type="button" class="btn btn-primary">光圈+</button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="row "> |
| | | <div class="col-md-10"> |
| | | <div class="row top-buffer"> |
| | | <div class="input-group"> |
| | | <span class="input-group-addon">目的坐标值:</span> |
| | | <input id="targetPostion" class="form-control" placeholder="目的坐标"/> |
| | | <button id="setTargetPostion" type="button" class="btn btn-default">指向坐标</button> |
| | | </div> |
| | | <div class="input-group"> |
| | | <span class="input-group-addon">P值:</span> |
| | | <input id="p" class="form-control" placeholder="请输入P值"/> |
| | | </div> |
| | | <div class="input-group"> |
| | | <span class="input-group-addon">T值:</span> |
| | | <input id="t" class="form-control" placeholder="请输入T值"/> |
| | | </div> |
| | | <div class="input-group"> |
| | | <span class="input-group-addon">Z值:</span> |
| | | <input id="z" class="form-control" placeholder="请输入Z值"/> |
| | | </div> |
| | | </div> |
| | | <div class="row top-buffer"> |
| | | <div class="btn-group" role="group"> |
| | | <button id="getPTZ" type="button" class="btn btn-default">获取ptz</button> |
| | | <button id="setPTZ" type="button" class="btn btn-default">设置ptz</button> |
| | | <button id="setPreset" type="button" class="btn btn-default">设预置点</button> |
| | | <button id="gotoPreset" type="button" class="btn btn-default">调预置点</button> |
| | | <button id="setZeroPTZ" type="button" class="btn btn-default">设置零方位角</button> |
| | | </div> |
| | | </div> |
| | | <div class="row top-buffer"> |
| | | <div class="btn-group" role="group"> |
| | | <button id="FocusMode" type="button" class="btn btn-default">手动聚焦</button> |
| | | <div id="focusDiv" class="input-group"> |
| | | <span class="input-group-addon">聚焦值:</span> |
| | | <input id="focus" class="form-control" placeholder="聚焦值"/> |
| | | </div> |
| | | <button id="getFocusPos" type="button" class="btn btn-default">获取聚焦值</button> |
| | | <button id="setFocusPos" type="button" class="btn btn-default">设置聚焦值</button> |
| | | </div> |
| | | </div> |
| | | <div class="row top-buffer"> |
| | | <div class="btn-group" role="group"> |
| | | <button id="WiperPwron" type="button" class="btn btn-default">开启雨刷</button> |
| | | <button id="Defogcfg" type="button" class="btn btn-default">开启透雾</button> |
| | | <button id="Infrarecfg" type="button" class="btn btn-default">开启红外</button> |
| | | <button id="HeateRpwron" type="button" class="btn btn-default">开启云台加热</button> |
| | | <button id="CameraDeicing" type="button" class="btn btn-default">开启镜头加热</button> |
| | | </div> |
| | | </div> |
| | | <div class="row top-buffer"> |
| | | <div class="btn-group" role="group"> |
| | | <button id="voice" type="button" class="btn btn-default">开始语音对讲</button> |
| | | <button id="record" type="button" class="btn btn-default">开始录像</button> |
| | | <button id="saveCutPic" type="button" class="btn btn-default">存储抓图</button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="col-md-6"> |
| | | <div class="row"> |
| | | <div class="row top-buffer"> |
| | | <button id="preview" type="button" class="btn btn-primary">预览</button> |
| | | <button id="previewStop" type="button" class="btn btn-primary">停止</button> |
| | | </div> |
| | | <div class="row top-buffer"> |
| | | <video id="video" muted autoplay loop controls |
| | | style="width: 100%; height: 360px; object-fit: fill; border: 2px solid #3498db;"/> |
| | | </div> |
| | | <div class="row"> |
| | | <img class="thumbnail" id="imgContainer" |
| | | style="width: 100%; height: 360px; border: 2px solid #3498db;"/> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <script th:inline="javascript"> |
| | | var cameraId, opt, token; |
| | | var cameraId, chanNo,opt, optOpen, optClose, token; |
| | | window.onload = function () { |
| | | $.ajax({ |
| | | url: "../hik/list", |
| | | type: "get", |
| | | success: function (data) { |
| | | console.log(data); |
| | | var arr = data.data; |
| | | for (var i = 0; i < arr.length; i++) { |
| | | console.log(arr[i].id); |
| | | var camera = { |
| | | type: arr[i].gdtype, |
| | | ipaddr: arr[i].ip, |
| | | username: arr[i].username, |
| | | password: arr[i].password, |
| | | port: arr[i].rtspPort, |
| | | longitude: arr[i].longitude, |
| | | latitude: arr[i].latitude, |
| | | altitude: arr[i].altitude |
| | | }; |
| | | cameraMap.set(arr[i].id, camera); |
| | | //先创建好select里面的option元素 |
| | | var option = document.createElement("option"); |
| | | //给option的text赋值,这就是你点开下拉框能够看到的东西 |
| | | $(option).text(arr[i].id); |
| | | //获取select 下拉框对象,并将option添加进select |
| | | $('#select').append(option); |
| | | } |
| | | } |
| | | }) |
| | | |
| | | console.log(RTCRtpReceiver.getCapabilities('video').codecs) |
| | | opt = {"username": "admin", "password": "admin123"}; |
| | | $.ajax({ |
| | | headers: { |
| | |
| | | dataType: "json", |
| | | data: JSON.stringify(opt), |
| | | success: function (data) { |
| | | console.log(data); |
| | | token = data.token; |
| | | getDeviceList();// 获取设备列表 |
| | | } |
| | | }) |
| | | // 初始化内容 |
| | | console.log(cameraMap); |
| | | } |
| | | //云台上下左右 |
| | | $("#up").mousedown(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 2; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | |
| | | }) |
| | | $("#up").mouseup(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 2; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#down").mousedown(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 8; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#down").mouseup(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 8; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#left").mousedown(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 4; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#left").mouseup(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 4; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#right").mousedown(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 6; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#right").mouseup(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 6; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | //变倍 |
| | | $("#controlZoomIn").mousedown(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 10; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlZoomIn").mouseup(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 10; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlZoomOut").mousedown(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 11; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlZoomOut").mouseup(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 11; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | //变焦 |
| | | $("#controlFocusNear").mousedown(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 12; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlFocusNear").mouseup(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 12; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlFocusFar").mousedown(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 13; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlFocusFar").mouseup(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 13; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | //光圈 |
| | | $("#controlIrisOpen").mousedown(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 14; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlIrisOpen").mouseup(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 14; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlIrisClose").mousedown(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 15; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlIrisClose").mouseup(function () { |
| | | var url = "../hik/PTZControlWithSpeed"; |
| | | var code = 15; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | //获取设备 |
| | | function getDeviceList() { |
| | | $.ajax({ |
| | | url: "../cameraSdk/list", |
| | | type: "get", |
| | | success: function (data) { |
| | | console.log(data); |
| | | var arr = data.data; |
| | | for (var i = 0; i < arr.length; i++) { |
| | | console.log(arr[i].id); |
| | | console.log(arr[i].name) |
| | | var camera = { |
| | | name: arr[i].name, |
| | | factory: arr[i].factory, |
| | | ipaddr: arr[i].ip, |
| | | username: arr[i].username, |
| | | password: arr[i].password, |
| | | port: arr[i].rtspPort, |
| | | longitude: arr[i].longitude, |
| | | latitude: arr[i].latitude, |
| | | altitude: arr[i].altitude |
| | | }; |
| | | cameraMap.set(arr[i].id, camera); |
| | | //先创建好select里面的option元素 |
| | | var option = $("<option>"); |
| | | //给option的text赋值,这就是你点开下拉框能够看到的东西 |
| | | $(option).val(arr[i].id); |
| | | $(option).text(arr[i].name); |
| | | //获取select 下拉框对象,并将option添加进select |
| | | $('#selectDev').append(option); |
| | | } |
| | | $("#selectDev").trigger("change"); |
| | | } |
| | | }) |
| | | } |
| | | |
| | | $("#setPreset").click(function () { |
| | | cameraId = $('#select option:selected').val(); |
| | | opt = {"cameraId": cameraId, "channelNum": 1, "speed": 8, "presetIndex": 1}; |
| | | //选择设备 |
| | | $("#selectDev").change(function () { |
| | | // 在这里处理选择事件 |
| | | var cameraId = $(this).find("option:selected").val(); |
| | | var name = $(this).find("option:selected").text(); |
| | | getChannelList(cameraId); |
| | | console.log("选择了:" + cameraId + "---" + name); |
| | | }); |
| | | |
| | | //获取通道 |
| | | function getChannelList(cameraId) { |
| | | console.log(cameraId) |
| | | var myEntity = { |
| | | deviceId: cameraId, |
| | | pageNum: 1, |
| | | pageSize: 64 |
| | | } |
| | | var queryString = $.param(myEntity); |
| | | $.ajax({ |
| | | headers: { |
| | | 'Accept': 'application/json', |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/setPreset", |
| | | url: "../device/channel/list?" + queryString, |
| | | type: "get", |
| | | success: function (data) { |
| | | console.log(data); |
| | | var arr = data.rows; |
| | | $('#selectChn').empty(); |
| | | for (var i = 0; i < arr.length; i++) { |
| | | console.log(arr[i].chanNo); |
| | | console.log(arr[i].name); |
| | | //先创建好select里面的option元素 |
| | | var option = document.createElement("option"); |
| | | //给option的text赋值,这就是你点开下拉框能够看到的东西 |
| | | $(option).text(arr[i].name); |
| | | $(option).val(arr[i].chanNo); |
| | | //获取select 下拉框对象,并将option添加进select |
| | | $('#selectChn').append(option); |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | |
| | | //预览 |
| | | $('#preview').click(() => { |
| | | var cameraId = $('#selectDev option:selected').val(); |
| | | var chanNo = $('#selectChn option:selected').val(); |
| | | console.log(cameraId + " " + chanNo) |
| | | $.ajax({ |
| | | headers: { |
| | | 'Accept': 'application/json', |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../vtdu/media/" + cameraId + "_" + chanNo, |
| | | type: "get", |
| | | dataType: "json", |
| | | success: function (data) { |
| | | realView(data.data.webrtcUrl + "/", "video"); |
| | | } |
| | | }) |
| | | }); |
| | | //停止 |
| | | $('#previewStop').click(() => { |
| | | webrtcClient.stop(); |
| | | }); |
| | | //云台上下左右 |
| | | $("#up").mousedown(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 2; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | |
| | | }) |
| | | $("#up").mouseup(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 2; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#down").mousedown(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 8; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#down").mouseup(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 8; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#left").mousedown(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 4; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#left").mouseup(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 4; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#right").mousedown(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 6; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#right").mouseup(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 6; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | //变倍 |
| | | $("#controlZoomIn").mousedown(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 10; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlZoomIn").mouseup(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 10; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlZoomOut").mousedown(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 11; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlZoomOut").mouseup(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 11; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | //变焦 |
| | | $("#controlFocusNear").mousedown(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 12; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlFocusNear").mouseup(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 12; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlFocusFar").mousedown(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 13; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlFocusFar").mouseup(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 13; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | //光圈 |
| | | $("#controlIrisOpen").mousedown(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 14; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlIrisOpen").mouseup(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 14; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlIrisClose").mousedown(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 15; |
| | | var enable = true; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | $("#controlIrisClose").mouseup(function () { |
| | | var url = "../cameraSdk/PTZControlWithSpeed"; |
| | | var code = 15; |
| | | var enable = false; |
| | | commondMethod(url, code, enable); |
| | | }) |
| | | |
| | | $("#setPreset").click(function () { |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | opt = {"cameraId": cameraId, "chanNo": chanNo, "speed": 8, "presetIndex": 1}; |
| | | $.ajax({ |
| | | headers: { |
| | | 'Accept': 'application/json', |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../cameraSdk/setPreset", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(opt), |
| | |
| | | }) |
| | | }) |
| | | $("#gotoPreset").click(function () { |
| | | cameraId = $('#select option:selected').val(); |
| | | opt = {"cameraId": cameraId, "channelNum": 1, "speed": 8, "presetIndex": 1}; |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | opt = {"cameraId": cameraId, "chanNo": chanNo, "speed": 8, "presetIndex": 1}; |
| | | $.ajax({ |
| | | headers: { |
| | | 'Accept': 'application/json', |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/gotoPreset", |
| | | url: "../cameraSdk/gotoPreset", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(opt), |
| | |
| | | }) |
| | | }) |
| | | $("#getPTZ").click(function () { |
| | | cameraId = $('#select option:selected').val(); |
| | | opt = {"cameraId": cameraId, "channelNum": 1}; |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | opt = {"cameraId": cameraId, "chanNo": chanNo}; |
| | | $.ajax({ |
| | | headers: { |
| | | 'Accept': 'application/json', |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/getPTZ", |
| | | url: "../cameraSdk/getPTZ", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(opt), |
| | |
| | | }) |
| | | }) |
| | | $("#setPTZ").click(function () { |
| | | cameraId = $('#select option:selected').val(); |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | var p = $('#p').val(); |
| | | var t = $('#t').val(); |
| | | var z = $('#z').val(); |
| | | //定义一个带有Map字段的实体对象 |
| | | var myEntity = { |
| | | channelNum: 1, |
| | | chanNo: chanNo, |
| | | cameraId: cameraId, |
| | | ptzMap: { |
| | | p: p, |
| | |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/setPTZ", |
| | | url: "../cameraSdk/setPTZ", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(myEntity), |
| | |
| | | }) |
| | | }) |
| | | $("#setTargetPostion").click(function () { |
| | | cameraId = $('#select option:selected').val(); |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | var camera = cameraMap.get(cameraId); |
| | | var camP = camera.longitude + ',' + camera.latitude + ',' + camera.altitude; |
| | | var targetP = $('#targetPostion').val(); |
| | | var arr = targetP.split(","); |
| | | arr = arr.map(item => parseFloat(item)); |
| | | //定义一个带有Map字段的实体对象 |
| | | var myEntity = { |
| | | channelNum: 1, |
| | | chanNo: chanNo, |
| | | cameraId: cameraId, |
| | | targetPosition: targetP, |
| | | camPosition: camP, |
| | | targetPosition: arr |
| | | }; |
| | | console.log(myEntity) |
| | | $.ajax({ |
| | |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/setTargetPosition", |
| | | url: "../cameraSdk/setTargetPosition", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(myEntity), |
| | |
| | | }) |
| | | }) |
| | | $("#setZeroPTZ").click(function () { |
| | | cameraId = $('#select option:selected').val(); |
| | | opt = {"cameraId": cameraId, "channelNum": 1}; |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | opt = {"cameraId": cameraId, "chanNo": chanNo}; |
| | | $.ajax({ |
| | | headers: { |
| | | 'Accept': 'application/json', |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/setZeroPTZ", |
| | | url: "../cameraSdk/setZeroPTZ", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(opt), |
| | |
| | | }) |
| | | }) |
| | | $("#WiperPwron").click(function () { |
| | | cameraId = $('#select option:selected').val(); |
| | | opt = {"cameraId": cameraId, "channelNum": 1, "speed": 8, "enable": true, "code": 16}; |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | opt = {"cameraId": cameraId, "chanNo": chanNo, "speed": 8, "enable": true, "code": 16}; |
| | | $.ajax({ |
| | | headers: { |
| | | 'Accept': 'application/json', |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/PTZControlWithSpeed", |
| | | url: "../cameraSdk/PTZControlWithSpeed", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(opt), |
| | |
| | | }) |
| | | var defogflag = true; |
| | | $("#Defogcfg").click(function () { |
| | | cameraId = $('#select option:selected').val(); |
| | | optOpen = {"cameraId": cameraId, "channelNum": 1, "enable": true}; |
| | | optClose = {"cameraId": cameraId, "channelNum": 1, "enable": false}; |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true}; |
| | | optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false}; |
| | | if (defogflag) { |
| | | $(this).text("关闭透雾"); |
| | | defogflag = false; |
| | |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/defogcfg", |
| | | url: "../cameraSdk/defogcfg", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(optOpen), |
| | |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/defogcfg", |
| | | url: "../cameraSdk/defogcfg", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(optClose), |
| | |
| | | }) |
| | | var infrareflag = true; |
| | | $("#Infrarecfg").click(function () { |
| | | cameraId = $('#select option:selected').val(); |
| | | optOpen = {"cameraId": cameraId, "channelNum": 1, "enable": true}; |
| | | optClose = {"cameraId": cameraId, "channelNum": 1, "enable": false}; |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true}; |
| | | optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false}; |
| | | if (infrareflag) { |
| | | $(this).text("关闭红外"); |
| | | infrareflag = false; |
| | |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/infrarecfg", |
| | | url: "../cameraSdk/infrarecfg", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(optOpen), |
| | |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/infrarecfg", |
| | | url: "../cameraSdk/infrarecfg", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(optClose), |
| | |
| | | }) |
| | | var focusModeflag = true; |
| | | $("#FocusMode").click(function () { |
| | | cameraId = $('#select option:selected').val(); |
| | | optOpen = {"cameraId": cameraId, "channelNum": 1, "enable": true}; |
| | | optClose = {"cameraId": cameraId, "channelNum": 1, "enable": false}; |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true}; |
| | | optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false}; |
| | | if (focusModeflag) { |
| | | $(this).text("自动聚焦"); |
| | | focusModeflag = false; |
| | |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/focusMode", |
| | | url: "../cameraSdk/focusMode", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(optOpen), |
| | |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/focusMode", |
| | | url: "../cameraSdk/focusMode", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(optClose), |
| | |
| | | }) |
| | | } |
| | | }) |
| | | $("#getFocusPos").click(function () { |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | opt = {"cameraId": cameraId, "chanNo": chanNo}; |
| | | $.ajax({ |
| | | headers: { |
| | | 'Accept': 'application/json', |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../cameraSdk/getFocusPos", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(opt), |
| | | success: function (datas) { |
| | | console.log(datas); |
| | | $("#focus").val(datas.data); |
| | | } |
| | | }) |
| | | }) |
| | | var heateRpwronflag = true; |
| | | $("#HeateRpwron").click(function () { |
| | | cameraId = $('#select option:selected').val(); |
| | | optOpen = {"cameraId": cameraId, "channelNum": 1, "enable": true}; |
| | | optClose = {"cameraId": cameraId, "channelNum": 1, "enable": false}; |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true}; |
| | | optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false}; |
| | | if (heateRpwronflag) { |
| | | $(this).text("关闭云台加热"); |
| | | heateRpwronflag = false; |
| | |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/heateRpwron", |
| | | url: "../cameraSdk/heateRpwron", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(optOpen), |
| | |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/heateRpwron", |
| | | url: "../cameraSdk/heateRpwron", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(optClose), |
| | |
| | | }) |
| | | var CameraDeicingflag = true; |
| | | $("#CameraDeicing").click(function () { |
| | | cameraId = $('#select option:selected').val(); |
| | | optOpen = {"cameraId": cameraId, "channelNum": 1, "enable": true}; |
| | | optClose = {"cameraId": cameraId, "channelNum": 1, "enable": false}; |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true}; |
| | | optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false}; |
| | | if (CameraDeicingflag) { |
| | | $(this).text("关闭镜头加热"); |
| | | CameraDeicingflag = false; |
| | | $.ajax({ |
| | | headers: { |
| | | 'Accept': 'application/json', |
| | | 'Content-Type': 'application/json' |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/cameraDeicing", |
| | | url: "../cameraSdk/cameraDeicing", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(optOpen), |
| | |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/cameraDeicing", |
| | | url: "../cameraSdk/cameraDeicing", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(optClose), |
| | |
| | | } |
| | | }) |
| | | $("#realCutPic").click(function () { |
| | | cameraId = $('#select option:selected').val(); |
| | | opt = {"cameraId": cameraId, "channelNum": 1}; |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | opt = {"cameraId": cameraId, "chanNo": chanNo}; |
| | | $.ajax({ |
| | | headers: { |
| | | 'Accept': 'application/json', |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/captureJPEGPicture", |
| | | url: "../cameraSdk/captureJPEGPicture", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(opt), |
| | |
| | | }) |
| | | }) |
| | | $("#saveCutPic").click(function () { |
| | | cameraId = $('#select option:selected').val(); |
| | | opt = {"cameraId": cameraId, "channelNum": 1}; |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | opt = {"cameraId": cameraId, "chanNo": chanNo}; |
| | | $.ajax({ |
| | | headers: { |
| | | 'Accept': 'application/json', |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/picCutCate", |
| | | url: "../cameraSdk/picCutCate", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(opt), |
| | | success: function (data) { |
| | | console.log(data.data); |
| | | $('#imgContainer').attr('src', data.data); |
| | | setTimeout(() => { |
| | | $('#imgContainer').attr('src', data.data); |
| | | }, 1000) |
| | | |
| | | } |
| | | }) |
| | | }) |
| | | var recordflag = true; |
| | | $("#record").click(function () { |
| | | cameraId = $('#select option:selected').val(); |
| | | optOpen = {"cameraId": cameraId, "channelNum": 1, "enable": true}; |
| | | optClose = {"cameraId": cameraId, "channelNum": 1, "enable": false}; |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true}; |
| | | optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false}; |
| | | if (recordflag) { |
| | | $(this).text("停止录像"); |
| | | recordflag = false; |
| | |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/record", |
| | | url: "../cameraSdk/record", |
| | | type: "post", |
| | | dataType: "json", |
| | | data: JSON.stringify(optOpen), |
| | |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../hik/record", |
| | | url: "../cameraSdk/record", |
| | | dataType: "json", |
| | | data: JSON.stringify(optClose), |
| | | type: "post", |
| | |
| | | |
| | | /*云台公共方法*/ |
| | | function commondMethod(url, code, enable) { |
| | | cameraId = $('#select option:selected').val(); |
| | | opt = {"cameraId": cameraId, "channelNum": 1, "speed": 8, "enable": enable, "code": code}; |
| | | cameraId = $('#selectDev option:selected').val(); |
| | | chanNo = $('#selectChn option:selected').val(); |
| | | opt = {"cameraId": cameraId, "chanNo": chanNo, "speed": 4, "enable": enable, "code": code}; |
| | | $.ajax({ |
| | | headers: { |
| | | 'Accept': 'application/json', |
| | |
| | | }) |
| | | } |
| | | |
| | | let webRtcServer = null; |
| | | let videoMap = new Map(); |
| | | $('video').click(function (e) { |
| | | let ID = e.target.id;//获取当前点击事件的元素 |
| | | console.log(ID); |
| | | if (videoMap.get(ID) != null) { |
| | | closeVideo(ID, videoMap.get(ID)); |
| | | } else { |
| | | var cameraId = $('#select option:selected').val(); |
| | | let camera = cameraMap.get(cameraId); |
| | | console.log(camera); |
| | | if (camera.type == "ys") { |
| | | realViewYs("127.0.0.1", ID, camera.username, camera.password, camera.ipaddr, camera.port); |
| | | } else if (camera.type == "dh") { |
| | | realViewDh("127.0.0.1", ID, camera.username, camera.password, camera.ipaddr, camera.port); |
| | | } else { |
| | | realViewHik("127.0.0.1", ID, camera.username, camera.password, camera.ipaddr, camera.port); |
| | | var cameraId = $('#selectDev option:selected').val(); |
| | | var chanNo = $('#selectChn option:selected').val(); |
| | | console.log(cameraId + " " + chanNo) |
| | | $.ajax({ |
| | | headers: { |
| | | 'Accept': 'application/json', |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token |
| | | }, |
| | | url: "../vtdu/media/" + cameraId + "_" + chanNo, |
| | | type: "get", |
| | | dataType: "json", |
| | | success: function (data) { |
| | | realView(data.data.webrtcUrl + "/", e.target.id); |
| | | } |
| | | } |
| | | }) |
| | | }); |
| | | |
| | | //预览海康相机 |
| | | function realViewHik(serverip, elem, username, password, ipaddr, port) { |
| | | webRtcServer = new WebRtcStreamer(elem, "http://" + serverip + ":8000"); |
| | | let rtspUrl = "rtsp://" + username + ":" + password + "@" + ipaddr + ":" + port + "/ch1/main/av_stream"; |
| | | let option = "rtptransport=tcp"; |
| | | console.log("rtsp地址:" + rtspUrl); |
| | | webRtcServer.connect(rtspUrl, null, option, null); |
| | | videoMap.set(elem, webRtcServer); |
| | | } |
| | | let webrtcClient; |
| | | //whep操作方法 |
| | | const retryPause = 2000; |
| | | |
| | | //预览大华相机 |
| | | function realViewDh(serverip, elem, username, password, ipaddr, port) { |
| | | webRtcServer = new WebRtcStreamer(elem, "http://" + serverip + ":8000"); |
| | | let rtspUrl = "rtsp://" + username + ":" + password + "@" + ipaddr + ":" + port + "/cam/realmonitor?channel=1&subtype=0"; |
| | | let option = "rtptransport=tcp"; |
| | | console.log("rtsp地址:" + rtspUrl); |
| | | const video = document.getElementById('video'); |
| | | const message = document.getElementById('message'); |
| | | |
| | | webRtcServer.connect(rtspUrl, null, option, null); |
| | | videoMap.set(elem, webRtcServer); |
| | | } |
| | | let nonAdvertisedCodecs = []; |
| | | let pc = null; |
| | | let restartTimeout = null; |
| | | let sessionUrl = ''; |
| | | let offerData = ''; |
| | | let queuedCandidates = []; |
| | | let defaultControls = false; |
| | | |
| | | //预览宇视相机 |
| | | function realViewYs(serverip, elem, username, password, ipaddr, port) { |
| | | webRtcServer = new WebRtcStreamer(elem, "http://" + serverip + ":8000"); |
| | | let rtspUrl = "rtsp://" + username + ":" + password + "@" + ipaddr + ":" + port + "/media/video1/multicast"; |
| | | console.log("rtsp地址:" + rtspUrl); |
| | | let option = "rtptransport=tcp"; |
| | | webRtcServer.connect(rtspUrl, null, option, null); |
| | | videoMap.set(elem, webRtcServer); |
| | | } |
| | | const setMessage = (str) => { |
| | | if (str !== '') { |
| | | video.controls = false; |
| | | } else { |
| | | video.controls = defaultControls; |
| | | } |
| | | message.innerText = str; |
| | | }; |
| | | |
| | | function closeVideo(id, webrtc) { |
| | | webrtc.disconnect(); |
| | | videoMap.delete(id); |
| | | } |
| | | const unquoteCredential = (v) => ( |
| | | JSON.parse(`"${v}"`) |
| | | ); |
| | | |
| | | //页面退出时销毁 |
| | | window.onbeforeunload = function () { |
| | | webRtcServer.disconnect(); |
| | | const linkToIceServers = (links) => ( |
| | | (links !== null) ? links.split(', ').map((link) => { |
| | | const m = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i); |
| | | const ret = { |
| | | urls: [m[1]], |
| | | }; |
| | | |
| | | if (m[3] !== undefined) { |
| | | ret.username = unquoteCredential(m[3]); |
| | | ret.credential = unquoteCredential(m[4]); |
| | | ret.credentialType = 'password'; |
| | | } |
| | | |
| | | return ret; |
| | | }) : [] |
| | | ); |
| | | |
| | | const parseOffer = (sdp) => { |
| | | const ret = { |
| | | iceUfrag: '', |
| | | icePwd: '', |
| | | medias: [], |
| | | }; |
| | | |
| | | for (const line of sdp.split('\r\n')) { |
| | | if (line.startsWith('m=')) { |
| | | ret.medias.push(line.slice('m='.length)); |
| | | } else if (ret.iceUfrag === '' && line.startsWith('a=ice-ufrag:')) { |
| | | ret.iceUfrag = line.slice('a=ice-ufrag:'.length); |
| | | } else if (ret.icePwd === '' && line.startsWith('a=ice-pwd:')) { |
| | | ret.icePwd = line.slice('a=ice-pwd:'.length); |
| | | } |
| | | } |
| | | |
| | | return ret; |
| | | }; |
| | | |
| | | const enableStereoPcmau = (section) => { |
| | | let lines = section.split('\r\n'); |
| | | |
| | | lines[0] += ' 118'; |
| | | lines.splice(lines.length - 1, 0, 'a=rtpmap:118 PCMU/8000/2'); |
| | | lines.splice(lines.length - 1, 0, 'a=rtcp-fb:118 transport-cc'); |
| | | |
| | | lines[0] += ' 119'; |
| | | lines.splice(lines.length - 1, 0, 'a=rtpmap:119 PCMA/8000/2'); |
| | | lines.splice(lines.length - 1, 0, 'a=rtcp-fb:119 transport-cc'); |
| | | |
| | | return lines.join('\r\n'); |
| | | }; |
| | | |
| | | const enableMultichannelOpus = (section) => { |
| | | let lines = section.split('\r\n'); |
| | | |
| | | lines[0] += " 112"; |
| | | lines.splice(lines.length - 1, 0, "a=rtpmap:112 multiopus/48000/3"); |
| | | lines.splice(lines.length - 1, 0, "a=fmtp:112 channel_mapping=0,2,1;num_streams=2;coupled_streams=1"); |
| | | lines.splice(lines.length - 1, 0, "a=rtcp-fb:112 transport-cc"); |
| | | |
| | | lines[0] += " 113"; |
| | | lines.splice(lines.length - 1, 0, "a=rtpmap:113 multiopus/48000/4"); |
| | | lines.splice(lines.length - 1, 0, "a=fmtp:113 channel_mapping=0,1,2,3;num_streams=2;coupled_streams=2"); |
| | | lines.splice(lines.length - 1, 0, "a=rtcp-fb:113 transport-cc"); |
| | | |
| | | lines[0] += " 114"; |
| | | lines.splice(lines.length - 1, 0, "a=rtpmap:114 multiopus/48000/5"); |
| | | lines.splice(lines.length - 1, 0, "a=fmtp:114 channel_mapping=0,4,1,2,3;num_streams=3;coupled_streams=2"); |
| | | lines.splice(lines.length - 1, 0, "a=rtcp-fb:114 transport-cc"); |
| | | |
| | | lines[0] += " 115"; |
| | | lines.splice(lines.length - 1, 0, "a=rtpmap:115 multiopus/48000/6"); |
| | | lines.splice(lines.length - 1, 0, "a=fmtp:115 channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2"); |
| | | lines.splice(lines.length - 1, 0, "a=rtcp-fb:115 transport-cc"); |
| | | |
| | | lines[0] += " 116"; |
| | | lines.splice(lines.length - 1, 0, "a=rtpmap:116 multiopus/48000/7"); |
| | | lines.splice(lines.length - 1, 0, "a=fmtp:116 channel_mapping=0,4,1,2,3,5,6;num_streams=4;coupled_streams=4"); |
| | | lines.splice(lines.length - 1, 0, "a=rtcp-fb:116 transport-cc"); |
| | | |
| | | lines[0] += " 117"; |
| | | lines.splice(lines.length - 1, 0, "a=rtpmap:117 multiopus/48000/8"); |
| | | lines.splice(lines.length - 1, 0, "a=fmtp:117 channel_mapping=0,6,1,4,5,2,3,7;num_streams=5;coupled_streams=4"); |
| | | lines.splice(lines.length - 1, 0, "a=rtcp-fb:117 transport-cc"); |
| | | |
| | | return lines.join('\r\n'); |
| | | }; |
| | | |
| | | const enableL16 = (section) => { |
| | | let lines = section.split('\r\n'); |
| | | |
| | | lines[0] += " 120"; |
| | | lines.splice(lines.length - 1, 0, "a=rtpmap:120 L16/8000/2"); |
| | | lines.splice(lines.length - 1, 0, "a=rtcp-fb:120 transport-cc"); |
| | | |
| | | lines[0] += " 121"; |
| | | lines.splice(lines.length - 1, 0, "a=rtpmap:121 L16/16000/2"); |
| | | lines.splice(lines.length - 1, 0, "a=rtcp-fb:121 transport-cc"); |
| | | |
| | | lines[0] += " 122"; |
| | | lines.splice(lines.length - 1, 0, "a=rtpmap:122 L16/48000/2"); |
| | | lines.splice(lines.length - 1, 0, "a=rtcp-fb:122 transport-cc"); |
| | | |
| | | return lines.join('\r\n'); |
| | | }; |
| | | |
| | | const enableStereoOpus = (section) => { |
| | | let opusPayloadFormat = ''; |
| | | let lines = section.split('\r\n'); |
| | | |
| | | for (let i = 0; i < lines.length; i++) { |
| | | if (lines[i].startsWith('a=rtpmap:') && lines[i].toLowerCase().includes('opus/')) { |
| | | opusPayloadFormat = lines[i].slice('a=rtpmap:'.length).split(' ')[0]; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (opusPayloadFormat === '') { |
| | | return section; |
| | | } |
| | | |
| | | for (let i = 0; i < lines.length; i++) { |
| | | if (lines[i].startsWith('a=fmtp:' + opusPayloadFormat + ' ')) { |
| | | if (!lines[i].includes('stereo')) { |
| | | lines[i] += ';stereo=1'; |
| | | } |
| | | if (!lines[i].includes('sprop-stereo')) { |
| | | lines[i] += ';sprop-stereo=1'; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return lines.join('\r\n'); |
| | | }; |
| | | |
| | | const editOffer = (sdp) => { |
| | | const sections = sdp.split('m='); |
| | | |
| | | for (let i = 0; i < sections.length; i++) { |
| | | if (sections[i].startsWith('audio')) { |
| | | sections[i] = enableStereoOpus(sections[i]); |
| | | |
| | | if (nonAdvertisedCodecs.includes('pcma/8000/2')) { |
| | | sections[i] = enableStereoPcmau(sections[i]); |
| | | } |
| | | |
| | | if (nonAdvertisedCodecs.includes('multiopus/48000/6')) { |
| | | sections[i] = enableMultichannelOpus(sections[i]); |
| | | } |
| | | |
| | | if (nonAdvertisedCodecs.includes('L16/48000/2')) { |
| | | sections[i] = enableL16(sections[i]); |
| | | } |
| | | |
| | | break; |
| | | } |
| | | } |
| | | |
| | | return sections.join('m='); |
| | | }; |
| | | |
| | | const generateSdpFragment = (od, candidates) => { |
| | | const candidatesByMedia = {}; |
| | | for (const candidate of candidates) { |
| | | const mid = candidate.sdpMLineIndex; |
| | | if (candidatesByMedia[mid] === undefined) { |
| | | candidatesByMedia[mid] = []; |
| | | } |
| | | candidatesByMedia[mid].push(candidate); |
| | | } |
| | | |
| | | let frag = 'a=ice-ufrag:' + od.iceUfrag + '\r\n' |
| | | + 'a=ice-pwd:' + od.icePwd + '\r\n'; |
| | | |
| | | let mid = 0; |
| | | |
| | | for (const media of od.medias) { |
| | | if (candidatesByMedia[mid] !== undefined) { |
| | | frag += 'm=' + media + '\r\n' |
| | | + 'a=mid:' + mid + '\r\n'; |
| | | |
| | | for (const candidate of candidatesByMedia[mid]) { |
| | | frag += 'a=' + candidate.candidate + '\r\n'; |
| | | } |
| | | } |
| | | mid++; |
| | | } |
| | | |
| | | return frag; |
| | | }; |
| | | |
| | | const loadStream = () => { |
| | | requestICEServers(); |
| | | }; |
| | | |
| | | const supportsNonAdvertisedCodec = (codec, fmtp) => ( |
| | | new Promise((resolve, reject) => { |
| | | const pc = new RTCPeerConnection({ iceServers: [] }); |
| | | pc.addTransceiver('audio', { direction: 'recvonly' }); |
| | | pc.createOffer() |
| | | .then((offer) => { |
| | | if (offer.sdp.includes(' ' + codec)) { // codec is advertised, there's no need to add it manually |
| | | resolve(false); |
| | | return; |
| | | } |
| | | const sections = offer.sdp.split('m=audio'); |
| | | const lines = sections[1].split('\r\n'); |
| | | lines[0] += ' 118'; |
| | | lines.splice(lines.length - 1, 0, 'a=rtpmap:118 ' + codec); |
| | | if (fmtp !== undefined) { |
| | | lines.splice(lines.length - 1, 0, 'a=fmtp:118 ' + fmtp); |
| | | } |
| | | sections[1] = lines.join('\r\n'); |
| | | offer.sdp = sections.join('m=audio'); |
| | | return pc.setLocalDescription(offer); |
| | | }) |
| | | .then(() => { |
| | | return pc.setRemoteDescription(new RTCSessionDescription({ |
| | | type: 'answer', |
| | | sdp: 'v=0\r\n' |
| | | + 'o=- 6539324223450680508 0 IN IP4 0.0.0.0\r\n' |
| | | + 's=-\r\n' |
| | | + 't=0 0\r\n' |
| | | + 'a=fingerprint:sha-256 0D:9F:78:15:42:B5:4B:E6:E2:94:3E:5B:37:78:E1:4B:54:59:A3:36:3A:E5:05:EB:27:EE:8F:D2:2D:41:29:25\r\n' |
| | | + 'm=audio 9 UDP/TLS/RTP/SAVPF 118\r\n' |
| | | + 'c=IN IP4 0.0.0.0\r\n' |
| | | + 'a=ice-pwd:7c3bf4770007e7432ee4ea4d697db675\r\n' |
| | | + 'a=ice-ufrag:29e036dc\r\n' |
| | | + 'a=sendonly\r\n' |
| | | + 'a=rtcp-mux\r\n' |
| | | + 'a=rtpmap:118 ' + codec + '\r\n' |
| | | + ((fmtp !== undefined) ? 'a=fmtp:118 ' + fmtp + '\r\n' : ''), |
| | | })); |
| | | }) |
| | | .then(() => { |
| | | resolve(true); |
| | | }) |
| | | .catch((err) => { |
| | | resolve(false); |
| | | }) |
| | | .finally(() => { |
| | | pc.close(); |
| | | }); |
| | | }) |
| | | ); |
| | | |
| | | const getNonAdvertisedCodecs = () => { |
| | | Promise.all([ |
| | | ['pcma/8000/2'], |
| | | ['multiopus/48000/6', 'channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2'], |
| | | ['L16/48000/2'] |
| | | ].map((c) => supportsNonAdvertisedCodec(c[0], c[1]).then((r) => (r) ? c[0] : false))) |
| | | .then((c) => c.filter((e) => e !== false)) |
| | | .then((codecs) => { |
| | | nonAdvertisedCodecs = codecs; |
| | | loadStream(); |
| | | }); |
| | | }; |
| | | |
| | | const onError = (err) => { |
| | | if (restartTimeout === null) { |
| | | setMessage(err + ', retrying in some seconds'); |
| | | |
| | | if (pc !== null) { |
| | | pc.close(); |
| | | pc = null; |
| | | } |
| | | |
| | | restartTimeout = window.setTimeout(() => { |
| | | restartTimeout = null; |
| | | loadStream(); |
| | | }, retryPause); |
| | | |
| | | if (sessionUrl) { |
| | | fetch(sessionUrl, { |
| | | method: 'DELETE', |
| | | }); |
| | | } |
| | | sessionUrl = ''; |
| | | |
| | | queuedCandidates = []; |
| | | } |
| | | }; |
| | | |
| | | const sendLocalCandidates = (candidates) => { |
| | | fetch(sessionUrl + window.location.search, { |
| | | method: 'PATCH', |
| | | headers: { |
| | | 'Content-Type': 'application/trickle-ice-sdpfrag', |
| | | 'If-Match': '*', |
| | | }, |
| | | body: generateSdpFragment(offerData, candidates), |
| | | }) |
| | | .then((res) => { |
| | | switch (res.status) { |
| | | case 204: |
| | | break; |
| | | case 404: |
| | | throw new Error('stream not found'); |
| | | default: |
| | | throw new Error(`bad status code ${res.status}`); |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | onError(err.toString()); |
| | | }); |
| | | }; |
| | | |
| | | const onLocalCandidate = (evt) => { |
| | | if (restartTimeout !== null) { |
| | | return; |
| | | } |
| | | |
| | | if (evt.candidate !== null) { |
| | | if (sessionUrl === '') { |
| | | queuedCandidates.push(evt.candidate); |
| | | } else { |
| | | sendLocalCandidates([evt.candidate]) |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const onRemoteAnswer = (sdp) => { |
| | | if (restartTimeout !== null) { |
| | | return; |
| | | } |
| | | |
| | | pc.setRemoteDescription(new RTCSessionDescription({ |
| | | type: 'answer', |
| | | sdp, |
| | | })) |
| | | .then(() => { |
| | | if (queuedCandidates.length !== 0) { |
| | | sendLocalCandidates(queuedCandidates); |
| | | queuedCandidates = []; |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | onError(err.toString()); |
| | | }); |
| | | }; |
| | | |
| | | const sendOffer = (offer) => { |
| | | fetch(new URL('whep', window.location.href) + window.location.search, { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/sdp', |
| | | }, |
| | | body: offer.sdp, |
| | | }) |
| | | .then((res) => { |
| | | switch (res.status) { |
| | | case 201: |
| | | break; |
| | | case 404: |
| | | throw new Error('stream not found'); |
| | | case 400: |
| | | return res.json().then((e) => { throw new Error(e.error); }); |
| | | default: |
| | | throw new Error(`bad status code ${res.status}`); |
| | | } |
| | | |
| | | sessionUrl = new URL(res.headers.get('location'), window.location.href).toString(); |
| | | |
| | | return res.text() |
| | | .then((sdp) => onRemoteAnswer(sdp)); |
| | | }) |
| | | .catch((err) => { |
| | | onError(err.toString()); |
| | | }); |
| | | }; |
| | | |
| | | const createOffer = () => { |
| | | pc.createOffer() |
| | | .then((offer) => { |
| | | offer.sdp = editOffer(offer.sdp); |
| | | offerData = parseOffer(offer.sdp); |
| | | pc.setLocalDescription(offer) |
| | | .then(() => { |
| | | sendOffer(offer); |
| | | }) |
| | | .catch((err) => { |
| | | onError(err.toString()); |
| | | }); |
| | | }) |
| | | .catch((err) => { |
| | | onError(err.toString()); |
| | | }); |
| | | }; |
| | | |
| | | const onConnectionState = () => { |
| | | if (restartTimeout !== null) { |
| | | return; |
| | | } |
| | | |
| | | if (pc.iceConnectionState === 'disconnected') { |
| | | onError('peer connection closed'); |
| | | } |
| | | }; |
| | | |
| | | const onTrack = (evt) => { |
| | | setMessage(''); |
| | | video.srcObject = evt.streams[0]; |
| | | }; |
| | | |
| | | const requestICEServers = () => { |
| | | fetch(new URL('whep', window.location.href) + window.location.search, { |
| | | method: 'OPTIONS', |
| | | }) |
| | | .then((res) => { |
| | | pc = new RTCPeerConnection({ |
| | | iceServers: linkToIceServers(res.headers.get('Link')), |
| | | // https://webrtc.org/getting-started/unified-plan-transition-guide |
| | | sdpSemantics: 'unified-plan', |
| | | }); |
| | | |
| | | const direction = 'sendrecv'; |
| | | pc.addTransceiver('video', { direction }); |
| | | pc.addTransceiver('audio', { direction }); |
| | | |
| | | pc.onicecandidate = (evt) => onLocalCandidate(evt); |
| | | pc.oniceconnectionstatechange = () => onConnectionState(); |
| | | pc.ontrack = (evt) => onTrack(evt); |
| | | |
| | | createOffer(); |
| | | }) |
| | | .catch((err) => { |
| | | onError(err.toString()); |
| | | }); |
| | | }; |
| | | |
| | | const parseBoolString = (str, defaultVal) => { |
| | | str = (str || ''); |
| | | |
| | | if (['1', 'yes', 'true'].includes(str.toLowerCase())) { |
| | | return true; |
| | | } |
| | | if (['0', 'no', 'false'].includes(str.toLowerCase())) { |
| | | return false; |
| | | } |
| | | return defaultVal; |
| | | }; |
| | | |
| | | const loadAttributesFromQuery = () => { |
| | | const params = new URLSearchParams(window.location.search); |
| | | video.controls = parseBoolString(params.get('controls'), true); |
| | | video.muted = parseBoolString(params.get('muted'), true); |
| | | video.autoplay = parseBoolString(params.get('autoplay'), true); |
| | | video.playsInline = parseBoolString(params.get('playsinline'), true); |
| | | defaultControls = video.controls; |
| | | }; |
| | | |
| | | |
| | | function realView(whepUrl, videoId) { |
| | | console.log(whepUrl) |
| | | webrtcClient = new WHEPClient(whepUrl, videoId); |
| | | } |
| | | </script> |
| | | </body> |
| | | </html> |
| | | </html> |