h5-video标签

之前的项目中有使用的video的原因,所以也踩了不少的坑。好久之前的总结了,今天打算找出来共享一下
使用vodeo标签,目前尝试了两种方式,一种是原生的video的标签,一些东西需要自己手写功能,另外一个就是使用video.js已经封装过的项目。
其中也有好多是来自别人的博客,直接搬来用了

  • h5原生的video标签
video标签的一些属性
  <video
    id="my-video"
    src="test.mp4"
    controls = ""    /*禁掉默认控制条-- 必要*/
    poster="poster.jpg"  /*视频封面*/
    preload="auto"  /*预加载*/
    webkit-playsinline="true"  /*iOS 10中设置可以让视频在小窗内播放*/
    playsinline="true"
    x-webkit-airplay="allow"  /*启用AirPlay支持*/
    x5-playsinline
    x5-video-player-type="h5"  /*对于x5内核声明启用同层H5播放器*/
    x5-video-player-fullscreen="true"   /*全屏设置设置为 true 是防止横屏*/
    x5-video-orientation="portraint"  /*播放器的方向,默认为竖屏*/
    x5-video-orientation="portraint" /*播放器支付的方向,landscape横屏,portraint竖屏,默认值为竖屏*/
    style="object-fit:fill" /*去除黑边*/
    >
   </video>
下面我们来看看这些属性的作用:
  1. @ src:要嵌到页面的视频的URL。可选;你也可以使用video块内的 <source> 元素来指定需要嵌到页面的视频

  2. @ autoplay:布尔属性;指定后,视频会马上自动开始播放,不会停下来等着数据载入结束

  3. @ controls:加上这个属性,Gecko 会提供用户控制,允许用户控制视频的播放,包括音量,跨帧,暂停/恢复播放

  4. @ poster:一个海报帧的URL,用于在用户播放或者跳帧之前展示。如果属性未指定,那么在第一帧可用之前5什么都不会展示;之后第一帧就像海报帧一样展示

  5. @ preload:该枚举属性旨在告诉浏览器作者认为达到最佳的用户体验的方式是什么。可能是下列值之一:
    none:提示作者认为用户不需要查看该视频,服务器也想要最小化访问流量;换句话说就是提示浏览器该视频不需要缓存
    metadata:提示尽管作者认为用户不需要查看该视频,不过抓取元数据(比如:长度)还是很合理的
    auto:用户需要这个视频优先加载;换句话说就是提示:如果需要的话,可以下载整个视频,即使用户并不一定会用它
    空字符串:也就代指 auto 值

  6. @ buffered:这个属性可以读取到哪段时间范围内的媒体被缓存了。该属性包含了一个 TimeRanges 对象

  7. @ played:一个 TimeRanges 对象,指明了视频已经播放的所有范围

  8. @ loop:布尔属性;指定后,会在视频结尾的地方,自动返回视频开始的地方

