前端使用hls方式播放h265(HEVC)格式的.ts在线文件,附vue2.x封装h265web.js代码,附github项目地址

接上一篇h265web.js 同时播放多个视频,修改官方example 中player.js并重新编译,这里使用vue2.x封装最新版本h265web.js v20220916 tag 【注意: 后面有新版本了,tag v20220916之前的版本内存溢出我只想到定时刷新页面来解决,之后的版本未曾尝试过,自行测试】
也就是这个:

image.png

其实h265web.js作者在demo中提供了vue demo的,如下:


image.png

分别是Vue Cli 和 Vite创建的,这都不是重点,重点是demo里面的播放器很简陋,可以看到里面都是简单的播放暂停,而且没有样式。


image.png

这样对于初学者来说不够友好,有没有一个能用的哦,我只想复制粘贴。。。


image.png

来来来,各位看官看这里,拿走不谢,也希望大家多多分享自己的学习心得,让后来的同学们少走弯路。
这里提一下啊,大家写文章的时候要确保正确而且要结合自己的感悟,有时候看别人的博客写得没头没尾的,而且有的相互copy,点开都是一样的内容,有的代码拿来也跑不起来,各种报错。。。真的很浪费时间啊喂。

扯远了,本文这里其实是结合上篇文章【见文章开头】的v20211204 tag 中的example,简单将html和css拿过来包装修改,改成vue中的版本,并带上视频底部controller。最后页面效果如下:

image.png

image.png

代码如下:

1.首先去官网下载最新h265web.js v20220916 tag ,下载后解压将dist目录中的文件复制到自己的项目中。

image.png

在index.html中引入missile.js
image.png

开始封装,这是我的文件目录:


image.png

2.组件封装代码如下,这里使用了[email protected][email protected]
index.vue









player20220916.js

import H265webjsModule from '../../../../static/h265webjs/index'

const SHOW_LOADING = "加载中...";
const SHOW_DONE = "已就绪";

function durationFormatSubVal(val) {
  let valStr = val.toString();
  if (valStr.length < 2) {
    return '0' + valStr;
  }
  return valStr;
}

function durationText(duration) {
  if (duration < 0) {
    return "Play";
  }
  let durationSecInt = Math.round(duration);
  return durationFormatSubVal(Math.floor(durationSecInt / 3600))
    + ":" + durationFormatSubVal(Math.floor((durationSecInt % 3600) / 60))
    + ":" + durationFormatSubVal(Math.floor(durationSecInt % 60));
}

const getMsTime = () => {
  return new Date().getTime();
};

/***************************************************
 *
 *
 *
 * 1. H.265/HEVC MP4/FLV/HLS/TS
 * Demo for create player(MP4/FLV/HLS/TS)
 * 点播/直播播放器创建Demo(MP4/FLV/HLS/TS)
 *
 *
 *
 ***************************************************/
