h265视频流播放前端实战

对于h265视频流的播放,这里采用videojs-flvh265
话不多说,看如何实战(基于vue项目)。

使用

下载videojs-flvh265

链接:https://www.npmjs.com/package/videojs-flvh265
npm install --save videojs-flvh265

public引入包

prod.h265.asm.combine.js
prod.h265.wasm.combine.js
videojs-flvh265.js
上面文件在百度网盘中可下载。链接: https://pan.baidu.com/s/12XXO6vafJJFdzLPeB4_WTw?pwd=8hms 提取码: 8hms

main.js引入videojs css

import 'video.js/dist/video-js.css';

template使用

<video
      id="player"
      height="360"
      width="200"
      class="video-js vjs-big-play-centered"
      controls
      autoplay
      loop
      ish265
      islive
      hasvideo
      hasaudio
    >
      <source
        src="https://livevideo-test.slivee.com/slivee-test/2vW_nTy5WYy_h265test.flv"
        type="video/x-flv"
      />
    </video>

javascript使用

import videojs from "video.js";
import "./plugin”;【见下面文件】
export default {
  name: "Welcome",
  mounted() {
    let player = videojs("player", {
      techOrder: ["html5", "flvh265"],
      controlBar: {
        volumePanel: { inline: false },
        pictureInPictureToggle: false,
      },
    });
    player.on("error", function () {
      console.log(this.error());
    });
    player.on("loadstart", function () {
      console.log("loadstart");
      console.time();
    });
    player.on("loadedmetadata", function () {
      console.log("loadedmetadata");
      console.timeEnd();
    });
  },
 destroyed() {
    this.player.dispose();
  },

};

文件

plugin.js

import videojs from 'video.js';
import WXInlinePlayer from 'wx-inline-player-new';

const Tech = videojs.getComponent('Tech');
const Dom = videojs.dom;
const createTimeRange = videojs.createTimeRange;

/**
 * 生命周期对应的状态
 */
const STATE = {
  created: "created",
  play: "play",
  playing: "playing",
  buffering: "buffering",
  /**video.js没有stopped状态,paused也包含stopped */
  paused: "paused",
  resumed: "resumed",
  // ended: "ended", video.js没有ended这个状态
  stopped: "stopped",
  destroyed: "destroyed"
};


/**
 * An array of events available on the `FlvH265` tech.
 * no used now.
 *
 * @private
 * @type {JSON}
 */
const EVENT = {
  loadstart: "",
  loadedmetadata: "loadSuccess",
  play: "play",
  pause: "paused",
  playing: "playing",
  waiting: "buffering",
  ended: "ended",
  volumechange: "",
  durationchange: "timeUpdate",
  error: "loadError"
};

/**
 * 支持的自定义属性,作为<video>标签的属性。
 * 外部设置属性时并不区分大小写。
 */
const customAttrs = ['isH265', 'isLive', 'hasVideo', 'hasAudio', 'lib'];

/**
 * Media Controller - Wrapper for Media API
 *
 * @mixes WXInlinePlayer
 * @extends Tech
 */
class FlvH265 extends Tech {

  constructor(options, ready) {
    super(options, ready);

    let self = this;

    self.debug = false;
    self.currentTime_ = 0;
    self.sate = STATE.created; //状态,hack for video.js
    self.isEnded = false; //因为videol.js没有ended状态,这里单独设置一个变量(非状态)标志是否播放完,

    //7.8.4丢失了这个属性,和github源码不一致,手动补全
    self.options_.disablePictureInPicture = true;


    // Merge default parames with ones passed in
    self.params = Object.assign({
      asmUrl: `/prod.h265.asm.combine.js`,
      wasmUrl: `/prod.h265.wasm.combine.js`,
      url: self.options_.source.src,
      $container: self.el_,
      volume: 1.0,
      muted: self.options_.muted !== undefined ? self.options_.muted : false,
      autoplay: self.options_.autoplay,
      loop: self.options_.loop !== undefined ? self.options_.loop : false,
      chunkSize: 128 * 1024,
      preloadTime: 5e2,
      bufferingTime: 1e3,
      cacheSegmentCount: 64,
      customLoader: null
    }, self.params);

    WXInlinePlayer.ready(self.params).then(player => {
      self.triggerReady();
      self.player = player;
      self.initEvent_(self.params);

      if (self.params.autoplay)
        self.play();
    });
  }

