PlaybackTimebox.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. <template>
  2. <div class="">
  3. <div class="box box-primary">
  4. <div class="box-header">
  5. <h4 class="text-primary text-center">设备录像时间轴({{devid}}-{{channel}})</h4>
  6. </div>
  7. <div class="box-body">
  8. <form class="form-inline">
  9. <div class="form-group form-group-sm">
  10. <button type="button" class="btn btn-sm btn-primary" @click.prevent="$router.go(-1)">
  11. <i class="fa fa-chevron-left"></i> 返回
  12. </button>
  13. </div>
  14. <div class="form-group pull-right">
  15. <div class="input-group input-group-sm">
  16. <DatePicker class="form-control" @update:day="updateDay" :day="day" ref="datePicker"></DatePicker>
  17. <div class="input-group-btn">
  18. <button type="button" class="btn btn-sm btn-default" @click.prevent="showDatePicker">
  19. <i class="fa fa-calendar"></i>
  20. </button>
  21. <button @click.prevent="toListView" class="btn btn-default btn-sm">
  22. <i class="fa fa-hand-o-right"></i> 列表视图
  23. </button>
  24. </div>
  25. </div>
  26. </div>
  27. </form>
  28. <br>
  29. <div class="clearfix"></div>
  30. <LivePlayer live muted :videoUrl="videoUrl" :currentTime="currentTime" @ended="onVideoEnd" @timeupdate="onVideoTimeUpdate"
  31. v-loading="videoLoading" element-loading-text="加载中" element-loading-background="#000"
  32. style="margin:0 auto; max-width:700px;">
  33. </LivePlayer>
  34. <div class="text-center text-gray" v-if="isDemoUser(serverInfo, userInfo)">
  35. <br>
  36. 提示: 演示系统限制匿名登录播放时间, 若需测试长时间播放, 请<a target="_blank" href="//www.liveqing.com/docs/download/LiveGBS.html">下载使用</a>
  37. </div>
  38. <br>
  39. <br>
  40. <TimeRule :videos="videos" @timeChange="onTimeChange" ref="timeRule" v-loading="loading"></TimeRule>
  41. <div class="clearfix"></div>
  42. <br>
  43. </div>
  44. </div>
  45. </div>
  46. </template>
  47. <script>
  48. import _ from "lodash";
  49. import moment from "moment";
  50. import DatePicker from "components/DatePicker.vue";
  51. import TimeRule from 'components/TimeRule.vue'
  52. import LivePlayer from '@liveqing/liveplayer'
  53. import { mapState } from "vuex";
  54. export default {
  55. props: {
  56. devid: {
  57. type: String,
  58. default: ""
  59. },
  60. channel: {
  61. type: String,
  62. default: ""
  63. },
  64. mode: {
  65. type: String,
  66. default: "timebox"
  67. },
  68. day: {
  69. type: String,
  70. default: () => moment().format("YYYYMMDD")
  71. }
  72. },
  73. data() {
  74. return {
  75. timerange: [
  76. moment(this.day, "YYYYMMDD").startOf('hour').toDate(),
  77. moment(this.day, "YYYYMMDD").startOf('hour').toDate()
  78. ],
  79. videoLoading: false,
  80. loading: false,
  81. center: "",
  82. indistinct: "",
  83. records: [],
  84. currentTime: null,
  85. videos: [],
  86. video: null,
  87. videoUrl: "",
  88. streamID: "",
  89. touchTimer: 0
  90. };
  91. },
  92. computed: {
  93. ...mapState(['userInfo', 'serverInfo']),
  94. },
  95. components: {
  96. DatePicker, LivePlayer, TimeRule
  97. },
  98. methods: {
  99. ready(){
  100. this.$watch('day', function(newVal, oldVal) {
  101. this.timerange = [
  102. moment(this.day, "YYYYMMDD").startOf('hour').toDate(),
  103. moment(this.day, "YYYYMMDD").startOf('hour').toDate()
  104. ]
  105. });
  106. this.$watch('center', function(newVal, oldVal) {
  107. this.timerange = [
  108. moment(this.day, "YYYYMMDD").startOf('hour').toDate(),
  109. moment(this.day, "YYYYMMDD").startOf('hour').toDate()
  110. ]
  111. });
  112. this.$watch('indistinct', function(newVal, oldVal) {
  113. this.timerange = [
  114. moment(this.day, "YYYYMMDD").startOf('hour').toDate(),
  115. moment(this.day, "YYYYMMDD").startOf('hour').toDate()
  116. ]
  117. });
  118. this.$watch('video', function(newVal, oldVal) {
  119. if(newVal && newVal != oldVal) {
  120. this.startPlayback();
  121. } else {
  122. this.stopPlayback();
  123. }
  124. });
  125. let mmt = moment();
  126. let n = mmt.diff(mmt.clone().startOf('day'), 'minutes');
  127. n -= 10;
  128. if(n < 0) n = 0;
  129. this.$refs.timeRule.clickMinute(n);
  130. console.log(this.devid, this.channel, this.day)
  131. this.getRecords(true);
  132. $(window).on("beforeunload", this.beforeUnload);
  133. },
  134. keyDown(e) {
  135. if(e.keyCode == 27) {
  136. this.$el.querySelector('.fa-chevron-left').click();
  137. }
  138. },
  139. isMobile() {
  140. return videojs.browser.IS_IOS || videojs.browser.IS_ANDROID;
  141. },
  142. showDatePicker() {
  143. $(this.$refs.datePicker.$el).focus();
  144. },
  145. updateDay(day) {
  146. this.$nextTick(() => {
  147. this.$router.replace({
  148. path: `/devices/playback/${this.mode}/${this.devid}/${this.channel}/${day}`,
  149. query: Object.assign({}, this.$route.query, { center: this.center, indistinct: this.indistinct}),
  150. });
  151. })
  152. },
  153. nextTimeRange() {
  154. var end = moment(this.day, "YYYYMMDD").endOf('day');
  155. var now = moment().startOf("second");
  156. if(end.isAfter(now, "second")) {
  157. end = now;
  158. }
  159. var r1 = moment(this.timerange[1]);
  160. if(r1.isSameOrAfter(end, "second")){
  161. return false;
  162. }
  163. var r2 = moment(this.timerange[1]).add(6, 'hours');
  164. if(r2.isAfter(end)) {
  165. r2 = end;
  166. }
  167. if(r2.clone().startOf("minute").isSameOrBefore(r1.clone().startOf("minute"), "second")) {
  168. return false;
  169. }
  170. console.log(r1.format("YY-MM-DD HH:mm:ss"), "~", r2.format("YY-MM-DD HH:mm:ss"), "loading...");
  171. this.timerange = [r1.toDate(), r2.toDate()];
  172. return true;
  173. },
  174. getRecords(refresh) {
  175. if(refresh) {
  176. this.loading = true;
  177. this.records = [];
  178. }
  179. if(!this.nextTimeRange()){
  180. this.videos = this.records;
  181. this.loading = false;
  182. return
  183. }
  184. $.ajax("/api/v1/playback/recordlist", {
  185. type: 'get',
  186. global: false,
  187. data: {
  188. timeout: 5,
  189. serial: this.devid,
  190. code: this.channel,
  191. center: this.center,
  192. indistinct: this.indistinct,
  193. starttime: moment(this.timerange[0]).format("YYYY-MM-DDTHH:mm:ss"),
  194. endtime: moment(this.timerange[1]).format("YYYY-MM-DDTHH:mm:ss")
  195. }
  196. }).then(ret => {
  197. var items = ret.RecordList || [];
  198. this.records = this.records.concat(items.filter(item => {
  199. if(!item || !item.StartTime || !item.EndTime) {
  200. return false;
  201. }
  202. return true;
  203. }));
  204. }).always(() => {
  205. this.$nextTick(() => {
  206. this.getRecords(false);
  207. })
  208. });
  209. },
  210. formatName(row, col, cell) {
  211. if (cell) return cell;
  212. return "-";
  213. },
  214. stopPlayback() {
  215. return new Promise((resolve, reject) => {
  216. if(this.touchTimer) {
  217. clearInterval(this.touchTimer);
  218. this.touchTimer = 0;
  219. }
  220. if(!this.streamID) {
  221. resolve();
  222. return
  223. }
  224. $.get("/api/v1/playback/stop", {
  225. streamid: this.streamID
  226. }).always(() => {
  227. this.streamID = "";
  228. this.videoUrl = "";
  229. resolve();
  230. })
  231. })
  232. },
  233. beforeUnload(event) {
  234. this.stopPlayback();
  235. event.preventDefault();
  236. event.returnValue = '';
  237. },
  238. async startPlayback() {
  239. await this.stopPlayback();
  240. if(!this.video) return;
  241. this.videoLoading = true;
  242. $.get("/api/v1/playback/start", {
  243. serial: this.devid,
  244. code: this.channel,
  245. starttime: this.video.StartTime,
  246. endtime: this.video.EndTime
  247. }).then(streamInfo => {
  248. var videoUrl = this.isMobile() ? streamInfo.HLS : streamInfo.RTMP;
  249. if(this.flvSupported()) {
  250. videoUrl = this.isIE() ? streamInfo.WS_FLV : streamInfo.FLV;
  251. }
  252. switch(String(this.serverInfo.PreferStreamFmt).toUpperCase()) {
  253. case "WEBRTC":
  254. if(this.rtcSupported()) {
  255. videoUrl = streamInfo.WEBRTC;
  256. }
  257. break;
  258. case "FLV":
  259. if(this.flvSupported() && !this.isIE()) {
  260. videoUrl = streamInfo.FLV;
  261. }
  262. break;
  263. case "WS_FLV":
  264. case "WS-FLV":
  265. if(this.flvSupported()) {
  266. videoUrl = streamInfo.WS_FLV;
  267. }
  268. break;
  269. case "HLS":
  270. videoUrl = streamInfo.HLS;
  271. break;
  272. case "RTMP":
  273. videoUrl = streamInfo.RTMP;
  274. break;
  275. }
  276. this.streamID = streamInfo.StreamID;
  277. this.videoUrl = videoUrl;
  278. // no need since v1.2
  279. // this.touchTimer = setInterval(() => {
  280. // this.touchPlayback()
  281. // }, 15000);
  282. }).always(() => {
  283. this.videoLoading = false;
  284. });
  285. },
  286. touchPlayback() {
  287. if(!this.streamID) return;
  288. $.get("/api/v1/playback/touch",{
  289. streamid: this.streamID
  290. })
  291. },
  292. toListView() {
  293. this.$router.replace({
  294. path: `/devices/playback/list/${this.devid}/${this.channel}/${this.day}`,
  295. query: Object.assign({}, this.$route.query, { center: this.center, indistinct: this.indistinct }),
  296. });
  297. },
  298. onTimeChange(video) {
  299. this.video = video;
  300. },
  301. onVideoEnd() {
  302. },
  303. onVideoTimeUpdate() {
  304. }
  305. },
  306. mounted() {
  307. // let mmt = moment();
  308. // let n = mmt.diff(mmt.clone().startOf('day'), 'minutes');
  309. // n -= 10;
  310. // if(n < 0) n = 0;
  311. // this.$refs.timeRule.clickMinute(n);
  312. // console.log(this.devid, this.channel, this.day)
  313. // this.getRecords(true);
  314. // $(window).on("beforeunload", this.beforeUnload);
  315. },
  316. beforeDestroy() {
  317. $(window).off("beforeunload", this.beforeUnload);
  318. this.stopPlayback();
  319. },
  320. beforeRouteLeave(to, from, next) {
  321. this.stopPlayback();
  322. next();
  323. },
  324. beforeRouteEnter(to, from, next) {
  325. next(vm => {
  326. vm.center = to.query.center;
  327. vm.indistinct = to.query.indistinct;
  328. vm.ready();
  329. })
  330. },
  331. beforeRouteUpdate(to, from, next) {
  332. this.center = to.query.center;
  333. this.indistinct = to.query.indistinct;
  334. this.stopPlayback();
  335. next();
  336. this.$nextTick(() => {
  337. if(!this.loading) {
  338. console.log(this.devid, this.channel, this.day)
  339. this.getRecords(true);
  340. }
  341. })
  342. }
  343. };
  344. </script>