// clear cache count
H265webjsModule.clear();
export function makeH265webjs(videoURL, config, wrapBox) {
  let configEventCallback = config.event // 官网事件回调
  let playerId = config.player;

  let playerObj = H265webjsModule.createPlayer(videoURL, config);

  let playerDom = document.querySelector('#' + playerId);
  let playerCont = document.querySelector(wrapBox);
  let controllerCont = document.querySelector(wrapBox + ' .controller');
  let progressCont = document.querySelector(wrapBox + ' .progress-contaniner');
  let progressContW = progressCont.offsetWidth;
  let cachePts = progressCont.querySelector(wrapBox + ' .cachePts');
  let progressPts = progressCont.querySelector(wrapBox + ' .progressPts');
  let progressVoice = document.querySelector(wrapBox + ' .progressVoice');
  let playBar = document.querySelector(wrapBox + ' .playBtn');
  let playBtn = playBar.getElementsByTagName('a')[0];
  let showLabel = document.querySelector(wrapBox + ' .showLabel');
  let ptsLabel = document.querySelector(wrapBox + ' .ptsLabel');
  // let coverToast = document.querySelector(wrapBox + ' .coverLayer');
  // let coverBtn = document.querySelector(wrapBox + ' .coverLayerBtn');
  let muteBtn = document.querySelector(wrapBox + ' .muteBtn');
  // let debugYUVBtn     = document.querySelector('#debugYUVBtn');
  // let debugYUVATag    = document.querySelector('#debugYUVUrl');
  let fullScreenBtn = document.querySelector(wrapBox + ' .fullScreenBtn');
  let mediaInfo = null;

  playBtn.disabled = true;
  // playBar.textContent = '>';
  showLabel.textContent = SHOW_LOADING;
  playerCont.style.width = config.width + 'px';
  playerCont.style.height = config.height + 'px';
  controllerCont.style.width = config.width + 'px';

  let muteState = false;

  // controllerCont.style.left = playerContainer.clientLeft;
  // controllerCont.style.bottom = playerContainer.clientBottom;
  // alert(playerContainer.clientLeft);

  let playAction = () => {
    console.log("is playing:", playerObj.isPlaying());
    if (playerObj.isPlaying()) {
      console.log("bar pause============>");
      // playBar.textContent = '>';
      playBar.setAttribute('class', 'playBtn');
      playerObj.pause();
    } else {
      // playBar.textContent = '||';
      playBar.setAttribute('class', 'pauseBtn');
      playerObj.play();
    }
  };

  playerCont.onmouseover = function () {
    controllerCont.hidden = false;
  };

  playerCont.onmouseout = function () {
    controllerCont.hidden = true;
  };

  playerDom.onmouseup = function () {
    playAction();
  };

  playBtn.onclick = () => {
    playAction();
  };

  // 喇叭点击
  muteBtn.onclick = () => {
    // console.log(playerObj.getVolume());
    if (muteState === true) {
      playerObj.setVoice(1.0);
      progressVoice.value = 100;
    } else {
      playerObj.setVoice(0.0);
      progressVoice.value = 0;
    }
    muteState = !muteState;
  };

  fullScreenBtn.onclick = () => {
    playerObj.fullScreen();
    // setTimeout(() => {
    //     playerObj.closeFullScreen();
    // }, 2000);
  };

  // 进度条点击
  // progressCont.addEventListener('click', (e) => {
  //   showLabel.textContent = SHOW_LOADING;
  //   let x = e.pageX - progressCont.getBoundingClientRect().left; // or e.offsetX (less support, though)
  //   let y = e.pageY - progressCont.getBoundingClientRect().top;  // or e.offsetY
  //   let clickedValue = x * progressCont.max / progressCont.offsetWidth;
  //   // alert(clickedValue);
  //   playerObj.seek(clickedValue);
  // });

  // 声音进度条点击
  progressVoice.addEventListener('click', (e) => {
    let x = e.pageX - progressVoice.getBoundingClientRect().left; // or e.offsetX (less support, though)
    let y = e.pageY - progressVoice.getBoundingClientRect().top;  // or e.offsetY
    let clickedValue = x * progressVoice.max / progressVoice.offsetWidth;
    progressVoice.value = clickedValue;
    let volume = clickedValue / 100;
    // alert(volume);
    // console.log(
    //     progressVoice.offsetLeft, // 209
    //     x, y, // 324 584
    //     progressVoice.max, progressVoice.offsetWidth);
    playerObj.setVoice(volume);
  });

  playerObj.onSeekStart = (pts) => {
    showLabel.textContent = SHOW_LOADING + " seek to:" + parseInt(pts);
  };

  playerObj.onSeekFinish = () => {
    showLabel.textContent = SHOW_DONE;
  };

  playerObj.onPlayFinish = () => {
    console.log("============= FINISHED ===============");
    // playBar.textContent = '>';
    playBar.setAttribute('class', 'playBtn');
    // playerObj.release();
    // console.log("=========> release ok");
  };

  playerObj.onRender = (width, height, imageBufferY, imageBufferB, imageBufferR) => {
    console.log("on render");
  };

  playerObj.onOpenFullScreen = () => {
    console.log("onOpenFullScreen");
  };

  playerObj.onCloseFullScreen = () => {
    console.log("onCloseFullScreen");
  };

  // Seek完成
  playerObj.onSeekFinish = () => {
    showLabel.textContent = SHOW_DONE;
  };

  // 当前正在缓存帧数据
  playerObj.onLoadCache = () => {
    showLabel.textContent = "Caching...";
  };

  // 帧数据缓存完成
  playerObj.onLoadCacheFinshed = () => {
    showLabel.textContent = SHOW_DONE;
  };

  // 播放器封面图加载完成
  playerObj.onReadyShowDone = () => {
    console.log("onReadyShowDone");
    showLabel.textContent = "Cover Img OK";
  };

  // 媒体文件当前加载成功,可以进行播放
  playerObj.onLoadFinish = () => {
    if(configEventCallback && configEventCallback.onLoadFinish){
      configEventCallback.onLoadFinish()
    }
    playerObj.setVoice(1.0);
    mediaInfo = playerObj.mediaInfo();
    console.log("mediaInfo===========>", mediaInfo);
    /*
    meta:
        durationMs: 144400
        fps: 25
        sampleRate: 44100
        size: {
            width: 864,
            height: 480
        },
        audioNone : false
    videoType: "vod"
    */
    if (mediaInfo.meta.isHEVC === false) {
      console.log("is not HEVC/H.265 media!");
      //coverToast.removeAttribute('hidden');
      //coverBtn.style.width = '100%';
      //coverBtn.style.fontSize = '50px';
      //coverBtn.innerHTML = 'is not HEVC/H.265 media!';
      //return;
    }
    //console.log("is HEVC/H.265 media.");

    playBtn.disabled = false;

    if (mediaInfo.meta.audioNone) {
      progressVoice.value = 0;
      progressVoice.style.display = 'none';
    } else {
      playerObj.setVoice(0.5);
    }

    if (mediaInfo.videoType == "vod") {
      cachePts.max = mediaInfo.meta.durationMs / 1000;
      progressCont.max = mediaInfo.meta.durationMs / 1000;
      ptsLabel.textContent = durationText(0) + '/' + durationText(progressCont.max);
    } else {
      cachePts.hidden = true;
      progressCont.hidden = true;
      ptsLabel.textContent = 'LIVE';

      if (mediaInfo.meta.audioNone === true) {
        // playBar.textContent = '||';
       // playerObj.play();
      } else {

        // coverToast.removeAttribute('hidden');
        // coverBtn.onclick = () => {
        //   // playBar.textContent = '||';
        //   playAction();
        //   coverToast.setAttribute('hidden', 'hidden');
        // };
      }

    }

    showLabel.textContent = SHOW_DONE;
  };

  // 播放器缓冲进度回调 -- no
  playerObj.onCacheProcess = (cPts) => {
    // 追帧设置
    // if(playerObj.isPlaying()){
    //   // setInterval(()=>{
    //   //   playerObj.seek(cPts);
    //   // }, 1000)
    // }


    // console.log("onCacheProcess => ", cPts, playerObj.mediaInfo().meta.durationMs);
    try {
      // cachePts.value = cPts;
      let precent = cPts / progressCont.max;
      let cacheWidth = precent * progressContW;
      // console.log(precent, precent * progressCont.offsetWidth);
      cachePts.style.width = cacheWidth + 'px';
    } catch (err) {
      console.log(err);
    }
  };

  // 播放器当前播放PTS时刻更新
  playerObj.onPlayTime = (videoPTS) => {

    // console.log(videoPTS, playerObj.mediaInfo().meta.durationMs, 111)

    if (mediaInfo.videoType == "vod") {
      // progressPts.value = videoPTS;
      let precent = videoPTS / progressCont.max;
      let progWidth = precent * progressContW;
      // console.log(precent, precent * progressCont.offsetWidth);
      progressPts.style.width = progWidth + 'px';

      ptsLabel.textContent = durationText(videoPTS) + '/' + durationText(progressCont.max);
    } else {
      // ptsLabel.textContent = durationText(videoPTS) + '/LIVE';
      ptsLabel.textContent = '/LIVE';
    }
  };

  playerObj.do();
  return playerObj;
};
// export function exposeAll(){
//   H265webjsModule.clear();
// }

