index.vue 6.8 KB

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