最近接触到angular项目,有一个播放视频的需求,故尝试用Rxjs实现了该封装。主要实现的功能有:播放控制、进度控制(点击控制条以及拖动进度条)、音量控制(点击控制条以及拖动进度条)、全屏、错误信息展示。
注:
以下代码中创建的Subscription皆不展示退订代码,实际开发中切记使用unsubscribe
进行退订操作。
-
播放控制
即暂停时点击按钮播放,播放时点击按钮暂停,核心代码如下:
get isplaying(): boolean {
return this.player && !this.player.paused;
}
initPlayOrPauseEvent():void {
this.playOrPause$ = fromEvent(this.doms.playOrPause, 'click').subscribe(
() => {
if (!this.canOperate) {
return;
}
if (this.isplaying) {
this.pause();
} else {
this.play();
}
}
);
}
-
进度控制 && 音量控制
这里我把这两个控制操作抽象了progress.service.ts
服务,核心代码(Rxjs实现拖拽)如下:
constructor(options: {
// 传入控制条元素
container: Element;
// 传入控制条拖动元素
slider?: Element;
// 拖动方向 (视频控制条左右、音量控制条上下)
direction?: number;
}) {
...
...
...
this.init();
}
init() {
this.mouseClick$ = fromEvent(this.$container, 'click');
this.mouseDown$ = fromEvent(this.$slider, 'mousedown');
this.mouseMove$ = fromEvent(document.body, 'mousemove');
this.mouseUp$ = fromEvent(document.body, 'mouseup');
// 通过点击控制条控制进度
this.mouseClickSub$ = this.mouseClick$.subscribe((event: MouseEvent) => {
const { direction } = this;
const { clientX, clientY } = event;
const bounds = this.$container.getBoundingClientRect();
const { left, bottom, width, height } = bounds;
let progress;
if (direction === 0) {
progress = ((clientX - left) / width) * 100;
}
if (direction === 1) {
progress = ((bottom - clientY) / height) * 100;
}
progress = Math.min(100, Math.max(0, progress));
this.$emit(PROGRESS_EVENT.CHANGE_END, {
type: 'click',
progress,
event
});
});
// 通过拖动滑块控制进度
this.dragSub$ = this.mouseDown$
.pipe(
tap((e: MouseEvent) => {
const { clientX, clientY } = e;
this.initPos = {
left: this.getStyle(this.$slider, 'left'),
top: this.getStyle(this.$slider, 'top')
};
this.initMousePos = {
x: clientX,
y: clientY
};
this._draging = true;
this.$emit(PROGRESS_EVENT.CHANGE_START);
}),
switchMap(_ =>
this.mouseMove$.pipe(
takeUntil(
this.mouseUp$.pipe(
tap(_ => {
this._draging = false;
this.$emit(PROGRESS_EVENT.CHANGE_END);
})
)
),
map((e: MouseEvent) => {
const { clientX, clientY } = e;
return {
x: clientX - this.initMousePos.x,
y: clientY - this.initMousePos.y
};
})
)
)
)
.subscribe(movedPos => {
const targetPos = {
left: this.initPos.left + movedPos.x,
top: this.initPos.top + movedPos.y
};
const { clientWidth, clientHeight } = this.$container;
const {
clientWidth: clientWidthSlider,
clientHeight: clientHeightSlider
} = this.$slider;
targetPos.left = Math.min(
clientWidth - clientWidthSlider / 2,
Math.max(-clientWidthSlider / 2, targetPos.left)
);
targetPos.top = Math.min(
clientHeight - clientHeightSlider / 2,
Math.max(-clientHeightSlider / 2, targetPos.top)
);
let progress: number;
if (this.direction === 0) {
progress =
((targetPos.left + clientWidthSlider / 2) / clientWidth) * 100;
} else {
progress =
((targetPos.top + clientHeightSlider / 2) / clientHeight) * 100;
progress = 100 - progress;
}
progress = Math.min(100, Math.max(0, progress));
this.$emit(PROGRESS_EVENT.CHANGING, {
type: 'drag',
progress
});
});
}
使用:
initEvents() {
this.videoProgress = new ProgressService({
container: this.doms.play_progess
}).$on(PROGRESS_EVENT.CHANGE_START, () => {})
.$on(PROGRESS_EVENT.CHANGING, () => [})
.$on(PROGRESS_EVENT.CHANGE_END, () => {});
this.soundProgress = new ProgressService({
container: this.doms.sound_progress,
direction: 1
}).$on(
...
...
...
)
}
-
全屏控制
同进度控制,这里抽象为fullscreen.service.ts
服务,核心代码如下:
constructor(options: {
// 触发全屏的元素 一般为按钮
trigger: Element;
// 全屏的目标元素
target: Element;
}
) {
...
...
...
this.init();
}
hasFullscreen() {
const element = document as any;
return !!(
element.isFullScreen ||
element.mozIsFullScreen ||
element.msIsFullScreen ||
element.webkitIsFullScreen ||
element.fullScreenElement ||
element.msFullscreenElement ||
element.mozFullScreenElement ||
element.webkitFullscreenElement
);
}
fullScreen() {
const element = this.$target as any;
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
}
}
exitFullscreen() {
const element = document as any;
if (element.exitFullscreen) {
element.exitFullscreen();
} else if ((element as any).msExitFullscreen) {
(element as any).msExitFullscreen();
} else if ((element as any).mozCancelFullScreen) {
(element as any).mozCancelFullScreen();
} else if ((element as any).webkitExitFullscreen) {
(element as any).webkitExitFullscreen();
}
}
init() {
this.triggerClick$ = fromEvent(this.$trigger, 'click').subscribe(() => {
if (this.hasFullscreen()) {
this.exitFullscreen();
} else {
this.fullScreen();
}
this.$emit(FULLSCREEN_EVENTS.CHANGE, this.hasFullscreen());
});
}
使用:
initQuanpinEvent() {
this.fullscreenService = new FullscreenService({
trigger: this.doms.quanpin,
target: this.doms.container
});
}
-
错误信息提示
即当视频加载出现错误时给用户以提示,视频地址无效,跨域等。之前尝试监听video事件,onerror
、onabort
、onstalled
、onemptied
...皆无果,最后解决方案如下:
get errorTips() {
if (!this.player) {
return '';
}
const { error } = this.player;
if (!error) {
return '';
}
const { code, message } = error;
return (
message ||
['', '意外的中止', '网络错误', '视频解码错误', '无效的视频地址'][code]
);
}
网友评论