1***@163.com
1***@163.com
  • 发布:2023-07-10 19:10
  • 更新:2023-07-10 19:10
  • 阅读:115

【报Bug】video 回放直播流在ios微信浏览器下的问题

分类:uni-app

产品分类: uniapp/H5

PC开发环境操作系统: Mac

PC开发环境操作系统版本号: macOs 10.15.5

HBuilderX类型: 正式

HBuilderX版本号: 3.8.7

浏览器平台: 微信内置浏览器

浏览器版本: 微信最新浏览器

项目创建方式: HBuilderX

示例代码:
<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>

操作步骤:

视频加载报错,走onerror事件,调changSrc方法

预期结果:

视频重新加载

实际结果:

视频没有重新加载

bug描述:

在微信浏览器下视频报错走changeSrc会执行,但是视频不会重新播放。ps:回放视频(ts文件重0开始)
在其他浏览器或安卓下都是报错完能继续播放。
没使用框架直接开发的也能报错继续播放。

2023-07-10 19:10 负责人:无 分享
已邀请:

要回复问题请先登录注册