|
|
@@ -1,30 +1,17 @@
|
|
|
<template>
|
|
|
- <!-- <div v-loading="loading">
|
|
|
-
|
|
|
- </div> -->
|
|
|
- <video
|
|
|
- :id="'video' + index"
|
|
|
- style="background-color: #333"
|
|
|
- class="video"
|
|
|
- muted
|
|
|
- playsinline
|
|
|
- ></video>
|
|
|
+ <div style="height: 100%; width: 100%">
|
|
|
+ <div id="video-player" ref="videoPlayer" class="video-player"></div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
- import mpegts from 'mpegts.js';
|
|
|
+ import Player from 'xgplayer';
|
|
|
+ import FlvPlugin from 'xgplayer-flv';
|
|
|
+ import 'xgplayer/dist/index.min.css'; // 引入播放器默认样式
|
|
|
|
|
|
export default {
|
|
|
name: 'VideoPlayer',
|
|
|
props: {
|
|
|
- videoUrl: {
|
|
|
- type: String,
|
|
|
- default: ''
|
|
|
- },
|
|
|
- index: {
|
|
|
- type: Number,
|
|
|
- default: 0
|
|
|
- },
|
|
|
playDelaySeconds: {
|
|
|
type: Number,
|
|
|
default: 5 // 延迟2秒播放
|
|
|
@@ -33,214 +20,76 @@
|
|
|
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
|
|
|
- }
|
|
|
+ url: ''
|
|
|
};
|
|
|
},
|
|
|
- watch: {
|
|
|
- videoUrl: {
|
|
|
- handler(newUrl, oldUrl) {
|
|
|
- if (newUrl && newUrl !== oldUrl) {
|
|
|
- this.reconnectPlayer();
|
|
|
- }
|
|
|
- },
|
|
|
- immediate: false
|
|
|
- }
|
|
|
- },
|
|
|
+ watch: {},
|
|
|
mounted() {
|
|
|
- window.addEventListener('beforeunload', this.handleBeforeUnload);
|
|
|
- this.startPlay();
|
|
|
- },
|
|
|
- beforeDestroy() {
|
|
|
- this.isPageUnloading = true;
|
|
|
- this.isManualDestroy = true;
|
|
|
- window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
|
|
- this.cleanReconnectTimer();
|
|
|
- this.destroyPlayer();
|
|
|
+ // this.startPlay();
|
|
|
},
|
|
|
+ beforeDestroy() {},
|
|
|
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);
|
|
|
- }
|
|
|
- });
|
|
|
+ handleBeforeUnload() {},
|
|
|
|
|
|
- // 错误处理
|
|
|
- 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();
|
|
|
+ startPlay(url) {
|
|
|
+ if (url) {
|
|
|
+ this.url = url;
|
|
|
}
|
|
|
- },
|
|
|
|
|
|
- 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;
|
|
|
+ if (this.player) this.player.destroy();
|
|
|
+
|
|
|
+ // this.videoUrl = this.url.split(',');
|
|
|
+ this.flag = 0;
|
|
|
+ var config = {
|
|
|
+ id: 'video-player',
|
|
|
+ url: this.url,
|
|
|
+ autoplay: true,
|
|
|
+ autoplayMuted: true,
|
|
|
+ closeVideoClick: true,
|
|
|
+ // controls: false,
|
|
|
+ closeInactive: true,
|
|
|
+ fitVideoSize: 'auto',
|
|
|
+ plugins: [FlvPlugin], // 关键:引入 FLV 插件
|
|
|
+ errorTips: '<span style="color:red">请选择需要播放的摄像头</span>',
|
|
|
+ /**倍速播放 */
|
|
|
+ playbackRate: [1],
|
|
|
+ defaultPlaybackRate: 1,
|
|
|
+ flv: {
|
|
|
+ retryCount: 3, // 网络错误时重试次数[reference:8]
|
|
|
+ retryDelay: 1000, // 重试间隔(ms)[reference:9]
|
|
|
+ loadTimeout: 10000, // 加载超时时间(ms)[reference:10]
|
|
|
+ preloadTime: 5 // 抗抖动缓冲区大小(秒)[reference:11]
|
|
|
}
|
|
|
- }
|
|
|
- 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;
|
|
|
- }
|
|
|
+ };
|
|
|
+ var player = new Player(config);
|
|
|
+ player.once('ready', () => {});
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
- .video {
|
|
|
- height: 100%;
|
|
|
- width: 100%;
|
|
|
- background-color: #333;
|
|
|
+ :deep(.el-loading-mask) {
|
|
|
+ background-color: #000;
|
|
|
+ }
|
|
|
+ :deep(.xgplayer-skin-default.xgplayer-pause) {
|
|
|
+ .xgplayer-start {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ :deep(.xgplayer-progress) {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-player {
|
|
|
+ width: 100% !important;
|
|
|
+ height: 100% !important;
|
|
|
+ }
|
|
|
+ .video-playerrr {
|
|
|
+ position: absolute !important;
|
|
|
+ height: 300px !important;
|
|
|
+ top: 50px;
|
|
|
+ /* display: none; */
|
|
|
+ z-index: -100 !important;
|
|
|
}
|
|
|
</style>
|