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