IOS H5页面中 HLS视频无法正常播放,使用hls.插件

发布于:2024-04-18 ⋅ 阅读:(56) ⋅ 点赞:(0)

IOS H5页面中 HLS视频无法正常播放,使用hls.插件

HLS.js依靠 HTML5 视频和 MediaSource Extensions 进行播放。

所有 iPhone 浏览器 (iOS) 都没有可用的 MediaSourceExtension,因此Hls.js将不起作用。如果您在 iPhone 上检查 Hls.isSupported() ,您会看到该函数返回 false

  if (Hls.isSupported()) {
    var hls = new Hls();
    hls.loadSource(videoSrc);
    hls.attachMedia(video);
  } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
    video.src = videoSrc;
  }

iOS 可以使用普通的 <video><source></video> HTML,因为 Safari 具有原生 HLS 支持。初始化 Hls.js <video> 将阻止本机 HLS 功能根本无法工作。

Hls.isSupported return true

但这个解决方案对我不起作用,因为Hls.isSupported 总是返回 true。我做了以下调整以使其正常工作:

React:

import Hls from "hls.js";
import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";

interface PlayerProps {
  src?: string;
  poster: string;
  onPlay(): void;
  onPlayFailed(): void;
}

export const Player = forwardRef<{}, PlayerProps>(({ src, poster, onPlay, onPlayFailed }, ref) => {
  const videoRef = useRef<HTMLVideoElement | null>(null);
  const hlsRef = useRef<Hls | null>(null);

  useEffect(() => {
    if (!src || !videoRef.current) return;
    const supportHLS = Boolean(videoRef.current.canPlayType("application/vnd.apple.mpegurl"));

    if (supportHLS) {
      videoRef.current.src = src;
      videoRef.current.play().then(onPlay).catch(onPlayFailed);
    } else {
      hlsRef.current = new Hls();
      const hls = hlsRef.current;

      hls.on(Hls.Events.MEDIA_ATTACHED, function () {
        videoRef.current!.play().then(onPlay).catch(onPlayFailed);
      });
      hls.loadSource(src);
      hls.attachMedia(videoRef.current);
      
      return () => {
          hls.destroy();
      };
    }
  }, [src]);

  useImperativeHandle(ref, () => videoRef.current!, []);

  return (
    <video className="w-full h-full object-cover" playsInline ref={videoRef} poster={poster}>
      <source src={src} type="application/x-mpegURL"></source>
    </video>
  );
});

Vue:

然后,你可以在 Vue 组件中这样使用它:

<template>  
  <video  
    class="w-full h-full object-cover"  
    playsinline  
    :poster="poster"  
    @play="onPlay"  
    @error="onPlayFailed"  
    ref="videoElement"  
  >  
    <source :src="src" type="application/x-mpegURL"></source>  
  </video>  
</template>  
  
<script>  
import Hls from 'hls.js';  
  
export default {  
  props: {  
    src: {  
      type: String,  
      default: ''  
    },  
    poster: {  
      type: String,  
      required: true  
    }  
  },  
  data() {  
    return {  
      hlsInstance: null  
    };  
  },  
  mounted() {  
    this.initializePlayer();  
  },  
  beforeDestroy() {  
    if (this.hlsInstance) {  
      this.hlsInstance.destroy();  
      this.hlsInstance = null;  
    }  
  },  
  methods: {  
    initializePlayer() {  
      if (!this.src || !this.$refs.videoElement) return;  
      const supportsHLS = this.$refs.videoElement.canPlayType('application/vnd.apple.mpegurl');  
  
      if (supportsHLS) {  
        this.$refs.videoElement.src = this.src;  
        this.$refs.videoElement.play().then(this.onPlay).catch(this.onPlayFailed);  
      } else {  
        this.hlsInstance = new Hls();  
        const hls = this.hlsInstance;  
  
        hls.on(Hls.Events.MEDIA_ATTACHED, () => {  
          this.$refs.videoElement.play().then(this.onPlay).catch(this.onPlayFailed);  
        });  
        hls.loadSource(this.src);  
        hls.attachMedia(this.$refs.videoElement);  
      }  
    },  
    onPlay() {  
      this.$emit('onPlay');  
    },  
    onPlayFailed() {  
      this.$emit('onPlayFailed');  
    }  
  },  
  watch: {  
    src() {  
      this.initializePlayer();  
    }  
  }  
};  
</script>  
  
<style scoped>  
/* Add your styles here */  
</style>

在这个 Vue 组件中,我们使用了 Vue 的生命周期钩子 mounted 来初始化播放器,并在 beforeDestroy 钩子中销毁 hls.js 实例以避免内存泄漏。data 对象用来存储 hls.js 的实例。methods 包含了初始化播放器的方法以及处理播放和播放失败的方法。

