uniapp实现H5、APP、微信小程序播放.m3u8监控视频

发布于:2025-05-23 ⋅ 阅读:(16) ⋅ 点赞:(0)

       

目录

1.APP播放.m3u8监控视频

2.H5播放.m3u8监控视频

3.微信小程序播放.m3u8监控视频


       最近在写一个uniapp实现h5、app、微信小程序兼容三端的播放监控视频功能,我原本以为一套代码多处运行,但事实并非如此,h5可以运行,微信小程序不一定可以运行,APP也是如此。上网查阅很多资料,最终3端效果实现成功。

1.APP播放.m3u8监控视频

APP对原生支持比较好,可以直接使用uniapp官网的video组件,本人亲自试过正常播放。

video地址:uni-app官网

代码实现如下:

    <!-- APP 端 -->
    <video 
      id="myVideo"
      :src="videoUrl"
      controls
      autoplay
      enable-play-gesture
      object-fit="contain"
      class="video-player"
      @error="videoError"
    ></video>
    
    

代码还是比较简单的,将videoUrl替换成自己的.m3u8格式Url即可。

2.H5播放.m3u8监控视频

       这里一开始我也以为直接使用上面的代码就可以,但是uniapp运行起来发现根本播放不出来,虽然 HTML5 视频播放器支持多种视频格式,但并不是所有的浏览器都支持 .m3u8 格式的视频流。确保您使用的浏览器支持 HLS。我上网查阅资料最终效果实现成功!

实现代码:

<template>		
<div id="app1">
    <div class="video-js" ref="videos"></div>
</div>
</template>

<script>
export default {
      data() {
        return {
        videoUrl: '',
        // H5 相关状态
        player: null,
        visibilityChange: null,
        hidden: null,
       }
    }, 
    mounted() {
       // 动态加载video.js CDN资源
	   this.loadScript('https://vjs.zencdn.net/7.21.2/video.min.js', () => {
		 this.loadStyle('https://vjs.zencdn.net/7.21.2/video-js.min.css');
		 
		 // 设置页面可见性API的兼容性处理
		 this.setupVisibilityAPI();
		 
		 // 等待资源加载完成
		 setTimeout(() => {
		   this.setupVideoPlayer();
		 }, 300);
	   });
      },
	beforeDestroy() {
	 // 组件销毁时移除事件监听
	     document.removeEventListener(this.visibilityChange, 
     this.handleVisibilityChange);
	     
	     // 销毁播放器
	     if (this.player) {
	       this.player.dispose();
	       this.player = null;
	     }
	},
  methods: {
	     loadScript(src, callback) {
	          const script = document.createElement('script');
	          script.src = src;
	          script.onload = callback;
	          document.body.appendChild(script);
	        },
	        loadStyle(href) {
	          const link = document.createElement('link');
	          link.href = href;
	          link.rel = 'stylesheet';
	          document.head.appendChild(link);
	        },
	        setupVisibilityAPI() {
	          // 设置页面可见性API的兼容性处理
	          if (typeof document.hidden !== "undefined") {
	            this.hidden = "hidden";
	            this.visibilityChange = "visibilitychange";
	          } else if (typeof document.msHidden !== "undefined") {
	            this.hidden = "msHidden";
	            this.visibilityChange = "msvisibilitychange";
	          } else if (typeof document.webkitHidden !== "undefined") {
	            this.hidden = "webkitHidden";
	            this.visibilityChange = "webkitvisibilitychange";
	          }
	          
	          // 添加事件监听
	          document.addEventListener(this.visibilityChange, this.handleVisibilityChange.bind(this), false);
	        },
	        handleVisibilityChange() {
	          if (!this.player) return;
	          
	          if (document[this.hidden]) {
	            // 页面不可见时暂停播放
	            this.player.pause();
	          } else {
	            // 页面重新可见时恢复播放
	            this.player.play().catch(e => {
	              console.log('自动播放失败:', e);
	            });
	          }
	        },
	        setupVideoPlayer() {
	          if (!window.videojs) {
	            console.error('video.js未加载成功');
	            return;
	          }
	    
	          let video = document.createElement('video');
	          video.id = 'video';
	          video.className = 'video-js vjs-default-skin';
	          video.preload = "auto";
	          video.setAttribute('playsinline', true);
	          video.setAttribute('webkit-playsinline', true);
	          video.setAttribute('x5-video-player-type', 'h5');
	          
	          let source = document.createElement('source');
	          source.src = this.videoUrl;
	          video.appendChild(source);
	          
	          this.$refs.videos.appendChild(video);
	          
	          this.player = window.videojs(
	            'video', 
	            {
	              autoDisable: true,
	              preload: 'none',
	              language: 'zh-CN',
	              fluid: true,
	              muted: false,
	              aspectRatio: '16:9',
	              controls: true,
	              autoplay: false,
	              loop: true,
	              controlBar: {
	                volumePanel: {
	                  inline: true
	                },
	                timeDivider: true,
	                durationDisplay: true,
	                progressControl: true,
	                remainingTimeDisplay: true,
	                fullscreenToggle: true,
	                pictureInPictureToggle: false,
	              }
	            }, 
	            function() {
	              this.on('error', function(err) {
	                console.log("请求数据时遇到错误", err);
	              });
	              this.on('stalled', function(stalled) {
	                console.log("网速失速", stalled);
	              });
	            }
	          );
	        }
       }
}
</script>
<style>
#app1 {
    width: 100vw;
    height: 95vh;
    background: #000;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  /* 视频播放器主体 */
  .video-js {
    width: 90%;
    max-width: 1200px;
    height: auto;
    aspect-ratio: 16/9;
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
	margin-top: -200px;
  }

  /* 控制栏整体样式 */
  .video-js .vjs-control-bar {
    background: rgba(20, 20, 20, 0.8);
    height: 3.5em;
    padding: 0 10px;
  }

  /* 按钮样式 */
  .video-js .vjs-control {
    width: 2.5em;
    height: 2.5em;
    margin: 0 2px;
    color: #fff;
    transition: all 0.3s;
  }

  .video-js .vjs-control:hover {
    color: #00a1d6;
    transform: scale(1.1);
  }

  /* 进度条样式 */
  .video-js .vjs-progress-control {
    position: absolute;
    top: -1em;
    width: 100%;
    height: 0.5em;
  }

  .video-js .vjs-progress-holder {
    height: 100%;
    background: rgba(255, 255, 255, 0.2);
  }

  .video-js .vjs-play-progress {
    background: #00a1d6;
  }

  /* 音量控制 */
  .video-js .vjs-volume-panel {
    order: 4;
  }

  /* 时间显示 */
  .video-js .vjs-time-control {
    min-width: 3em;
    padding: 0 5px;
    font-size: 1.1em;
  }

  /* 全屏按钮 */
  .video-js .vjs-fullscreen-control {
    order: 5;
  }

  /* 加载动画 */
  .video-js .vjs-loading-spinner {
    border-color: rgba(0, 161, 214, 0.7);
  }

  /* 大播放按钮 */
  .video-js .vjs-big-play-button {
    width: 2.5em;
    height: 2.5em;
    line-height: 2.5em;
    border-radius: 50%;
    border: none;
    background: rgba(0, 161, 214, 0.8);
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    transition: all 0.3s;
  }

  .video-js .vjs-big-play-button:hover {
    background: rgba(0, 161, 214, 1);
    transform: translate(-50%, -50%) scale(1.1);
  }

  /* 响应式调整 */
  @media (max-width: 768px) {
    .video-js {
      width: 100%;
      border-radius: 0;
    }
    
    .video-js .vjs-control-bar {
      height: 2.5em;
    }
  }
 </style>

