playable
Version:
Video player based on HTML5Video
296 lines (239 loc) • 7.15 kB
text/typescript
import View from '../core/view';
import { IView } from '../core/types';
import { screenTemplate } from './templates';
import htmlToElement from '../core/htmlToElement';
import getElementByHook from '../core/getElementByHook';
import toggleElementClass from '../core/toggleElementClass';
import {
VideoViewMode,
IScreenViewStyles,
IScreenViewCallbacks,
IScreenViewConfig,
} from './types';
import styles from './screen.scss';
class ScreenView extends View<IScreenViewStyles>
implements IView<IScreenViewStyles> {
private _callbacks: IScreenViewCallbacks;
private _$rootElement: HTMLElement;
private _$canvas: HTMLCanvasElement;
private _$playbackElement: HTMLVideoElement;
private _isHorizontalStripes: boolean;
private _requestAnimationFrameID: number;
private _currentMode: string;
private _styleNamesByViewMode: {
[viewMode: string]: string;
};
constructor(config: IScreenViewConfig) {
super();
const { callbacks, nativeControls, playbackViewElement } = config;
this._callbacks = callbacks;
this._styleNamesByViewMode = {
[VideoViewMode.REGULAR]: this.styleNames.regularMode,
[VideoViewMode.BLUR]: this.styleNames.blurMode,
[VideoViewMode.FILL]: this.styleNames.fillMode,
};
this._bindCallbacks();
if (nativeControls) {
playbackViewElement.setAttribute('controls', 'true');
}
this._initDOM(playbackViewElement);
this._bindEvents();
this.setViewMode(VideoViewMode.REGULAR);
}
private _bindCallbacks() {
this._updateBackground = this._updateBackground.bind(this);
}
private _initDOM(playbackViewElement: HTMLElement) {
this._$rootElement = htmlToElement(
screenTemplate({
styles: this.styleNames,
}),
);
this._$playbackElement = playbackViewElement as HTMLVideoElement;
this._$rootElement.appendChild(playbackViewElement);
this._$canvas = getElementByHook(
this._$rootElement,
'background-canvas',
) as HTMLCanvasElement;
}
private _bindEvents() {
this._$rootElement.addEventListener(
'click',
this._callbacks.onWrapperMouseClick,
);
this._$rootElement.addEventListener(
'dblclick',
this._callbacks.onWrapperMouseDblClick,
);
}
private _unbindEvents() {
this._$rootElement.removeEventListener(
'click',
this._callbacks.onWrapperMouseClick,
);
this._$rootElement.removeEventListener(
'dblclick',
this._callbacks.onWrapperMouseDblClick,
);
}
focusOnNode() {
this._$rootElement.focus();
}
show() {
toggleElementClass(this._$rootElement, this.styleNames.hidden, false);
}
hide() {
toggleElementClass(this._$rootElement, this.styleNames.hidden, true);
}
getElement() {
return this._$rootElement;
}
hideCursor() {
toggleElementClass(this._$rootElement, this.styleNames.hiddenCursor, true);
}
showCursor() {
toggleElementClass(this._$rootElement, this.styleNames.hiddenCursor, false);
}
setViewMode(viewMode: VideoViewMode) {
if (this._styleNamesByViewMode[viewMode]) {
this.resetBackground();
Object.keys(this._styleNamesByViewMode).forEach(mode => {
toggleElementClass(
this._$rootElement,
this._styleNamesByViewMode[mode],
false,
);
});
toggleElementClass(
this._$rootElement,
this._styleNamesByViewMode[viewMode],
true,
);
if (viewMode === VideoViewMode.BLUR) {
this._startUpdatingBackground();
} else {
this._stopUpdatingBackground();
}
this._currentMode = viewMode;
}
}
setBackgroundSize(width: number, height: number) {
this.setBackgroundWidth(width);
this.setBackgroundHeight(height);
}
setBackgroundWidth(width: number) {
this._$canvas.width = width;
}
setBackgroundHeight(height: number) {
this._$canvas.height = height;
}
private _startUpdatingBackground() {
if (!this._requestAnimationFrameID) {
this._updateBackground();
}
}
private _stopUpdatingBackground() {
if (this._requestAnimationFrameID) {
cancelAnimationFrame(this._requestAnimationFrameID);
this._requestAnimationFrameID = null;
}
}
resetAspectRatio() {
const { videoWidth, videoHeight } = this._$playbackElement;
const { width, height } = this._$rootElement.getBoundingClientRect();
this._isHorizontalStripes =
width / height < (videoHeight ? videoWidth / videoHeight : 0);
toggleElementClass(
this._$rootElement,
this.styleNames.horizontalStripes,
this._isHorizontalStripes,
);
toggleElementClass(
this._$rootElement,
this.styleNames.verticalStripes,
!this._isHorizontalStripes,
);
}
resetBackground() {
if (this._currentMode === VideoViewMode.BLUR) {
this._clearBackground();
}
}
private _getSourceAreas(width: number, height: number): number[][] {
if (this._isHorizontalStripes) {
return [
[0, 0, width, 1],
[0, height - 1, width, 1],
];
}
return [
[0, 0, 1, height],
[width - 1, 0, 1, height],
];
}
private _getCanvasAreas(width: number, height: number): number[][] {
if (this._isHorizontalStripes) {
return [
[0, 0, width, height / 2],
[0, height / 2, width, height / 2],
];
}
return [
[0, 0, width / 2, height],
[width / 2, 0, width / 2, height],
];
}
private _drawAreaFromSource(source: number[], area: number[]) {
const [sourceX, sourceY, sourceWidth, sourceHeight] = source;
const [areaX, areaY, areaWidth, areaHeight] = area;
const ctx = this._$canvas.getContext('2d');
ctx.drawImage(
this._$playbackElement,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
areaX,
areaY,
areaWidth,
areaHeight,
);
}
private _drawBackground() {
const { videoWidth, videoHeight } = this._$playbackElement;
const canvasWidth: number = this._$canvas.width;
const canvasHeight: number = this._$canvas.height;
const sourceAreas: number[][] = this._getSourceAreas(
videoWidth,
videoHeight,
);
const canvasAreas: number[][] = this._getCanvasAreas(
canvasWidth,
canvasHeight,
);
this._drawAreaFromSource(sourceAreas[0], canvasAreas[0]);
this._drawAreaFromSource(sourceAreas[1], canvasAreas[1]);
}
private _updateBackground() {
this._drawBackground();
this._requestAnimationFrameID = requestAnimationFrame(
this._updateBackground,
);
}
private _clearBackground() {
const ctx = this._$canvas.getContext('2d');
ctx.clearRect(0, 0, this._$canvas.width, this._$canvas.height);
}
destroy() {
this._stopUpdatingBackground();
this._unbindEvents();
if (this._$rootElement.parentNode) {
this._$rootElement.parentNode.removeChild(this._$rootElement);
}
this._$rootElement = null;
this._$playbackElement = null;
this._$canvas = null;
}
}
ScreenView.extendStyleNames(styles);
export default ScreenView;