开发中遇到需要自定义HTML5的video控制面板功能,结合网上资料进行了优化和实现,还可以自定义其它功能,备份下来:
1.实现效果图:
2.html代码片段:
<div class="video-box">
<!--video 盒子-->
<div class="video-box-body">
<video class="video-body" [src]="playPath">
您的浏览器不支持 video 标签。
</video>
<!--控制条盒子-->
<div class="video-control">
<div class="video-control-left">
<!--刷新键-->
<div class="control-btn pull-left" (click)="loadHandle()" title="刷新"><i class="fa fa-refresh"></i></div>
<!--暂停/播放键-->
<div class="control-btn pull-left" [hidden]="errorFlag" (click)="playPauseHandle()" [title]="playPauseTitle"><i id="playPauseFa" class="fa fa-play"></i></div>
<div class="control-btn pull-left errorColor" [hidden]="!errorFlag" [title]="playPauseTitle"><i class="fa fa-play"></i></div>
</div>
<div class="progress-box">
<div class="progress-box-body">
<!--播放时长-->
<div class="current-time pull-left" id="currentTime">00:00</div>
<div class="durationbar-box pull-left">
<!--总视频长度进度条-->
<div class="durationbar" id="durationBar">
<!--缓冲进度条-->
<div class="bufferbar" id="bufferBar"></div>
<!--正在播放进度条-->
<div class="currentbar" id="currentBar"></div>
<div class="drawbar" id="drawBar"></div>
</div>
</div>
<!--总时长-->
<div class="duration-time pull-left" id="durationTime">00:00</div>
</div>
</div>
<div class="video-control-right">
<!--倍数键-->
<div class="control-multiple pull-left" title="倍数">
<p-dropdown id="multipleId" [options]="multiples" [(ngModel)]="multiple" (onChange)="multipleHandle()"
[disabled]="errorFlag" [style]="{'min-width':'20px', 'width': '100%'}"></p-dropdown>
</div>
<!--音量键-->
<div class="control-btn pull-left" [hidden]="errorFlag" (click)="mutedHandle()" [title]="mutedTitle"><i id="mutedFa" class="fa fa-volume-up"></i></div>
<div class="control-btn pull-left errorColor" [hidden]="!errorFlag" [title]="mutedTitle"><i class="fa fa-volume-up"></i></div>
<!--全屏键-->
<div class="control-btn pull-left" *ngIf="!isFullScreen" (click)="fullScreenHandle()" title="全屏"><i class="fa fa-expand"></i></div>
<div class="control-btn pull-left" *ngIf="isFullScreen" (click)="exitFullScreenHandle()" title="还原"><i class="fa fa-compress"></i></div>
</div>
</div>
</div>
</div>
3.scss样式代码:
/*video样式*/
.video-box {
overflow: hidden;
background: #000;
width: 100%;
height: calc(100% - 10px);
display: block;
margin: 0 auto;
-webkit-transition-duration: 300ms;
-moz-transition-duration: 300ms;
-ms-transition-duration: 300ms;
-o-transition-duration: 300ms;
transition-duration: 300ms;
z-index: 10;
}
/*video body样式*/
.video-box-body {
position: relative;
width: 100%;
height: 100%;
margin: 0 auto;
overflow: hidden;
.video-body {
display: block;
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 15;
object-fit: fill;
}
}
/*控制条样式*/
.video-control {
position: absolute;
width: 100%;
height: 50px;
padding: 10px;
line-height: 30px;
background: rgba(0, 0, 0, .5);
z-index: 99999999999;
left: 0;
right: 0;
bottom: 0;
div {
display: inline-block;
}
.control-btn {
display: inline-block;
width: 30px;
height: 30px;
background: transparent;
cursor: pointer;
font-size: 20px;
color: white;
border-radius: 100%;
text-align: center;
&:hover {
background-color: rgba(0, 0, 0, 0.5);
border-color: rgba(0, 0, 0, 0.5);
}
}
// 左
.video-control-left {
position: absolute;
height: 30px;
z-index: 5;
.control-btn {
margin-right: 10px;
&:last-child {
margin: 0;
}
}
}
// 中
.progress-box {
width: 100%;
height: 30px;
padding: 0 130px 0 70px;
.progress-box-body {
width: 100%;
height: 100%;
.current-time, .duration-time {
width: 60px;
text-align: center;
color: #fff;
}
.current-time {
margin-right: -60px;
position: relative;
z-index: 5;
}
.duration-time {
margin-left: -60px;
position: relative;
z-index: 5;
}
.durationbar-box {
width: 100%;
padding: 0 70px;
}
.durationbar {
width: 100%;
height: 10px;
margin-top: 10px;
background: rgba(65, 62, 62, 1);
-webkit-border-radius: 50px;
-moz-border-radius: 50px;
-ms-border-radius: 50px;
-o-border-radius: 50px;
border-radius: 50px;
position: relative;
}
.bufferbar, .currentbar {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 0;
background: rgba(117, 114, 114, 1);
-webkit-border-radius: 50px;
-moz-border-radius: 50px;
-ms-border-radius: 50px;
-o-border-radius: 50px;
border-radius: 50px;
z-index: 5;
cursor: pointer;
}
.currentbar {
background: #fff;
z-index: 10;
}
.drawbar {
position: absolute;
background: #fff;
width: 20px;
height: 20px;
left: 0;
top: -5px;
z-index: 10;
-webkit-border-radius: 50px;
-moz-border-radius: 50px;
-ms-border-radius: 50px;
-o-border-radius: 50px;
border-radius: 50px;
cursor: pointer;
}
}
}
// 右
.video-control-right {
position: absolute;
height: 30px;
margin-left: -130px;
z-index: 5;
.control-btn {
margin-left: 10px;
&:first-child {
margin: 0;
}
}
}
}
// 倍数
:host ::ng-deep .control-multiple {
width: 50px;
height: 30px;
border-radius: 5px;
.ui-dropdown {
border: none;
&:not(.ui-state-disabled):hover {
background-color: rgba(0, 0, 0, 0.5);
border-color: rgba(0, 0, 0, 0.5);
.ui-dropdown-label {
background: transparent;
}
}
.ui-inputtext {
color: white;
}
.ui-dropdown-label {
background: transparent;
}
}
}
video:-webkit-full-screen {
z-index: 9 !important;
width: 100% !important;
height: 100% !important;
}
video::-webkit-media-controls {
display: none !important;
}
.errorColor {
color: #838383 !important;
}
4.ts代码处理:
public playPath: any; // 播放路径
public videoDom: any; // 播放器对象
public playPauseDom: any; // 播放/暂停图标对象
public playPauseTitle: string = "播放"; // 播放/暂停提示
public mutedDom: any; // 音量图标对象
public mutedTitle: string = "关闭"; // 开启/关闭提示
public isFullScreen: boolean = false; // 是否全屏
public multiples: any = []; // 播放的倍数
public multiple: number = 1; // 选择的倍数
public errorFlag: boolean = true; // 加载错误
constructor(
public viewVideoService:ViewVideoService,
private renderer: Renderer2,
private el: ElementRef
) {
this.multiples = [{
label: '5.0X',
value: 5
},{
label: '2.0X',
value: 2
},{
label: '1.5X',
value: 1.5
},{
label: '1.0X',
value: 1
},{
label: '0.5X',
value: 0.5
}];
}
ngOnInit() {
// 路径源
this.playPath=this.viewVideoService.getPath();
}
ngAfterViewInit() {
// video等元素
this.videoDom = this.el.nativeElement.querySelector("video");
this.playPauseDom = this.el.nativeElement.querySelector("#playPauseFa");
this.mutedDom = this.el.nativeElement.querySelector("#mutedFa");
// 去除倍数播放下拉框多余元素
let multipleElement = this.el.nativeElement.querySelector("#multipleId");
let dropdownElement = multipleElement ? multipleElement.querySelector(".ui-dropdown-trigger"):null;
if (dropdownElement) dropdownElement.remove();
// 事件
this.eventHandle();
}
/**
* 结束或暂停播放状态图标切换
**/
private pauseHandle() { // 结束或暂停
// 刷新后已经暂停
this.playPauseTitle = '播放';
if (this.playPauseDom) {
//通过操作不同的class,来切换键的形态
this.renderer.removeClass(this.playPauseDom, 'fa-pause');
this.renderer.addClass(this.playPauseDom, 'fa-play');
}
}
/**
* 刷新视频,直接调用load()方法就行
**/
public loadHandle() { // 刷新
this.videoDom.load();
this.multipleHandle();
// 刷新后已经暂停
this.pauseHandle();
}
/**
* 播放/暂停功能
**/
public playPauseHandle() { // 播放暂停
//paused 返回的是视频是否是暂停状态,返回的是一个布尔值
if(this.videoDom.paused){
this.videoDom.play();
this.playPauseTitle = '暂停';
if (this.playPauseDom) {
//通过操作不同的class,来切换键的形态
this.renderer.removeClass(this.playPauseDom, 'fa-play');
this.renderer.addClass(this.playPauseDom, 'fa-pause');
}
}else{
this.videoDom.pause();
this.pauseHandle();
}
}
/**
* 播放倍数调节
**/
public multipleHandle() {
this.videoDom.playbackRate = this.multiple;
}
/**
* 音量键的开启和关闭
**/
public mutedHandle() {
//如果为静音则开启,如果为开启状态则关闭
this.videoDom.muted=!this.videoDom.muted;
if (this.videoDom.muted) { // 禁音状态
this.mutedTitle = '开启';
if (this.mutedDom) {
//通过操作不同的class,来切换键的形态
this.renderer.removeClass(this.mutedDom, 'fa-volume-up');
this.renderer.addClass(this.mutedDom, 'fa-volume-off');
}
} else { // 开启状态
this.mutedTitle = '关闭';
if (this.mutedDom) {
//通过操作不同的class,来切换键的形态
this.renderer.removeClass(this.mutedDom, 'fa-volume-off');
this.renderer.addClass(this.mutedDom, 'fa-volume-up');
}
}
}
/**
* 全屏调节
**/
public fullScreenHandle() {
const videoBox = this.el.nativeElement.querySelector(".video-box");
if(videoBox.webkitRequestFullScreen){
videoBox.webkitRequestFullScreen();
} else if (videoBox.mozRequestFullScreen){
videoBox.mozRequestFullScreen();
} else {
videoBox.requestFullScreen();
}
this.isFullScreen = true;
}
/**
* 退出全屏
**/
public exitFullScreenHandle() {
const videoBox = this.el.nativeElement.querySelector(".video-box");
if(videoBox.ownerDocument.webkitCancelFullScreen){
videoBox.ownerDocument.webkitCancelFullScreen();
} else if (videoBox.ownerDocument.mozCancelFullScreen){
videoBox.ownerDocument.mozCancelFullScreen();
} else {
videoBox.ownerDocument.exitFullscreen();
}
this.isFullScreen = false;
}
/**
* 事件监听定义
**/
public eventHandle() {
// 键盘事件监听
this.keyEvent();
// 退出全屏监听
this.escExitFullScreen();
// 播放时间监听
this.playTimeHanle();
// video控制面板显示/隐藏处理
this.showHideControlsHandle();
}
/**
* 键盘事件处理
**/
private keyEvent() {
let that = this;
let vol = 0.1; //1代表100%音量,每次增减0.1
let time = 5; //单位秒,每次增减5秒
//键盘事件
document.onkeyup = function (event) {
let e = event || window.event || arguments.callee.caller.arguments[0];
//鼠标上下键控制视频音量
if (e && e.keyCode === 38) {
// 按 向上键
that.videoDom.volume !== 1 ? that.videoDom.volume += vol : 1;
return false;
} else if (e && e.keyCode === 40) {
// 按 向下键
that.videoDom.volume !== 0 ? that.videoDom.volume -= vol : 1;
return false;
} else if (e && e.keyCode === 37) {
// 按 向左键
that.videoDom.currentTime !== 0 ? that.videoDom.currentTime -= time : 1;
return false;
} else if (e && e.keyCode === 39) {
// 按 向右键
that.videoDom.currentTime !== that.videoDom.duration ? that.videoDom.currentTime += time : 1;
return false;
} else if (e && e.keyCode === 32) {
// 按空格键 判断当前是否暂停
that.playPauseHandle();
return false;
}
};
}
/**
* 验证是否全屏ESC退出处理
**/
private escExitFullScreen(){
const videoBox = this.el.nativeElement.querySelector(".video-box");
let videoDoc = videoBox.ownerDocument;
let that = this;
let exitHandler = function() {
let isFull = videoDoc.webkitIsFullScreen;
if(isFull === undefined) isFull = false;
if (!isFull) that.isFullScreen = false;
};
videoBox.addEventListener('fullscreenchange', exitHandler);
videoBox.addEventListener('webkitfullscreenchange', exitHandler);
videoBox.addEventListener('mozfullscreenchange', exitHandler);
}
/**
* 因为我们获取的视频时长是秒数,所以我们需要将秒数转化为我们常见的时间格式
**/
private initTimeLength(timeLength) { // 根据秒数格式化时间
timeLength = parseInt(timeLength);
let second = timeLength % 60;
let minute = (timeLength - second) / 60;
return (minute<10?"0"+minute:minute)+":"+(second<10?"0"+second:second);
};
/**
* 视频时间等效果处理
**/
private playTimeHanle() {
this.videoDom.addEventListener('loadeddata', () => {
const durationTime = this.el.nativeElement.querySelector("#durationTime");
durationTime.innerHTML = this.initTimeLength(this.videoDom.duration);
// 缓冲进度
const bufferBar = this.el.nativeElement.querySelector("#bufferBar");
bufferBar.style.width = '0px';
this.bufferHandle(); // 缓冲进度条处理
this.videoSeekHandle(); // 点击进度条播放处理
this.errorFlag = false;
});
// ontimeupdate 当前视频播放位置反生改变触发的事件;
this.videoDom.addEventListener('timeupdate', () => {
// 视频时长
let durationProgress=this.videoDom.duration;
// currentTime 当前播放时长
let currentTimeProgress=this.videoDom.currentTime;
// 将当前播放时长填入左边时长元素中
const currentTime = this.el.nativeElement.querySelector("#currentTime");
currentTime.innerHTML = this.initTimeLength(this.videoDom.currentTime);
// 求当前播放时长的进度,从而显示出来进度条
let currentWidth=100*(currentTimeProgress/durationProgress);
const drawBar = this.el.nativeElement.querySelector("#drawBar");
const durationBar = this.el.nativeElement.querySelector("#durationBar");
const currentBar = this.el.nativeElement.querySelector("#currentBar");
let currentLeft=currentWidth-((drawBar.offsetWidth/2)/durationBar.offsetWidth*100);
currentBar.style.width = currentWidth > 0 ? currentWidth.toFixed(1)+'%' : '0px';
drawBar.style.left = currentLeft > 0 ? currentLeft.toFixed(1)+'%': '0px';
});
// 播放结束状态
this.videoDom.addEventListener('ended', () => {
// 播放结束后已经暂停
this.pauseHandle();
// 是否需要展示隐藏控制面板
this.hideControls();
});
// 播放错误
this.videoDom.addEventListener('error', () => {
this.errorFlag = true;
});
}
/**
* 缓冲进度条处理
**/
private bufferHandle() {
//视频时长
var maxduration = this.videoDom.duration;
//当前缓冲进度时长结束位置
var currentBuffer = this.videoDom.buffered.end(0);
// 求取百分比
var percentage = 100 * currentBuffer / maxduration;
const bufferBar = this.el.nativeElement.querySelector("#bufferBar");
bufferBar.style.width = percentage.toFixed(1)+'%';
// 在范围内每500毫秒进行一次递归,也就是调用一下自己;
if(currentBuffer<maxduration){
setTimeout( () => {
this.bufferHandle();
},500);
}
}
/**
* video控制面板显示/隐藏处理
**/
private showHideControlsHandle() {
this.videoDom.addEventListener('mouseover', () => { this.showControls(); });
this.videoDom.addEventListener('mouseout', () => { this.hideControls(); });
const videoControls = this.el.nativeElement.querySelector(".video-control");
videoControls.addEventListener('mouseover', () => { this.showControls(); });
videoControls.addEventListener('mouseout', () => { this.hideControls(); });
}
// 显示video的控制面板
private showControls(){
const videoControls = this.el.nativeElement.querySelector(".video-control");
videoControls.style.opacity = 1;
}
// 隐藏video的控制面板
private hideControls(){
const videoControls = this.el.nativeElement.querySelector(".video-control");
if(this.videoDom.paused){ // 暂停
videoControls.style.opacity = 1;
} else {
videoControls.style.opacity = 0;
}
}
/**
* 点击进度条播放处理
**/
private videoSeekHandle() {
const currentBar = this.el.nativeElement.querySelector("#currentBar");
const bufferBar = this.el.nativeElement.querySelector("#bufferBar");
currentBar.addEventListener('mousedown', (e) => { this.videoSeekChange(e); });
bufferBar.addEventListener('mousedown', (e) => { this.videoSeekChange(e); });
}
// 点击进度条的处理方法
private videoSeekChange(e) {
const durationBar = this.el.nativeElement.querySelector("#durationBar");
let length = e.offsetX;
let percent = length / durationBar.offsetWidth;
this.videoDom.currentTime = percent * this.videoDom.duration; // 更改currentTime后会触发timeupdate事件
}