|
|
@@ -1,87 +1,246 @@
|
|
|
<template>
|
|
|
+ <!-- <div v-loading="loading">
|
|
|
+
|
|
|
+ </div> -->
|
|
|
<video
|
|
|
:id="'video' + index"
|
|
|
style="background-color: #333"
|
|
|
class="video"
|
|
|
- autoplay
|
|
|
muted
|
|
|
+ playsinline
|
|
|
></video>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import mpegts from 'mpegts.js';
|
|
|
-
|
|
|
-export default {
|
|
|
- name: 'VideoPlayer',
|
|
|
-
|
|
|
- props: {
|
|
|
- videoUrl: {
|
|
|
- type: String,
|
|
|
- default: '',
|
|
|
+ import mpegts from 'mpegts.js';
|
|
|
+
|
|
|
+ export default {
|
|
|
+ name: 'VideoPlayer',
|
|
|
+ props: {
|
|
|
+ videoUrl: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ index: {
|
|
|
+ type: Number,
|
|
|
+ default: 0
|
|
|
+ },
|
|
|
+ playDelaySeconds: {
|
|
|
+ type: Number,
|
|
|
+ default: 5 // 延迟2秒播放
|
|
|
+ }
|
|
|
},
|
|
|
- index: {
|
|
|
- type: Number,
|
|
|
- default: 0,
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ player: null,
|
|
|
+ reconnectAttempts: 0,
|
|
|
+ maxReconnectAttempts: 5,
|
|
|
+ reconnectTimer: null,
|
|
|
+ isManualDestroy: false,
|
|
|
+ isPageUnloading: false, // 页面刷新/关闭标志
|
|
|
+ hasStarted: false, // 防止重复播放
|
|
|
+ config: {
|
|
|
+ liveBufferLatencyChasing: true,
|
|
|
+ liveBufferLatencyMinRemain: 0.5,
|
|
|
+ liveBufferLatencyMaxLatency: 1.5,
|
|
|
+ enableStashBuffer: false,
|
|
|
+ stashInitialSize: 128,
|
|
|
+ enableWorker: true,
|
|
|
+ lazyLoadMaxDuration: 3 * 60
|
|
|
+ }
|
|
|
+ };
|
|
|
},
|
|
|
- },
|
|
|
-
|
|
|
- data() {
|
|
|
- return {
|
|
|
- player: null,
|
|
|
- config: {
|
|
|
- liveBufferLatencyChasing: true,
|
|
|
- liveBufferLatencyChasingOnPaused: true,
|
|
|
- liveBufferLatencyMaxLatency: 1,
|
|
|
- enableWorker: true,
|
|
|
- }
|
|
|
- };
|
|
|
- },
|
|
|
-
|
|
|
- mounted() {
|
|
|
- this.startPlay();
|
|
|
- },
|
|
|
-
|
|
|
- beforeDestroy() {
|
|
|
- this.stopPlay();
|
|
|
- },
|
|
|
-
|
|
|
- methods: {
|
|
|
- startPlay() {
|
|
|
- if (mpegts.getFeatureList().mseLivePlayback) {
|
|
|
- let videoElement = document.getElementById('video' + this.index);
|
|
|
- this.player = mpegts.createPlayer(
|
|
|
- {
|
|
|
- type: 'flv',
|
|
|
- isLive: true,
|
|
|
- hasAudio: false,
|
|
|
- url: this.videoUrl,
|
|
|
- },
|
|
|
- this.config
|
|
|
- );
|
|
|
- this.player.attachMediaElement(videoElement);
|
|
|
- console.log(this.player,'this.player')
|
|
|
- this.player.load();
|
|
|
- setTimeout(()=>{
|
|
|
- this.player.play();
|
|
|
- },1000)
|
|
|
- } else {
|
|
|
- console.log('mpegts.js 不支持');
|
|
|
+ watch: {
|
|
|
+ videoUrl: {
|
|
|
+ handler(newUrl, oldUrl) {
|
|
|
+ if (newUrl && newUrl !== oldUrl) {
|
|
|
+ this.reconnectPlayer();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ immediate: false
|
|
|
}
|
|
|
},
|
|
|
-
|
|
|
- stopPlay() {
|
|
|
- if (this.player) {
|
|
|
- this.player.destroy();
|
|
|
- this.player = null;
|
|
|
+ mounted() {
|
|
|
+ window.addEventListener('beforeunload', this.handleBeforeUnload);
|
|
|
+ this.startPlay();
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ this.isPageUnloading = true;
|
|
|
+ this.isManualDestroy = true;
|
|
|
+ window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
|
|
+ this.cleanReconnectTimer();
|
|
|
+ this.destroyPlayer();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ handleBeforeUnload() {
|
|
|
+ // 页面刷新/关闭时立即标记,阻止任何后续异步操作
|
|
|
+ this.isPageUnloading = true;
|
|
|
+ this.isManualDestroy = true;
|
|
|
+ this.cleanReconnectTimer();
|
|
|
+ this.destroyPlayer();
|
|
|
+ },
|
|
|
+
|
|
|
+ startPlay() {
|
|
|
+ if (this.isPageUnloading || this.isManualDestroy) return;
|
|
|
+ if (!mpegts.getFeatureList().mseLivePlayback) {
|
|
|
+ console.warn('当前浏览器不支持 mpegts.js 直播回放');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const videoElement = document.getElementById('video' + this.index);
|
|
|
+ if (!videoElement) {
|
|
|
+ console.warn('video 元素未找到,延迟重试');
|
|
|
+ this.scheduleReconnect();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.player = mpegts.createPlayer(
|
|
|
+ {
|
|
|
+ type: 'flv',
|
|
|
+ isLive: true,
|
|
|
+ hasAudio: false,
|
|
|
+ url: this.videoUrl,
|
|
|
+ cors: true
|
|
|
+ },
|
|
|
+ this.config
|
|
|
+ );
|
|
|
+
|
|
|
+ this.player.attachMediaElement(videoElement);
|
|
|
+ this.player.load();
|
|
|
+
|
|
|
+ // 媒体信息就绪后,延迟播放
|
|
|
+ this.player.on(mpegts.Events.MEDIA_INFO, () => {
|
|
|
+ if (this.isPageUnloading || this.isManualDestroy) return;
|
|
|
+ if (this.hasStarted) return;
|
|
|
+ this.hasStarted = true;
|
|
|
+ this.$emit('setLoading', true);
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ this.$emit('setLoading', false);
|
|
|
+
|
|
|
+ if (this.isPageUnloading || this.isManualDestroy || !this.player)
|
|
|
+ return;
|
|
|
+ this.player.play().catch((err) => {
|
|
|
+ if (err.name !== 'NotAllowedError') {
|
|
|
+ this.scheduleReconnect();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }, this.playDelaySeconds * 1000);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 兜底:如果 MEDIA_INFO 未触发,LOADING_COMPLETE 时也尝试延迟播放
|
|
|
+ this.player.on(mpegts.Events.LOADING_COMPLETE, () => {
|
|
|
+ if (this.isPageUnloading || this.isManualDestroy) return;
|
|
|
+ if (!this.hasStarted) {
|
|
|
+ console.warn('未收到 MEDIA_INFO,但仍尝试延迟播放');
|
|
|
+ this.hasStarted = true;
|
|
|
+ setTimeout(() => {
|
|
|
+ if (
|
|
|
+ this.isPageUnloading ||
|
|
|
+ this.isManualDestroy ||
|
|
|
+ !this.player
|
|
|
+ )
|
|
|
+ return;
|
|
|
+ this.player.play().catch((e) => console.warn);
|
|
|
+ }, this.playDelaySeconds * 1000);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 错误处理
|
|
|
+ this.player.on(mpegts.Events.ERROR, (errorType, errorDetail) => {
|
|
|
+ if (this.isPageUnloading || this.isManualDestroy) return;
|
|
|
+ console.error('mpegts 错误:', errorType, errorDetail);
|
|
|
+ if (errorType === mpegts.ErrorTypes.NETWORK_ERROR) {
|
|
|
+ this.scheduleReconnect();
|
|
|
+ } else if (
|
|
|
+ errorType === mpegts.ErrorTypes.MEDIA_ERROR &&
|
|
|
+ this.reconnectAttempts === 0
|
|
|
+ ) {
|
|
|
+ this.scheduleReconnect(true);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 缓冲统计,重置重连计数
|
|
|
+ this.player.on(mpegts.Events.STATISTICS_INFO, (stats) => {
|
|
|
+ if (this.isPageUnloading || this.isManualDestroy) return;
|
|
|
+ if (stats && stats.bufferLength > 0.5) {
|
|
|
+ this.reconnectAttempts = 0;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } catch (err) {
|
|
|
+ console.error('创建播放器异常:', err);
|
|
|
+ this.scheduleReconnect();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ destroyPlayer() {
|
|
|
+ if (this.player) {
|
|
|
+ try {
|
|
|
+ this.player.pause();
|
|
|
+ this.player.unload();
|
|
|
+ this.player.detachMediaElement();
|
|
|
+ this.player.destroy();
|
|
|
+ } catch (e) {
|
|
|
+ // 刷新时可能已经销毁,静默处理
|
|
|
+ }
|
|
|
+ this.player = null;
|
|
|
+ }
|
|
|
+ // 手动清理 video 元素,彻底释放资源
|
|
|
+ const videoElement = document.getElementById('video' + this.index);
|
|
|
+ if (videoElement) {
|
|
|
+ videoElement.pause();
|
|
|
+ videoElement.src = '';
|
|
|
+ videoElement.load();
|
|
|
+ if (videoElement.srcObject) {
|
|
|
+ videoElement.srcObject = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.hasStarted = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ scheduleReconnect(onlyOnce = false) {
|
|
|
+ if (this.isPageUnloading || this.isManualDestroy) return;
|
|
|
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
|
+ console.error('已达最大重连次数,停止尝试');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (onlyOnce && this.reconnectAttempts > 0) return;
|
|
|
+
|
|
|
+ this.cleanReconnectTimer();
|
|
|
+ const delay = Math.min(
|
|
|
+ 1000 * Math.pow(1.5, this.reconnectAttempts),
|
|
|
+ 10000
|
|
|
+ );
|
|
|
+ console.log(
|
|
|
+ `${delay}ms 后尝试重连... (第${this.reconnectAttempts + 1}次)`
|
|
|
+ );
|
|
|
+ this.reconnectTimer = setTimeout(() => {
|
|
|
+ this.reconnectPlayer();
|
|
|
+ }, delay);
|
|
|
+ },
|
|
|
+
|
|
|
+ reconnectPlayer() {
|
|
|
+ if (this.isPageUnloading || this.isManualDestroy) return;
|
|
|
+ this.reconnectAttempts++;
|
|
|
+ console.log(`重连播放器,第${this.reconnectAttempts}次尝试`);
|
|
|
+ this.destroyPlayer();
|
|
|
+ this.startPlay();
|
|
|
+ },
|
|
|
+
|
|
|
+ cleanReconnectTimer() {
|
|
|
+ if (this.reconnectTimer) {
|
|
|
+ clearTimeout(this.reconnectTimer);
|
|
|
+ this.reconnectTimer = null;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
-};
|
|
|
+ };
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
-.video {
|
|
|
- height: 100%;
|
|
|
- width: 100%;
|
|
|
-}
|
|
|
+ .video {
|
|
|
+ height: 100%;
|
|
|
+ width: 100%;
|
|
|
+ background-color: #333;
|
|
|
+ }
|
|
|
</style>
|