<template>
<view class="video-player-container" :style="style">
<view :ref="setRef" class="video-wrapper">
<!-- #ifndef H5 -->
<video
:id="videoPlayerID"
class="video-ele video-js vjs-default-skin"
:src="_src"
:is-live="_isLive"
:object-fit="objectFit"
:show-mute-btn="true"
@error="videoErrorCallback"
></video>
<!-- #endif -->
<!-- #ifdef H5 -->
<!-- 在h5 为了避免uniapp对video标签包裹标签 使用动态创建video标签 -->
<!-- #endif -->
</view>
</view>
</template>
<script setup lang="ts">
/**
* @description: video组件
* 功能描述:
* 支持全平台播放mp4、m3u8(h.264)视频格式的视频
* 注意事项:
* 兼容适配说明:
* h5平台 -> 使用video.js 支持mp4+m3u8(h.264)
* mp-weixin微信小程序平台 -> 使用video标签 支持mp4+m3u8(h.264+h.265) 可设置直播源is-live属性(微信小程序(2.28.1+)支持)
* app平台 -> 使用video标签 支持本地视频(mp4/flv)、网络视频地址(mp4/flv/m3u8)及流媒体(rtmp/hls/rtsp)
* 链接参考:
* 相关videojs播放器属性配置,参考:https://videojs.com/guides/options/
* 关于自动播放autoplay属性,参考https://videojs.com/blog/autoplay-best-practices-with-video-js:
* 1、Never assume autoplay will work.
* 2、Using the muted attribute/option will improve the chances that autoplay will succeed.
* 3、Prefer programmatic autoplay via the player.play() method, avoiding the autoplay attribute/option.
*/
// #ifdef H5
import videojs from 'video.js'
import './index.css'
import video_zhCN from 'video.js/dist/lang/zh-CN.json'
// #endif
import { nextTick, onBeforeUnmount, onMounted, watch } from 'vue'
import { computed, ref, type Ref } from '@vue/reactivity'
// #ifdef H5
videojs.addLanguage('zh-CN', video_zhCN)
// #endif
type srcType = 'video/mp4' | 'application/x-mpegURL'
type objectFitType = 'contain' | 'fill' | 'cover'
interface ISrcObj {
src: string
type?: srcType
}
interface VideoOpts {
autoplay?: boolean // 自动播放
controls?: boolean // 控制条
muted?: boolean // 静音
loop?: boolean // 循环播放
preload?: 'auto' | 'metadata' | 'none' // 预加载
}
type Props = {
srcObj: ISrcObj // 视频的链接和类型 必填
options?: VideoOpts // videojs的播放器属性配置,h5适用
style?: Object // 播放器父级样式
objectFit?: objectFitType // video覆盖类型-仅支持小程序 h5可用css覆盖
}
const props = withDefaults(defineProps<Props>(), {
srcObj: () => ({ src: '' }),
options: () => ({
controls: true,
preload: 'auto'
}),
style: () => ({}),
objectFit: 'contain'
})
let player: any = null
let videoPlayerID = `videoPlayerID${new Date().getTime()}`
let videoRef: Ref<any> = ref()
let errorIndex=0
const videoOpts: any = {
autoplay: false,
controls: true,
muted: true,
loop: false,
language: 'zh-CN',
// techOrder: ['html5', 'flvjs'],
// ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl',
// 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton',
// 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle']
controlBar: {
children: [
{
name: 'playToggle' // 播放暂停按钮
},
{
name: 'currentTimeDisplay' // 播放时间 videojs不生效-css主动控制
},
{
name: 'progressControl' // 进度条
},
{
name: 'durationDisplay' // 总时长 videojs不生效-css主动控制
},
{
name: 'volumePanel', // 音量
inline: false // 非横向
},
{
name: 'fullscreenToggle' // 全屏
}
]
},
sources: []
}
const _src = computed(() => props.srcObj.src)
const _isLive = computed(() => {
// 是否直播流-适配小程序
let type = props.srcObj.type
let src = props.srcObj.src
return type == 'application/x-mpegURL' || (src && src.includes('.m3u8'))
})
// 事件
const emit = defineEmits<{
(event: 'error', e: any): void
}>()
/**
* @description: 动态ref
* @param {*} el
* @return {*}
*/
const setRef = (el: any) => {
if (el) videoRef.value = el
}
/**
* @description: 错误回调
* @param {*} e
* @return {*}
*/
const videoErrorCallback = (e: any) => {
console.error(e)
emit('error', e)
}
/**
* @description: 创建video并且添加属性
* 适配h5 为了避免uniapp对video标签包裹标签 使用动态创建video标签
* @return {*}
*/
const createPlayer = () => {
// #ifdef H5
// 在h5 为了避免uniapp对video标签包裹标签 使用动态创建video标签
// 创建video并且添加属性
errorIndex=0
let video = document.createElement('video') as HTMLVideoElement
video.id = videoPlayerID
video.setAttribute('class', 'video-js vjs-default-skin video-ele')
video.setAttribute('playsinline', 'true')
video.setAttribute('webkit-playsinline', 'true')
video.controls = true
videoRef.value.$el.appendChild(video)
nextTick(() => {
initPlayer()
})
// #endif
}
/**
* @description: videojs初始化播放器
* @return {*}
*/
const initPlayer = () => {
const options = Object.assign({}, videoOpts, props.options)
options.sources = [props.srcObj]
player = videojs(videoPlayerID, options, () => {
player.log('onPlayerReady', player)
player.on('error', (e: any) => {
console.error(e)
changeSrc(props.srcObj)
// emit('error', e)
})
player.on('play', () => {
console.log('playing')
})
player.on('emptied', () => {
// Fires when the current playlist is empty.
console.log('emptied')
})
player.on('stalled', () => {
// Fires when the browser is trying to get media data, but data is not available
console.log('stalled')
})
})
}
/**
* @description: videojs动态切换视频地址
* @param {ISrcObj} obj
* @return {*}
*/
const changeSrc = (obj: ISrcObj) => {
if (!player) return
if (player.pause) player.pause()
// 重置播放器
player.reset()
player.src(obj)
player.load()
player.play()
}
watch(
() => props.srcObj,
(newVal) => {
errorIndex=0
changeSrc(newVal)
},
{ deep: true }
)
onMounted(() => {
createPlayer()
})
onBeforeUnmount(() => {
if (player && player.dispose) {
player.dispose()
}
})
</script>
<style lang="scss">
.video-player-container {
width: 100%;
height: 100%;
.video-wrapper {
width: 100%;
height: 100%;
}
}
.video-ele {
width: 100%;
height: 100%;
min-height: 120rpx;
}
</style>

- 发布:2023-07-10 19:10
- 更新:2023-07-10 19:10
- 阅读:115
产品分类: uniapp/H5
PC开发环境操作系统: Mac
PC开发环境操作系统版本号: macOs 10.15.5
HBuilderX类型: 正式
HBuilderX版本号: 3.8.7
浏览器平台: 微信内置浏览器
浏览器版本: 微信最新浏览器
项目创建方式: HBuilderX
示例代码:
操作步骤:
视频加载报错,走onerror事件,调changSrc方法
视频加载报错,走onerror事件,调changSrc方法
预期结果:
视频重新加载
视频重新加载
实际结果:
视频没有重新加载
视频没有重新加载
bug描述:
在微信浏览器下视频报错走changeSrc会执行,但是视频不会重新播放。ps:回放视频(ts文件重0开始)
在其他浏览器或安卓下都是报错完能继续播放。
没使用框架直接开发的也能报错继续播放。
0 个回复