]>
Commit | Line | Data |
---|---|---|
2dd0a8a8 | 1 | import { PeerTubeMobileButtons } from './peertube-mobile-buttons' |
e98ef69d | 2 | import videojs from 'video.js' |
2dd0a8a8 C |
3 | import debug from 'debug' |
4 | ||
5 | const logger = debug('peertube:player:mobile') | |
f1a0555a C |
6 | |
7 | const Plugin = videojs.getPlugin('plugin') | |
8 | ||
9 | class PeerTubeMobilePlugin extends Plugin { | |
2dd0a8a8 C |
10 | private static readonly DOUBLE_TAP_DELAY_MS = 250 |
11 | private static readonly SET_CURRENT_TIME_DELAY = 1000 | |
12 | ||
13 | private peerTubeMobileButtons: PeerTubeMobileButtons | |
14 | ||
15 | private seekAmount = 0 | |
16 | ||
17 | private lastTapEvent: TouchEvent | |
f85b5afb | 18 | private tapTimeout: ReturnType<typeof setTimeout> |
2dd0a8a8 C |
19 | private newActiveState: boolean |
20 | ||
f85b5afb | 21 | private setCurrentTimeTimeout: ReturnType<typeof setTimeout> |
f1a0555a C |
22 | |
23 | constructor (player: videojs.Player, options: videojs.PlayerOptions) { | |
24 | super(player, options) | |
25 | ||
2dd0a8a8 | 26 | this.peerTubeMobileButtons = player.addChild('PeerTubeMobileButtons') as PeerTubeMobileButtons |
e98ef69d C |
27 | |
28 | if (videojs.browser.IS_ANDROID && screen.orientation) { | |
29 | this.handleFullscreenRotation() | |
30 | } | |
2dd0a8a8 C |
31 | |
32 | if (!this.player.options_.userActions) this.player.options_.userActions = {}; | |
33 | ||
34 | // FIXME: typings | |
35 | (this.player.options_.userActions as any).click = false | |
36 | this.player.options_.userActions.doubleClick = false | |
37 | ||
38 | this.player.one('play', () => { | |
39 | this.initTouchStartEvents() | |
40 | }) | |
e98ef69d C |
41 | } |
42 | ||
43 | private handleFullscreenRotation () { | |
44 | this.player.on('fullscreenchange', () => { | |
45 | if (!this.player.isFullscreen() || this.isPortraitVideo()) return | |
46 | ||
47 | screen.orientation.lock('landscape') | |
48 | .catch(err => console.error('Cannot lock screen to landscape.', err)) | |
49 | }) | |
50 | } | |
51 | ||
52 | private isPortraitVideo () { | |
53 | return this.player.videoWidth() < this.player.videoHeight() | |
f1a0555a | 54 | } |
2dd0a8a8 C |
55 | |
56 | private initTouchStartEvents () { | |
57 | this.player.on('touchstart', (event: TouchEvent) => { | |
58 | event.stopPropagation() | |
59 | ||
60 | if (this.tapTimeout) { | |
61 | clearTimeout(this.tapTimeout) | |
62 | this.tapTimeout = undefined | |
63 | } | |
64 | ||
65 | if (this.lastTapEvent && event.timeStamp - this.lastTapEvent.timeStamp < PeerTubeMobilePlugin.DOUBLE_TAP_DELAY_MS) { | |
66 | logger('Detected double tap') | |
67 | ||
68 | this.lastTapEvent = undefined | |
69 | this.onDoubleTap(event) | |
70 | return | |
71 | } | |
72 | ||
73 | this.newActiveState = !this.player.userActive() | |
74 | ||
75 | this.tapTimeout = setTimeout(() => { | |
76 | logger('No double tap detected, set user active state to %s.', this.newActiveState) | |
77 | ||
78 | this.player.userActive(this.newActiveState) | |
79 | }, PeerTubeMobilePlugin.DOUBLE_TAP_DELAY_MS) | |
80 | ||
81 | this.lastTapEvent = event | |
82 | }) | |
83 | } | |
84 | ||
85 | private onDoubleTap (event: TouchEvent) { | |
86 | const playerWidth = this.player.currentWidth() | |
87 | ||
88 | const rect = this.findPlayerTarget((event.target as HTMLElement)).getBoundingClientRect() | |
89 | const offsetX = event.targetTouches[0].pageX - rect.left | |
90 | ||
91 | logger('Calculating double tap zone (player width: %d, offset X: %d)', playerWidth, offsetX) | |
92 | ||
93 | if (offsetX > 0.66 * playerWidth) { | |
94 | if (this.seekAmount < 0) this.seekAmount = 0 | |
95 | ||
96 | this.seekAmount += 10 | |
97 | ||
98 | logger('Will forward %d seconds', this.seekAmount) | |
99 | } else if (offsetX < 0.33 * playerWidth) { | |
100 | if (this.seekAmount > 0) this.seekAmount = 0 | |
101 | ||
102 | this.seekAmount -= 10 | |
103 | logger('Will rewind %d seconds', this.seekAmount) | |
104 | } | |
105 | ||
106 | this.peerTubeMobileButtons.displayFastSeek(this.seekAmount) | |
107 | ||
108 | this.scheduleSetCurrentTime() | |
109 | ||
110 | } | |
111 | ||
112 | private findPlayerTarget (target: HTMLElement): HTMLElement { | |
113 | if (target.classList.contains('video-js')) return target | |
114 | ||
115 | return this.findPlayerTarget(target.parentElement) | |
116 | } | |
117 | ||
118 | private scheduleSetCurrentTime () { | |
119 | this.player.pause() | |
120 | this.player.addClass('vjs-fast-seeking') | |
121 | ||
122 | if (this.setCurrentTimeTimeout) clearTimeout(this.setCurrentTimeTimeout) | |
123 | ||
124 | this.setCurrentTimeTimeout = setTimeout(() => { | |
125 | let newTime = this.player.currentTime() + this.seekAmount | |
126 | this.seekAmount = 0 | |
127 | ||
128 | newTime = Math.max(0, newTime) | |
129 | newTime = Math.min(this.player.duration(), newTime) | |
130 | ||
131 | this.player.currentTime(newTime) | |
132 | this.seekAmount = 0 | |
133 | this.peerTubeMobileButtons.displayFastSeek(0) | |
134 | ||
135 | this.player.removeClass('vjs-fast-seeking') | |
136 | this.player.userActive(false) | |
137 | ||
138 | this.player.play() | |
139 | }, PeerTubeMobilePlugin.SET_CURRENT_TIME_DELAY) | |
140 | } | |
f1a0555a C |
141 | } |
142 | ||
143 | videojs.registerPlugin('peertubeMobile', PeerTubeMobilePlugin) | |
144 | export { PeerTubeMobilePlugin } |