??最近接觸到angular項(xiàng)目椒涯,有一個(gè)播放視頻的需求视搏,故嘗試用Rxjs實(shí)現(xiàn)了該封裝径密。主要實(shí)現(xiàn)的功能有:播放控制午阵、進(jìn)度控制(點(diǎn)擊控制條以及拖動(dòng)進(jìn)度條)、音量控制(點(diǎn)擊控制條以及拖動(dòng)進(jìn)度條)享扔、全屏趟庄、錯(cuò)誤信息展示。
注:
以下代碼中創(chuàng)建的Subscription皆不展示退訂代碼伪很,實(shí)際開(kāi)發(fā)中切記使用unsubscribe
進(jìn)行退訂操作戚啥。
-
播放控制
即暫停時(shí)點(diǎn)擊按鈕播放,播放時(shí)點(diǎn)擊按鈕暫停锉试,核心代碼如下:
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();
}
}
);
}
-
進(jìn)度控制 && 音量控制
這里我把這兩個(gè)控制操作抽象了progress.service.ts
服務(wù)猫十,核心代碼(Rxjs實(shí)現(xiàn)拖拽)如下:
constructor(options: {
// 傳入控制條元素
container: Element;
// 傳入控制條拖動(dòng)元素
slider?: Element;
// 拖動(dòng)方向 (視頻控制條左右、音量控制條上下)
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');
// 通過(guò)點(diǎn)擊控制條控制進(jìn)度
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
});
});
// 通過(guò)拖動(dòng)滑塊控制進(jìn)度
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(
...
...
...
)
}
-
全屏控制
同進(jìn)度控制呆盖,這里抽象為fullscreen.service.ts
服務(wù)拖云,核心代碼如下:
constructor(options: {
// 觸發(fā)全屏的元素 一般為按鈕
trigger: Element;
// 全屏的目標(biāo)元素
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
});
}
-
錯(cuò)誤信息提示
即當(dāng)視頻加載出現(xiàn)錯(cuò)誤時(shí)給用戶以提示,視頻地址無(wú)效应又,跨域等宙项。之前嘗試監(jiān)聽(tīng)video事件,onerror
株扛、onabort
尤筐、onstalled
、onemptied
...皆無(wú)果洞就,最后解決方案如下:
get errorTips() {
if (!this.player) {
return '';
}
const { error } = this.player;
if (!error) {
return '';
}
const { code, message } = error;
return (
message ||
['', '意外的中止', '網(wǎng)絡(luò)錯(cuò)誤', '視頻解碼錯(cuò)誤', '無(wú)效的視頻地址'][code]
);
}