注意这个路径改成你本地的:


image.png

3.使用:
h265webjs.vue








这个option的地址改成你的测试视频源地址即可:


image.png

这里还有个注意点,切换视频源是用的v-if + $nextTick的方式,为了避免不必要的报错,其实我一开始是组件内部监听的方式,但后面因为报错还是改成了这种。


image.png

4.参数说明:


image.png

image.png

附github demo 地址

因为不止一个小伙伴私下里找我了,这里还是贴上源码,可以直接运行方便看到效果。里面有两个版本,如果遇到最新版播放卡顿或内存溢出问题可暂时切换为低版本【内存溢出依旧存在,作者后续看到issue并提交了一版,但我还没有机会验证】,该版本全屏报错问题已在代码中修复。


image.png

啰嗦一句

我提供的demo中内存溢出问题依旧存在,只是低版本会好很多。当时我用的已经是最新版本tag v20220916了,最后我的解决方式选择低版本并“定时刷新页面”释放内存。然后我给作者提了issue,不想定时刷新的小伙伴可以试下tag v20220916后面的版本,若问题依旧存在可在github上向 h265web.js作者阐明问题(我这边因为项目结束原因没机会再测试了)

image.png

若对你有帮助,请点个赞吧,谢谢支持!
本文地址:https://www.jianshu.com/p/1f98a9b5e316,转载请注明出处,谢谢。

参考:
h265web.js github

你可能感兴趣的:(前端使用hls方式播放h265(HEVC)格式的.ts在线文件,附vue2.x封装h265web.js代码,附github项目地址)