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