9@muted:布尔属性,指明了视频里的音频的默认设置。设置后,音频会初始化为静音。默认值是 false ,意味着视频播放的时候音频也会播放

  1. @ height:视频展示区域的高度,单位是 CSS 像素

  2. @ width:视频显示区域的宽度,单位是 CSS 像素

  3. @ crossorigin:该枚举属性指明抓取相关图片是否必须用到CORS(跨域资源共享)。 支持CORS的资源 可在 <canvas>元素中被重用,而不会被污染。允许的值如下:
    anonymous:跨域请求会被执行,但是不发送凭证。
    use-credentials:跨域请求A cross-origin request会被执行,且凭证会被发送。

  4. @ TimeRanges 对象表示事件段,比如,视频快进的时间段,有一个 length 属性,表示时间段的个数,有两个方法 start() 和 end() ,分别返回时间段开始的时间点和结束的时间点

  5. @ webkit-playsinline和playsinline:视频播放时局域播放,不脱离文档流 。但是这个属性比较特别, 需要嵌入网页的APP比如WeChat中UIwebview 的allowsInlineMediaPlayback = YES webview.allowsInlineMediaPlayback = YES,才能生效。换句话说,如果APP不设置,你页面中加了这标签也无效,这也就是为什么安卓手机WeChat 播放视频总是全屏,因为APP不支持playsinline,而ISO的WeChat却支持。
    这里就要补充下,如果是想做全屏直播或者全屏H5体验的用户,IOS需要设置删除 webkit-playsinline 标签,因为你设置 false 是不支持的 ,安卓则不需要,因为默认全屏。但这时候全屏是有播放控件的,无论你有没有设置control。 做直播的可能用得着播放控件,但是全屏H5是不需要的,那么去除全屏播放时候的控件,需要以下设置:同层播放。

  6. @ x-webkit-airplay="allow"暂时无法确切的知道其作用,猜测,这个属性应该是使此视频支持ios的AirPlay功能。使用AirPlay可以直接从使用iOS的设备上的不同位置播放视频、音乐还有照片文件,也就是说通过AirPlay功能可以实现影音文件的无线播放,当然前提是播放的终端设备也要支持相应的功能。

  7. @ x5-video-player-type:启用同层H5播放器,就是在视频全屏的时候,div可以呈现在视频层上,也是WeChat安卓版特有的属性。同层播放别名也叫做沉浸式播放,播放的时候看似全屏,但是已经除去了control和微信的导航栏,只留下"X"和"<"两键。目前的同层播放器只在Android(包括微信)上生效,暂时不支持iOS。至于为什么同层播放只对安卓开放,是因为安卓不能像ISO一样局域播放,默认的全屏会使得一些界面操作被阻拦,如果是全屏H5还好,但是做直播的话,诸如弹幕那样的功能就无法实现了,所以这时候同层播放的概念就解决了这个问题。不过在测试的过程中发现,不同版本的IOS和安卓效果略有不同。

  8. x5-video-orientation:声明播放器支持的方向,可选值landscape 横屏, portraint竖屏。默认值portraint。无论是直播还是全屏H5一般都是竖屏播放,但是这个属性需要x5-video-player-type开启H5模式

  9. @ x5­-video­-player­-fullscreen:全屏设置。它又两个属性值,ture和false,true支持全屏播放,false不支持全屏播放。
    其实,IOS 微信浏览器是Chrome的内核,相关的属性都支持,也是为什么X5同层播放不支持的原因。安卓微信浏览器是X5内核,一些属性标签比如playsinline就不支持,所以始终全屏。

  10. @ 还有个问题,在Android的微信里面,就算加上了上面的属性,还会出现上下有黑边,不能全屏的问题。
    解决办法:给video加上object-fit: fill;的style属性。如果还是有黑边有可能是视频尺寸不合适。

事件交互中主要使用的属性
  1. currentTime:播放进行到的时间点,单位为秒
  2. duration:视频总时长,单位为秒
