LivePlayer.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. <template>
  2. <div class="live-player">
  3. <!-- 修改:所有录播和回放都使用同一个video元素,MP4和HLS都支持 -->
  4. <video
  5. v-if="videoParam.liveType == 2 || videoParam.liveType == 3"
  6. ref="videoPlayer"
  7. :loop="videoParam.liveType == 2"
  8. autoplay
  9. class="player"
  10. >
  11. <!-- 去掉type,让浏览器自动检测 -->
  12. </video>
  13. <video
  14. v-if="videoParam.liveType == 1"
  15. ref="livePlayer"
  16. autoplay
  17. class="player"
  18. >
  19. </video>
  20. </div>
  21. </template>
  22. <script>
  23. import Hls from "hls.js";
  24. export default {
  25. name: 'LivePlayer',
  26. props: {
  27. videoParam: {
  28. type: Object,
  29. required: true,
  30. default: () => ({
  31. startTime: '',
  32. livingUrl: '',
  33. liveType: 1,
  34. videoUrl: ''
  35. }),
  36. }
  37. },
  38. data() {
  39. return {
  40. videoDuration: 0,
  41. startTime: 0,
  42. hls: null,
  43. liveHls: null // 分开管理直播和录播的HLS实例
  44. };
  45. },
  46. mounted() {
  47. this.initPlayer();
  48. },
  49. methods: {
  50. videoPlay(url) {
  51. const videoElement = this.$refs.videoPlayer;
  52. if (!videoElement) {
  53. console.error('找不到 video 元素');
  54. return;
  55. }
  56. // 判断是否是MP4格式
  57. const isMp4 = url.toLowerCase().endsWith('.mp4') ||
  58. url.toLowerCase().includes('.mp4?');
  59. // 判断是否是HLS格式(m3u8)
  60. const isHls = url.toLowerCase().endsWith('.m3u8') ||
  61. url.toLowerCase().includes('.m3u8?');
  62. if (isHls && Hls.isSupported()) {
  63. // HLS格式使用hls.js播放
  64. this.hls = new Hls();
  65. this.hls.attachMedia(videoElement);
  66. this.hls.on(Hls.Events.MEDIA_ATTACHED, () => {
  67. this.hls.loadSource(url);
  68. this.hls.on(Hls.Events.STREAM_LOADED, (event, data) => {
  69. videoElement.play();
  70. });
  71. });
  72. this.hls.on(Hls.Events.ERROR, (event, data) => {
  73. console.error('HLS 错误:', data);
  74. });
  75. // 录播的时间位置计算
  76. if (this.videoParam.liveType === 2) {
  77. videoElement.addEventListener('loadedmetadata', () => {
  78. this.videoDuration = videoElement.duration;
  79. this.updateVideoPosition(videoElement);
  80. });
  81. }
  82. } else if (isMp4 || !isHls) {
  83. // MP4格式或非HLS格式直接使用video元素播放
  84. videoElement.src = url;
  85. // 录播的时间位置计算
  86. if (this.videoParam.liveType === 2) {
  87. videoElement.addEventListener('loadedmetadata', () => {
  88. this.videoDuration = videoElement.duration;
  89. this.updateVideoPosition(videoElement);
  90. });
  91. }
  92. // 触发播放
  93. videoElement.load();
  94. videoElement.play().catch(e => {
  95. console.warn('自动播放失败:', e);
  96. });
  97. } else {
  98. // 不支持HLS的浏览器,尝试直接播放
  99. console.warn('浏览器不支持HLS,尝试直接播放');
  100. videoElement.src = url;
  101. videoElement.load();
  102. videoElement.play().catch(e => {
  103. console.warn('播放失败:', e);
  104. });
  105. }
  106. },
  107. updateVideoPosition(video) {
  108. const currentTime = new Date().getTime();
  109. this.startTime = new Date(this.videoParam.startTime).getTime();
  110. const elapsedTime = currentTime - this.startTime;
  111. if (elapsedTime < 0) {
  112. video.currentTime = 0;
  113. return;
  114. }
  115. const elapsedSeconds = elapsedTime / 1000;
  116. if (this.videoDuration > 0) {
  117. video.currentTime = elapsedSeconds % this.videoDuration;
  118. }
  119. },
  120. livePlay(url) {
  121. if (Hls.isSupported()) {
  122. const videoElement = this.$refs.livePlayer;
  123. if (!videoElement) {
  124. console.error('找不到 video 元素');
  125. return;
  126. }
  127. this.liveHls = new Hls();
  128. this.liveHls.attachMedia(videoElement);
  129. this.liveHls.on(Hls.Events.MEDIA_ATTACHED, () => {
  130. this.liveHls.loadSource(url);
  131. this.liveHls.on(Hls.Events.STREAM_LOADED, (event, data) => {
  132. videoElement.play();
  133. });
  134. });
  135. this.liveHls.on(Hls.Events.ERROR, (event, data) => {
  136. console.error('HLS 错误:', data);
  137. });
  138. } else {
  139. // 浏览器原生支持HLS(如Safari)
  140. const videoElement = this.$refs.livePlayer;
  141. if (videoElement.canPlayType('application/vnd.apple.mpegurl')) {
  142. videoElement.src = url;
  143. videoElement.play().catch(e => {
  144. console.warn('自动播放失败:', e);
  145. });
  146. } else {
  147. console.error('浏览器不支持 HLS');
  148. }
  149. }
  150. },
  151. initPlayer() {
  152. // 清理之前的实例
  153. if (this.hls) {
  154. this.hls.destroy();
  155. this.hls = null;
  156. }
  157. if (this.liveHls) {
  158. this.liveHls.destroy();
  159. this.liveHls = null;
  160. }
  161. if (this.videoParam.liveType === 1) {
  162. // 直播
  163. const isUrl = this.videoParam.livingUrl === null || this.videoParam.livingUrl.trim() === '';
  164. if (isUrl) {
  165. console.error('直播地址为空,无法初始化播放器');
  166. return;
  167. }
  168. this.$nextTick(() => {
  169. this.livePlay(this.videoParam.livingUrl);
  170. });
  171. return;
  172. }
  173. const viUrl = this.videoParam.videoUrl === null || this.videoParam.videoUrl.trim() === '';
  174. if (viUrl) {
  175. console.error('播放地址为空,无法初始化播放器');
  176. return;
  177. }
  178. if (this.videoParam.liveType === 2 || this.videoParam.liveType === 3) {
  179. // 录播或直播回放
  180. this.$nextTick(() => {
  181. this.videoPlay(this.videoParam.videoUrl);
  182. });
  183. } else {
  184. console.error('直播类型错误,无法初始化播放器');
  185. }
  186. }
  187. },
  188. watch: {
  189. // 监听videoParam变化,重新初始化播放器
  190. videoParam: {
  191. handler() {
  192. this.$nextTick(() => {
  193. this.initPlayer();
  194. });
  195. },
  196. deep: true
  197. }
  198. },
  199. beforeDestroy() {
  200. // 销毁HLS实例
  201. this.hls?.destroy();
  202. this.liveHls?.destroy();
  203. // 清理video元素
  204. const videoElements = [
  205. this.$refs.videoPlayer,
  206. this.$refs.livePlayer
  207. ];
  208. videoElements.forEach(video => {
  209. if (video) {
  210. video.pause();
  211. video.src = '';
  212. video.load();
  213. }
  214. });
  215. }
  216. };
  217. </script>
  218. <style scoped>
  219. .live-player {
  220. margin-bottom: 20px;
  221. }
  222. .player {
  223. width: 100%;
  224. height: auto;
  225. border-radius: 8px;
  226. max-height: 300px; /* 设置最大高度,避免过大 */
  227. object-fit: contain; /* 保持比例,不拉伸 */
  228. background-color: #000; /* 黑色背景,避免视频加载时显示白色 */
  229. }
  230. </style>