live.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. <template>
  2. <div id="devicePosition" :style="`width:100vw; height: ${isFullScreen?'100vh':'91vh'}`" ref="container">
  3. <el-container v-loading="loading" style="height: 100%;" :element-loading-text="$t('loading')" >
  4. <el-aside width="300px" v-show="asideHide" :style="`background-color: ${isFullScreen?'#eee':'#ffffff'}`">
  5. <DeviceTree :clickEvent="clickEvent" :contextMenuEvent="contextMenuEvent"></DeviceTree>
  6. </el-aside>
  7. <el-container>
  8. <el-header height="5vh"
  9. :style="`text-align: left;font-size: 17px;line-height:5vh;
  10. background-color:${isFullScreen?'#000':'#fff'};
  11. color:${!isFullScreen?'#000':'#fff'}`
  12. ">
  13. <div class="b-left">
  14. <el-button :icon="`el-icon-s-${asideHide?'fold':'unfold'}`" circle size="small" @click="asideHide=!asideHide" :type="isFullScreen?'goon':''"></el-button>
  15. <el-button :icon="`el-icon-${isFullScreen?'files':'full-screen'}`" circle size="small" @click="switchFullScreenHandle" :type="isFullScreen?'goon':''"></el-button>
  16. {{ $t('menu.liveMonitor') }}:
  17. <i class="el-icon-full-screen btn" :class="{active:spilt==1}" @click="spilt=1"/>
  18. <i class="el-icon-menu btn" :class="{active:spilt==4}" @click="spilt=4"/>
  19. <i class="el-icon-s-grid btn" :class="{active:spilt==9}" @click="spilt=9"/>
  20. </div>
  21. <div class="b-right">
  22. <el-button></el-button>
  23. </div>
  24. </el-header>
  25. <el-main style="padding: 0;background-color: #000;">
  26. <div :style="`width: 100%;height: ${isFullScreen?'94vh':'85vh'};display: flex;flex-wrap: wrap;background-color: #000;`">
  27. <div v-for="i in spilt" :key="i" class="play-box"
  28. :style="liveStyle" :class="{redborder:playerIdx == (i-1)}"
  29. @click="playerIdx = (i-1)">
  30. <div v-if="!videoUrl[i-1]" style="color: #ffffff;font-size: 30px;font-weight: bold;">{{ i }}</div>
  31. <!-- <player ref="player" v-else :videoUrl="videoUrl[i-1]" fluent autoplay @screenshot="shot"-->
  32. <!-- @destroy="destroy"/>-->
  33. <rtc-player ref="player" @eventCallback="rtcPlayHandle" v-else :videoUrl="videoUrl[i-1]" fluent autoplay @screenshot="shot"
  34. @destroy="destroy"/>
  35. </div>
  36. </div>
  37. </el-main>
  38. </el-container>
  39. </el-container>
  40. </div>
  41. </template>
  42. <script>
  43. import uiHeader from "../layout/UiHeader.vue";
  44. import player from './common/jessibuca.vue'
  45. import DeviceTree from './common/DeviceTree.vue'
  46. import {exitFullscreen, launchIntoFullscreen} from "@/until/dom";
  47. import rtcPlayer from "@/components/dialog/rtcPlayer.vue";
  48. import handle from "@/until/handle";
  49. import {sleep} from "@/until/time";
  50. export default {
  51. name: "live",
  52. components: {
  53. uiHeader, player, DeviceTree, rtcPlayer
  54. },
  55. data() {
  56. return {
  57. videoUrl: [''],
  58. spilt: 1,//分屏
  59. playerIdx: 0,//激活播放器
  60. player: {
  61. webRTC: ["rtc", "rtcs"],
  62. flv: ["ws_flv", "wss_flv"],
  63. h265: ["ws_flv", "wss_flv"]
  64. },
  65. updateLooper: 0, //数据刷新轮训标志
  66. count: 15,
  67. total: 0,
  68. asideHide: true,// 是否隐藏侧边栏
  69. isFullScreen: false,// 是否全屏
  70. //channel
  71. loading: false,
  72. isPlay: false,// 是否已经开始推流
  73. };
  74. },
  75. mounted() {
  76. },
  77. created() {
  78. this.checkPlayByParam()
  79. },
  80. computed: {
  81. liveStyle() {
  82. let style = {width: '100%', height: '100%'}
  83. switch (this.spilt) {
  84. case 4:
  85. style = {width: '49%', height: '49%','margin-left':'0.5%'}
  86. break
  87. case 9:
  88. style = {width: '32%', height: '32%','margin-left':'0.8%'}
  89. break
  90. }
  91. this.$nextTick(() => {
  92. for (let i = 0; i < this.spilt; i++) {
  93. const player = this.$refs.player
  94. player && player[i] && player[i].updatePlayerDomSize()
  95. }
  96. })
  97. return style
  98. }
  99. },
  100. watch: {
  101. spilt(newValue) {
  102. console.log("切换画幅;" + newValue)
  103. let that = this
  104. for (let i = 1; i <= newValue; i++) {
  105. if (!that.$refs['player' + i]) {
  106. continue
  107. }
  108. this.$nextTick(() => {
  109. if (that.$refs['player' + i] instanceof Array) {
  110. that.$refs['player' + i][0].resize()
  111. } else {
  112. that.$refs['player' + i].resize()
  113. }
  114. })
  115. }
  116. window.localStorage.setItem('split', newValue)
  117. },
  118. '$route.fullPath': 'checkPlayByParam'
  119. },
  120. destroyed() {
  121. clearTimeout(this.updateLooper);
  122. },
  123. methods: {
  124. destroy(idx) {
  125. console.log(idx);
  126. this.clear(idx.substring(idx.length - 1))
  127. },
  128. clickEvent: function (device, data, isCatalog) {
  129. if (data.channelId && !isCatalog) {
  130. if (device.online === 0) {
  131. this.$message.error(this.$t('device.offlineNotAllowLive'));
  132. }else {
  133. this.sendDevicePush(data)
  134. }
  135. }
  136. },
  137. contextMenuEvent: function (device, event, data, isCatalog) {
  138. },
  139. //通知设备上传媒体流
  140. async sendDevicePush(itemData) {
  141. // if (itemData.status === 0) {
  142. // this.$message.error('设备离线!');
  143. // return
  144. // }
  145. this.save(itemData)
  146. let deviceId = itemData.deviceId;
  147. // this.isLoging = true;
  148. let channelId = itemData.channelId;
  149. console.log(itemData);
  150. console.log("通知设备推流1:" + deviceId + " : " + channelId);
  151. let idxTmp = this.playerIdx
  152. this.loading = true;
  153. let [err,res] = await handle(this.$axios.axios({
  154. method: 'get',
  155. url: '/api/play/start/' + deviceId + '/' + channelId
  156. }))
  157. this.loading = false
  158. if(err){
  159. this.$message.error(this.$t('device.pushFail'));
  160. return
  161. }
  162. if (res.data.code === 0 && res.data.data) {
  163. let videoUrl;
  164. if (location.protocol === "https:") {
  165. videoUrl = res.data.data[this.player.webRTC[1]];
  166. } else {
  167. videoUrl = res.data.data[this.player.webRTC[0]];
  168. }
  169. console.log("推流成功:" + videoUrl)
  170. itemData.playUrl = videoUrl;
  171. this.setPlayUrl(videoUrl, idxTmp);
  172. // 添加监听
  173. setTimeout(() => {
  174. this.loadSsrcList();
  175. this.autoLoad();
  176. }, 10 * 1000)
  177. } else {
  178. this.$message.error(res.data.msg);
  179. }
  180. },
  181. async setPlayUrl(url, idx) {
  182. console.log("设置播放地址:" + url + " : " + idx);
  183. this.$set(this.videoUrl, idx, "");
  184. await sleep(700);
  185. this.$set(this.videoUrl, idx, url)
  186. let _this = this
  187. await sleep(100);
  188. window.localStorage.setItem('videoUrl', JSON.stringify(_this.videoUrl))
  189. },
  190. checkPlayByParam() {
  191. let {deviceId, channelId} = this.$route.query
  192. if (deviceId && channelId) {
  193. this.sendDevicePush({deviceId, channelId})
  194. }
  195. },
  196. shot(e) {
  197. // console.log(e)
  198. // send({code:'image',data:e})
  199. var base64ToBlob = function (code) {
  200. let parts = code.split(';base64,');
  201. let contentType = parts[0].split(':')[1];
  202. let raw = window.atob(parts[1]);
  203. let rawLength = raw.length;
  204. let uInt8Array = new Uint8Array(rawLength);
  205. for (let i = 0; i < rawLength; ++i) {
  206. uInt8Array[i] = raw.charCodeAt(i);
  207. }
  208. return new Blob([uInt8Array], {
  209. type: contentType
  210. });
  211. };
  212. let aLink = document.createElement('a');
  213. let blob = base64ToBlob(e); //new Blob([content]);
  214. let evt = document.createEvent("HTMLEvents");
  215. evt.initEvent("click", true, true); //initEvent 不加后两个参数在FF下会报错 事件类型,是否冒泡,是否阻止浏览器的默认行为
  216. aLink.download = '截图';
  217. aLink.href = URL.createObjectURL(blob);
  218. aLink.click();
  219. },
  220. save(item) {
  221. let dataStr = window.localStorage.getItem('playData') || '[]'
  222. let data = JSON.parse(dataStr);
  223. data[this.playerIdx] = item
  224. window.localStorage.setItem('playData', JSON.stringify(data))
  225. },
  226. clear(idx) {
  227. let dataStr = window.localStorage.getItem('playData') || '[]'
  228. let data = JSON.parse(dataStr);
  229. data[idx - 1] = null;
  230. console.log(data);
  231. window.localStorage.setItem('playData', JSON.stringify(data))
  232. },
  233. switchFullScreenHandle(){
  234. if(this.isFullScreen){
  235. // 退出全屏
  236. exitFullscreen();
  237. this.isFullScreen= false;
  238. }else{
  239. let el = this.$refs.container;
  240. launchIntoFullscreen(el);
  241. this.isFullScreen= true;
  242. }
  243. },
  244. rtcPlayHandle(e){
  245. console.log(e);
  246. },
  247. addEventHandle(){
  248. },
  249. autoLoad(){
  250. if(!this.isPlay){
  251. return console.log(`暂未开始推流`)
  252. }
  253. this.updateLooper = setTimeout(() => {
  254. this.loadSsrcList();
  255. this.autoLoad();
  256. }, 10 * 1000);
  257. },
  258. // 加载当前推流的情况
  259. async loadSsrcList(){
  260. let [err,res] = await handle(this.$axios.axios({
  261. method: 'get',
  262. url: '/api/play/ssrc'
  263. }))
  264. if(err){
  265. this.$message.error(this.$t('device.getPushListFail'));
  266. return
  267. }
  268. let response = res.data;
  269. console.log(response);
  270. if (response.code === 0){
  271. }else{
  272. this.$message.error(response.msg);
  273. }
  274. }
  275. }
  276. };
  277. </script>
  278. <style>
  279. .btn {
  280. margin: 0 10px;
  281. }
  282. .btn:hover {
  283. color: #409EFF;
  284. }
  285. .btn.active {
  286. color: #409EFF;
  287. }
  288. .redborder {
  289. border: 2px solid red !important;
  290. }
  291. .play-box {
  292. background-color: #000000;
  293. border: 2px solid #505050;
  294. display: flex;
  295. align-items: center;
  296. justify-content: center;
  297. }
  298. </style>
  299. <style>
  300. .videoList {
  301. display: flex;
  302. flex-wrap: wrap;
  303. align-content: flex-start;
  304. }
  305. .video-item {
  306. position: relative;
  307. width: 15rem;
  308. height: 10rem;
  309. margin-right: 1rem;
  310. background-color: #000000;
  311. }
  312. .video-item-img {
  313. position: absolute;
  314. top: 0;
  315. bottom: 0;
  316. left: 0;
  317. right: 0;
  318. margin: auto;
  319. width: 100%;
  320. height: 100%;
  321. }
  322. .video-item-img:after {
  323. content: "";
  324. display: inline-block;
  325. position: absolute;
  326. z-index: 2;
  327. top: 0;
  328. bottom: 0;
  329. left: 0;
  330. right: 0;
  331. margin: auto;
  332. width: 3rem;
  333. height: 3rem;
  334. background-image: url("../assets/loading.png");
  335. background-size: cover;
  336. background-color: #000000;
  337. }
  338. .video-item-title {
  339. position: absolute;
  340. bottom: 0;
  341. color: #000000;
  342. background-color: #ffffff;
  343. line-height: 1.5rem;
  344. padding: 0.3rem;
  345. width: 14.4rem;
  346. }
  347. .baidumap {
  348. width: 100%;
  349. height: 100%;
  350. border: none;
  351. position: absolute;
  352. left: 0;
  353. top: 0;
  354. right: 0;
  355. bottom: 0;
  356. margin: auto;
  357. }
  358. /* 去除百度地图版权那行字 和 百度logo */
  359. .baidumap > .BMap_cpyCtrl {
  360. display: none !important;
  361. }
  362. .baidumap > .anchorBL {
  363. display: none !important;
  364. }
  365. .el-button--goon.is-active,
  366. .el-button--goon:active {
  367. background: #20B2AA;
  368. border-color: #20B2AA;
  369. color: #fff;
  370. }
  371. .el-button--goon:focus,
  372. .el-button--goon:hover {
  373. background: #48D1CC;
  374. border-color: #48D1CC;
  375. color: #fff;
  376. }
  377. .el-button--goon {
  378. color: #FFF;
  379. background-color: #292a2a;
  380. border-color: #295656;
  381. }
  382. </style>