From 2dd0a8a8fd2fc85180fa3b45c5a6a56d07320ed3 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 12 Jan 2022 15:07:21 +0100 Subject: Add fast forward/rewind on mobile --- .../player/mobile/peertube-mobile-buttons.ts | 72 +++++++++++-- .../assets/player/mobile/peertube-mobile-plugin.ts | 115 ++++++++++++++++++++- 2 files changed, 175 insertions(+), 12 deletions(-) (limited to 'client/src/assets/player/mobile') diff --git a/client/src/assets/player/mobile/peertube-mobile-buttons.ts b/client/src/assets/player/mobile/peertube-mobile-buttons.ts index d6f8f35e3..94eeec023 100644 --- a/client/src/assets/player/mobile/peertube-mobile-buttons.ts +++ b/client/src/assets/player/mobile/peertube-mobile-buttons.ts @@ -1,23 +1,18 @@ import videojs from 'video.js' -import debug from 'debug' - -const logger = debug('peertube:player:mobile') - const Component = videojs.getComponent('Component') class PeerTubeMobileButtons extends Component { + private rewind: Element + private forward: Element + private rewindText: Element + private forwardText: Element + createEl () { const container = super.createEl('div', { className: 'vjs-mobile-buttons-overlay' }) as HTMLDivElement - container.addEventListener('click', () => { - logger('Set user as inactive') - - this.player_.userActive(false) - }) - const mainButton = super.createEl('div', { className: 'main-button' }) as HTMLDivElement @@ -33,10 +28,67 @@ class PeerTubeMobileButtons extends Component { this.player_.pause() }) + this.rewind = super.createEl('div', { className: 'rewind-button vjs-hidden' }) + this.forward = super.createEl('div', { className: 'forward-button vjs-hidden' }) + + for (let i = 0; i < 3; i++) { + this.rewind.appendChild(super.createEl('span', { className: 'icon' })) + this.forward.appendChild(super.createEl('span', { className: 'icon' })) + } + + this.rewindText = this.rewind.appendChild(super.createEl('div', { className: 'text' })) + this.forwardText = this.forward.appendChild(super.createEl('div', { className: 'text' })) + + container.appendChild(this.rewind) container.appendChild(mainButton) + container.appendChild(this.forward) return container } + + displayFastSeek (amount: number) { + if (amount === 0) { + this.hideRewind() + this.hideForward() + return + } + + if (amount > 0) { + this.hideRewind() + this.displayForward(amount) + return + } + + if (amount < 0) { + this.hideForward() + this.displayRewind(amount) + return + } + } + + private hideRewind () { + this.rewind.classList.add('vjs-hidden') + this.rewindText.textContent = '' + } + + private displayRewind (amount: number) { + this.rewind.classList.remove('vjs-hidden') + this.rewindText.textContent = this.player().localize('{1} seconds', [ amount + '' ]) + } + + private hideForward () { + this.forward.classList.add('vjs-hidden') + this.forwardText.textContent = '' + } + + private displayForward (amount: number) { + this.forward.classList.remove('vjs-hidden') + this.forwardText.textContent = this.player().localize('{1} seconds', [ amount + '' ]) + } } videojs.registerComponent('PeerTubeMobileButtons', PeerTubeMobileButtons) + +export { + PeerTubeMobileButtons +} diff --git a/client/src/assets/player/mobile/peertube-mobile-plugin.ts b/client/src/assets/player/mobile/peertube-mobile-plugin.ts index 2ce6b4b33..3c0365e5b 100644 --- a/client/src/assets/player/mobile/peertube-mobile-plugin.ts +++ b/client/src/assets/player/mobile/peertube-mobile-plugin.ts @@ -1,18 +1,43 @@ -import './peertube-mobile-buttons' +import { PeerTubeMobileButtons } from './peertube-mobile-buttons' import videojs from 'video.js' +import debug from 'debug' + +const logger = debug('peertube:player:mobile') const Plugin = videojs.getPlugin('plugin') class PeerTubeMobilePlugin extends Plugin { + private static readonly DOUBLE_TAP_DELAY_MS = 250 + private static readonly SET_CURRENT_TIME_DELAY = 1000 + + private peerTubeMobileButtons: PeerTubeMobileButtons + + private seekAmount = 0 + + private lastTapEvent: TouchEvent + private tapTimeout: NodeJS.Timeout + private newActiveState: boolean + + private setCurrentTimeTimeout: NodeJS.Timeout constructor (player: videojs.Player, options: videojs.PlayerOptions) { super(player, options) - player.addChild('PeerTubeMobileButtons') + this.peerTubeMobileButtons = player.addChild('PeerTubeMobileButtons') as PeerTubeMobileButtons if (videojs.browser.IS_ANDROID && screen.orientation) { this.handleFullscreenRotation() } + + if (!this.player.options_.userActions) this.player.options_.userActions = {}; + + // FIXME: typings + (this.player.options_.userActions as any).click = false + this.player.options_.userActions.doubleClick = false + + this.player.one('play', () => { + this.initTouchStartEvents() + }) } private handleFullscreenRotation () { @@ -27,6 +52,92 @@ class PeerTubeMobilePlugin extends Plugin { private isPortraitVideo () { return this.player.videoWidth() < this.player.videoHeight() } + + private initTouchStartEvents () { + this.player.on('touchstart', (event: TouchEvent) => { + event.stopPropagation() + + if (this.tapTimeout) { + clearTimeout(this.tapTimeout) + this.tapTimeout = undefined + } + + if (this.lastTapEvent && event.timeStamp - this.lastTapEvent.timeStamp < PeerTubeMobilePlugin.DOUBLE_TAP_DELAY_MS) { + logger('Detected double tap') + + this.lastTapEvent = undefined + this.onDoubleTap(event) + return + } + + this.newActiveState = !this.player.userActive() + + this.tapTimeout = setTimeout(() => { + logger('No double tap detected, set user active state to %s.', this.newActiveState) + + this.player.userActive(this.newActiveState) + }, PeerTubeMobilePlugin.DOUBLE_TAP_DELAY_MS) + + this.lastTapEvent = event + }) + } + + private onDoubleTap (event: TouchEvent) { + const playerWidth = this.player.currentWidth() + + const rect = this.findPlayerTarget((event.target as HTMLElement)).getBoundingClientRect() + const offsetX = event.targetTouches[0].pageX - rect.left + + logger('Calculating double tap zone (player width: %d, offset X: %d)', playerWidth, offsetX) + + if (offsetX > 0.66 * playerWidth) { + if (this.seekAmount < 0) this.seekAmount = 0 + + this.seekAmount += 10 + + logger('Will forward %d seconds', this.seekAmount) + } else if (offsetX < 0.33 * playerWidth) { + if (this.seekAmount > 0) this.seekAmount = 0 + + this.seekAmount -= 10 + logger('Will rewind %d seconds', this.seekAmount) + } + + this.peerTubeMobileButtons.displayFastSeek(this.seekAmount) + + this.scheduleSetCurrentTime() + + } + + private findPlayerTarget (target: HTMLElement): HTMLElement { + if (target.classList.contains('video-js')) return target + + return this.findPlayerTarget(target.parentElement) + } + + private scheduleSetCurrentTime () { + this.player.pause() + this.player.addClass('vjs-fast-seeking') + + if (this.setCurrentTimeTimeout) clearTimeout(this.setCurrentTimeTimeout) + + this.setCurrentTimeTimeout = setTimeout(() => { + let newTime = this.player.currentTime() + this.seekAmount + this.seekAmount = 0 + + newTime = Math.max(0, newTime) + newTime = Math.min(this.player.duration(), newTime) + + this.player.currentTime(newTime) + this.seekAmount = 0 + this.peerTubeMobileButtons.displayFastSeek(0) + + this.player.removeClass('vjs-fast-seeking') + this.player.userActive(false) + + this.player.play() + }, PeerTubeMobilePlugin.SET_CURRENT_TIME_DELAY) + } } videojs.registerPlugin('peertubeMobile', PeerTubeMobilePlugin) -- cgit v1.2.3