此外,我们使用 watch 选项来监听 src 属性的变化,如果它变化了,我们重新初始化播放器。

最后,我们通过 $refs 访问到 DOM 元素,这和 React 中的 useRef 类似。我们还通过 $emit 触发自定义事件,以便父组件可以监听这些事件。

注意:在模板中,我们使用 :src="src":poster="poster" 来绑定属性,这和 React 中的属性绑定类似。

确保在父组件中监听 onPlayonPlayFailed 事件,就像这样:

<template>  
  <Player :src="videoSrc" :poster="videoPoster" @onPlay="handlePlay" @onPlayFailed="handlePlayFailed"></Player>  
</template>  
  
<script>  
import Player from './Player.vue';  
  
export default {  
  components: {  
    Player  
  },  
  data() {  
    return {  
      videoSrc: 'your-video-source-url',  
      videoPoster: 'your-poster-image-url'  
    };  
  },  
  methods: {  
    handlePlay() {  
      // 处理播放事件  
    },  
    handlePlayFailed() {  
      // 处理播放失败事件  
    }  
  }  
};  
</script>

画面为初始页面,视频进度会走,有声音

但使用之后,发现项目中的hls视频加载成功,可以播放,但是播放只有声音,画面为初始的画面,调查打断点之后,发现解决方案

<template>
  <div class="hls-video-player">
    <video
      class="videoElement"
      ref="videoElement"
      autoplay
      muted
      preload="true"
      playsinline="true" webkit-playsinline="true"
      @loadedmetadata="onLoadedMetadata"
      controls
    >
      <source :src="hlsUrl" type="application/x-mpegURL"/>
    </video>
  </div>
</template>

<script>
import Hls from 'hls.js'
export default {
  name: 'HlsVideoPlayer',
  props: {
    hlsUrl: {
      type: String,
      required: true
    }
  },
  data () {
    return {
      hlsInstance: null
    }
  },
  mounted () {
    this.initializePlayer()
  },
  beforeDestroy () {
    if (this.hlsInstance) {
      this.hlsInstance.destroy()
      this.hlsInstance = null
    }
  },
  methods: {
    onLoadedMetadata () {
      console.log('Video metadata loaded')
    },
    initializePlayer () {
      if (!this.hlsUrl || !this.$refs.videoElement) return
      const supportsHLS = this.$refs.videoElement.canPlayType('application/vnd.apple.mpegurl')
      console.log(Hls.isSupported(), supportsHLS, 'hls.isSupported', 'supportsHLS')
      // if (supportsHLS) {
      //   this.$refs.videoElement.src = this.hlsUrl
      //   this.$refs.videoElement.play().then(this.onPlay).catch(this.onPlayFailed)
      // } else {
      //   this.hlsInstance = new Hls()
      //   const hls = this.hlsInstance
      //   hls.on(Hls.Events.MEDIA_ATTACHED, () => {
      //     this.$refs.videoElement.play().then(this.onPlay).catch(this.onPlayFailed)
      //   })
      //   hls.loadSource(this.hlsUrl)
      //   hls.attachMedia(this.$refs.videoElement)
      // }
      if (Hls.isSupported()) {
        // 如果支持 hls.js(MediaSource Extensions)
        this.hlsInstance = new Hls()
        const hls = this.hlsInstance
        hls.on(Hls.Events.MEDIA_ATTACHED, () => {
          this.$refs.videoElement.play().then(this.onPlay).catch(this.onPlayFailed)
        })
        hls.loadSource(this.hlsUrl)
        hls.attachMedia(this.$refs.videoElement)
      } else if (supportsHLS) {
        // 如果支持原生播放
        // video.src = url
        // // 自动播放
        // video.addEventListener('canplay', function () {
        //   video.play()
        // })
        this.$refs.videoElement.src = this.hlsUrl
        this.$refs.videoElement.play().then(this.onPlay).catch(this.onPlayFailed)
      }
    },
    onPlay () {
      console.log('onPlay')
      // this.$emit('onPlay')
    },
    onPlayFailed () {
      console.log('onPlayFailed')
      // this.$emit('onPlayFailed')
    }
  },
  watch: {
    hlsUrl () {
      this.initializePlayer()
    }
  }
}
</script>

<style scoped>
.hls-video-player {
  width: 100%;
  max-width: 600px; /* 或其他你需要的宽度 */
  margin: 0 auto;
  display: flex;
  margin: 0 auto;
  justify-content: center;
  align-items: center;
}
.videoElement{
  width: 100%;
}
</style>