|
@@ -1,11 +1,10 @@
|
|
|
<template>
|
|
<template>
|
|
|
<div class="video-content-wrapper">
|
|
<div class="video-content-wrapper">
|
|
|
- <div class="videoLoading" v-loading="!isVideoLoadingFailed" :element-loading-text="loadingText">
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div class="videoLoading" v-loading="!isVideoLoadingFailed"> </div>
|
|
|
|
|
|
|
|
<div class="loadingError" v-if="isVideoLoadingFailed">视频加载失败</div>
|
|
<div class="loadingError" v-if="isVideoLoadingFailed">视频加载失败</div>
|
|
|
|
|
|
|
|
- <video v-loading="true" id="video" autoplay muted class="video-js video-content">
|
|
|
|
|
|
|
+ <video v-loading="true" ref="videoRef" autoplay muted class="video-js video-content">
|
|
|
<source :src="props.url" />
|
|
<source :src="props.url" />
|
|
|
</video>
|
|
</video>
|
|
|
</div>
|
|
</div>
|
|
@@ -14,12 +13,9 @@
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import { onMounted, onBeforeUnmount, watch, ref, computed } from 'vue';
|
|
import { onMounted, onBeforeUnmount, watch, ref, computed } from 'vue';
|
|
|
import mpegts from 'mpegts.js';
|
|
import mpegts from 'mpegts.js';
|
|
|
-
|
|
|
|
|
const restartNum = ref(0);
|
|
const restartNum = ref(0);
|
|
|
|
|
|
|
|
- const MAX_RESTART_NUM = 20;
|
|
|
|
|
let isVideoLoadingFailed = ref(false);
|
|
let isVideoLoadingFailed = ref(false);
|
|
|
-
|
|
|
|
|
const props = defineProps<{
|
|
const props = defineProps<{
|
|
|
url: string;
|
|
url: string;
|
|
|
}>();
|
|
}>();
|
|
@@ -27,17 +23,23 @@
|
|
|
const emit = defineEmits(['timeUpdate']);
|
|
const emit = defineEmits(['timeUpdate']);
|
|
|
|
|
|
|
|
let player: mpegts.Player | null;
|
|
let player: mpegts.Player | null;
|
|
|
|
|
+ let lastDecodedFrames = 0; // 10s前的解码帧数
|
|
|
|
|
+ let currentDecodedFrames = 0; // 当前解码帧数
|
|
|
|
|
+
|
|
|
|
|
+ const videoRef = ref<HTMLVideoElement | null>(null);
|
|
|
|
|
|
|
|
- const loadingText = computed(() => {
|
|
|
|
|
- if (restartNum.value === 0) return '视频加载中...';
|
|
|
|
|
- return `正在尝试第${restartNum.value}次重连...`;
|
|
|
|
|
- });
|
|
|
|
|
const handleTimeUpdate = (event) => {
|
|
const handleTimeUpdate = (event) => {
|
|
|
emit('timeUpdate', event.target.currentTime);
|
|
emit('timeUpdate', event.target.currentTime);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const initPlay = () => {
|
|
const initPlay = () => {
|
|
|
- const videoElement = document.getElementById('video') as HTMLMediaElement;
|
|
|
|
|
|
|
+ if (!props.url || !videoRef.value) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const videoElement = videoRef.value;
|
|
|
|
|
+ lastDecodedFrames = 0;
|
|
|
|
|
+ currentDecodedFrames = 0;
|
|
|
player = mpegts.createPlayer(
|
|
player = mpegts.createPlayer(
|
|
|
{
|
|
{
|
|
|
type: 'flv',
|
|
type: 'flv',
|
|
@@ -56,37 +58,48 @@
|
|
|
player.attachMediaElement(videoElement);
|
|
player.attachMediaElement(videoElement);
|
|
|
player.load();
|
|
player.load();
|
|
|
player.on(mpegts.Events.MEDIA_INFO, () => {
|
|
player.on(mpegts.Events.MEDIA_INFO, () => {
|
|
|
- // testStore.setVideoReady(true);
|
|
|
|
|
|
|
+ console.log('视频开始播放');
|
|
|
});
|
|
});
|
|
|
player.on(mpegts.Events.ERROR, (e) => {
|
|
player.on(mpegts.Events.ERROR, (e) => {
|
|
|
- // testStore.setVideoReady(true);
|
|
|
|
|
- console.log('video error', e);
|
|
|
|
|
- if (restartNum.value >= MAX_RESTART_NUM) {
|
|
|
|
|
- isVideoLoadingFailed.value = true;
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- if (player) {
|
|
|
|
|
- player.pause();
|
|
|
|
|
- player.unload();
|
|
|
|
|
- player.detachMediaElement();
|
|
|
|
|
- player.destroy();
|
|
|
|
|
- player = null;
|
|
|
|
|
- restartNum.value += 1;
|
|
|
|
|
- initPlay();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ console.log('视频加载错误', e);
|
|
|
|
|
+ // 当发生error时,这里会发生死循环,所以要注销掉。 interval方式中已经包含了此种错误的处理
|
|
|
|
|
+ // reloadPlayer();
|
|
|
|
|
+ });
|
|
|
|
|
+ player.on(mpegts.Events.RECOVERED_EARLY_EOF, () => {
|
|
|
|
|
+ console.log('视频播放结束');
|
|
|
|
|
+ });
|
|
|
|
|
+ player.on(mpegts.Events.STATISTICS_INFO, (e) => {
|
|
|
|
|
+ // console.log("视频播放信息", e.decodedFrames);
|
|
|
|
|
+ const frame = e.decodedFrames || 0;
|
|
|
|
|
+ currentDecodedFrames = frame;
|
|
|
});
|
|
});
|
|
|
// player.play();
|
|
// player.play();
|
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
|
- player?.play();
|
|
|
|
|
|
|
+ player?.play() as Promise<void>;
|
|
|
}, 50);
|
|
}, 50);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ const interval = setInterval(() => {
|
|
|
|
|
+ if (currentDecodedFrames === lastDecodedFrames) {
|
|
|
|
|
+ console.log(
|
|
|
|
|
+ '视频播放卡顿,10s前解码帧数为 ' +
|
|
|
|
|
+ lastDecodedFrames +
|
|
|
|
|
+ ' ,当前解码帧数为 ' +
|
|
|
|
|
+ currentDecodedFrames,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ reloadPlayer();
|
|
|
|
|
+ }
|
|
|
|
|
+ lastDecodedFrames = currentDecodedFrames;
|
|
|
|
|
+ }, 15000);
|
|
|
|
|
+
|
|
|
const destroyPlayer = () => {
|
|
const destroyPlayer = () => {
|
|
|
- // testStore.setVideoReady(false);
|
|
|
|
|
if (player) {
|
|
if (player) {
|
|
|
|
|
+ console.log('视频判断需要销毁');
|
|
|
player.pause();
|
|
player.pause();
|
|
|
player.unload();
|
|
player.unload();
|
|
|
player.detachMediaElement();
|
|
player.detachMediaElement();
|
|
|
|
|
+ console.log('视频播放器销毁');
|
|
|
player.destroy();
|
|
player.destroy();
|
|
|
player = null;
|
|
player = null;
|
|
|
}
|
|
}
|
|
@@ -96,13 +109,19 @@
|
|
|
initPlay();
|
|
initPlay();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ const reloadPlayer = () => {
|
|
|
|
|
+ destroyPlayer();
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ initPlay();
|
|
|
|
|
+ }, 100);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
//切换播放url
|
|
//切换播放url
|
|
|
watch(
|
|
watch(
|
|
|
() => props.url,
|
|
() => props.url,
|
|
|
() => {
|
|
() => {
|
|
|
- destroyPlayer();
|
|
|
|
|
if (props.url) {
|
|
if (props.url) {
|
|
|
- initPlay();
|
|
|
|
|
|
|
+ reloadPlayer();
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
@@ -112,6 +131,7 @@
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
onBeforeUnmount(() => {
|
|
|
destroyPlayer();
|
|
destroyPlayer();
|
|
|
|
|
+ clearInterval(interval);
|
|
|
});
|
|
});
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|