监控指标
  1. 播放:start:1(首次播放)2(重播)

  2. 播放:end:1

  3. 播放暂停:pause:1

  4. 播放中止:pause:1

  5. 快进/快退:Jump:1(快进)2(快退)

  6. 错误:fail: 1(取回过程);2(当下载时发生错误);3(当解码时发生错误);4(不支持音频/视频)

  7. 播放等待: wait:1

  8. 播放时长:totaltime:秒(包含重播)

  9. 播放中止
    具体场景是移动端浏览器切换tab导致的隐藏和用户按home键退出浏览器

    html5 提供了 Page Visibility API 来支持监听tab切换,与之对应新增了

    document.hidden 属性,它显示页面是否为用户当前观看的页面,值为 ture 或 false
    document.visibilityState 属性, visible 表示页面被展现, hidden 表示页面未被展现, prerender 表示页面在重新生成,用户不可见
    visibilitychange 事件,监听页面在 visible 与 hidden 之间的切换

  1. 播放时长
    起初的思路是获取到开始播放到停止播放的事件差,记下时间点使用了 currentTime 属性,主要实现在两方面

    playing 时记下时间点startT, pause 和 ended 和 seeked 时记下时间点endT,endT - startT 即播放时长
    seeked 时记下时间点startT, seeking 时记下时间点endT,endT - startT 即播放时长
    这个思路在 ios 下是看似没有问题的,但是 android 下确实不行,主要原因是 seeking 事件的监听没理解到位,seeking 事件触发点是用户目标跳跃到的位置,比如:视频播放在 0 秒点时,用户点击到了 60 秒点处,这是取到的 currentTime 就是 60 ,本来以为会是 0 , ios 下看似没有问题是因为它的全屏播放模式下,进度条是要拖拽的,不能直接点击到某个点

    于是,使用 timeupdate 来获取 seeking 触发前的时间点,就可以获取到相对准确的播放时长了

  1. error事件
    监听 error 事件会返回 error.code 来标识错误类型:

    1 = MEDIA_ERR_ABORTED - 取回过程被用户中止
    2 = MEDIA_ERR_NETWORK - 当下载时发生错误
    3 = MEDIA_ERR_DECODE - 当解码时发生错误
    4 = MEDIA_ERR_SRC_NOT_SUPPORTED - 不支持音频/视频
    获取横竖屏的信息

    1@  window.onorientationchange = function(){
          switch(window.orientation){
              case -90:
              case 90:
                  alert("横屏:" + window.orientation);
              case 0:
              case 180:
                   alert("竖屏:" + window.orientation);
              break;
          }
        }
    2@  @media (orientation: portrait) { } 横屏
        @media (orientation: landscape) { }竖屏
    3@  旋转 用css3的属性
        transform: rotate(90deg);
遇到的一些状况
  1. 没有 <source> 元素且 src 属性为空时播放会触发 error 事件,状态码为4
    解决:忽略 src 属性为空时的报错

  2. 播放结束会触发暂停
    解决:声明状态变量,随着具体操作更新状态,播放状态下才会执行暂停操作,结束状态不执行

  3. 播放结束后重播会触发 seeking 和 seeked ,一般浏览器触发一次, android 下uc浏览器触发多次
    解决:同上

  4. 一些浏览器监听不到 seeking 和 seeked
    解决:在 timeupdate 里来分析猜测用户行为

  5. 一些浏览器存在多次连续触发 seeking + seeked 的情况
    解决:时间戳 + 节流 等待最后一次

  6. seeking 和 seeked 与 timeupdate 需要保证不会同时执行
    解决:监听到 seeking 触发,就不再执行 timeupdate 模拟
    走过的坑:我曾设想在播放时直接判断出是否支持 seeking ,方式是播放时设置 currentTime 为 0.01 ,然后检测 seeking 属性,后来发现浏览器在这样设置后的 seeking 属性值不一致

  7. 个别浏览器播放状态下不触发 seeking 和 seeked ,但是在重播的时候触发
    解决:声明状态变量,随着具体操作更新状态,结束状态不监听 seeking 触发
    å

默认控件的隐藏
    *::-webkit-media-controls-enclosure {
      display:none !important;
      -webkit-appearance: none;
    }
    *::-webkit-media-controls-panel {
      display: none!important;
      -webkit-appearance: none;
    }
    *::-webkit-media-controls-panel-container {
      display: none!important;
      -webkit-appearance: none;
    }
    *::--webkit-media-controls-play-button {
      display: none!important;
      -webkit-appearance: none;
    }
    *::-webkit-media-controls-start-playback-button {
      display: none!important;
      -webkit-appearance: none;
    }
    *::-webkit-media-controls {
    display: none!important;
    -webkit-appearance: none;
    }
  • 使用video.js来处理video标签
    参考video.js的文档,引入video.js
    实例
    var player = videojs('example_video_1');
    

videojs是全局函数,它可以接收三个参数(id,options,onready)第三个是回掉函数

  • options

