Vue2 结合 vue-video-player 播放 FLV 直播流

21 阅读1分钟

Vue2 结合 vue-video-player 播放 FLV 直播流

一. 依赖包版本

  • vue-video-player@5.0.2
  • video.js@6.13.0
  • videojs-flvjs@0.3.1
  • flv.js@1.6.2

二. main.js 的配置及加载顺序:

import videojs from 'video.js';
import flvjs from 'flv.js';
window.videojs = videojs;
window.flvjs = flvjs;
import 'videojs-flvjs';
import VideoPlayer from 'vue-video-player';
Vue.use(VideoPlayer);

如果使用 video.js@7.x,可能会出现 No compatible source was found for this media. 的兼容问题。

三. FlvVideoPlayer 组件代码

<template>
    <div class="flv-player-box">
        <video-player
            ref="videoPlayer"
            class="vjs-big-play-centered"
            :options="playerOptions"
            @playerReady="onPlayerReady"
        />
    </div>
</template>

<script>
export default {
    name: 'FlvVideoPlayer',
    props: {
        flvUrl: {
            type: String,
            required: true,
        },
        autoplay: {
            type: Boolean,
            default: true,
        },
        muted: {
            type: Boolean,
            default: true,
        },
        controls: {
            type: Boolean,
            default: true,
        },
        fluid: {
            type: Boolean,
            default: true,
        },
        preload: {
            type: String,
            default: 'auto',
        },
        poster: {
            type: String,
            default: '',
        },
        playsinline: {
            type: Boolean,
            default: true,
        },
        controlBar: {
            type: Object,
            default: () => ({
                volumePanel: { inline: false },
                progressControl: false,
            }),
        },
        retryOnError: {
            type: Boolean,
            default: true,
        },
        retryDelay: {
            type: Number,
            default: 3000,
        },
        maxRetry: {
            type: Number,
            default: 3,
        },
        techOrder: {
            type: Array,
            default: () => ['flvjs', 'html5'],
        },
    },
    data() {
        return {
            player: null,
            retryCount: 0,
            retryTimer: null,
        };
    },
    computed: {
        playerOptions() {
            return {
                autoplay: this.autoplay,
                muted: this.muted,
                controls: this.controls,
                fluid: this.fluid,
                preload: this.preload,
                poster: this.poster,
                playsinline: this.playsinline,
                techOrder: this.techOrder,
                sources: [
                    {
                        src: this.flvUrl,
                        type: 'video/x-flv',
                    },
                ],
                controlBar: this.controlBar,
            };
        },
    },
    watch: {
        flvUrl(newUrl, oldUrl) {
            if (newUrl && newUrl !== oldUrl) {
                this.retryCount = 0;
                this.updateSource(newUrl);
            }
        },
    },
    beforeDestroy() {
        this.destroyPlayer();
    },
    methods: {
        onPlayerReady(player) {
            this.player = player;
            this.player.on('error', this.handleError);
            this.player.on('play', () => this.$emit('play'));
            this.player.on('pause', () => this.$emit('pause'));
            this.player.on('loadedmetadata', () => this.$emit('loadedmetadata'));
            this.$emit('ready', player);
            this.updateSource(this.flvUrl);
            if (this.autoplay) {
                this.player.play().catch(() => {});
            }
        },
        updateSource(url) {
            if (!this.player) {
                return;
            }
            this.clearRetryTimer();
            this.player.src({
                src: url,
                type: 'video/x-flv',
            });
            this.player.load();
            if (this.autoplay) {
                this.player.play().catch(() => {});
            }
        },
        handleError() {
            this.$emit('error', this.player.error());
            if (!this.retryOnError) {
                return;
            }
            if (this.maxRetry >= 0 && this.retryCount >= this.maxRetry) {
                this.$emit('retry-failed', this.retryCount);
                return;
            }
            this.retryCount += 1;
            this.retryTimer = setTimeout(() => {
                if (this.player) {
                    this.updateSource(this.flvUrl);
                    this.$emit('retry', this.retryCount);
                }
            }, this.retryDelay);
        },
        clearRetryTimer() {
            if (this.retryTimer) {
                clearTimeout(this.retryTimer);
                this.retryTimer = null;
            }
        },
        destroyPlayer() {
            this.clearRetryTimer();
            if (this.player) {
                this.player.off('error', this.handleError);
                this.player.dispose();
                this.player = null;
            }
        },
        reload() {
            this.retryCount = 0;
            this.updateSource(this.flvUrl);
        },
    },
};
</script>

