index.vue 6.8 KB

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