有两种方式可以改变videojs的行为:

  1. 通过添加video标签的data-setup属性:
 <video data-setup='{"autoplay":"true",.....}'
  var player = videojs('example_video_1',{autoplay:true,....}) , 

直接传入options

由于第一种方式是写在html标签中,通过JSON.parse解析,性能低,还容易报错。个人更倾向于方法2.

这里有大量关于options的配置参数:http://docs.videojs.com/tutorial-options.html

常用几个项有:
autoplay :  true/false   
//播放器准备好之后,是否自动播放 【默认false】If true/present as an attribute, begins playback when the player is ready

controls : true/false 
//是否拥有控制条 【默认true】,如果设为false ,那么只能通过api进行控制了。也就是说界面上不会出现任何控制按钮

height: 300px
//视频容器的高度,字符串或数字 单位像素  比如: height:300 or height:'300px'

width: 300px
//视频容器的宽度, 字符串或数字 单位像素

loop : true/false //视频播放结束后,是否循环播放

muted : true/false //是否静音

poster: 播放前显示的视频画面,播放开始之后自动移除。通常传入一个URL

preload:auto//预加载
   auto //自动
   metadata //元数据信息 ,比如视频长度,尺寸等
   none  //不预加载任何数据,直到用户开始播放才开始下载

children: Array | Object  
//可选子组件  从基础的Component组件继承而来的子组件,数组中的顺序将影响组件的创建顺序哦。

// 下面的方式只使用bigPlayButton和controlBar两个子组件
videojs('my-player', {
 children: [
     'bigPlayButton',
     'controlBar'
     ]
});

sources:Array //资源文件

  videojs('my-player', {
      sources: [{
          src: '//path/to/video.mp4',
          type: 'video/mp4'
          }, {
          src: '//path/to/video.webm',
          type: 'video/webm'
        }]
    });

等价于html中的形式:

<video ...>
    <source src="//path/to/video.mp4" type="video/mp4">
    <source src="//path/to/video.webm" type="video/webm">
</video>
techOrder: Array //使用哪种技术播放,可选值有'html5','flash'  默认为['html5'], 注意: 在v6.0.0 及以上的版本中,默认不包含flash的使用代码。如果要使用flash播放的,需要手动引入flash相关逻辑的代码。且需要指定swf文件的路径。
// 全局指定swf文件的位置
videojs.options.flash.swf = 'video-js.swf'
    // Create a player.
    var player = videojs('example_video_1',{
        teachOrder:['flash']
        },function(){
          console.log(this)
        }
     });

方法
autoplay
buffered
bufferedEnd
bufferedPercent
cancelFullScreen deprecated
controls
currentSrc
currentTime
currentType
dispose
duration
ended
error
exitFullscreen
init
isFullScreen deprecated
isFullscreen
language
load
loop
muted
pause
paused
play
playbackRate
poster
preload
remainingTime
requestFullScreen deprecated
requestFullscreen
seeking
src
volume
addChild inherited
addClass inherited
buildCSSClass inherited
children inherited
contentEl inherited
createEl inherited
dimensions inherited
el inherited
enableTouchActivity inherited
getChild inherited
getChildById inherited
hasClass inherited
height inherited
hide inherited
id inherited
initChildren inherited
name inherited
off inherited
on inherited
one inherited
options inherited
player inherited
ready inherited
removeChild inherited
removeClass inherited
show inherited
trigger inherited
triggerReady inherited
width inherited
事件
durationchange
ended
firstplay
fullscreenchange
loadedalldata
loadeddata
loadedmetadata
loadstart
pause
play
progress
seeked
seeking
timeupdate
volumechange
waiting
resize inherited
组件
  Player
      PosterImage
      TextTrackDisplay
      LoadingSpinner
      BigPlayButton
      ControlBar
          PlayToggle
          FullscreenToggle
          CurrentTimeDisplay
          TimeDivider
          DurationDisplay
          RemainingTimeDisplay
          ProgressControl
              SeekBar
                LoadProgressBar
                PlayProgressBar
                SeekHandle
          VolumeControl
              VolumeBar
                  VolumeLevel
                  VolumeHandle
          MuteToggle


