index.vue 7.2 KB

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