index.vue 4.14 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 { StreamType, UserActionEventType, XGPlayerProps } from './types'
import { isShareMode } from '@/views/share/hook'
import { getJwtToken, getShareJwtToken } from '@/utils/external/auth'

const props = withDefaults(
  defineProps<{
    streamType?: StreamType
    autoPlay?: boolean
    url?: string
    withToken?: boolean
    config?: Omit<IPlayerOptions, 'url'>
  }>(),
  {
    streamType: 'auto',
    autoPlay: true,
    config: () => ({})
  }
)

const emits = defineEmits<{
  (eventName: 'ready', player: PresetPlayer): void
  (eventName: 'userAction', event: UserActionEventType, player: PresetPlayer): void
  (eventName: 'onUnmounted', player: PresetPlayer): void
}>()

function parsePlayUrl(url: string) {
  try {
    return new URL(url).pathname
  } catch {
    return url
  }
}

function getStreamTypeByUrl(url = ''): StreamType | undefined {
  url = parsePlayUrl(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?.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.on(Events.USER_ACTION, (event: UserActionEventType) => {
    emits('userAction', event, 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()
    }
  })
}

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>