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