本人亲自实践,电脑浏览器,与手机浏览器访问,都可以成功。

3.微信小程序播放.m3u8监控视频

     在这里我是卡的最久的,因为直接使用video组件播放,在微信开发者工具中可以正常播放,但是在真机调试,小程序查看就是一直黑屏转圈圈,有时候可以播放成功,但是几秒中过后就是又是一直转圈圈,最后就会报错。有大佬会的可以在下方留言,最后采用web-view组件,实现播放,直接页面跳转。但是使用web-view跳转监控视频,又会有另一个问题,本地测试正常,但是上线,线上微信小程序就会出现提示不支持打开该页面。一定得在微信开发者后台校验文件才可以打开,因为这是微信小程序的强制规则,这个校验文件必须放在对应服务器才可以成功,假如我是调整萤石云的监控,那按照微信的说法,就要将校验文件放入萤石云服务器的后台,这样显然不现实,所以这里有两种方案,第一使用代理,第二自己在写一个页面部署到自己的服务器,通过web-view跳转到自己写的页面中,将监控视频url一并传入页面,即可完成播放。


实现代码:

 <web-view :src="'https://你的域名/player.html
 ?videoUrl='+encodeURIComponent(videoUrl)+
'&cameraTitle='+encodeURIComponent(cameraTitle)"></web-view>

注意,这里跳转必须是https,在这里viderUrl是视频监控的链接,cameraTitle是标题,比如你播放的是那个监控,可加可不加,我这边是加上了。

