diff options
author | Chocobozzz <me@florianbigard.com> | 2019-02-06 10:39:50 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-02-11 09:13:02 +0100 |
commit | 6ec0b75beb9c8bcd84e178912319913b91830da2 (patch) | |
tree | d6fbcb774c5019fc20f42e8f9da4529545fdcbf9 /client | |
parent | 092092969633bbcf6d4891a083ea497a7d5c3154 (diff) | |
download | PeerTube-6ec0b75beb9c8bcd84e178912319913b91830da2.tar.gz PeerTube-6ec0b75beb9c8bcd84e178912319913b91830da2.tar.zst PeerTube-6ec0b75beb9c8bcd84e178912319913b91830da2.zip |
Fallback HLS to webtorrent
Diffstat (limited to 'client')
5 files changed, 78 insertions, 19 deletions
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index f77316712..e1766255b 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -23,7 +23,13 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
23 | import { environment } from '../../../environments/environment' | 23 | import { environment } from '../../../environments/environment' |
24 | import { VideoCaptionService } from '@app/shared/video-caption' | 24 | import { VideoCaptionService } from '@app/shared/video-caption' |
25 | import { MarkdownService } from '@app/shared/renderer' | 25 | import { MarkdownService } from '@app/shared/renderer' |
26 | import { P2PMediaLoaderOptions, PeertubePlayerManager, PlayerMode, WebtorrentOptions } from '../../../assets/player/peertube-player-manager' | 26 | import { |
27 | P2PMediaLoaderOptions, | ||
28 | PeertubePlayerManager, | ||
29 | PeertubePlayerManagerOptions, | ||
30 | PlayerMode, | ||
31 | WebtorrentOptions | ||
32 | } from '../../../assets/player/peertube-player-manager' | ||
27 | 33 | ||
28 | @Component({ | 34 | @Component({ |
29 | selector: 'my-video-watch', | 35 | selector: 'my-video-watch', |
@@ -395,10 +401,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
395 | src: environment.apiUrl + c.captionPath | 401 | src: environment.apiUrl + c.captionPath |
396 | })) | 402 | })) |
397 | 403 | ||
398 | const options = { | 404 | const options: PeertubePlayerManagerOptions = { |
399 | common: { | 405 | common: { |
400 | autoplay: this.isAutoplay(), | 406 | autoplay: this.isAutoplay(), |
407 | |||
401 | playerElement: this.playerElement, | 408 | playerElement: this.playerElement, |
409 | onPlayerElementChange: (element: HTMLVideoElement) => this.playerElement = element, | ||
410 | |||
402 | videoDuration: this.video.duration, | 411 | videoDuration: this.video.duration, |
403 | enableHotkeys: true, | 412 | enableHotkeys: true, |
404 | inactivityTimeout: 2500, | 413 | inactivityTimeout: 2500, |
@@ -424,6 +433,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
424 | serverUrl: environment.apiUrl, | 433 | serverUrl: environment.apiUrl, |
425 | 434 | ||
426 | videoCaptions: playerCaptions | 435 | videoCaptions: playerCaptions |
436 | }, | ||
437 | |||
438 | webtorrent: { | ||
439 | videoFiles: this.video.files | ||
427 | } | 440 | } |
428 | } | 441 | } |
429 | 442 | ||
@@ -431,6 +444,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
431 | const hlsPlaylist = this.video.getHlsPlaylist() | 444 | const hlsPlaylist = this.video.getHlsPlaylist() |
432 | if (hlsPlaylist) { | 445 | if (hlsPlaylist) { |
433 | mode = 'p2p-media-loader' | 446 | mode = 'p2p-media-loader' |
447 | |||
434 | const p2pMediaLoader = { | 448 | const p2pMediaLoader = { |
435 | playlistUrl: hlsPlaylist.playlistUrl, | 449 | playlistUrl: hlsPlaylist.playlistUrl, |
436 | segmentsSha256Url: hlsPlaylist.segmentsSha256Url, | 450 | segmentsSha256Url: hlsPlaylist.segmentsSha256Url, |
@@ -442,11 +456,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
442 | Object.assign(options, { p2pMediaLoader }) | 456 | Object.assign(options, { p2pMediaLoader }) |
443 | } else { | 457 | } else { |
444 | mode = 'webtorrent' | 458 | mode = 'webtorrent' |
445 | const webtorrent = { | ||
446 | videoFiles: this.video.files | ||
447 | } as WebtorrentOptions | ||
448 | |||
449 | Object.assign(options, { webtorrent }) | ||
450 | } | 459 | } |
451 | 460 | ||
452 | this.zone.runOutsideAngular(async () => { | 461 | this.zone.runOutsideAngular(async () => { |
diff --git a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts index f9a2707fb..022a9c16f 100644 --- a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts +++ b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts | |||
@@ -51,17 +51,25 @@ class P2pMediaLoaderPlugin extends Plugin { | |||
51 | src: options.src | 51 | src: options.src |
52 | }) | 52 | }) |
53 | 53 | ||
54 | player.on('play', () => { | ||
55 | player.addClass('vjs-has-big-play-button-clicked') | ||
56 | }) | ||
57 | |||
54 | player.ready(() => this.initialize()) | 58 | player.ready(() => this.initialize()) |
55 | } | 59 | } |
56 | 60 | ||
57 | dispose () { | 61 | dispose () { |
62 | if (this.hlsjs) this.hlsjs.destroy() | ||
63 | if (this.p2pEngine) this.p2pEngine.destroy() | ||
64 | |||
58 | clearInterval(this.networkInfoInterval) | 65 | clearInterval(this.networkInfoInterval) |
59 | } | 66 | } |
60 | 67 | ||
61 | private initialize () { | 68 | private initialize () { |
62 | initHlsJsPlayer(this.hlsjs) | 69 | initHlsJsPlayer(this.hlsjs) |
63 | 70 | ||
64 | this.p2pEngine = this.player.tech_.options_.hlsjsConfig.loader.getEngine() | 71 | const tech = this.player.tech_ |
72 | this.p2pEngine = tech.options_.hlsjsConfig.loader.getEngine() | ||
65 | 73 | ||
66 | // Avoid using constants to not import hls.hs | 74 | // Avoid using constants to not import hls.hs |
67 | // https://github.com/video-dev/hls.js/blob/master/src/events.js#L37 | 75 | // https://github.com/video-dev/hls.js/blob/master/src/events.js#L37 |
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts index 3fdba6fdf..0ba9bcb11 100644 --- a/client/src/assets/player/peertube-player-manager.ts +++ b/client/src/assets/player/peertube-player-manager.ts | |||
@@ -41,6 +41,7 @@ export type P2PMediaLoaderOptions = { | |||
41 | 41 | ||
42 | export type CommonOptions = { | 42 | export type CommonOptions = { |
43 | playerElement: HTMLVideoElement | 43 | playerElement: HTMLVideoElement |
44 | onPlayerElementChange: (element: HTMLVideoElement) => void | ||
44 | 45 | ||
45 | autoplay: boolean | 46 | autoplay: boolean |
46 | videoDuration: number | 47 | videoDuration: number |
@@ -71,13 +72,14 @@ export type CommonOptions = { | |||
71 | 72 | ||
72 | export type PeertubePlayerManagerOptions = { | 73 | export type PeertubePlayerManagerOptions = { |
73 | common: CommonOptions, | 74 | common: CommonOptions, |
74 | webtorrent?: WebtorrentOptions, | 75 | webtorrent: WebtorrentOptions, |
75 | p2pMediaLoader?: P2PMediaLoaderOptions | 76 | p2pMediaLoader?: P2PMediaLoaderOptions |
76 | } | 77 | } |
77 | 78 | ||
78 | export class PeertubePlayerManager { | 79 | export class PeertubePlayerManager { |
79 | 80 | ||
80 | private static videojsLocaleCache: { [ path: string ]: any } = {} | 81 | private static videojsLocaleCache: { [ path: string ]: any } = {} |
82 | private static playerElementClassName: string | ||
81 | 83 | ||
82 | static getServerTranslations (serverUrl: string, locale: string) { | 84 | static getServerTranslations (serverUrl: string, locale: string) { |
83 | const path = PeertubePlayerManager.getLocalePath(serverUrl, locale) | 85 | const path = PeertubePlayerManager.getLocalePath(serverUrl, locale) |
@@ -95,6 +97,8 @@ export class PeertubePlayerManager { | |||
95 | static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions) { | 97 | static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions) { |
96 | let p2pMediaLoader: any | 98 | let p2pMediaLoader: any |
97 | 99 | ||
100 | this.playerElementClassName = options.common.playerElement.className | ||
101 | |||
98 | if (mode === 'webtorrent') await import('./webtorrent/webtorrent-plugin') | 102 | if (mode === 'webtorrent') await import('./webtorrent/webtorrent-plugin') |
99 | if (mode === 'p2p-media-loader') { | 103 | if (mode === 'p2p-media-loader') { |
100 | [ p2pMediaLoader ] = await Promise.all([ | 104 | [ p2pMediaLoader ] = await Promise.all([ |
@@ -112,6 +116,13 @@ export class PeertubePlayerManager { | |||
112 | videojs(options.common.playerElement, videojsOptions, function (this: any) { | 116 | videojs(options.common.playerElement, videojsOptions, function (this: any) { |
113 | const player = this | 117 | const player = this |
114 | 118 | ||
119 | player.tech_.on('error', () => { | ||
120 | // Fallback to webtorrent? | ||
121 | if (mode === 'p2p-media-loader') { | ||
122 | self.fallbackToWebTorrent(player, options) | ||
123 | } | ||
124 | }) | ||
125 | |||
115 | self.addContextMenu(mode, player, options.common.embedUrl) | 126 | self.addContextMenu(mode, player, options.common.embedUrl) |
116 | 127 | ||
117 | return res(player) | 128 | return res(player) |
@@ -119,6 +130,32 @@ export class PeertubePlayerManager { | |||
119 | }) | 130 | }) |
120 | } | 131 | } |
121 | 132 | ||
133 | private static async fallbackToWebTorrent (player: any, options: PeertubePlayerManagerOptions) { | ||
134 | const newVideoElement = document.createElement('video') | ||
135 | newVideoElement.className = this.playerElementClassName | ||
136 | |||
137 | // VideoJS wraps our video element inside a div | ||
138 | const currentParentPlayerElement = options.common.playerElement.parentNode | ||
139 | currentParentPlayerElement.parentNode.insertBefore(newVideoElement, currentParentPlayerElement) | ||
140 | |||
141 | options.common.playerElement = newVideoElement | ||
142 | options.common.onPlayerElementChange(newVideoElement) | ||
143 | |||
144 | player.dispose() | ||
145 | |||
146 | await import('./webtorrent/webtorrent-plugin') | ||
147 | |||
148 | const mode = 'webtorrent' | ||
149 | const videojsOptions = this.getVideojsOptions(mode, options) | ||
150 | |||
151 | const self = this | ||
152 | videojs(newVideoElement, videojsOptions, function (this: any) { | ||
153 | const player = this | ||
154 | |||
155 | self.addContextMenu(mode, player, options.common.embedUrl) | ||
156 | }) | ||
157 | } | ||
158 | |||
122 | private static loadLocaleInVideoJS (serverUrl: string, locale: string) { | 159 | private static loadLocaleInVideoJS (serverUrl: string, locale: string) { |
123 | const path = PeertubePlayerManager.getLocalePath(serverUrl, locale) | 160 | const path = PeertubePlayerManager.getLocalePath(serverUrl, locale) |
124 | // It is the default locale, nothing to translate | 161 | // It is the default locale, nothing to translate |
@@ -166,7 +203,7 @@ export class PeertubePlayerManager { | |||
166 | } | 203 | } |
167 | } | 204 | } |
168 | 205 | ||
169 | if (p2pMediaLoaderOptions) { | 206 | if (mode === 'p2p-media-loader') { |
170 | const p2pMediaLoader: P2PMediaLoaderPluginOptions = { | 207 | const p2pMediaLoader: P2PMediaLoaderPluginOptions = { |
171 | redundancyBaseUrls: options.p2pMediaLoader.redundancyBaseUrls, | 208 | redundancyBaseUrls: options.p2pMediaLoader.redundancyBaseUrls, |
172 | type: 'application/x-mpegURL', | 209 | type: 'application/x-mpegURL', |
@@ -209,7 +246,7 @@ export class PeertubePlayerManager { | |||
209 | html5 = streamrootHls.html5 | 246 | html5 = streamrootHls.html5 |
210 | } | 247 | } |
211 | 248 | ||
212 | if (webtorrentOptions) { | 249 | if (mode === 'webtorrent') { |
213 | const webtorrent = { | 250 | const webtorrent = { |
214 | autoplay, | 251 | autoplay, |
215 | videoDuration: commonOptions.videoDuration, | 252 | videoDuration: commonOptions.videoDuration, |
@@ -235,7 +272,7 @@ export class PeertubePlayerManager { | |||
235 | : undefined, // Undefined so the player knows it has to check the local storage | 272 | : undefined, // Undefined so the player knows it has to check the local storage |
236 | 273 | ||
237 | poster: commonOptions.poster, | 274 | poster: commonOptions.poster, |
238 | autoplay, | 275 | autoplay: autoplay === true ? 'any' : autoplay, // Use 'any' instead of true to get notifier by videojs if autoplay fails |
239 | inactivityTimeout: commonOptions.inactivityTimeout, | 276 | inactivityTimeout: commonOptions.inactivityTimeout, |
240 | playbackRates: [ 0.5, 0.75, 1, 1.25, 1.5, 2 ], | 277 | playbackRates: [ 0.5, 0.75, 1, 1.25, 1.5, 2 ], |
241 | plugins, | 278 | plugins, |
diff --git a/client/src/assets/player/peertube-plugin.ts b/client/src/assets/player/peertube-plugin.ts index aacbf5f6e..7ea4a06d4 100644 --- a/client/src/assets/player/peertube-plugin.ts +++ b/client/src/assets/player/peertube-plugin.ts | |||
@@ -47,7 +47,11 @@ class PeerTubePlugin extends Plugin { | |||
47 | this.videoDuration = options.videoDuration | 47 | this.videoDuration = options.videoDuration |
48 | this.videoCaptions = options.videoCaptions | 48 | this.videoCaptions = options.videoCaptions |
49 | 49 | ||
50 | if (this.autoplay === true) this.player.addClass('vjs-has-autoplay') | 50 | if (options.autoplay === true) this.player.addClass('vjs-has-autoplay') |
51 | |||
52 | this.player.on('autoplay-failure', () => { | ||
53 | this.player.removeClass('vjs-has-autoplay') | ||
54 | }) | ||
51 | 55 | ||
52 | this.player.ready(() => { | 56 | this.player.ready(() => { |
53 | const playerOptions = this.player.options_ | 57 | const playerOptions = this.player.options_ |
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index 1e58d42d9..c5c46d0c5 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -311,7 +311,10 @@ class PeerTubeEmbed { | |||
311 | videoCaptions, | 311 | videoCaptions, |
312 | inactivityTimeout: 1500, | 312 | inactivityTimeout: 1500, |
313 | videoViewUrl: this.getVideoUrl(videoId) + '/views', | 313 | videoViewUrl: this.getVideoUrl(videoId) + '/views', |
314 | |||
314 | playerElement: this.videoElement, | 315 | playerElement: this.videoElement, |
316 | onPlayerElementChange: (element: HTMLVideoElement) => this.videoElement = element, | ||
317 | |||
315 | videoDuration: videoInfo.duration, | 318 | videoDuration: videoInfo.duration, |
316 | enableHotkeys: true, | 319 | enableHotkeys: true, |
317 | peertubeLink: true, | 320 | peertubeLink: true, |
@@ -321,6 +324,10 @@ class PeerTubeEmbed { | |||
321 | serverUrl: window.location.origin, | 324 | serverUrl: window.location.origin, |
322 | language: navigator.language, | 325 | language: navigator.language, |
323 | embedUrl: window.location.origin + videoInfo.embedPath | 326 | embedUrl: window.location.origin + videoInfo.embedPath |
327 | }, | ||
328 | |||
329 | webtorrent: { | ||
330 | videoFiles: videoInfo.files | ||
324 | } | 331 | } |
325 | } | 332 | } |
326 | 333 | ||
@@ -336,12 +343,6 @@ class PeerTubeEmbed { | |||
336 | videoFiles: videoInfo.files | 343 | videoFiles: videoInfo.files |
337 | } as P2PMediaLoaderOptions | 344 | } as P2PMediaLoaderOptions |
338 | }) | 345 | }) |
339 | } else { | ||
340 | Object.assign(options, { | ||
341 | webtorrent: { | ||
342 | videoFiles: videoInfo.files | ||
343 | } | ||
344 | }) | ||
345 | } | 346 | } |
346 | 347 | ||
347 | this.player = await PeertubePlayerManager.initialize(this.mode, options) | 348 | this.player = await PeertubePlayerManager.initialize(this.mode, options) |