GBRecordDetail.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. <template>
  2. <div style="width: 100%">
  3. <div class="page-header" >
  4. <div class="page-title">
  5. <el-page-header @back="goBack" content="国标录像"></el-page-header>
  6. </div>
  7. </div>
  8. <el-container>
  9. <el-aside width="300px">
  10. <div class="record-list-box-box">
  11. <el-date-picker size="mini" v-model="chooseDate" type="date" value-format="yyyy-MM-dd" placeholder="日期" @change="dateChange()"></el-date-picker>
  12. <div class="record-list-box" v-loading="recordsLoading" :style="recordListStyle">
  13. <ul v-if="detailFiles.length >0" class="infinite-list record-list" >
  14. <li v-for="item in detailFiles" class="infinite-list-item record-list-item" >
  15. <el-tag v-if="chooseFile != item" @click="checkedFile(item)">
  16. <i class="el-icon-video-camera" ></i>
  17. {{ moment(item.startTime).format('HH:mm:ss')}}-{{ moment(item.endTime).format('HH:mm:ss')}}
  18. </el-tag>
  19. <el-tag v-if="chooseFile == item" type="danger" >
  20. <i class="el-icon-video-camera" ></i>
  21. {{ moment(item.startTime).format('HH:mm:ss')}}-{{ moment(item.endTime).format('HH:mm:ss')}}
  22. </el-tag>
  23. <i style="color: #409EFF;margin-left: 5px;" class="el-icon-download" @click="downloadRecord(item)" ></i>
  24. </li>
  25. </ul>
  26. </div>
  27. <div size="mini" v-if="detailFiles.length ==0" class="record-list-no-val" >暂无数据</div>
  28. </div>
  29. </el-aside>
  30. <el-main style="padding-bottom: 10px;">
  31. <div class="playBox" :style="playerStyle">
  32. <player ref="recordVideoPlayer"
  33. :videoUrl="videoUrl"
  34. :error="videoError"
  35. :message="videoError"
  36. :hasAudio="hasAudio"
  37. style="max-height: 100%"
  38. fluent autoplay live ></player>
  39. </div>
  40. <div class="player-option-box">
  41. <div>
  42. <el-button-group >
  43. <el-time-picker
  44. size="mini"
  45. is-range
  46. align="left"
  47. v-model="timeRange"
  48. value-format="yyyy-MM-dd HH:mm:ss"
  49. range-separator="至"
  50. start-placeholder="开始时间"
  51. end-placeholder="结束时间"
  52. @change="timePickerChange"
  53. placeholder="选择时间范围">
  54. </el-time-picker>
  55. </el-button-group>
  56. <el-button-group >
  57. <el-button size="mini" class="iconfont icon-zanting" title="开始" @click="gbPause()"></el-button>
  58. <el-button size="mini" class="iconfont icon-kaishi" title="暂停" @click="gbPlay()"></el-button>
  59. <el-dropdown size="mini" title="播放倍速" @command="gbScale">
  60. <el-button size="mini">
  61. 倍速 <i class="el-icon-arrow-down el-icon--right"></i>
  62. </el-button>
  63. <el-dropdown-menu slot="dropdown">
  64. <el-dropdown-item command="0.25">0.25倍速</el-dropdown-item>
  65. <el-dropdown-item command="0.5">0.5倍速</el-dropdown-item>
  66. <el-dropdown-item command="1.0">1倍速</el-dropdown-item>
  67. <el-dropdown-item command="2.0">2倍速</el-dropdown-item>
  68. <el-dropdown-item command="4.0">4倍速</el-dropdown-item>
  69. </el-dropdown-menu>
  70. </el-dropdown>
  71. <el-button size="mini" class="iconfont icon-xiazai1" title="下载选定录像" @click="downloadRecord()"></el-button>
  72. <el-button v-if="sliderMIn === 0 && sliderMax === 86400" size="mini" class="iconfont icon-slider" title="放大滑块" @click="setSliderFit()"></el-button>
  73. <el-button v-if="sliderMIn !== 0 || sliderMax !== 86400" size="mini" class="iconfont icon-slider-right" title="恢复滑块" @click="setSliderFit()"></el-button>
  74. </el-button-group>
  75. </div>
  76. <el-slider
  77. class="playtime-slider"
  78. v-model="playTime"
  79. id="playtimeSlider"
  80. :disabled="detailFiles.length === 0"
  81. :min="sliderMIn"
  82. :max="sliderMax"
  83. :range="true"
  84. :format-tooltip="playTimeFormat"
  85. @change="playTimeChange"
  86. :marks="playTimeSliderMarks">
  87. </el-slider>
  88. <div class="slider-val-box">
  89. <div class="slider-val" v-for="item of detailFiles" :style="'width:' + getDataWidth(item) + '%; left:' + getDataLeft(item) + '%'"></div>
  90. </div>
  91. </div>
  92. </el-main>
  93. </el-container>
  94. <recordDownload ref="recordDownload"></recordDownload>
  95. </div>
  96. </template>
  97. <script>
  98. import uiHeader from '../layout/UiHeader.vue'
  99. import player from './common/jessibuca.vue'
  100. import moment from 'moment'
  101. import recordDownload from './dialog/recordDownload.vue'
  102. export default {
  103. name: 'app',
  104. components: {
  105. uiHeader, player,recordDownload
  106. },
  107. data() {
  108. return {
  109. deviceId: this.$route.params.deviceId,
  110. channelId: this.$route.params.channelId,
  111. recordsLoading: false,
  112. streamId: "",
  113. hasAudio: false,
  114. detailFiles: [],
  115. chooseDate: null,
  116. videoUrl: null,
  117. chooseFile: null,
  118. streamInfo: null,
  119. app: null,
  120. mediaServerId: null,
  121. ssrc: null,
  122. sliderMIn: 0,
  123. sliderMax: 86400,
  124. autoPlay: true,
  125. taskUpdate: null,
  126. tabVal: "running",
  127. recordListStyle: {
  128. height: this.winHeight + "px",
  129. overflow: "auto",
  130. margin: "10px auto 10px auto"
  131. },
  132. playerStyle: {
  133. "margin": "0 auto 20px auto",
  134. "height": this.winHeight + "px",
  135. },
  136. winHeight: window.innerHeight - 240,
  137. playTime: null,
  138. timeRange: null,
  139. startTime: null,
  140. endTime: null,
  141. playTimeSliderMarks: {
  142. 0: "00:00",
  143. 3600: "01:00",
  144. 7200: "02:00",
  145. 10800: "03:00",
  146. 14400: "04:00",
  147. 18000: "05:00",
  148. 21600: "06:00",
  149. 25200: "07:00",
  150. 28800: "08:00",
  151. 32400: "09:00",
  152. 36000: "10:00",
  153. 39600: "11:00",
  154. 43200: "12:00",
  155. 46800: "13:00",
  156. 50400: "14:00",
  157. 54000: "15:00",
  158. 57600: "16:00",
  159. 61200: "17:00",
  160. 64800: "18:00",
  161. 68400: "19:00",
  162. 72000: "20:00",
  163. 75600: "21:00",
  164. 79200: "22:00",
  165. 82800: "23:00",
  166. 86400: "24:00",
  167. },
  168. };
  169. },
  170. computed: {
  171. },
  172. mounted() {
  173. this.recordListStyle.height = this.winHeight + "px";
  174. this.playerStyle["height"] = this.winHeight + "px";
  175. this.chooseDate = moment().format('YYYY-MM-DD')
  176. this.dateChange();
  177. window.addEventListener('beforeunload', this.stopPlayRecord)
  178. },
  179. destroyed() {
  180. this.$destroy('recordVideoPlayer');
  181. window.removeEventListener('beforeunload', this.stopPlayRecord)
  182. },
  183. methods: {
  184. dateChange(){
  185. if (!this.chooseDate) {
  186. return;
  187. }
  188. this.setTime(this.chooseDate + " 00:00:00", this.chooseDate + " 23:59:59");
  189. this.recordsLoading = true;
  190. this.detailFiles = [];
  191. this.$axios({
  192. method: 'get',
  193. url: '/api/gb_record/query/' + this.deviceId + '/' + this.channelId + '?startTime=' + this.startTime + '&endTime=' + this.endTime
  194. }).then((res)=>{
  195. this.recordsLoading = false;
  196. if(res.data.code === 0) {
  197. // 处理时间信息
  198. this.detailFiles = res.data.data.recordList;
  199. }else {
  200. this.$message({
  201. showClose: true,
  202. message: res.data.msg,
  203. type: "error",
  204. });
  205. }
  206. }).catch((e)=> {
  207. this.recordsLoading = false;
  208. // that.videoHistory.searchHistoryResult = falsificationData.recordData;
  209. });
  210. },
  211. moment: function (v) {
  212. return moment(v)
  213. },
  214. setTime: function (startTime, endTime){
  215. this.startTime = startTime;
  216. this.endTime = endTime;
  217. let start = (new Date(this.startTime).getTime() - new Date(this.chooseDate + " 00:00:00").getTime())/1000;
  218. let end = (new Date(this.endTime).getTime() - new Date(this.chooseDate + " 00:00:00").getTime())/1000;
  219. console.log(start)
  220. console.log(end)
  221. this.playTime = [start, end];
  222. this.timeRange = [startTime, endTime];
  223. },
  224. videoError: function (e) {
  225. console.log("播放器错误:" + JSON.stringify(e));
  226. },
  227. checkedFile(file){
  228. this.chooseFile = file;
  229. this.setTime(file.startTime, file.endTime);
  230. // 开始回放
  231. this.playRecord()
  232. },
  233. playRecord: function () {
  234. if (this.streamId !== "") {
  235. this.stopPlayRecord(()=> {
  236. this.streamId = "";
  237. this.playRecord();
  238. })
  239. } else {
  240. this.$axios({
  241. method: 'get',
  242. url: '/api/playback/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + this.startTime + '&endTime=' +
  243. this.endTime
  244. }).then((res)=> {
  245. if (res.data.code === 0) {
  246. this.streamInfo = res.data.data;
  247. this.app = this.streamInfo.app;
  248. this.streamId = this.streamInfo.stream;
  249. this.mediaServerId = this.streamInfo.mediaServerId;
  250. this.ssrc = this.streamInfo.ssrc;
  251. this.videoUrl = this.getUrlByStreamInfo();
  252. }else {
  253. this.$message({
  254. showClose: true,
  255. message: res.data.msg,
  256. type: "error",
  257. });
  258. }
  259. });
  260. }
  261. },
  262. gbPlay(){
  263. console.log('前端控制:播放');
  264. this.$axios({
  265. method: 'get',
  266. url: '/api/playback/resume/' + this.streamId
  267. }).then((res)=> {
  268. this.$refs["recordVideoPlayer"].play(this.videoUrl)
  269. });
  270. },
  271. gbPause(){
  272. console.log('前端控制:暂停');
  273. this.$axios({
  274. method: 'get',
  275. url: '/api/playback/pause/' + this.streamId
  276. }).then(function (res) {});
  277. },
  278. gbScale(command){
  279. console.log('前端控制:倍速 ' + command);
  280. this.$axios({
  281. method: 'get',
  282. url: `/api/playback/speed/${this.streamId }/${command}`
  283. }).then(function (res) {});
  284. },
  285. downloadRecord: function (row) {
  286. if (!row) {
  287. let startTimeStr = moment(new Date(this.chooseDate + " 00:00:00").getTime() + this.playTime[0]*1000).format("YYYY-MM-DD HH:mm:ss");
  288. let endTimeStr = moment(new Date(this.chooseDate + " 00:00:00").getTime() + this.playTime[1]*1000).format("YYYY-MM-DD HH:mm:ss");
  289. console.log(startTimeStr);
  290. console.log(endTimeStr);
  291. row = {
  292. startTime: startTimeStr,
  293. endTime: endTimeStr
  294. }
  295. }
  296. if (this.streamId !== "") {
  297. this.stopPlayRecord(()=> {
  298. this.streamId = "";
  299. this.downloadRecord(row);
  300. })
  301. }else {
  302. this.$axios({
  303. method: 'get',
  304. url: '/api/gb_record/download/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + row.startTime + '&endTime=' +
  305. row.endTime + '&downloadSpeed=4'
  306. }).then( (res)=> {
  307. if (res.data.code === 0) {
  308. let streamInfo = res.data.data;
  309. this.$refs.recordDownload.openDialog(this.deviceId, this.channelId, streamInfo.app, streamInfo.stream, streamInfo.mediaServerId);
  310. }else {
  311. this.$message({
  312. showClose: true,
  313. message: res.data.msg,
  314. type: "error",
  315. });
  316. }
  317. });
  318. }
  319. },
  320. stopDownloadRecord: function (callback) {
  321. this.$refs["recordVideoPlayer"].pause();
  322. this.videoUrl = '';
  323. this.$axios({
  324. method: 'get',
  325. url: '/api/gb_record/download/stop/' + this.deviceId + "/" + this.channelId+ "/" + this.streamId
  326. }).then((res)=> {
  327. if (callback) callback(res)
  328. });
  329. },
  330. stopPlayRecord: function (callback) {
  331. console.log("停止录像回放")
  332. if (this.streamId !== "") {
  333. this.$refs["recordVideoPlayer"].pause();
  334. this.videoUrl = '';
  335. this.$axios({
  336. method: 'get',
  337. url: '/api/playback/stop/' + this.deviceId + "/" + this.channelId + "/" + this.streamId
  338. }).then(function (res) {
  339. if (callback) callback()
  340. });
  341. }
  342. },
  343. getDataWidth(item){
  344. let timeForFile = this.getTimeForFile(item);
  345. let result = (timeForFile[2])/((this.sliderMax - this.sliderMIn)*1000)
  346. return result*100
  347. },
  348. getDataLeft(item){
  349. let timeForFile = this.getTimeForFile(item);
  350. let differenceTime = timeForFile[0].getTime() - new Date(this.chooseDate + " 00:00:00").getTime()
  351. return parseFloat((differenceTime - this.sliderMIn * 1000)/((this.sliderMax - this.sliderMIn)*1000))*100 ;
  352. },
  353. getUrlByStreamInfo(){
  354. if (location.protocol === "https:") {
  355. this.videoUrl = this.streamInfo["wss_flv"]
  356. }else {
  357. this.videoUrl = this.streamInfo["ws_flv"]
  358. }
  359. return this.videoUrl;
  360. },
  361. timePickerChange: function (val){
  362. this.setTime(val[0], val[1])
  363. },
  364. playTimeChange(val){
  365. console.log(val)
  366. let startTimeStr = moment(new Date(this.chooseDate + " 00:00:00").getTime() + val[0]*1000).format("YYYY-MM-DD HH:mm:ss");
  367. let endTimeStr = moment(new Date(this.chooseDate + " 00:00:00").getTime() + val[1]*1000).format("YYYY-MM-DD HH:mm:ss");
  368. this.setTime(startTimeStr, endTimeStr)
  369. this.playRecord();
  370. },
  371. setSliderFit() {
  372. if (this.sliderMIn === 0 && this.sliderMax === 86400) {
  373. if (this.detailFiles.length > 0){
  374. let timeForFile = this.getTimeForFile(this.detailFiles[0]);
  375. let lastTimeForFile = this.getTimeForFile(this.detailFiles[this.detailFiles.length - 1]);
  376. let timeNum = timeForFile[0].getTime() - new Date(this.chooseDate + " " + "00:00:00").getTime()
  377. let lastTimeNum = lastTimeForFile[1].getTime() - new Date(this.chooseDate + " " + "00:00:00").getTime()
  378. this.playTime = parseInt(timeNum/1000)
  379. this.sliderMIn = parseInt(timeNum/1000 - timeNum/1000%(60*60))
  380. this.sliderMax = parseInt(lastTimeNum/1000 - lastTimeNum/1000%(60*60)) + 60*60
  381. this.playTime = [this.sliderMIn, this.sliderMax];
  382. }
  383. }else {
  384. this.sliderMIn = 0;
  385. this.sliderMax = 86400;
  386. }
  387. },
  388. getTimeForFile(file){
  389. let startTime = new Date(file.startTime);
  390. let endTime = new Date(file.endTime);
  391. return [startTime, endTime, endTime.getTime() - startTime.getTime()];
  392. },
  393. playTimeFormat(val){
  394. let h = parseInt(val/3600);
  395. let m = parseInt((val - h*3600)/60);
  396. let s = parseInt(val - h*3600 - m*60);
  397. let hStr = h;
  398. let mStr = m;
  399. let sStr = s;
  400. if (h < 10) {
  401. hStr = "0" + hStr;
  402. }
  403. if (m < 10) {
  404. mStr = "0" + mStr;s
  405. }
  406. if (s < 10) {
  407. sStr = "0" + sStr;
  408. }
  409. return hStr + ":" + mStr + ":" + sStr
  410. },
  411. goBack(){
  412. // 如果正在进行录像回放则,发送停止
  413. if (this.streamId !== "") {
  414. this.stopPlayRecord(()=> {
  415. this.streamId = "";
  416. })
  417. }
  418. window.history.go(-1);
  419. },
  420. }
  421. };
  422. </script>
  423. <style>
  424. .el-slider__runway {
  425. background-color:rgba(206, 206, 206, 0.47) !important;
  426. }
  427. .el-slider__bar {
  428. background-color: rgba(153, 153, 153, 0) !important;
  429. }
  430. .playtime-slider {
  431. position: relative;
  432. z-index: 100;
  433. }
  434. .data-picker-true{
  435. }
  436. .data-picker-true:after{
  437. content: "";
  438. position: absolute;
  439. width: 4px;
  440. height: 4px;
  441. background-color: #606060;
  442. border-radius: 4px;
  443. left: 45%;
  444. top: 74%;
  445. }
  446. .data-picker-false{
  447. }
  448. .slider-val-box{
  449. height: 6px;
  450. position: relative;
  451. top: -22px;
  452. }
  453. .slider-val{
  454. height: 6px;
  455. background-color: #007CFF;
  456. position: absolute;
  457. }
  458. .record-list-box-box{
  459. width: 250px;
  460. float: left;
  461. }
  462. .record-list-box{
  463. overflow: auto;
  464. width: 220px;
  465. list-style: none;
  466. padding: 0;
  467. margin: 0;
  468. margin-top: 0px;
  469. padding: 1rem 0;
  470. background-color: #FFF;
  471. margin-top: 10px;
  472. }
  473. .record-list{
  474. list-style: none;
  475. padding: 0;
  476. margin: 0;
  477. background-color: #FFF;
  478. }
  479. .record-list-no-val {
  480. position: absolute;
  481. color: #9f9f9f;
  482. top: 50%;
  483. left: 110px;
  484. }
  485. .record-list-item{
  486. padding: 0;
  487. margin: 0;
  488. margin: 0.5rem 0;
  489. cursor: pointer;
  490. }
  491. .record-list-option {
  492. width: 10px;
  493. float: left;
  494. margin-top: 39px;
  495. }
  496. .player-option-box{
  497. padding: 0 20px;
  498. }
  499. </style>