//eg:移除静音按钮
var player = videojs(‘video-id‘, {
  controlBar: {
    muteToggle: false
  }
});
自定义组件(低版本不支持)
    // Get the Component base class from Video.js
    // 从Videojs中获取一个基础组件
    var Component = videojs.getComponent('Component');

    // The videojs.extend function is used to assist with inheritance. In
    // an ES6 environment, `class TitleBar extends Component` would work
    // identically.
    // videojs.extend方法用来实现继承,等同于ES6环境中的class titleBar extends Component用法
    var TitleBar = videojs.extend(Component, {

    // The constructor of a component receives two arguments: the
    // player it will be associated with and an object of options.
    // 这个构造函数接收两个参数:
    // player将被用来关联options中的参数
    constructor: function(player, options) {

    // It is important to invoke the superclass before anything else,
    // to get all the features of components out of the box!
    // 在做其它事之前先调用父类的构造函数是很重要的,
    // 这样可以使父组件的所有特性在子组件中开箱即用。
    Component.apply(this, arguments);

    // If a `text` option was passed in, update the text content of
    // the component.
    // 如果在options中传了text属性,那么更新这个组件的文字显示
    if (options.text) {
      this.updateTextContent(options.text);
    }
    },

    // The `createEl` function of a component creates its DOM element.
    // 创建一个DOM元素
    createEl: function() {
    return videojs.dom.createEl('div', {

      // Prefixing classes of elements within a player with "vjs-"
      // is a convention used in Video.js.
      //给元素加vjs-开头的样式名,是videojs内置样式约定俗成的做法
      className: 'vjs-title-bar'
    });
    },

    // This function could be called at any time to update the text
    // contents of the component.
    // 这个方法可以在任何需要更新这个组件内容的时候调用
    updateTextContent: function(text) {

    // If no text was provided, default to "Text Unknown"
    // 如果options中没有提供text属性,默认显示Text Unknow
    if (typeof text !== 'string') {
      text = 'Text Unknown';
    }

    // Use Video.js utility DOM methods to manipulate the content
    // of the component's element.
    // 使用Video.js提供的DOM方法来操作组件元素
    videojs.dom.emptyEl(this.el());
    videojs.dom.appendContent(this.el(), text);
    }
    });

    // Register the component with Video.js, so it can be used in players.
    // 在videojs中注册这个组件,才可以使用哦.
    videojs.registerComponent('TitleBar', TitleBar);


    //使用组件
    player.addChild('TitleBar', {text: '这里是标题'});

我自己在github上的总结以及一些demo

参考资料

原生video
videojs 文档
videojs github
html5--移动端视频video的android兼容,去除播放控件、全屏等
移动端手机网页强制横屏或全屏模仿横评的js和css3方法
videojs 使用以及创建组件
videojs的使用 方法 事件 比较全
video.js--很赞的H5视频播放库
video.js 的文档

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,064评论 25 707
  • 用到的组件 1、通过CocoaPods安装 2、第三方类库安装 3、第三方服务 友盟社会化分享组件 友盟用户反馈 ...
    SunnyLeong阅读 14,582评论 1 180
  • RTB 广告简单来说,就是把访客的每次页面浏览,通过拍卖的形势卖给广告主,谁出的价高就把访客的这次浏览卖给谁,然后...
    耗子吴阅读 8,688评论 1 19
  • 刚听到键盘侠这个词的时候,我脑海里闪过的第一个形象是日剧《Legal high》里那个怯弱说不出话但是打字速度飞快...
    默默喜欢你阅读 411评论 13 9
  • 冬阳,暖暖的我敞开羽绒服,坐在门口低头,盯着手机。搜听世界的声音“帮我摘菜。”一旁的母亲叫我放下手机,一边摘着青菜...
    鸿蒙一叶阅读 235评论 14 24