XGPlayer.vue 4.48 KB
<script setup lang="ts">
  import Player, { Events, IError } from 'xgplayer';
  import { FlvPlugin } from 'xgplayer-flv';
  import Mp4Plugin from 'xgplayer-mp4';
  import { HlsPlugin } from 'xgplayer-hls';
  import { onMounted, shallowRef, computed, unref, toRaw, onUnmounted, ref, watch } from 'vue';
  import PresetPlayer from 'xgplayer';
  import { IPlayerOptions } from 'xgplayer/es/player';
  import 'xgplayer/dist/index.min.css';
  import { isShareMode } from '/@/views/sys/share/hook';
  import { getJwtToken, getShareJwtToken } from '/@/utils/auth';
  import { XGPlayerProps } from '/@/components/Video/src/types';
  import { StreamType } from './types';

  const props = withDefaults(defineProps<XGPlayerProps>(), {
    streamType: 'auto',
    autoPlay: true,
    config: () => ({}),
  });

  const emits = defineEmits<{
    (eventName: 'ready', player: PresetPlayer): void;
    (eventName: 'play', player: PresetPlayer): void;
    (eventName: 'pause', player: PresetPlayer): void;
    (eventName: 'ended', player: PresetPlayer): void;
    (eventName: 'onUnmounted', player: PresetPlayer): void;
  }>();

  function getStreamTypeByUrl(url = ''): StreamType | undefined {
    url = url || '';
    if (url.endsWith('.m3u8')) return 'hls';
    else if (url.endsWith('.mp4')) return 'mp4';
    else if (url.endsWith('.flv')) {
      return 'flv';
    } else return;
  }

  const getPluginByStreamType = (): IPlayerOptions => {
    let { url, withToken } = props;
    let { streamType } = props;
    streamType = streamType === 'auto' ? getStreamTypeByUrl(url)! : streamType;

    const liveConfig = {
      targetLatency: 10,
      maxLatency: 20,
      disconnectTime: 0,
      fetchOptions: withToken
        ? {
            headers: {
              'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`,
            },
          }
        : {},
    };
    const config: IPlayerOptions = {
      flv: liveConfig,
      hls: liveConfig,
    };
    switch (streamType) {
      case 'hls':
        config.plugins = [HlsPlugin];
        break;
      case 'mp4':
        config.plugins = [Mp4Plugin];
        break;
      case 'flv':
        config.plugins = [FlvPlugin];
        break;
    }
    return config;
  };

  const videoElRef = shallowRef<Nullable<HTMLDivElement>>();

  const playerRef = shallowRef<Nullable<PresetPlayer>>();

  const propsRef = ref<XGPlayerProps>({});

  const getPlayerConfig = computed<IPlayerOptions>(() => {
    const { url, autoPlay, config } = props;

    const basicConfig: IPlayerOptions = {
      ...config,
      ...propsRef,
      url,
      lang: 'zh',
      isLive: true,
      autoplay: autoPlay,
      autoplayMuted: autoPlay,
      ...getPluginByStreamType(),
    };
    return basicConfig;
  });

  function onDecodeError() {
    console.warn('player happend decode error');
    // playerRef.value?.destroy?.();
    // initializePlayer();
    playerRef.value?.switchURL(props.url!);
  }

  function initializePlayer() {
    if (unref(playerRef)) {
      playerRef.value?.destroy?.();
      playerRef.value = null;
    }

    const config = toRaw(unref(getPlayerConfig));

    if (!unref(videoElRef)) return;

    const player = (playerRef.value = new Player(Object.assign(config, { el: unref(videoElRef) })));

    player.on(Events.READY, () => {
      emits('ready', player);
    });

    player.setEventsMiddleware({
      error: (event, callback) => {
        const code = (
          event as unknown as {
            error: MediaError;
          }
        ).error.code;
        if (code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED) {
          if (!props.url) {
            return;
          }
          callback();
          return;
        }

        if (code === MediaError.MEDIA_ERR_DECODE) {
          // 视频流可以播放 中途解码失败重载
          if (playerRef.value?.isPlaying) {
            onDecodeError();
          }
          return;
        }

        callback();
      },
    });

    player.on(Events.PAUSE, () => {
      emits('pause', player);
    });

    player.on(Events.ENDED, () => {
      emits('ended', player);
    });

    player.on(Events.PLAY, () => {
      emits('play', player);
    });
  }

  onMounted(() => {
    initializePlayer();
  });

  onUnmounted(() => {
    emits('onUnmounted', unref(playerRef)!);
    playerRef.value?.destroy?.();
  });

  watch(
    () => props.url,
    () => {
      initializePlayer();
    }
  );

  defineExpose({
    getPlayerInstance: () => unref(playerRef),
  });
</script>

<template>
  <div ref="videoElRef"></div>
</template>