H5自带播放器
优势
- 开箱即用,支持全屏等各功能
问题
PC上使用video可以正常播放,但是在移动上使用video标签会有诸多问题。可以参考H5-Video 能做的事&存在的坑
1. 各环境中Video UI不一致
2. 各种环境中API实现与支持程度的差异:
看看下面这一堆配置都是什么鬼?(本小节参考资料)
<video
src="video.mp4"
controls
poster="images.jpg"
preload="auto"
webkit-playsinline="true"
playsinline="true"
x-webkit-airplay="allow"
x5-video-player-type="h5"
x5-video-player-fullscreen="true"
x5-video-orientation="portraint"
style="object-fit:fill">
</video>
我们针对不同的环境,必须做一些符合它的要求的配置,以期望能尽量达成我们想要的目的。
- 但是不得不说的是,标准API里的controls、poster、autoplay也都未必有效。
- 自动播放在移动端浏览器上是不被允许的,可以尝试mute下自动播放是否支持
- 前面说的通过source来设置不同媒体源的方案,部分浏览器也可能是不认识的。
例如监听数据加载拿到视频数据的场景,
canplay
和canplaythrough
在不同系统版本的设备上支持程度不同。一般我们会使用兼容性好的onloadedmetadata
来获取视频的duration
。3. 事件交互行为不一致
通过MediaEvents&API 检测,会发现各环境中播放进度变化、事件触发频率不同、部分事件触发时相应状态值未必可靠、部分场景缺少事件(全屏状态变化、系统播放器劫持)、seek时不一定触发play...
- 例如ios部分环境监听
canplay
和canplaythrough
(是否已缓冲了足够的数据可以流畅播放),当加载时是不会触发的,即使preload="auto"
也没用,但在pc的chrome调试器下和android下,是会在加载阶段就触发,ios需要播放后才会触发。 - 部分环境中视频从开始播到能展现画面会有短暂的黑屏(处理视频源数据的时间),为了避免这个黑屏,可以在视频上加个浮层或设置容器背景并在播放前隐藏video,然后监听相关事件,开始播放或认为有画面的时候再切换到Video界面。
- 部分环境中在你没设置poster的时候点击播放时也会尝试加载poster,导致到播放这段时间会有一段时间黑屏。
4. 媒体格式的支持无法保证
MP4 总体支持较好、M3U8在高版本移动环境较好、媒体点播直播中现存大量 flv 播放需求原生Video是不支持的。
手写移动端播放器
目标
- 封面
- 播放/暂停/重播
- 拖动滚动条调节进度
- 全屏与非全屏
- 自定义按钮如分享
实现
第一步:H5适配模版
<video
:src="videoSrc"
:poster="defaultPoster"
preload="auto"
@click.stop.prevent="handleVideoClick"
class="video__content"
playsinline="true"
x5-playsinline="true"
webkit-playsinline="true"
x-webkit-airplay="allow"
x5-video-player-type="h5"
x5-video-player-fullscreen="true"
x5-video-orientation="portraint"
></video>
preload
: 属性规定在页面加载后载入视频。controls
: 默认会false
,后面我们会自己实现控制条控制。webkit-playsinline
,x5-playsinline
和playsinline
: 视频播放时局域播放,不脱离文档流。x5-video-player-type
: 启用同层H5播放器,就是在视频全屏的时候,div可以呈现在视频层上,也是WeChat安卓版特有的属性x5-video-orientation
: 声明播放器支持的方向:landscape
横屏,portraint
竖屏x5-video-player-fullscreen
:全屏设置:true
支持全屏播放,false
不支持全屏播放。object-fill
:fill
(会强制填充满播放器,视频比例可能会被改变)/contain
(视频按照比例撑满播放,宽高不能同时撑满播放器)/...
第二步:poster
原理:在video上面放一个div容器,让这个容器填满video组件。通过设置div的background-image
加载图片。
<template>
<div
class="video-poster"
:style="style"
>
</div>
</template>
<script lang="ts">
import {
Vue, Prop, Component
} from 'vue-property-decorator';
@Component({})
export default class extends Vue {
@Prop()
public videoPoster?: string;
get style() {
return `background-image:url(${this.videoPoster})`;
}
}
</script>
<style lang="scss" scoped>
.video-poster{
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 3; // 显示在video组件最上层
background-repeat: no-repeat;
background-size: 100% 100%; // 100%填充
background-position: 50%;// 居中放置
}
</style>
第三步:实现滚动条进度控制
支持点击进度条调整进度和拖动圆点调整进度:放置一个滚动条容器,里面放一个实际进度区域和一个圆点元素。
currentTime决定进度条进度
活动的进度条长度 / 进度条总长度 = currentTime / duration
通过传入不同currentTime就可以实现不同的进度展示。
在次基础上可以封装出包含播放/暂停等动作的全屏下的进度条和非全屏幕下的进度条。
进度条前进方案有两种:
活动进度条的长度100%,初始时将活动进度条向左偏移100%,设置超出容器是
hidden
。通过计算出的距离,使用translateX
控制移动。- 优点:前进非常流畅
- 缺点:横竖屏切换的时候需要重新计算位置
通过活动进度条的长度
0-100%
来控制进度- 优点:自动适配横竖屏切换
- 缺点:前进的时候有闪烁,性能越差越明显。
比较发现前进的时候有闪烁可以接受,使用了第二种方案。
// active controls 样式
public getWindowActiveMoveStyle(): Object {
const percent = this.currentTime / this.duration;
return {
'width':`${Math.round(percent * 10000) / 100}%`,
'background-color':`${this.activeControlBarColor}`
};
}
点击进度条,换算成currentTime
// 点击进度条,换算成currentTime
private getTouchedTime(e: TouchEvent): number {
const clientRects = this.windowProgressEl.getClientRects() || []; // IOS>=6
const touchedClientX = e.touches[0].clientX;
const touchedClientY = e.touches[0].clientY;
const { left = 1, top = 0, width = 0, height= 0 } = clientRects[0] || {};
/** 根据是否全屏,计算方式也有区别:主要是旋转之后一些属性值发生了变化 */
const unit = this.fullscreen ? height : width;
const touchedClientUnit = this.fullscreen ? touchedClientY : touchedClientX;
const positionUnit = this.fullscreen ? top : left;
return (touchedClientUnit - positionUnit) / unit * this.duration;
}
第四步:实现全屏播放
全屏后画面大小设置:
video{ // object-fit: fill; 这个会填充满区域,有些问题 object-fit: contain }
非全屏下,播放画面大小受外层容器限制;全屏下如何使画面充满而不受外面容器限制
z-index:100; width : 100vmax; height : 100vmin; transform-origin: top left; transform: rotate(90deg) translate(0,-100vmin); background-color: #000;
转屏之后换算成currentTime需要注意
private getTouchedTime(e: TouchEvent): number {
...
/** 根据是否全屏,计算方式也有区别:主要是旋转之后一些属性值发生了变化 */
const unit = this.fullscreen ? height : width;
const touchedClientUnit = this.fullscreen ? touchedClientY : touchedClientX;
const positionUnit = this.fullscreen ? top : left;
return (touchedClientUnit - positionUnit) / unit * this.duration;
}
其他
全局loading
this.videoRef.addEventListener('waiting', () => { this.isLoading = true; }, false);
断网处理
public mounted(){ // 网络状况处理 window.addEventListener('offline', this.handleNetwork, false); window.addEventListener('online', this.handleNetwork, false); } private handleNetwork() { if (navigator.onLine) { this.isOnline = true; } else { this.isOnline = false; } }
细节处理
边距问题
- 返回按键和进度条/播放按钮 位置要考虑曲面屏
- 进度圆圈要留padding,防止点不到
整体需要蒙层
- loading下需要一个蒙层,显示出loading 动画转起来
- 播放器上各个图标(白色)、进度条下需要一个蒙层,防止视频内容是白色的时候显示不出来
参考资料:
- H5-Video问题:H5-Video 能做的事&存在的坑
- H5video标签设置: 视频H5 video标签最佳实践
- 视频格式了解:视屏播放那些事
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。