GBRecordDetail.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  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. this.hasAudio = this.streamInfo.tracks && this.streamInfo.tracks.length > 1
  253. }else {
  254. this.$message({
  255. showClose: true,
  256. message: res.data.msg,
  257. type: "error",
  258. });
  259. }
  260. });
  261. }
  262. },
  263. gbPlay(){
  264. console.log('前端控制:播放');
  265. this.$axios({
  266. method: 'get',
  267. url: '/api/playback/resume/' + this.streamId
  268. }).then((res)=> {
  269. this.$refs["recordVideoPlayer"].play(this.videoUrl)
  270. });
  271. },
  272. gbPause(){
  273. console.log('前端控制:暂停');
  274. this.$axios({
  275. method: 'get',
  276. url: '/api/playback/pause/' + this.streamId
  277. }).then(function (res) {});
  278. },
  279. gbScale(command){
  280. console.log('前端控制:倍速 ' + command);
  281. this.$axios({
  282. method: 'get',
  283. url: `/api/playback/speed/${this.streamId }/${command}`
  284. }).then(function (res) {});
  285. },
  286. downloadRecord: function (row) {
  287. if (!row) {
  288. let startTimeStr = moment(new Date(this.chooseDate + " 00:00:00").getTime() + this.playTime[0]*1000).format("YYYY-MM-DD HH:mm:ss");
  289. let endTimeStr = moment(new Date(this.chooseDate + " 00:00:00").getTime() + this.playTime[1]*1000).format("YYYY-MM-DD HH:mm:ss");
  290. console.log(startTimeStr);
  291. console.log(endTimeStr);
  292. row = {
  293. startTime: startTimeStr,
  294. endTime: endTimeStr
  295. }
  296. }
  297. if (this.streamId !== "") {
  298. this.stopPlayRecord(()=> {
  299. this.streamId = "";
  300. this.downloadRecord(row);
  301. })
  302. }else {
  303. this.$axios({
  304. method: 'get',
  305. url: '/api/gb_record/download/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + row.startTime + '&endTime=' +
  306. row.endTime + '&downloadSpeed=4'
  307. }).then( (res)=> {
  308. if (res.data.code === 0) {
  309. let streamInfo = res.data.data;
  310. this.$refs.recordDownload.openDialog(this.deviceId, this.channelId, streamInfo.app, streamInfo.stream, streamInfo.mediaServerId);
  311. }else {
  312. this.$message({
  313. showClose: true,
  314. message: res.data.msg,
  315. type: "error",
  316. });
  317. }
  318. });
  319. }
  320. },
  321. stopDownloadRecord: function (callback) {
  322. this.$refs["recordVideoPlayer"].pause();
  323. this.videoUrl = '';
  324. this.$axios({
  325. method: 'get',
  326. url: '/api/gb_record/download/stop/' + this.deviceId + "/" + this.channelId+ "/" + this.streamId
  327. }).then((res)=> {
  328. if (callback) callback(res)
  329. });
  330. },
  331. stopPlayRecord: function (callback) {
  332. console.log("停止录像回放")
  333. if (this.streamId !== "") {
  334. this.$refs["recordVideoPlayer"].pause();
  335. this.videoUrl = '';
  336. this.$axios({
  337. method: 'get',
  338. url: '/api/playback/stop/' + this.deviceId + "/" + this.channelId + "/" + this.streamId
  339. }).then(function (res) {
  340. if (callback) callback()
  341. });
  342. }
  343. },
  344. getDataWidth(item){
  345. let timeForFile = this.getTimeForFile(item);
  346. let result = (timeForFile[2])/((this.sliderMax - this.sliderMIn)*1000)
  347. return result*100
  348. },
  349. getDataLeft(item){
  350. let timeForFile = this.getTimeForFile(item);
  351. let differenceTime = timeForFile[0].getTime() - new Date(this.chooseDate + " 00:00:00").getTime()
  352. return parseFloat((differenceTime - this.sliderMIn * 1000)/((this.sliderMax - this.sliderMIn)*1000))*100 ;
  353. },
  354. getUrlByStreamInfo(){
  355. if (location.protocol === "https:") {
  356. this.videoUrl = this.streamInfo["wss_flv"]
  357. }else {
  358. this.videoUrl = this.streamInfo["ws_flv"]
  359. }
  360. return this.videoUrl;
  361. },
  362. timePickerChange: function (val){
  363. this.setTime(val[0], val[1])
  364. },
  365. playTimeChange(val){
  366. console.log(val)
  367. let startTimeStr = moment(new Date(this.chooseDate + " 00:00:00").getTime() + val[0]*1000).format("YYYY-MM-DD HH:mm:ss");
  368. let endTimeStr = moment(new Date(this.chooseDate + " 00:00:00").getTime() + val[1]*1000).format("YYYY-MM-DD HH:mm:ss");
  369. this.setTime(startTimeStr, endTimeStr)
  370. this.playRecord();
  371. },
  372. setSliderFit() {
  373. if (this.sliderMIn === 0 && this.sliderMax === 86400) {
  374. if (this.detailFiles.length > 0){
  375. let timeForFile = this.getTimeForFile(this.detailFiles[0]);
  376. let lastTimeForFile = this.getTimeForFile(this.detailFiles[this.detailFiles.length - 1]);
  377. let timeNum = timeForFile[0].getTime() - new Date(this.chooseDate + " " + "00:00:00").getTime()
  378. let lastTimeNum = lastTimeForFile[1].getTime() - new Date(this.chooseDate + " " + "00:00:00").getTime()
  379. this.playTime = parseInt(timeNum/1000)
  380. this.sliderMIn = parseInt(timeNum/1000 - timeNum/1000%(60*60))
  381. this.sliderMax = parseInt(lastTimeNum/1000 - lastTimeNum/1000%(60*60)) + 60*60
  382. this.playTime = [this.sliderMIn, this.sliderMax];
  383. }
  384. }else {
  385. this.sliderMIn = 0;
  386. this.sliderMax = 86400;
  387. }
  388. },
  389. getTimeForFile(file){
  390. let startTime = new Date(file.startTime);
  391. let endTime = new Date(file.endTime);
  392. return [startTime, endTime, endTime.getTime() - startTime.getTime()];
  393. },
  394. playTimeFormat(val){
  395. let h = parseInt(val/3600);
  396. let m = parseInt((val - h*3600)/60);
  397. let s = parseInt(val - h*3600 - m*60);
  398. let hStr = h;
  399. let mStr = m;
  400. let sStr = s;
  401. if (h < 10) {
  402. hStr = "0" + hStr;
  403. }
  404. if (m < 10) {
  405. mStr = "0" + mStr;s
  406. }
  407. if (s < 10) {
  408. sStr = "0" + sStr;
  409. }
  410. return hStr + ":" + mStr + ":" + sStr
  411. },
  412. goBack(){
  413. // 如果正在进行录像回放则,发送停止
  414. if (this.streamId !== "") {
  415. this.stopPlayRecord(()=> {
  416. this.streamId = "";
  417. })
  418. }
  419. window.history.go(-1);
  420. },
  421. }
  422. };
  423. </script>
  424. <style>
  425. .el-slider__runway {
  426. background-color:rgba(206, 206, 206, 0.47) !important;
  427. }
  428. .el-slider__bar {
  429. background-color: rgba(153, 153, 153, 0) !important;
  430. }
  431. .playtime-slider {
  432. position: relative;
  433. z-index: 100;
  434. }
  435. .data-picker-true{
  436. }
  437. .data-picker-true:after{
  438. content: "";
  439. position: absolute;
  440. width: 4px;
  441. height: 4px;
  442. background-color: #606060;
  443. border-radius: 4px;
  444. left: 45%;
  445. top: 74%;
  446. }
  447. .data-picker-false{
  448. }
  449. .slider-val-box{
  450. height: 6px;
  451. position: relative;
  452. top: -22px;
  453. }
  454. .slider-val{
  455. height: 6px;
  456. background-color: #007CFF;
  457. position: absolute;
  458. }
  459. .record-list-box-box{
  460. width: 250px;
  461. float: left;
  462. }
  463. .record-list-box{
  464. overflow: auto;
  465. width: 220px;
  466. list-style: none;
  467. padding: 0;
  468. margin: 0;
  469. margin-top: 0px;
  470. padding: 1rem 0;
  471. background-color: #FFF;
  472. margin-top: 10px;
  473. }
  474. .record-list{
  475. list-style: none;
  476. padding: 0;
  477. margin: 0;
  478. background-color: #FFF;
  479. }
  480. .record-list-no-val {
  481. position: absolute;
  482. color: #9f9f9f;
  483. top: 50%;
  484. left: 110px;
  485. }
  486. .record-list-item{
  487. padding: 0;
  488. margin: 0;
  489. margin: 0.5rem 0;
  490. cursor: pointer;
  491. }
  492. .record-list-option {
  493. width: 10px;
  494. float: left;
  495. margin-top: 39px;
  496. }
  497. .player-option-box{
  498. padding: 0 20px;
  499. }
  500. </style>