跳转playeer.html页面代码实现:
其实下面的代码保存,videoUrl替换即可播放,我只不过写了两套,你们可以选择一套使用。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title id="pageTitle">监控播放器</title>
  <style>
    body {
      margin: 0;
      padding: 270px 0px 0px 0px;
      font-family: Arial, sans-serif;
      background: #000;
    }
  }
 /* 容器样式 */
  #app1 {
    background: #000;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  /* 视频播放器主体 */
  .video-js {
    width: 100%;
    max-width: 1200px;
    height: auto;
    aspect-ratio: 16/9;
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  }
  </style>
</head>
<body>
  <div id="app1">
    <div class="video-js" id="videos"></div>
  </div>

  <script>
  function getQueryParam(name) {
  const query = window.location.search.substring(1);
  const vars = query.split('&');
  for (let i = 0; i < vars.length; i++) {
    const pair = vars[i].split('=');
    if (decodeURIComponent(pair[0]) === name) {
      return decodeURIComponent(pair[1]);
    }
  }
  return null;
}

// 从URL参数获取视频地址
const videoUrl = getQueryParam('videoUrl');

// 从URL获取摄像头标题并设置页面标题
    const cameraTitle = getQueryParam('cameraTitle');
    if (cameraTitle) {
      document.title = cameraTitle;
      document.getElementById('pageTitle').textContent = cameraTitle;
    }

    document.addEventListener('DOMContentLoaded', function() {
      const app = {
        player: null,
        visibilityChange: null,
        hidden: null,
        
        init: function() {
          // 动态加载video.js CDN资源
          this.loadScript('https://vjs.zencdn.net/7.21.2/video.min.js', () => {
            this.loadStyle('https://vjs.zencdn.net/7.21.2/video-js.min.css');
            
            // 设置页面可见性API的兼容性处理
            this.setupVisibilityAPI();
            
            // 等待资源加载完成
            setTimeout(() => {
              this.setupVideoPlayer();
            }, 300);
          });
        },
        
        loadScript: function(src, callback) {
          const script = document.createElement('script');
          script.src = src;
          script.onload = callback;
          document.body.appendChild(script);
        },
        
        loadStyle: function(href) {
          const link = document.createElement('link');
          link.href = href;
          link.rel = 'stylesheet';
          document.head.appendChild(link);
        },
        
        setupVisibilityAPI: function() {
          // 设置页面可见性API的兼容性处理
          if (typeof document.hidden !== "undefined") {
            this.hidden = "hidden";
            this.visibilityChange = "visibilitychange";
          } else if (typeof document.msHidden !== "undefined") {
            this.hidden = "msHidden";
            this.visibilityChange = "msvisibilitychange";
          } else if (typeof document.webkitHidden !== "undefined") {
            this.hidden = "webkitHidden";
            this.visibilityChange = "webkitvisibilitychange";
          }
          
          // 添加事件监听
          document.addEventListener(this.visibilityChange, this.handleVisibilityChange.bind(this), false);
        },
        
        handleVisibilityChange: function() {
          if (!this.player) return;
          
          if (document[this.hidden]) {
            // 页面不可见时暂停播放
            this.player.pause();
          } else {
            // 页面重新可见时恢复播放
            this.player.play().catch(e => {
              console.log('自动播放失败:', e);
            });
          }
        },
        
        setupVideoPlayer: function() {
          if (!window.videojs) {
            console.error('video.js未加载成功');
            return;
          }

          let video = document.createElement('video');
          video.id = 'video';
          video.className = 'video-js vjs-default-skin';
          video.preload = "auto";
          video.setAttribute('playsinline', true);
          video.setAttribute('webkit-playsinline', true);
          video.setAttribute('x5-video-player-type', 'h5');
          
          let source = document.createElement('source');
   
             source.src = videoUrl ;
          video.appendChild(source);
          
          document.getElementById('videos').appendChild(video);
          
          this.player = window.videojs(
            'video', 
            {
              autoDisable: true,
              preload: 'none',
              language: 'zh-CN',
              fluid: true,
              muted: false,
              aspectRatio: '16:9',
              controls: true,
              autoplay: false,
              loop: true,
              controlBar: {
                volumePanel: {
                  inline: true
                },
                timeDivider: true,
                durationDisplay: true,
                progressControl: true,
                remainingTimeDisplay: true,
                fullscreenToggle: true,
                pictureInPictureToggle: false,
              }
            }, 
            function() {
              this.on('error', function(err) {
                console.log("请求数据时遇到错误", err);
              });
              this.on('stalled', function(stalled) {
                console.log("网速失速", stalled);
              });
            }
          );
        }
      };

      // 初始化应用
      app.init();
    });
  </script>
</body>
</html>

本人亲自测试,3端都可以播放,有问题可以在下方评论留言。


网站公告

今日签到

点亮在社区的每一天
去签到