]>
Commit | Line | Data |
---|---|---|
1 | import { peertubeTranslate } from '../../../../../shared/core-utils/i18n' | |
2 | import { | |
3 | HTMLServerConfig, | |
4 | LiveVideo, | |
5 | Video, | |
6 | VideoCaption, | |
7 | VideoDetails, | |
8 | VideoPlaylistElement, | |
9 | VideoState, | |
10 | VideoStreamingPlaylistType | |
11 | } from '../../../../../shared/models' | |
12 | import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode, VideoJSCaption } from '../../../assets/player' | |
13 | import { | |
14 | getBoolOrDefault, | |
15 | getParamString, | |
16 | getParamToggle, | |
17 | isP2PEnabled, | |
18 | logger, | |
19 | peertubeLocalStorage, | |
20 | UserLocalStorageKeys, | |
21 | videoRequiresAuth | |
22 | } from '../../../root-helpers' | |
23 | import { PeerTubePlugin } from './peertube-plugin' | |
24 | import { PlayerHTML } from './player-html' | |
25 | import { PlaylistTracker } from './playlist-tracker' | |
26 | import { Translations } from './translations' | |
27 | import { VideoFetcher } from './video-fetcher' | |
28 | ||
29 | export class PlayerManagerOptions { | |
30 | private autoplay: boolean | |
31 | ||
32 | private controls: boolean | |
33 | private controlBar: boolean | |
34 | ||
35 | private muted: boolean | |
36 | private loop: boolean | |
37 | private subtitle: string | |
38 | private enableApi = false | |
39 | private startTime: number | string = 0 | |
40 | private stopTime: number | string | |
41 | ||
42 | private title: boolean | |
43 | private warningTitle: boolean | |
44 | private peertubeLink: boolean | |
45 | private p2pEnabled: boolean | |
46 | private bigPlayBackgroundColor: string | |
47 | private foregroundColor: string | |
48 | ||
49 | private mode: PlayerMode | |
50 | private scope = 'peertube' | |
51 | ||
52 | constructor ( | |
53 | private readonly playerHTML: PlayerHTML, | |
54 | private readonly videoFetcher: VideoFetcher, | |
55 | private readonly peertubePlugin: PeerTubePlugin | |
56 | ) {} | |
57 | ||
58 | hasAPIEnabled () { | |
59 | return this.enableApi | |
60 | } | |
61 | ||
62 | hasAutoplay () { | |
63 | return this.autoplay | |
64 | } | |
65 | ||
66 | hasControls () { | |
67 | return this.controls | |
68 | } | |
69 | ||
70 | hasTitle () { | |
71 | return this.title | |
72 | } | |
73 | ||
74 | hasWarningTitle () { | |
75 | return this.warningTitle | |
76 | } | |
77 | ||
78 | hasP2PEnabled () { | |
79 | return !!this.p2pEnabled | |
80 | } | |
81 | ||
82 | hasBigPlayBackgroundColor () { | |
83 | return !!this.bigPlayBackgroundColor | |
84 | } | |
85 | ||
86 | getBigPlayBackgroundColor () { | |
87 | return this.bigPlayBackgroundColor | |
88 | } | |
89 | ||
90 | hasForegroundColor () { | |
91 | return !!this.foregroundColor | |
92 | } | |
93 | ||
94 | getForegroundColor () { | |
95 | return this.foregroundColor | |
96 | } | |
97 | ||
98 | getMode () { | |
99 | return this.mode | |
100 | } | |
101 | ||
102 | getScope () { | |
103 | return this.scope | |
104 | } | |
105 | ||
106 | // --------------------------------------------------------------------------- | |
107 | ||
108 | loadParams (config: HTMLServerConfig, video: VideoDetails) { | |
109 | try { | |
110 | const params = new URL(window.location.toString()).searchParams | |
111 | ||
112 | this.autoplay = getParamToggle(params, 'autoplay', false) | |
113 | // Disable auto play on live videos that are not streamed | |
114 | if (video.state.id === VideoState.LIVE_ENDED || video.state.id === VideoState.WAITING_FOR_LIVE) { | |
115 | this.autoplay = false | |
116 | } | |
117 | ||
118 | this.controls = getParamToggle(params, 'controls', true) | |
119 | this.controlBar = getParamToggle(params, 'controlBar', true) | |
120 | ||
121 | this.muted = getParamToggle(params, 'muted', undefined) | |
122 | this.loop = getParamToggle(params, 'loop', false) | |
123 | this.title = getParamToggle(params, 'title', true) | |
124 | this.enableApi = getParamToggle(params, 'api', this.enableApi) | |
125 | this.warningTitle = getParamToggle(params, 'warningTitle', true) | |
126 | this.peertubeLink = getParamToggle(params, 'peertubeLink', true) | |
127 | this.p2pEnabled = getParamToggle(params, 'p2p', this.isP2PEnabled(config, video)) | |
128 | ||
129 | this.scope = getParamString(params, 'scope', this.scope) | |
130 | this.subtitle = getParamString(params, 'subtitle') | |
131 | this.startTime = getParamString(params, 'start') | |
132 | this.stopTime = getParamString(params, 'stop') | |
133 | ||
134 | this.bigPlayBackgroundColor = getParamString(params, 'bigPlayBackgroundColor') | |
135 | this.foregroundColor = getParamString(params, 'foregroundColor') | |
136 | ||
137 | const modeParam = getParamString(params, 'mode') | |
138 | ||
139 | if (modeParam) { | |
140 | if (modeParam === 'p2p-media-loader') this.mode = 'p2p-media-loader' | |
141 | else this.mode = 'webtorrent' | |
142 | } else { | |
143 | if (Array.isArray(video.streamingPlaylists) && video.streamingPlaylists.length !== 0) this.mode = 'p2p-media-loader' | |
144 | else this.mode = 'webtorrent' | |
145 | } | |
146 | } catch (err) { | |
147 | logger.error('Cannot get params from URL.', err) | |
148 | } | |
149 | } | |
150 | ||
151 | // --------------------------------------------------------------------------- | |
152 | ||
153 | async getPlayerOptions (options: { | |
154 | video: VideoDetails | |
155 | captionsResponse: Response | |
156 | live?: LiveVideo | |
157 | ||
158 | forceAutoplay: boolean | |
159 | ||
160 | authorizationHeader: () => string | |
161 | videoFileToken: () => string | |
162 | ||
163 | serverConfig: HTMLServerConfig | |
164 | ||
165 | autoplayFromPreviousVideo: boolean | |
166 | ||
167 | translations: Translations | |
168 | ||
169 | playlistTracker?: PlaylistTracker | |
170 | playNextPlaylistVideo?: () => any | |
171 | playPreviousPlaylistVideo?: () => any | |
172 | onVideoUpdate?: (uuid: string) => any | |
173 | }) { | |
174 | const { | |
175 | video, | |
176 | captionsResponse, | |
177 | autoplayFromPreviousVideo, | |
178 | videoFileToken, | |
179 | translations, | |
180 | forceAutoplay, | |
181 | playlistTracker, | |
182 | live, | |
183 | authorizationHeader, | |
184 | serverConfig | |
185 | } = options | |
186 | ||
187 | const videoCaptions = await this.buildCaptions(captionsResponse, translations) | |
188 | ||
189 | const playerOptions: PeertubePlayerManagerOptions = { | |
190 | common: { | |
191 | // Autoplay in playlist mode | |
192 | autoplay: autoplayFromPreviousVideo ? true : this.autoplay, | |
193 | forceAutoplay, | |
194 | ||
195 | controls: this.controls, | |
196 | controlBar: this.controlBar, | |
197 | ||
198 | muted: this.muted, | |
199 | loop: this.loop, | |
200 | ||
201 | p2pEnabled: this.p2pEnabled, | |
202 | ||
203 | captions: videoCaptions.length !== 0, | |
204 | subtitle: this.subtitle, | |
205 | ||
206 | startTime: playlistTracker | |
207 | ? playlistTracker.getCurrentElement().startTimestamp | |
208 | : this.startTime, | |
209 | stopTime: playlistTracker | |
210 | ? playlistTracker.getCurrentElement().stopTimestamp | |
211 | : this.stopTime, | |
212 | ||
213 | videoCaptions, | |
214 | inactivityTimeout: 2500, | |
215 | videoViewUrl: this.videoFetcher.getVideoViewsUrl(video.uuid), | |
216 | metricsUrl: window.location.origin + '/api/v1/metrics/playback', | |
217 | ||
218 | videoShortUUID: video.shortUUID, | |
219 | videoUUID: video.uuid, | |
220 | ||
221 | playerElement: this.playerHTML.getPlayerElement(), | |
222 | onPlayerElementChange: (element: HTMLVideoElement) => { | |
223 | this.playerHTML.setPlayerElement(element) | |
224 | }, | |
225 | ||
226 | videoDuration: video.duration, | |
227 | enableHotkeys: true, | |
228 | ||
229 | peertubeLink: this.peertubeLink, | |
230 | instanceName: serverConfig.instance.name, | |
231 | ||
232 | poster: window.location.origin + video.previewPath, | |
233 | theaterButton: false, | |
234 | ||
235 | serverUrl: window.location.origin, | |
236 | language: navigator.language, | |
237 | embedUrl: window.location.origin + video.embedPath, | |
238 | embedTitle: video.name, | |
239 | ||
240 | requiresAuth: videoRequiresAuth(video), | |
241 | authorizationHeader, | |
242 | videoFileToken, | |
243 | ||
244 | errorNotifier: () => { | |
245 | // Empty, we don't have a notifier in the embed | |
246 | }, | |
247 | ||
248 | ...this.buildLiveOptions(video, live), | |
249 | ||
250 | ...this.buildPlaylistOptions(options) | |
251 | }, | |
252 | ||
253 | webtorrent: { | |
254 | videoFiles: video.files | |
255 | }, | |
256 | ||
257 | ...this.buildP2PMediaLoaderOptions(video), | |
258 | ||
259 | pluginsManager: this.peertubePlugin.getPluginsManager() | |
260 | } | |
261 | ||
262 | return playerOptions | |
263 | } | |
264 | ||
265 | private buildLiveOptions (video: VideoDetails, live: LiveVideo) { | |
266 | if (!video.isLive) return { isLive: false } | |
267 | ||
268 | return { | |
269 | isLive: true, | |
270 | liveOptions: { | |
271 | latencyMode: live.latencyMode | |
272 | } | |
273 | } | |
274 | } | |
275 | ||
276 | private buildPlaylistOptions (options: { | |
277 | playlistTracker?: PlaylistTracker | |
278 | playNextPlaylistVideo?: () => any | |
279 | playPreviousPlaylistVideo?: () => any | |
280 | onVideoUpdate?: (uuid: string) => any | |
281 | }) { | |
282 | const { playlistTracker, playNextPlaylistVideo, playPreviousPlaylistVideo, onVideoUpdate } = options | |
283 | ||
284 | if (!playlistTracker) return {} | |
285 | ||
286 | return { | |
287 | playlist: { | |
288 | elements: playlistTracker.getPlaylistElements(), | |
289 | playlist: playlistTracker.getPlaylist(), | |
290 | ||
291 | getCurrentPosition: () => playlistTracker.getCurrentPosition(), | |
292 | ||
293 | onItemClicked: (videoPlaylistElement: VideoPlaylistElement) => { | |
294 | playlistTracker.setCurrentElement(videoPlaylistElement) | |
295 | ||
296 | onVideoUpdate(videoPlaylistElement.video.uuid) | |
297 | } | |
298 | }, | |
299 | ||
300 | nextVideo: () => playNextPlaylistVideo(), | |
301 | hasNextVideo: () => playlistTracker.hasNextPlaylistElement(), | |
302 | ||
303 | previousVideo: () => playPreviousPlaylistVideo(), | |
304 | hasPreviousVideo: () => playlistTracker.hasPreviousPlaylistElement() | |
305 | } | |
306 | } | |
307 | ||
308 | private buildP2PMediaLoaderOptions (video: VideoDetails) { | |
309 | if (this.mode !== 'p2p-media-loader') return {} | |
310 | ||
311 | const hlsPlaylist = video.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) | |
312 | ||
313 | return { | |
314 | p2pMediaLoader: { | |
315 | playlistUrl: hlsPlaylist.playlistUrl, | |
316 | segmentsSha256Url: hlsPlaylist.segmentsSha256Url, | |
317 | redundancyBaseUrls: hlsPlaylist.redundancies.map(r => r.baseUrl), | |
318 | trackerAnnounce: video.trackerUrls, | |
319 | videoFiles: hlsPlaylist.files | |
320 | } as P2PMediaLoaderOptions | |
321 | } | |
322 | } | |
323 | ||
324 | // --------------------------------------------------------------------------- | |
325 | ||
326 | private async buildCaptions (captionsResponse: Response, translations: Translations): Promise<VideoJSCaption[]> { | |
327 | if (captionsResponse.ok) { | |
328 | const { data } = await captionsResponse.json() | |
329 | ||
330 | return data.map((c: VideoCaption) => ({ | |
331 | label: peertubeTranslate(c.language.label, translations), | |
332 | language: c.language.id, | |
333 | src: window.location.origin + c.captionPath | |
334 | })) | |
335 | } | |
336 | ||
337 | return [] | |
338 | } | |
339 | ||
340 | // --------------------------------------------------------------------------- | |
341 | ||
342 | private isP2PEnabled (config: HTMLServerConfig, video: Video) { | |
343 | const userP2PEnabled = getBoolOrDefault( | |
344 | peertubeLocalStorage.getItem(UserLocalStorageKeys.P2P_ENABLED), | |
345 | config.defaults.p2p.embed.enabled | |
346 | ) | |
347 | ||
348 | return isP2PEnabled(video, config, userP2PEnabled) | |
349 | } | |
350 | } |