h265視頻流播放前端實(shí)戰(zhàn)

對于h265視頻流的播放,這里采用videojs-flvh265椿胯。
話不多說,看如何實(shí)戰(zhàn)(基于vue項(xiàng)目)。

使用

下載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
上面文件在百度網(wǎng)盤中可下載擅耽。鏈接: 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;

/**
 * 生命周期對應(yīng)的狀態(tài)
 */
const STATE = {
  created: "created",
  play: "play",
  playing: "playing",
  buffering: "buffering",
  /**video.js沒有stopped狀態(tài),paused也包含stopped */
  paused: "paused",
  resumed: "resumed",
  // ended: "ended", video.js沒有ended這個(gè)狀態(tài)
  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>標(biāo)簽的屬性秫筏。
 * 外部設(shè)置屬性時(shí)并不區(qū)分大小寫。
 */
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; //狀態(tài)挎挖,hack for video.js
    self.isEnded = false; //因?yàn)関ideol.js沒有ended狀態(tài)这敬,這里單獨(dú)設(shè)置一個(gè)變量(非狀態(tài))標(biāo)志是否播放完,

    //7.8.4丟失了這個(gè)屬性蕉朵,和github源碼不一致崔涂,手動(dòng)補(bǔ)全
    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的這個(gè)鉤子函數(shù)包括多種職責(zé)(這是不妥的)
   * 1.首次播放
   * 2.暫停后繼續(xù)播放
   * 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;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末始衅,一起剝皮案震驚了整個(gè)濱河市冷蚂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌汛闸,老刑警劉巖蝙茶,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異诸老,居然都是意外死亡隆夯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門别伏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蹄衷,“玉大人,你說我怎么就攤上這事厘肮±⒖冢” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵类茂,是天一觀的道長耍属。 經(jīng)常有香客問我,道長巩检,這世上最難降的妖魔是什么恬涧? 我笑而不...
    開封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮碴巾,結(jié)果婚禮上溯捆,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好提揍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開白布啤月。 她就那樣靜靜地躺著,像睡著了一般劳跃。 火紅的嫁衣襯著肌膚如雪谎仲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天刨仑,我揣著相機(jī)與錄音郑诺,去河邊找鬼。 笑死杉武,一個(gè)胖子當(dāng)著我的面吹牛辙诞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播轻抱,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼飞涂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了祈搜?” 一聲冷哼從身側(cè)響起较店,我...
    開封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎容燕,沒想到半個(gè)月后梁呈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蘸秘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年官卡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秘血。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖评甜,靈堂內(nèi)的尸體忽然破棺而出灰粮,到底是詐尸還是另有隱情,我是刑警寧澤忍坷,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布粘舟,位于F島的核電站,受9級(jí)特大地震影響佩研,放射性物質(zhì)發(fā)生泄漏柑肴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一旬薯、第九天 我趴在偏房一處隱蔽的房頂上張望晰骑。 院中可真熱鬧,春花似錦绊序、人聲如沸硕舆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抚官。三九已至扬跋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凌节,已是汗流浹背钦听。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留倍奢,地道東北人朴上。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像娱挨,于是被迫代替她去往敵國和親余指。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容