diff options
Diffstat (limited to 'client/src/assets/player/peertube-plugin.ts')
-rw-r--r-- | client/src/assets/player/peertube-plugin.ts | 302 |
1 files changed, 0 insertions, 302 deletions
diff --git a/client/src/assets/player/peertube-plugin.ts b/client/src/assets/player/peertube-plugin.ts deleted file mode 100644 index 4ffc9ce3e..000000000 --- a/client/src/assets/player/peertube-plugin.ts +++ /dev/null | |||
@@ -1,302 +0,0 @@ | |||
1 | import debug from 'debug' | ||
2 | import videojs from 'video.js' | ||
3 | import { timeToInt } from '@shared/core-utils' | ||
4 | import { | ||
5 | getStoredLastSubtitle, | ||
6 | getStoredMute, | ||
7 | getStoredVolume, | ||
8 | saveLastSubtitle, | ||
9 | saveMuteInStore, | ||
10 | saveVideoWatchHistory, | ||
11 | saveVolumeInStore | ||
12 | } from './peertube-player-local-storage' | ||
13 | import { PeerTubePluginOptions, UserWatching, VideoJSCaption } from './peertube-videojs-typings' | ||
14 | import { SettingsButton } from './settings/settings-menu-button' | ||
15 | import { isMobile } from './utils' | ||
16 | |||
17 | const logger = debug('peertube:player:peertube') | ||
18 | |||
19 | const Plugin = videojs.getPlugin('plugin') | ||
20 | |||
21 | class PeerTubePlugin extends Plugin { | ||
22 | private readonly videoViewUrl: string | ||
23 | private readonly videoDuration: number | ||
24 | private readonly CONSTANTS = { | ||
25 | USER_WATCHING_VIDEO_INTERVAL: 5000 // Every 5 seconds, notify the user is watching the video | ||
26 | } | ||
27 | |||
28 | private videoCaptions: VideoJSCaption[] | ||
29 | private defaultSubtitle: string | ||
30 | |||
31 | private videoViewInterval: any | ||
32 | private userWatchingVideoInterval: any | ||
33 | |||
34 | private isLive: boolean | ||
35 | |||
36 | private menuOpened = false | ||
37 | private mouseInControlBar = false | ||
38 | private mouseInSettings = false | ||
39 | private readonly initialInactivityTimeout: number | ||
40 | |||
41 | constructor (player: videojs.Player, options?: PeerTubePluginOptions) { | ||
42 | super(player) | ||
43 | |||
44 | this.videoViewUrl = options.videoViewUrl | ||
45 | this.videoDuration = options.videoDuration | ||
46 | this.videoCaptions = options.videoCaptions | ||
47 | this.isLive = options.isLive | ||
48 | this.initialInactivityTimeout = this.player.options_.inactivityTimeout | ||
49 | |||
50 | if (options.autoplay) this.player.addClass('vjs-has-autoplay') | ||
51 | |||
52 | this.player.on('autoplay-failure', () => { | ||
53 | this.player.removeClass('vjs-has-autoplay') | ||
54 | }) | ||
55 | |||
56 | this.player.ready(() => { | ||
57 | const playerOptions = this.player.options_ | ||
58 | |||
59 | const volume = getStoredVolume() | ||
60 | if (volume !== undefined) this.player.volume(volume) | ||
61 | |||
62 | const muted = playerOptions.muted !== undefined ? playerOptions.muted : getStoredMute() | ||
63 | if (muted !== undefined) this.player.muted(muted) | ||
64 | |||
65 | this.defaultSubtitle = options.subtitle || getStoredLastSubtitle() | ||
66 | |||
67 | this.player.on('volumechange', () => { | ||
68 | saveVolumeInStore(this.player.volume()) | ||
69 | saveMuteInStore(this.player.muted()) | ||
70 | }) | ||
71 | |||
72 | if (options.stopTime) { | ||
73 | const stopTime = timeToInt(options.stopTime) | ||
74 | const self = this | ||
75 | |||
76 | this.player.on('timeupdate', function onTimeUpdate () { | ||
77 | if (self.player.currentTime() > stopTime) { | ||
78 | self.player.pause() | ||
79 | self.player.trigger('stopped') | ||
80 | |||
81 | self.player.off('timeupdate', onTimeUpdate) | ||
82 | } | ||
83 | }) | ||
84 | } | ||
85 | |||
86 | this.player.textTracks().addEventListener('change', () => { | ||
87 | const showing = this.player.textTracks().tracks_.find(t => { | ||
88 | return t.kind === 'captions' && t.mode === 'showing' | ||
89 | }) | ||
90 | |||
91 | if (!showing) { | ||
92 | saveLastSubtitle('off') | ||
93 | return | ||
94 | } | ||
95 | |||
96 | saveLastSubtitle(showing.language) | ||
97 | }) | ||
98 | |||
99 | this.player.on('sourcechange', () => this.initCaptions()) | ||
100 | |||
101 | this.player.duration(options.videoDuration) | ||
102 | |||
103 | this.initializePlayer() | ||
104 | this.runViewAdd() | ||
105 | |||
106 | this.runUserWatchVideo(options.userWatching, options.videoUUID) | ||
107 | }) | ||
108 | } | ||
109 | |||
110 | dispose () { | ||
111 | if (this.videoViewInterval) clearInterval(this.videoViewInterval) | ||
112 | if (this.userWatchingVideoInterval) clearInterval(this.userWatchingVideoInterval) | ||
113 | } | ||
114 | |||
115 | onMenuOpened () { | ||
116 | this.menuOpened = true | ||
117 | this.alterInactivity() | ||
118 | } | ||
119 | |||
120 | onMenuClosed () { | ||
121 | this.menuOpened = false | ||
122 | this.alterInactivity() | ||
123 | } | ||
124 | |||
125 | displayFatalError () { | ||
126 | this.player.addClass('vjs-error-display-enabled') | ||
127 | } | ||
128 | |||
129 | hideFatalError () { | ||
130 | this.player.removeClass('vjs-error-display-enabled') | ||
131 | } | ||
132 | |||
133 | private initializePlayer () { | ||
134 | if (isMobile()) this.player.addClass('vjs-is-mobile') | ||
135 | |||
136 | this.initSmoothProgressBar() | ||
137 | |||
138 | this.initCaptions() | ||
139 | |||
140 | this.listenControlBarMouse() | ||
141 | |||
142 | this.listenFullScreenChange() | ||
143 | } | ||
144 | |||
145 | private runViewAdd () { | ||
146 | this.clearVideoViewInterval() | ||
147 | |||
148 | // After 30 seconds (or 3/4 of the video), add a view to the video | ||
149 | let minSecondsToView = 30 | ||
150 | |||
151 | if (!this.isLive && this.videoDuration < minSecondsToView) { | ||
152 | minSecondsToView = (this.videoDuration * 3) / 4 | ||
153 | } | ||
154 | |||
155 | let secondsViewed = 0 | ||
156 | this.videoViewInterval = setInterval(() => { | ||
157 | if (this.player && !this.player.paused()) { | ||
158 | secondsViewed += 1 | ||
159 | |||
160 | if (secondsViewed > minSecondsToView) { | ||
161 | // Restart the loop if this is a live | ||
162 | if (this.isLive) { | ||
163 | secondsViewed = 0 | ||
164 | } else { | ||
165 | this.clearVideoViewInterval() | ||
166 | } | ||
167 | |||
168 | this.addViewToVideo().catch(err => console.error(err)) | ||
169 | } | ||
170 | } | ||
171 | }, 1000) | ||
172 | } | ||
173 | |||
174 | private runUserWatchVideo (options: UserWatching, videoUUID: string) { | ||
175 | let lastCurrentTime = 0 | ||
176 | |||
177 | this.userWatchingVideoInterval = setInterval(() => { | ||
178 | const currentTime = Math.floor(this.player.currentTime()) | ||
179 | |||
180 | if (currentTime - lastCurrentTime >= 1) { | ||
181 | lastCurrentTime = currentTime | ||
182 | |||
183 | if (options) { | ||
184 | this.notifyUserIsWatching(currentTime, options.url, options.authorizationHeader) | ||
185 | .catch(err => console.error('Cannot notify user is watching.', err)) | ||
186 | } else { | ||
187 | saveVideoWatchHistory(videoUUID, currentTime) | ||
188 | } | ||
189 | } | ||
190 | }, this.CONSTANTS.USER_WATCHING_VIDEO_INTERVAL) | ||
191 | } | ||
192 | |||
193 | private clearVideoViewInterval () { | ||
194 | if (this.videoViewInterval !== undefined) { | ||
195 | clearInterval(this.videoViewInterval) | ||
196 | this.videoViewInterval = undefined | ||
197 | } | ||
198 | } | ||
199 | |||
200 | private addViewToVideo () { | ||
201 | if (!this.videoViewUrl) return Promise.resolve(undefined) | ||
202 | |||
203 | return fetch(this.videoViewUrl, { method: 'POST' }) | ||
204 | } | ||
205 | |||
206 | private notifyUserIsWatching (currentTime: number, url: string, authorizationHeader: string) { | ||
207 | const body = new URLSearchParams() | ||
208 | body.append('currentTime', currentTime.toString()) | ||
209 | |||
210 | const headers = new Headers({ Authorization: authorizationHeader }) | ||
211 | |||
212 | return fetch(url, { method: 'PUT', body, headers }) | ||
213 | } | ||
214 | |||
215 | private listenFullScreenChange () { | ||
216 | this.player.on('fullscreenchange', () => { | ||
217 | if (this.player.isFullscreen()) this.player.focus() | ||
218 | }) | ||
219 | } | ||
220 | |||
221 | private listenControlBarMouse () { | ||
222 | const controlBar = this.player.controlBar | ||
223 | const settingsButton: SettingsButton = (controlBar as any).settingsButton | ||
224 | |||
225 | controlBar.on('mouseenter', () => { | ||
226 | this.mouseInControlBar = true | ||
227 | this.alterInactivity() | ||
228 | }) | ||
229 | |||
230 | controlBar.on('mouseleave', () => { | ||
231 | this.mouseInControlBar = false | ||
232 | this.alterInactivity() | ||
233 | }) | ||
234 | |||
235 | settingsButton.dialog.on('mouseenter', () => { | ||
236 | this.mouseInSettings = true | ||
237 | this.alterInactivity() | ||
238 | }) | ||
239 | |||
240 | settingsButton.dialog.on('mouseleave', () => { | ||
241 | this.mouseInSettings = false | ||
242 | this.alterInactivity() | ||
243 | }) | ||
244 | } | ||
245 | |||
246 | private alterInactivity () { | ||
247 | if (this.menuOpened || this.mouseInSettings || this.mouseInControlBar) { | ||
248 | this.setInactivityTimeout(0) | ||
249 | return | ||
250 | } | ||
251 | |||
252 | this.setInactivityTimeout(this.initialInactivityTimeout) | ||
253 | this.player.reportUserActivity(true) | ||
254 | } | ||
255 | |||
256 | private setInactivityTimeout (timeout: number) { | ||
257 | (this.player as any).cache_.inactivityTimeout = timeout | ||
258 | this.player.options_.inactivityTimeout = timeout | ||
259 | |||
260 | logger('Set player inactivity to ' + timeout) | ||
261 | } | ||
262 | |||
263 | private initCaptions () { | ||
264 | for (const caption of this.videoCaptions) { | ||
265 | this.player.addRemoteTextTrack({ | ||
266 | kind: 'captions', | ||
267 | label: caption.label, | ||
268 | language: caption.language, | ||
269 | id: caption.language, | ||
270 | src: caption.src, | ||
271 | default: this.defaultSubtitle === caption.language | ||
272 | }, false) | ||
273 | } | ||
274 | |||
275 | this.player.trigger('captionsChanged') | ||
276 | } | ||
277 | |||
278 | // Thanks: https://github.com/videojs/video.js/issues/4460#issuecomment-312861657 | ||
279 | private initSmoothProgressBar () { | ||
280 | const SeekBar = videojs.getComponent('SeekBar') as any | ||
281 | SeekBar.prototype.getPercent = function getPercent () { | ||
282 | // Allows for smooth scrubbing, when player can't keep up. | ||
283 | // const time = (this.player_.scrubbing()) ? | ||
284 | // this.player_.getCache().currentTime : | ||
285 | // this.player_.currentTime() | ||
286 | const time = this.player_.currentTime() | ||
287 | const percent = time / this.player_.duration() | ||
288 | return percent >= 1 ? 1 : percent | ||
289 | } | ||
290 | SeekBar.prototype.handleMouseMove = function handleMouseMove (event: any) { | ||
291 | let newTime = this.calculateDistance(event) * this.player_.duration() | ||
292 | if (newTime === this.player_.duration()) { | ||
293 | newTime = newTime - 0.1 | ||
294 | } | ||
295 | this.player_.currentTime(newTime) | ||
296 | this.update() | ||
297 | } | ||
298 | } | ||
299 | } | ||
300 | |||
301 | videojs.registerPlugin('peertube', PeerTubePlugin) | ||
302 | export { PeerTubePlugin } | ||