index.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. <template>
  2. <div
  3. class="o-video is-ratio--16_9"
  4. :class="{ offline: !online, mask: !isPlaying, controls, floating, fullscreen: isFullscreen }"
  5. @mousemove="onMouseMove"
  6. @click.stop="onClick"
  7. >
  8. <template v-if="online">
  9. <video
  10. ref="video"
  11. class="o-video__player o-simple-video"
  12. :poster="poster"
  13. autoplay
  14. muted
  15. @play="onVideoPlay"
  16. @pause="onVideoPause"
  17. @waiting="onVideoWaiting"
  18. @playing="onVideoPlaying"
  19. @error="onVideoError"
  20. @ended="onVideoEnded"
  21. />
  22. <div class="o-video__mask">
  23. <div
  24. class="l-flex--row center o-video__btn u-pointer"
  25. @click.stop="onPlayOrPause"
  26. >
  27. <i :class="statusIconClass" />
  28. </div>
  29. </div>
  30. </template>
  31. <div
  32. v-if="controls"
  33. class="l-flex--row c-footer u-font-size--sm"
  34. >
  35. <div class="l-flex__fill c-sibling-item u-ellipsis">
  36. {{ device.name }}
  37. </div>
  38. <slot
  39. name="controls"
  40. :can-play="canPlay"
  41. :on-full-screen="onFullScreen"
  42. :waiting-or-loading="loading || waiting"
  43. :online="online"
  44. :is-playing="isPlaying"
  45. >
  46. <template v-if="online">
  47. <i
  48. v-if="loadingQuality"
  49. class="c-sibling-item el-icon-loading"
  50. />
  51. <div
  52. v-else
  53. class="l-flex__none c-sibling-item o-quality-menu u-pointer"
  54. @click.stop="toggleQualityMenu"
  55. >
  56. <div
  57. v-if="showQualityMenu"
  58. class="o-quality-menu__list u-font-size--sm u-text--center"
  59. @click.stop
  60. >
  61. <div
  62. v-for="item in qualities"
  63. :key="item.value"
  64. class="o-quality-menu__item has-active"
  65. :class="{ 'u-color--success': quality.value === item.value }"
  66. @click.stop="changeQuality(item)"
  67. >
  68. {{ item.label }}
  69. </div>
  70. </div>
  71. <span class="has-active">
  72. {{ quality.label }}
  73. </span>
  74. </div>
  75. <i
  76. class="c-sibling-item el-icon-full-screen has-active"
  77. @click.stop="onFullScreen"
  78. />
  79. </template>
  80. </slot>
  81. </div>
  82. </div>
  83. </template>
  84. <script>
  85. import {
  86. GATEWAY,
  87. Quality
  88. } from '@/constant.js'
  89. import {
  90. authCode,
  91. getRecordConfig,
  92. addRecordConfig,
  93. updateRecordConfig,
  94. getDevice
  95. } from '@/api/device.js'
  96. import playerMixin from '../player.js'
  97. export default {
  98. name: 'DevicePlayer',
  99. mixins: [playerMixin],
  100. props: {
  101. device: {
  102. type: Object,
  103. required: true
  104. },
  105. checkOnline: {
  106. type: [Boolean, String],
  107. default: false
  108. },
  109. useInitial: {
  110. type: [Boolean, String],
  111. default: false
  112. }
  113. },
  114. data () {
  115. return {
  116. online: false,
  117. floating: true,
  118. qualities: [
  119. { value: 'f', label: '标清' },
  120. { value: 'ff', label: '高清' },
  121. { value: 'fff', label: '超清' }
  122. ],
  123. loadingQuality: true,
  124. showQualityMenu: false,
  125. quality: null,
  126. recordConfig: null
  127. }
  128. },
  129. computed: {
  130. deviceOnline () {
  131. return this.device.onlineStatus === 1
  132. },
  133. canPlay () {
  134. return !this.loading && !this.needReset
  135. }
  136. },
  137. watch: {
  138. deviceOnline (val) {
  139. if (this.checkOnline) {
  140. return
  141. }
  142. this.updateOnlineStatus(val)
  143. },
  144. isFullscreen () {
  145. this.onMouseMove()
  146. }
  147. },
  148. created () {
  149. this.$delayMoveTimer = -1
  150. this.$checkTimer = -1
  151. },
  152. mounted () {
  153. if (this.checkOnline && !this.useInitial) {
  154. this.createPlayer()
  155. } else {
  156. this.updateOnlineStatus(this.deviceOnline)
  157. }
  158. },
  159. beforeDestroy () {
  160. clearTimeout(this.$delayMoveTimer)
  161. clearTimeout(this.$checkTimer)
  162. this.hideQualityMenu()
  163. },
  164. methods: {
  165. onClick () {
  166. this.$emit('click', this.device)
  167. },
  168. onMouseMove () {
  169. if (this.keep) {
  170. return
  171. }
  172. clearTimeout(this.$delayMoveTimer)
  173. this.floating = false
  174. if (!this.isFullscreen) {
  175. this.$delayMoveTimer = setTimeout(() => {
  176. this.floating = true
  177. }, 3000)
  178. }
  179. },
  180. onVideoReset () {
  181. this.recordConfig = null
  182. this.$playerInfo = null
  183. },
  184. getRecordConfig () {
  185. this.loadingQuality = true
  186. getRecordConfig(this.device.id, { custom: true }).then(
  187. ({ data }) => {
  188. if (data) {
  189. this.loadingQuality = false
  190. this.onUpdateRecordConfig(data)
  191. } else {
  192. this.setRecordConfig(this.qualities[0])
  193. }
  194. },
  195. () => {
  196. this.loadingQuality = false
  197. this.onVideoDestroyByError('获取回采配置失败')
  198. }
  199. )
  200. },
  201. onUpdateRecordConfig (data) {
  202. const { videoWidth } = data
  203. const key = Object.keys(Quality).find(key => Quality[key].videoWidth === videoWidth) || 'fff'
  204. this.quality = this.qualities.find(({ value }) => value === key) || this.qualities[0]
  205. this.recordConfig = data
  206. this.onCreatePlayer()
  207. },
  208. setRecordConfig (quality) {
  209. this.loadingQuality = true
  210. ;(this.recordConfig ? updateRecordConfig : addRecordConfig)({
  211. deviceId: this.device.id,
  212. ...Quality[quality.value]
  213. }, { custom: true }).then(
  214. ({ data }) => {
  215. this.loadingQuality = false
  216. this.destroyPlayer()
  217. this.onUpdateRecordConfig(data)
  218. },
  219. () => {
  220. this.loadingQuality = false
  221. if (!this.recordConfig) {
  222. this.onVideoDestroyByError('设置初始回采配置失败')
  223. }
  224. }
  225. )
  226. },
  227. hideQualityMenu () {
  228. if (this.showQualityMenu) {
  229. this.showQualityMenu = false
  230. document.removeEventListener('click', this.hideQualityMenu)
  231. }
  232. },
  233. toggleQualityMenu () {
  234. if (this.showQualityMenu) {
  235. this.hideQualityMenu()
  236. } else {
  237. document.addEventListener('click', this.hideQualityMenu)
  238. this.showQualityMenu = true
  239. }
  240. },
  241. changeQuality (quality) {
  242. if (this.quality.value !== quality.value) {
  243. this.setRecordConfig(quality)
  244. } else if (this.needReset) {
  245. this.createPlayer()
  246. }
  247. this.hideQualityMenu()
  248. },
  249. getAuthCode () {
  250. this.$playerInfo = null
  251. authCode(this.recordConfig.stream, { custom: true }).then(
  252. ({ data }) => {
  253. this.$playerInfo = data
  254. this.onCreatePlayer()
  255. },
  256. () => {
  257. this.onVideoDestroyByError('获取授权失败')
  258. }
  259. )
  260. },
  261. updateOnlineStatus (online) {
  262. this.online = online
  263. if (online) {
  264. this.$nextTick(this.createPlayer)
  265. } else {
  266. this.hideQualityMenu()
  267. this.onVideoReset()
  268. this.destroyPlayer()
  269. }
  270. },
  271. createPlayer () {
  272. if (this.$timer === null) {
  273. return
  274. }
  275. this.loading = true
  276. if (this.checkOnline) {
  277. clearTimeout(this.$checkTimer)
  278. getDevice(this.device.id, { custom: true }).then(
  279. ({ data }) => {
  280. if (this.$timer === null) {
  281. return
  282. }
  283. if (data) {
  284. const online = data.onlineStatus === 1
  285. this.online = online
  286. if (online) {
  287. this.$nextTick(this.onCreatePlayer)
  288. } else {
  289. this.hideQualityMenu()
  290. this.destroyPlayer()
  291. this.onVideoReset()
  292. this.checkOnlineStatus(5000)
  293. }
  294. }
  295. },
  296. () => {
  297. if (this.$timer === null) {
  298. return
  299. }
  300. this.checkOnlineStatus(2000)
  301. }
  302. )
  303. } else {
  304. this.onCreatePlayer()
  305. }
  306. },
  307. onCreatePlayer () {
  308. if (this.$timer === null) {
  309. return
  310. }
  311. if (!this.recordConfig) {
  312. this.getRecordConfig()
  313. return
  314. }
  315. // if (!this.$playerInfo) {
  316. // this.getAuthCode()
  317. // return
  318. // }
  319. // const { vhost = '__defaultVhost__', token, timestamp, expire } = this.$playerInfo
  320. // if (Number(timestamp) + Number(expire) * 1000 <= Date.now()) {
  321. // this.getAuthCode()
  322. // return
  323. // }
  324. // this.playUrl(`${GATEWAY}/live/${this.recordConfig.stream}.flv?vhost=${vhost}&authorization=${token}&timestamp=${timestamp}&expire=${expire}`)
  325. this.playUrl(`${GATEWAY}/live/${this.recordConfig.stream}.flv?vhost=__defaultVhost__`)
  326. },
  327. checkOnlineStatus (delay = 5000) {
  328. clearTimeout(this.$checkTimer)
  329. this.$checkTimer = setTimeout(this.createPlayer, delay + Math.random() * delay | 0)
  330. }
  331. }
  332. }
  333. </script>
  334. <style lang="scss" scoped>
  335. .o-quality-menu {
  336. display: inline-block;
  337. position: relative;
  338. padding: 0 $padding--xs;
  339. z-index: 1;
  340. &__list {
  341. position: absolute;
  342. right: 0;
  343. bottom: 100%;
  344. width: 100%;
  345. margin-bottom: $spacing--2xs;
  346. border-radius: $radius;
  347. background-color: rgba(#000, 0.85);
  348. z-index: -1;
  349. }
  350. &__item {
  351. padding: $padding--2xs 0;
  352. }
  353. }
  354. </style>