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