如何更快速的跟进H5播放器相关业务迭代
奇舞团
胡尊杰
<video src="https://chimee.org/vod/1.mp4" controls>
您的浏览器不支持Video标签。
</video>
video 标签效果
使用source标签给video设置多个备用媒体资源
<video controls>
<source src="https://chimee.org/vod/2.webm">
<source src="https://chimee.org/vod/2.ogg">
<source src="https://chimee.org/vod/2.mp4">
<!--
<source src='video.mp4' type='video/mp4; codecs="mp4v.20.8, mp4a.40.2"'>
<source src='video.mp4' type='video/mp4; codecs="mp4v.20.240, mp4a.40.2"'>
-->
<p>当前环境不支持video标签。</p>
</video>
video 标签效果
let videoEl = document.createElement("video");
// 是否支持 MP4
videoEl.canPlayType('video/mp4') !== '';
// 是否支持 MP4 & 特定编码的
videoEl.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"') !== '';
// 是否支持 webm & 特定编码的
videoEl.canPlayType('video/webm; codecs="vp8, vorbis"') !== '';
// 是否支持 ogg & 特定编码的
videoEl.canPlayType('video/ogg; codecs="theora, vorbis"') !== '';
// 是否支持 HLS 的 m3u8
videoEl.canPlayType('application/vnd.apple.mpegURL') !== '';
// 是否支持 HLS 的 TS 切片
videoEl.canPlayType('video/mp2t; codecs="avc1.42E01E,mp4a.40.2"') !== '';
使用 canPlayType 判断容器类型兼容性
一个基于video交互实现的 RPG 广告片(片段)
let iptFileEl = document.querySelector('input[type="file"]');
let videoEl = document.querySelector('video');
iptFileEl.onchange = e =>{
let file = iptFileEl.files && iptFileEl.files[0];
playFile(file);
};
function playFile(file){
if(file){
let fileReader = new FileReader();
fileReader.onload = evt => {
if(FileReader.DONE == fileReader.readyState){
videoEl.src = fileReader.result;
}else{
console.log('FileReader Error:', evt);
}
}
fileReader.readAsDataURL(file);
}else{
videoEl.src = '';
}
}
基于 FileReader API 播放本地文件
一个线上应用场景:https://gif.75team.com/
navigator.getUserMedia(
{ audio: false, video: true},
function(stream) {
let video = document.querySelector('video');
video.srcObject = stream;
video.onloadedmetadata = () => video.play();
window.streamTrack = stream.getTracks()[0];
},
function(err) {
alert('getUserMedia error: ' + err.message);
}
);
基于 getUserMedia API 播放摄像头视频流
配合 canvas 实现人脸识别
配合 MediaRecorder 实现视频录制
var video = document.querySelector('video');
var mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', function() {
var sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');
fetchAB('https://nickdesaulniers.github.io/netfix/demo/frag_bunny.mp4', function (buf) {
sourceBuffer.addEventListener('updateend', function () {
mediaSource.endOfStream();
video.play();
});
sourceBuffer.appendBuffer(buf);
});
});
function fetchAB (url, cb) {
var xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.responseType = 'arraybuffer';
xhr.onload = function () { cb(xhr.response) };
xhr.send();
};
Web前端可以做更多更有趣的事情了诶~
部分环境劫持 video
playsinline webkit-playsinline="true"
x-webkit-airplay="true"
x5-video-player-type="h5"
MediaEvents&API 检测:https://www.w3.org/2010/05/video/mediaevents.html
资源争夺可能带来未知的BUG
如果已经实现了业务支持
后续迭代优化,在业务代码中插入收集逻辑?
一套基于video实现的H5多容器兼容播放器
import ChimeePlayer from 'chimee-player';
new ChimeePlayer({
wrapper: '.chimee-container',
src: 'https://chimee.org/vod/1.mp4',
controls: true
});
// 直播
new ChimeePlayer({
wrapper: '#wrapper',
src: 'http://chimee.org/xxx/fff.flv',
box: 'flv',
isLive: true,
autoplay: true,
controls: true
});
chimee-player 默认包含 flv、hls 解码器,controlbar、center-state、contextmenu、log 插件
ChimeePlayer 应用
new ChimeePlayer({
wrapper: '#wrapper', // video dom容器
src: 'http://chimee.org/vod/1.mp4',
autoplay: true,
controls: true,
plugins: [{
name: 'chimeeLog',
// 告诉 chimeeLog 插件你有一个可以接受日志上报的服务端接口
logPostUrl: 'https://myDomain.xx/log_push'
}]
});
import ChimeePlayer from 'chimee-player';
// 基于 popup 工厂方法灵活控制插件展示位置
var aggdPlugin = ChimeePlayer.popupFactory({
name: 'my-plugin',
className: 'css-cls',
title: false,
body: '<em>广告示例</em>',
offset: '0px 10px auto auto',
operable: false
});
ChimeePlayer.install(aggdPlugin);
var player = new ChimeePlayer({
wrapper: '.chimee-container',
src: 'http://chimee.org/vod/1.mp4',
isLive: false,
autoplay: false,
controls: true,
plugin: [aggdPlugin.name]
});
自定义插件示例
const plugin = {
// 插件名为 controller
name: 'controller',
// 插件实体为按钮
el: '<button>play</button>',
data: {
text: 'play'
},
methods: {
changeVideoStatus () {
this[this.text]();
},
changeButtonText (text) {
this.text = text;
this.$dom.innerText = this.text;
}
},
// 在插件创建的阶段,我们为插件绑定事件。
create () {
this.$dom.addEventListener('click', this.changeVideoStatus);
// 可以 watch 监听当前插件或video上的属性变化
this.$watch('controls', (newVal, oldVal) => console.log(newVal, oldVal));
},
// 插件会在播放暂停操作发生后改变自己的文案及相应的行为
events: {
pause () {
this.changeButtonText('play');
},
// 视频播放钩子,如果返回的是 false 或者 Promise.reject(), 则事件被阻截。
// 如果返回的是处于 pending 状态的 Promise, 则可以理解为事件被挂起。
beforePlay () { },
play () {
this.changeButtonText('pause');
}
},
computed: {
type_str(){
return this.$videoConfig.isLive?'live':'vod';
}
}
};
更多功能用法,参见 plugin-API
import Chimee from 'chimee';
import chimeeControl from 'chimee-plugin-controlbar';
import chimeeCenterState from 'chimee-plugin-center-state';
import chimeeContextmenu from 'chimee-plugin-contextmenu';
import chimeeLog from 'chimee-plugin-log';
import popupFactory from 'chimee-plugin-popup';
import chimeeKernelHls from 'chimee-kernel-hls';
import {isObject, isArray} from 'chimee-helper';
import './index.css';
// 为播放器增加“水滴直播”台标
const shuidiLogo = popupFactory({
name: 'shuidiLogo',
className: 'shuidi-logo',
title: false,
body: '<a href="http://jia.360.cn/pc" target="_blank"></a>',
offset: '5px',
create () {
const $logo = this.$domWrap.find('a');
const {topicid, sn, channel} = this.$videoConfig;
// 有在实例化时候传入台号,则在台标处展示之
channel && $logo.text(channel + ' 台').addClass('sd-channel');
// 为台标增加链接
const idQuery = topicid ? `topicid=${topicid}` : sn ? `sn=${sn}` : '';
idQuery !== '' && $logo.attr('href', 'http://jia.360.cn/pc/view.html?' + idQuery);
}
});
Chimee.install(chimeeControl);
Chimee.install(chimeeCenterState);
Chimee.install(chimeeContextmenu);
Chimee.install(chimeeLog);
Chimee.install(shuidiLogo);
class ShuiDiPlayer extends Chimee {
constructor (config) {
if(!isObject(config)) throw new TypeError('You must pass an Object as config when you new ShuiDiPlayer');
if(!isArray(config.plugin)) config.plugin = [];
const innerPlugins = [
chimeeControl.name, chimeeCenterState.name,
chimeeContextmenu.name, chimeeLog.name,
shuidiLogo.name
];
const configPluginNames = config.plugin.map(item => isObject(item) ? item.name : item);
innerPlugins.forEach(name => {
if(configPluginNames.indexOf(name) > -1) return;
config.plugin.push(name);
});
if(!isObject(config.preset)) {
config.preset = {};
}
if(!config.preset.hls) {
config.preset.hls = chimeeKernelHls;
}
super(config);
this.on('play', () => {
this.chimeeContextmenu.updatemenu([{text: '暂停', action: 'pause'}]);
});
this.on('pause', () => {
this.chimeeContextmenu.updatemenu([{text: '播放', action: 'play'}]);
});
}
}
定制效果:水滴直播
反馈