  /**
   * Create the `FlvH265` Tech's DOM element.
   *
   * @return {Element}
   *         The element that gets created.
   */
  createEl() {
    let self = this;
    self.params = FlvH265.getAttributes_(document.getElementById(self.options_.playerId));

    const options = self.options_;

    // Generate ID for canvas object
    const objId = options.techId;

    self.el_ = FlvH265.embed(objId);

    self.el_.tech = self;

    return self.el_;
  }

  static getAttributes_(tag) {
    const obj = {};
    const tmpArr = customAttrs.map(item => item.toLocaleLowerCase());
    const tmpArrBoolean = tmpArr.slice(0, tmpArr.length - 1);
    // known boolean attributes
    // we can check for matching boolean properties, but not all browsers
    // and not all tags know about these attributes, so, we still want to check them manually
    const knownBooleans = ',' + tmpArrBoolean.join(',') + ',';

    if (tag && tag.attributes && tag.attributes.length > 0) {
      const attrs = tag.attributes;

      for (let i = attrs.length - 1; i >= 0; i--) {
        const attrName = attrs[i].name;
        let finalAttrName = '';
        let index = tmpArr.indexOf(attrName);
        if (index === -1)
          continue;
        else {
          finalAttrName = customAttrs[index];
        }

        let attrVal = attrs[i].value;

        // check for known booleans
        // the matching element property will return a value for typeof
        if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
          // the value of an included boolean attribute is typically an empty
          // string ('') which would equal false if we just check for a false value.
          // we also don't want support bad code like autoplay='false'
          attrVal = (attrVal !== null) ? true : false;
        }

        obj[finalAttrName] = attrVal;
      }
    }