<style scoped>
.flv-player-box {
    width: 100%;
    background: #000;
}
</style>

四. 页面使用

<FlvVideoPlayer
  flvUrl="https://example.com/live.flv"
  :autoplay="true"
  :muted="true"
  :retryOnError="true"
  :retryDelay="3000"
  :maxRetry="3"
/>

五. 拓展: m3u8 流播放组件

<template>
    <div class="hls-player-box">
        <video-player
            ref="videoPlayer"
            class="vjs-big-play-centered"
            :options="playerOptions"
            @playerReady="onPlayerReady"
        />
    </div>
</template>

<script>
export default {
    name: 'HlsLivePlayer',
    props: {
        m3u8Url: {
            type: String,
            required: true,
        },
        autoplay: {
            type: Boolean,
            default: true,
        },
        muted: {
            type: Boolean,
            default: true,
        },
        controls: {
            type: Boolean,
            default: true,
        },
        fluid: {
            type: Boolean,
            default: true,
        },
        preload: {
            type: String,
            default: 'auto',
        },
        poster: {
            type: String,
            default: '',
        },
        playsinline: {
            type: Boolean,
            default: true,
        },
        isLive: {
            type: Boolean,
            default: true,
        },
        retryOnError: {
            type: Boolean,
            default: true,
        },
        retryDelay: {
            type: Number,
            default: 3000,
        },
        maxRetry: {
            type: Number,
            default: 3,
        },
        techOrder: {
            type: Array,
            default: () => ['html5'],
        },
        controlBar: {
            type: Object,
            default: () => ({
                volumePanel: { inline: false },
                progressControl: false,
            }),
        },
    },
    data() {
        return {
            player: null,
            retryCount: 0,
            retryTimer: null,
        };
    },
    computed: {
        playerOptions() {
            return {
                autoplay: this.autoplay,
                muted: this.muted,
                controls: this.controls,
                fluid: this.fluid,
                preload: this.preload,
                poster: this.poster,
                playsinline: this.playsinline,
                techOrder: this.techOrder,
                isLive: this.isLive,
                sources: [
                    {
                        src: this.m3u8Url,
                        type: 'application/x-mpegURL',
                    },
                ],
                controlBar: this.controlBar,
            };
        },
    },
    watch: {
        m3u8Url(newUrl, oldUrl) {
            if (newUrl && newUrl !== oldUrl) {
                this.retryCount = 0;
                this.updateSource(newUrl);
            }
        },
    },
    beforeDestroy() {
        this.destroyPlayer();
    },
    methods: {
        onPlayerReady(player) {
            this.player = player;
            this.player.on('error', this.handleError);
            this.player.on('play', () => this.$emit('play'));
            this.player.on('pause', () => this.$emit('pause'));
            this.player.on('loadedmetadata', () => this.$emit('loadedmetadata'));
            this.$emit('ready', player);
            if (this.autoplay) {
                this.player.play().catch(() => {});
            }
        },
        updateSource(url) {
            if (!this.player) {
                return;
            }
            this.clearRetryTimer();
            this.player.src({
                src: url,
                type: 'application/x-mpegURL',
            });
            this.player.load();
            if (this.autoplay) {
                this.player.play().catch(() => {});
            }
        },
        handleError() {
            this.$emit('error', this.player.error());
            if (!this.retryOnError) {
                return;
            }
            if (this.maxRetry >= 0 && this.retryCount >= this.maxRetry) {
                this.$emit('retry-failed', this.retryCount);
                return;
            }
            this.retryCount += 1;
            this.retryTimer = setTimeout(() => {
                if (this.player) {
                    this.$emit('retry', this.retryCount);
                    this.updateSource(this.m3u8Url);
                }
            }, this.retryDelay);
        },
        clearRetryTimer() {
            if (this.retryTimer) {
                clearTimeout(this.retryTimer);
                this.retryTimer = null;
            }
        },
        destroyPlayer() {
            this.clearRetryTimer();
            if (this.player) {
                this.player.off('error', this.handleError);
                this.player.dispose();
                this.player = null;
            }
        },
        reload() {
            this.retryCount = 0;
            this.updateSource(this.m3u8Url);
        },
    },
};
</script>

<style scoped>
.hls-player-box {
    width: 100%;
    max-width: 1200px;
    margin: 0 auto;
    background: #000;
}
</style>