UNPKG

paella-core

Version:
343 lines (289 loc) 10.6 kB
import { DomClass, createElementWithHtmlText } from 'paella-core/js/core/dom'; import Events, { bindEvent } from 'paella-core/js/core/Events'; import { resolveResourcePath, secondsToTime } from 'paella-core/js/core/utils'; import { loadPluginsOfType, unloadPluginsOfType } from 'paella-core/js/core/plugin_tools'; import ProgressIndicatorTimer from './ProgressIndicatorTimer'; export function getCurrentFrame(sortedFrameList,time) { if (!sortedFrameList || sortedFrameList.length === 0) { return null; } let result = sortedFrameList[0]; let prevTime = result.time; sortedFrameList.forEach(frame => { if (frame.time>prevTime && frame.time<Math.floor(time)) { result = frame; prevTime = result.time; } }) return result; } const g_canvasScale = 4; function updateFrameThumbnail(offsetX,time) { let frame = getCurrentFrame(this.frameList, time); if (frame) { this._frameThumbnail.style.display = "block"; const thumbWidth = this._frameThumbnail.getBoundingClientRect().width; const playbackBar = this.playbackBar; const { top, left, bottom, width, height } = playbackBar.getBoundingClientRect(); const centerX = width / 2; this.frameThumbnail.style.bottom = `${ height }px`; if (centerX > offsetX) { this.frameThumbnail.style.left = `${ offsetX }px`; } else { this.frameThumbnail.style.left = `${ offsetX - thumbWidth }px`; } const frameImage = resolveResourcePath(this.player, frame.url); const thumbImageContainer = this.frameThumbnail.getElementsByClassName("thumbnail-image")[0]; const timeContainer = this.frameThumbnail.getElementsByClassName("thumbnail-time")[0]; if (frameImage !== this._prevFrameImage) { thumbImageContainer.src = frameImage; thumbImageContainer.alt = frame.id; this._prevFrameImage = frameImage; } timeContainer.innerHTML = secondsToTime(time); } } function updateCanvas() { if (Array.isArray(this._canvasPlugins)) { const backgroundContext = this._canvasContext[0]; const foregroundContext = this._canvasContext[1]; const width = this._canvas[0].clientWidth; const height = this._canvas[0].clientHeight; this._canvasPlugins.forEach(plugin => { plugin.drawForeground(foregroundContext, width, height, this._isHover, g_canvasScale); plugin.drawBackground(backgroundContext, width, height, this._isHover, g_canvasScale); }) this._updateCanvas = false; } } function updateHeight() { const size = { w: this.element.offsetWidth * g_canvasScale, h: this.element.offsetHeight * g_canvasScale }; this._canvas.forEach(c => { c.width = size.w; c.height = size.h; }); const height = this._isHover ? this._minHeightHover : this._minHeight this.element.style.minHeight = `${ height }px`; this._canvas.forEach(canvas => canvas.height = this.element.clientHeight * g_canvasScale); updateCanvas.apply(this); } function getTimerParentContainer(config, playbackBar) { const parentContainer = config.progressIndicator?.parentContainer || "progressIndicator"; const side = config.progressIndicator?.side || "left"; if (parentContainer === "progressIndicator") { return this.element; } else if (parentContainer === "buttonArea") { const timerContainer = playbackBar.timerContainer; timerContainer.classList.add( `${ side }-side`); return timerContainer; } else { throw new Error(`Error in player configuration: invalid progress indicator parent container: ${ parentContainer }. Valid values are 'progressIndicator' or 'buttonArea'`) } } export default class ProgressIndicator extends DomClass { constructor(player, playbackBar) { const parent = playbackBar.element; const inlineMode = player.config.progressIndicator?.inlineMode ?? false; const attributes = { "class": `progress-indicator${ inlineMode ? ' inline-mode' : ' top-mode' }` }; const handler = player.config.progressIndicator?.showHandler ? '<i class="progress-indicator-handler" style="pointer-events: none"></i>' : ""; const children = ` <canvas class="progress-canvas canvas-layer-0"></canvas> <div class="progress-indicator-container"> <div style="width: 0px;" class="progress-indicator-content"></div> ${ handler } <div class="progress-indicator-remaining"></div> </div> <canvas class="progress-canvas canvas-layer-1"></canvas> `; super(player, { attributes, children, parent }); const parentContainer = getTimerParentContainer.apply(this, [player.config, playbackBar]); this._progressIndicatorTimer = new ProgressIndicatorTimer(player, parentContainer); this._frameThumbnail = createElementWithHtmlText(` <div class="frame-thumbnail"> <img src="" alt="" class="thumbnail-image" /> <p class="thumbnail-time">00:00</p> </div>`, player.containerElement); this._frameThumbnail.style.display = "none"; this._frameThumbnail.style.position = "absolute"; this._isHover = false; this._canvas = [0,1].map(i => this.element.getElementsByClassName("progress-canvas")[i]); this._canvasContext = this._canvas.map(canvas => canvas.getContext("2d")); this._progressContainer = this.element.getElementsByClassName("progress-indicator-container")[0]; this._progressIndicator = this.element.getElementsByClassName("progress-indicator-content")[0]; this._handler = this.element.getElementsByClassName("progress-indicator-handler")[0]; this._remainingContainer = this.element.getElementsByClassName("progress-indicator-remaining")[0]; if (this.handler && player.config.progressIndicator?.hideHandlerOnMouseOut) { this.handler.style.display = "none"; } if (!player.config.progressIndicator?.showRemainingProgress) { this._remainingContainer.style.display = "none"; } this._frameList = player.frameList.frames; this._frameList?.sort((a,b) => a.time-b.time); this.onResize(); let drag = false; const updateProgressIndicator = async (currentTime) => { const containerWidth = this.progressContainer.clientWidth; const handlerWidth = this.handler?.clientWidth || 0; const duration = await player.videoContainer.duration(); const newWidth = currentTime * 100 / duration; this.progressIndicator.style.width = `${ newWidth }%`; if (this.handler) { const leftPosition = newWidth / 100 * containerWidth; this.handler.style.left = `${ leftPosition - handlerWidth / 2 }px`; } } const positionToTime = async (pos) => { const barWidth = this.element.offsetWidth; const duration = await player.videoContainer.duration(); return pos * duration / barWidth; } bindEvent(this.player, Events.TIMEUPDATE, async ({ currentTime }) => { if (!drag) { await updateProgressIndicator(currentTime); } }); bindEvent(this.player, Events.SEEK, async ({ prevTime, newTime }) => { if (!drag) { await updateProgressIndicator(newTime); } }); bindEvent(this.player, Events.STOP, async () => { await updateProgressIndicator(0); }) this.progressContainer.addEventListener("mousedown", async (evt) => { drag = true; const newTime = await positionToTime(evt.offsetX); await updateProgressIndicator(newTime); }); this.progressContainer.addEventListener("mouseover", evt => { this._isHover = true; updateHeight.apply(this); if (this.handler && player.config.progressIndicator?.hideHandlerOnMouseOut) { this.handler.style.display = ""; } }); this.progressContainer._progressIndicator = this; this.progressContainer.addEventListener("mousemove", async (evt) => { const { isTrimEnabled, trimStart } = this.player.videoContainer; const offset = isTrimEnabled ? trimStart : 0; const newTime = await positionToTime(evt.offsetX); if (drag) { await updateProgressIndicator(newTime); } updateFrameThumbnail.apply(this, [evt.offsetX,newTime + offset]); }); this.progressContainer.addEventListener("mouseup", async (evt) => { const newTime = await positionToTime(evt.offsetX); await updateProgressIndicator(newTime); await player.videoContainer.setCurrentTime(newTime); drag = false; }); this.progressContainer.addEventListener("mouseleave", async (evt) => { if (drag) { const newTime = await positionToTime(evt.offsetX); await player.videoContainer.setCurrentTime(newTime); drag = false; } this.frameThumbnail.style.display = "none"; this._isHover = false; updateHeight.apply(this); if (this.handler && player.config.progressIndicator?.hideHandlerOnMouseOut) { this.handler.style.display = "none"; } }); const updateCanvasProcess = () => { this._updateCanvasTimer = setTimeout(() => { if (this._updateCanvas) { updateHeight.apply(this); } updateCanvasProcess(); }, 250); } this._updateCanvas = true; updateCanvasProcess(); } requestUpdateCanvas() { this._updateCanvas = true; } async loadPlugins() { let minHeight = 0; let minHeightHover = 0; this._canvasPlugins = []; await loadPluginsOfType(this.player, "progressIndicator", async plugin => { this.player.log.debug(` Progress indicator plugin: ${ plugin.name }`); minHeight = minHeight < plugin.minHeight ? plugin.minHeight : minHeight; minHeightHover = minHeightHover < plugin.minHeightHover ? plugin.minHeightHover : minHeightHover; this._canvasPlugins.push(plugin); }, async plugin => { return await plugin.isEnabled(); }); this._minHeight = minHeight; this._minHeightHover = minHeightHover; updateHeight.apply(this); } async unloadPlugins() { this._canvasPlugins = []; await unloadPluginsOfType(this.player, "progressIndicator"); } hideTimeLine(hideProgressTimer=true) { if(hideProgressTimer){ this.hideProgressTimer(); } this.hideProgressContainer(); } hideProgressContainer() { this.progressContainer.style.display = "none"; } hideProgressTimer() { this.progressTimer.style.display = "none"; } showTimeLine() { this.showProgressContainer(); this.showProgressTimer(); } showProgressContainer() { this.progressContainer.style.display = ""; } showProgressTimer() { this.progressTimer.style.display = ""; } get playbackBar() { return this.element.parentElement; } get canvasLayer0() { return this._canvas[0]; } get canvasLayer1() { return this._canvas[1]; } get progressIndicator() { return this._progressIndicator; } get handler() { return this._handler; } get progressTimer() { return this._progressIndicatorTimer.element; } get progressContainer() { return this._progressContainer; } get frameThumbnail() { return this._frameThumbnail; } get frameList() { return this._frameList; } onResize() { this.requestUpdateCanvas(); } }