    return Object.assign({
      isH265: false,
      isLive: false,
      hasVideo: false,
      hasAudio: false
    }, obj);
  }

  initEvent_(params) {
    let self = this;
    let $canvas = self.$canvas = params.$container;
    let videoHeight = self.el_.parentElement.offsetHeight;
    let videoWidth = self.el_.parentElement.offsetWidth;

    // set the canvas' height and width
    self.player.on('mediaInfo', mediaInfo => {
      $canvas.style.height = '100%'; //videoHeight + `px`;
      $canvas.style.width = '100%'; //videoWidth + `px`;
    });

    //set other events
    self.player.on('loadSuccess', function () {
      self.trigger('loadedmetadata');
    });

    self.player.on('play', function () {
      self.trigger('loadstart');
      // show loading at first
      self.trigger('waiting');
      // thir first time play
      self.trigger('play');
      self.state = STATE.play;
      self.isEnded = false;
    });
    self.player.on('resumed', function () {
      self.trigger('play');
      self.state = STATE.play;
    });

    self.player.on('playing', function () {
      // document.querySelector("#"+self.options_.techId).parentElement.querySelector(".vjs-big-play-button").style.display='none';
      self.trigger('playing');
      self.state = STATE.playing;
    });

    self.player.on('buffering', function () {
      self.trigger('waiting');
    });

    self.player.on('paused', function () {
      // document.querySelector("#"+self.options_.techId).parentElement.querySelector(".vjs-big-play-button").style.display='block';
      self.trigger('pause');
      self.state = STATE.paused;
    });
    //video.js没有stopped,适配一下video.js的事件
    self.player.on('stopped', function () {
      // document.querySelector("#"+self.options_.techId).parentElement.querySelector(".vjs-big-play-button").style.display='block';
      self.trigger('pause');
      self.state = STATE.paused;
    });

    self.player.on('timeUpdate', function (d) {
      self.log()(self.duration(), d / 1000)
      self.trigger('durationchange', d / 1000);
    });

    self.player.on('ended', function () {
      self.trigger('ended');
      self.state = STATE.paused;
      self.isEnded = true;
    });

    self.player.on('loadError', function (e) {
      self.error(e);
    });

  }

  /**
   * Called by {@link Player#play} to play using the `FlvH265` Tech.
   * videojs的这个钩子函数包括多种职责(这是不妥的)
   * 1.首次播放
   * 2.暂停后继续播放
   * 3.重播
   */
  play() {
    //重播
    if (this.ended()) {
      this.currentTime(0);
      this.player.stop();
      this.player.play();
    }
    //非重播
    else {
      if (this.state == STATE.paused)
        this.params.isLive ? this.player.play() : this.player.resume();
      else
        this.player.play();
    }
  }

  played() {
  }

  /**
   * Called by {@link Player#pause} to pause using the `FlvH265` `Tech`.
   */
  pause() {
    this.params.isLive ? this.player.stop() : this.player.pause();
  }

  paused() {
    return this.state == STATE.paused;
  }

  /**
   * Get the current playback time in seconds
   *
   * @return {number}
   *         The current time of playback in seconds.
   */
  currentTime(p) {
    if (p == undefined) {
      return this.player.currentTime() / 1000;
    } else {
      this.player.currentTime(p * 1000);
    }
  }

  /**
   * Get the total duration of the current media.
   *
   * @return {number}
   8          The total duration of the current media.
   */
  duration() {
    return this.player.getDuration() / 1000;
  }

  /**
   * Get and create a `TimeRange` object for buffering.
   *
   * @return {TimeRange}
   *         The time range object that was created.
   */
  buffered() {
    return createTimeRange(0, 1024 * 1024);
  }

  /**
   * Get fullscreen support
   *
   * @return {boolean}
   *         The `FlvH265` tech support fullscreen
   */
  supportsFullScreen() {
    return true;
  }

  enterFullScreen() {
    self.$canvas.requestFullscreen();
  }

  dispose() {
    this.player && this.player.destroy();
    super.dispose();
  }

  muted(p) {
    // this.trigger("volumechange");
    return this.player.mute(p);
  }

  setMuted(p) {
    this.muted(p);
    this.trigger("volumechange");
  }

  volume(p) {
    // this.trigger("volumechange");
    return this.player.volume(p);
  }

  setVolume(p) {
    this.volume(p);
    this.trigger("volumechange");
  }

  ended() {
    return this.isEnded;
  }

  requestPictureInPicture() {
    if (!this.disablePictureInPicture())
      throw new Error(`flvh265 don't support Picture In Picture.`)
  }

  disablePictureInPicture(p) {
    if (p === undefined) {
      return this.options_.disablePictureInPicture;
    }
    this.options_.disablePictureInPicture = p;
  }

  playbackRate() {
    return 1;
  }

  log() {
    if (this.debug) {
      return console.log;
    } else return () => { }
  }

}

/**
 * Check if the `FlvH265` tech is currently supported.
 *
 * @return {boolean}
 */
FlvH265.isSupported = function () {
  return WXInlinePlayer.isSupport();
};

/*
 * Determine if the specified media type can be played back
 * by the Tech
 *
 * @param  {String} type  A media type description
 * @return {String}         'probably', 'maybe', or '' (empty string)
 */
FlvH265.canPlayType = function (type) {
  return (type.indexOf('/x-flv-h265') !== -1) ? 'probably' : (type.indexOf('/x-flv') !== -1) ? 'maybe' : '';
};

/*
 * Check if the tech can support the given source
 * @param  {Object} srcObj  The source object
 * @return {String}         'probably', 'maybe', or '' (empty string)
 */
FlvH265.canPlaySource = function (srcObj) {
  return FlvH265.canPlayType(srcObj.type);
};

FlvH265.embed = function (objId) {
  const code = `<canvas id="${objId}"></canvas>`;

  // Get element by embedding code and retrieving created element
  const obj = Dom.createEl('div', {
    innerHTML: code
  }).childNodes[0];

  return obj;
};


Tech.registerTech('Flvh265', FlvH265);
export default FlvH265;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容