| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- <template>
- <div class="videoWrapper">
- <video ref="videoRef" autoplay muted class="video-js video-content" :poster="poster" disablePictureInPicture>
- <source :src="urlWithToken" />
- </video>
- <img class="loading" :src="loadingImg" v-show="loading" />
- </div>
- </template>
- <script setup lang="ts">
- import { onMounted, onBeforeUnmount, watch, ref, computed } from 'vue';
- import mpegts from 'mpegts.js';
- import { storeToRefs } from 'pinia';
- import { useUserStore } from '@/store/modules/user';
- import loadingImg from '@/assets/images/nine-square-grid/loading.gif';
- const props = defineProps<{
- url: string;
- poster?: string;
- }>();
- const userStore = useUserStore();
- const { token } = storeToRefs(userStore);
- let player: mpegts.Player | null;
- let lastDecodedFrames = 0; // 10s前的解码帧数
- let currentDecodedFrames = 0; // 当前解码帧数
- let loadingTimeout = 0;
- const videoRef = ref<HTMLVideoElement | null>(null);
- const loading = ref(true);
- const urlWithToken = computed(() => {
- if (!props.url) return '';
- return props.url;
- });
- const handleLoadeddata = () => {
- loading.value = false;
- };
- const initPlay = () => {
- if (!props.url || !videoRef.value || !token.value) {
- return;
- }
- const videoElement = videoRef.value;
- lastDecodedFrames = 0;
- currentDecodedFrames = 0;
- player = mpegts.createPlayer(
- {
- type: 'flv',
- isLive: true,
- hasAudio: false,
- url: urlWithToken.value,
- },
- {
- liveBufferLatencyChasing: true,
- /**
- * 控制直播视频流在缓冲区中允许的最大延迟时间。
- * 较小的值使播放更接近实时,但可能会因为网络波动或数据传输不及时导致播放卡顿。
- * 较大的值则会允许更多的缓冲,这样可以更好地应对网络波动,减少卡顿的可能性,但会增加播放延迟。
- */
- liveBufferLatencyMaxLatency: 4,
- },
- );
- videoElement.removeEventListener('loadeddata', handleLoadeddata);
- videoElement.addEventListener('loadeddata', handleLoadeddata);
- player.attachMediaElement(videoElement);
- player.load();
- player.on(mpegts.Events.ERROR, (e, detail, data) => {
- loading.value = true;
- console.log('视频加载错误类型', e);
- console.log('视频加载错误详情类型', detail);
- console.log('视频加载错误信息', data);
- // 当发生error时,这里会发生死循环,所以要注销掉。 interval方式中已经包含了此种错误的处理
- // reloadPlayer();
- });
- player.on(mpegts.Events.RECOVERED_EARLY_EOF, () => {
- console.log('视频播放结束');
- loading.value = true;
- });
- player.on(mpegts.Events.STATISTICS_INFO, (e) => {
- // console.log("视频播放信息", e.decodedFrames);
- const frame = e.decodedFrames || 0;
- handleLoading(frame);
- currentDecodedFrames = frame;
- });
- // player.play();
- setTimeout(() => {
- player?.play() as Promise<void>;
- console.log('视频play()触发');
- }, 50);
- };
- /** 处理loading信息 */
- const handleLoading = (nextFrame: number) => {
- if (currentDecodedFrames === nextFrame) {
- if (loadingTimeout) return;
- loadingTimeout = window.setTimeout(() => {
- loading.value = true;
- loadingTimeout = 0;
- }, 6000);
- } else {
- clearTimeout(loadingTimeout);
- loadingTimeout = 0;
- loading.value = false;
- }
- };
- const interval = setInterval(() => {
- if (currentDecodedFrames === lastDecodedFrames) {
- console.log('视频播放卡顿,10s前解码帧数为 ' + lastDecodedFrames + ' ,当前解码帧数为 ' + currentDecodedFrames);
- reloadPlayer();
- }
- lastDecodedFrames = currentDecodedFrames;
- }, 15000);
- const destroyPlayer = () => {
- // liveLoaded.value = false;
- if (player) {
- console.log('视频判断需要销毁');
- player!.pause();
- player!.unload();
- player!.detachMediaElement();
- console.log('视频播放器销毁');
- player!.destroy();
- player = null;
- }
- };
- onMounted(() => {
- initPlay();
- });
- const reloadPlayer = () => {
- loading.value = true;
- destroyPlayer();
- setTimeout(() => {
- initPlay();
- }, 100);
- };
- //切换播放url
- watch(
- () => props.url,
- () => {
- if (props.url) {
- reloadPlayer();
- }
- },
- {
- deep: true,
- },
- );
- onBeforeUnmount(() => {
- destroyPlayer();
- clearInterval(interval);
- clearTimeout(loadingTimeout);
- });
- </script>
- <style scoped lang="less">
- .video-content {
- width: 100%;
- height: 100%;
- background-color: transparent !important;
- object-fit: contain; // 0729fill->contain: 应急处置-指挥中心按需修改,若有其余地方使用出错再行修改
- }
- .videoWrapper {
- width: 100%;
- height: 100%;
- display: inline-block;
- position: relative;
- }
- .loading {
- position: absolute;
- z-index: 1;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- margin: auto;
- width: 60px;
- }
- </style>
|