VideoPlayerCard.vue 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. <template>
  2. <div class="video-player-wrapper">
  3. <div id="dplayer" ref="dplayerContainer"></div>
  4. </div>
  5. </template>
  6. <script>
  7. import flvjs from 'flv.js'
  8. import DPlayer from 'dplayer'
  9. import { videoUrl } from '@/api/video'
  10. export default {
  11. name: 'VideoPlayerCard',
  12. props: {
  13. videoId: { type: String, required: true },
  14. videoData: { type: Object, required: true },
  15. userToken: { type: String, default: null },
  16. sendEvent: { type: Boolean, default: true }
  17. },
  18. data() {
  19. return {
  20. player: null,
  21. wsClient: null,
  22. wsUrl: null,
  23. wsReconnectLock: false,
  24. intervalEvent: null,
  25. danmakuApi: process.env.VUE_APP_SERVER_URL + '/api/comment/danmaku/'
  26. }
  27. },
  28. watch: {
  29. videoId: {
  30. immediate: true,
  31. handler(val) {
  32. if (val) this.loadVideoSource()
  33. }
  34. }
  35. },
  36. beforeDestroy() {
  37. this.destroyResources()
  38. },
  39. methods: {
  40. destroyResources() {
  41. if (this.player) this.player.destroy()
  42. if (this.wsClient) this.wsClient.close()
  43. if (this.intervalEvent) clearInterval(this.intervalEvent)
  44. },
  45. loadVideoSource() {
  46. this.destroyResources()
  47. videoUrl(this.videoId).then(res => {
  48. if (res.code === 0) {
  49. // 初始化 WebSocket
  50. if (this.userToken && this.sendEvent) {
  51. this.wsUrl = this.getWsUrl(this.videoId)
  52. this.initWebSocket()
  53. }
  54. // 初始化播放器
  55. const { type, urls, currentTime } = res.data
  56. if (type === 'mp4') {
  57. urls.forEach(u => u.type = 'normal')
  58. this.initMp4Player(urls, currentTime)
  59. } else if (type === 'flv') {
  60. this.$message.error('flv player not implement')
  61. }
  62. }
  63. })
  64. },
  65. getWsUrl(videoId) {
  66. const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://'
  67. const host = window.location.host
  68. return `${protocol}${host}/ws/media?userToken=${this.userToken}&videoId=${videoId}`
  69. },
  70. initMp4Player(urls, pos) {
  71. this.player = new DPlayer({
  72. container: this.$refs.dplayerContainer,
  73. lang: 'zh-cn',
  74. screenshot: true,
  75. volume: 0.1,
  76. video: { pic: this.videoData.coverUrl, defaultQuality: 0, quality: urls },
  77. danmaku: { id: this.videoId, api: this.danmakuApi, token: this.userToken, bottom: '15%', unlimited: true }
  78. })
  79. this.player.seek(pos)
  80. this.bindEvents()
  81. },
  82. bindEvents() {
  83. let ended = false
  84. this.player.on('play', () => {
  85. if (this.sendEvent) {
  86. clearInterval(this.intervalEvent)
  87. this.intervalEvent = setInterval(() => {
  88. if (!ended) this.sendProgress(false)
  89. }, 5000)
  90. }
  91. })
  92. this.player.on('ended', () => {
  93. ended = true
  94. clearInterval(this.intervalEvent)
  95. this.sendProgress(true)
  96. })
  97. },
  98. sendProgress(isEnded) {
  99. const jsonData = {
  100. type: 'progress',
  101. direction: 'c2s',
  102. data: {
  103. mediaId: this.videoId,
  104. mediaType: 1,
  105. currentTime: this.player.video.currentTime,
  106. ended: isEnded
  107. }
  108. }
  109. if (this.wsClient && this.wsClient.readyState === WebSocket.OPEN) {
  110. this.wsClient.send(JSON.stringify(jsonData))
  111. }
  112. },
  113. initWebSocket() {
  114. this.wsClient = new WebSocket(this.wsUrl)
  115. this.wsClient.onopen = () => this.$emit('ws-status', true)
  116. this.wsClient.onclose = () => {
  117. this.$emit('ws-status', false)
  118. this.reconnect()
  119. }
  120. this.wsClient.onerror = () => this.reconnect()
  121. this.wsClient.onmessage = (evt) => {
  122. const msg = JSON.parse(evt.data)
  123. this.$emit('update-view-count', msg.viewCount)
  124. }
  125. },
  126. reconnect() {
  127. if (this.wsReconnectLock) return
  128. this.wsReconnectLock = true
  129. setTimeout(() => {
  130. this.initWebSocket()
  131. this.wsReconnectLock = false
  132. }, 5000)
  133. }
  134. }
  135. }
  136. </script>
  137. <style scoped>
  138. .video-player-wrapper {
  139. background: #000;
  140. border-radius: 4px;
  141. overflow: hidden;
  142. width: 100%;
  143. }
  144. #dplayer {
  145. height: 520px;
  146. }
  147. @media screen and (max-width: 768px) {
  148. #dplayer { height: 240px; }
  149. }
  150. /* 隐藏网页全屏按钮(左侧那个按钮) */
  151. ::v-deep .dplayer-full-in-icon {
  152. display: none !important;
  153. }
  154. /* 调整剩下的全屏按钮间距,防止右侧留白过大 */
  155. ::v-deep .dplayer-full-icon {
  156. margin-right: 5px !important;
  157. }
  158. </style>