From 6ec0b75beb9c8bcd84e178912319913b91830da2 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 6 Feb 2019 10:39:50 +0100 Subject: [PATCH] Fallback HLS to webtorrent --- .../+video-watch/video-watch.component.ts | 23 +++++++--- .../p2p-media-loader-plugin.ts | 10 ++++- .../assets/player/peertube-player-manager.ts | 45 +++++++++++++++++-- client/src/assets/player/peertube-plugin.ts | 6 ++- client/src/standalone/videos/embed.ts | 13 +++--- 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' import { environment } from '../../../environments/environment' import { VideoCaptionService } from '@app/shared/video-caption' import { MarkdownService } from '@app/shared/renderer' -import { P2PMediaLoaderOptions, PeertubePlayerManager, PlayerMode, WebtorrentOptions } from '../../../assets/player/peertube-player-manager' +import { + P2PMediaLoaderOptions, + PeertubePlayerManager, + PeertubePlayerManagerOptions, + PlayerMode, + WebtorrentOptions +} from '../../../assets/player/peertube-player-manager' @Component({ selector: 'my-video-watch', @@ -395,10 +401,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy { src: environment.apiUrl + c.captionPath })) - const options = { + const options: PeertubePlayerManagerOptions = { common: { autoplay: this.isAutoplay(), + playerElement: this.playerElement, + onPlayerElementChange: (element: HTMLVideoElement) => this.playerElement = element, + videoDuration: this.video.duration, enableHotkeys: true, inactivityTimeout: 2500, @@ -424,6 +433,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy { serverUrl: environment.apiUrl, videoCaptions: playerCaptions + }, + + webtorrent: { + videoFiles: this.video.files } } @@ -431,6 +444,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { const hlsPlaylist = this.video.getHlsPlaylist() if (hlsPlaylist) { mode = 'p2p-media-loader' + const p2pMediaLoader = { playlistUrl: hlsPlaylist.playlistUrl, segmentsSha256Url: hlsPlaylist.segmentsSha256Url, @@ -442,11 +456,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { Object.assign(options, { p2pMediaLoader }) } else { mode = 'webtorrent' - const webtorrent = { - videoFiles: this.video.files - } as WebtorrentOptions - - Object.assign(options, { webtorrent }) } 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 { src: options.src }) + player.on('play', () => { + player.addClass('vjs-has-big-play-button-clicked') + }) + player.ready(() => this.initialize()) } dispose () { + if (this.hlsjs) this.hlsjs.destroy() + if (this.p2pEngine) this.p2pEngine.destroy() + clearInterval(this.networkInfoInterval) } private initialize () { initHlsJsPlayer(this.hlsjs) - this.p2pEngine = this.player.tech_.options_.hlsjsConfig.loader.getEngine() + const tech = this.player.tech_ + this.p2pEngine = tech.options_.hlsjsConfig.loader.getEngine() // Avoid using constants to not import hls.hs // 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 = { export type CommonOptions = { playerElement: HTMLVideoElement + onPlayerElementChange: (element: HTMLVideoElement) => void autoplay: boolean videoDuration: number @@ -71,13 +72,14 @@ export type CommonOptions = { export type PeertubePlayerManagerOptions = { common: CommonOptions, - webtorrent?: WebtorrentOptions, + webtorrent: WebtorrentOptions, p2pMediaLoader?: P2PMediaLoaderOptions } export class PeertubePlayerManager { private static videojsLocaleCache: { [ path: string ]: any } = {} + private static playerElementClassName: string static getServerTranslations (serverUrl: string, locale: string) { const path = PeertubePlayerManager.getLocalePath(serverUrl, locale) @@ -95,6 +97,8 @@ export class PeertubePlayerManager { static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions) { let p2pMediaLoader: any + this.playerElementClassName = options.common.playerElement.className + if (mode === 'webtorrent') await import('./webtorrent/webtorrent-plugin') if (mode === 'p2p-media-loader') { [ p2pMediaLoader ] = await Promise.all([ @@ -112,6 +116,13 @@ export class PeertubePlayerManager { videojs(options.common.playerElement, videojsOptions, function (this: any) { const player = this + player.tech_.on('error', () => { + // Fallback to webtorrent? + if (mode === 'p2p-media-loader') { + self.fallbackToWebTorrent(player, options) + } + }) + self.addContextMenu(mode, player, options.common.embedUrl) return res(player) @@ -119,6 +130,32 @@ export class PeertubePlayerManager { }) } + private static async fallbackToWebTorrent (player: any, options: PeertubePlayerManagerOptions) { + const newVideoElement = document.createElement('video') + newVideoElement.className = this.playerElementClassName + + // VideoJS wraps our video element inside a div + const currentParentPlayerElement = options.common.playerElement.parentNode + currentParentPlayerElement.parentNode.insertBefore(newVideoElement, currentParentPlayerElement) + + options.common.playerElement = newVideoElement + options.common.onPlayerElementChange(newVideoElement) + + player.dispose() + + await import('./webtorrent/webtorrent-plugin') + + const mode = 'webtorrent' + const videojsOptions = this.getVideojsOptions(mode, options) + + const self = this + videojs(newVideoElement, videojsOptions, function (this: any) { + const player = this + + self.addContextMenu(mode, player, options.common.embedUrl) + }) + } + private static loadLocaleInVideoJS (serverUrl: string, locale: string) { const path = PeertubePlayerManager.getLocalePath(serverUrl, locale) // It is the default locale, nothing to translate @@ -166,7 +203,7 @@ export class PeertubePlayerManager { } } - if (p2pMediaLoaderOptions) { + if (mode === 'p2p-media-loader') { const p2pMediaLoader: P2PMediaLoaderPluginOptions = { redundancyBaseUrls: options.p2pMediaLoader.redundancyBaseUrls, type: 'application/x-mpegURL', @@ -209,7 +246,7 @@ export class PeertubePlayerManager { html5 = streamrootHls.html5 } - if (webtorrentOptions) { + if (mode === 'webtorrent') { const webtorrent = { autoplay, videoDuration: commonOptions.videoDuration, @@ -235,7 +272,7 @@ export class PeertubePlayerManager { : undefined, // Undefined so the player knows it has to check the local storage poster: commonOptions.poster, - autoplay, + autoplay: autoplay === true ? 'any' : autoplay, // Use 'any' instead of true to get notifier by videojs if autoplay fails inactivityTimeout: commonOptions.inactivityTimeout, playbackRates: [ 0.5, 0.75, 1, 1.25, 1.5, 2 ], 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 { this.videoDuration = options.videoDuration this.videoCaptions = options.videoCaptions - if (this.autoplay === true) this.player.addClass('vjs-has-autoplay') + if (options.autoplay === true) this.player.addClass('vjs-has-autoplay') + + this.player.on('autoplay-failure', () => { + this.player.removeClass('vjs-has-autoplay') + }) this.player.ready(() => { 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 { videoCaptions, inactivityTimeout: 1500, videoViewUrl: this.getVideoUrl(videoId) + '/views', + playerElement: this.videoElement, + onPlayerElementChange: (element: HTMLVideoElement) => this.videoElement = element, + videoDuration: videoInfo.duration, enableHotkeys: true, peertubeLink: true, @@ -321,6 +324,10 @@ class PeerTubeEmbed { serverUrl: window.location.origin, language: navigator.language, embedUrl: window.location.origin + videoInfo.embedPath + }, + + webtorrent: { + videoFiles: videoInfo.files } } @@ -336,12 +343,6 @@ class PeerTubeEmbed { videoFiles: videoInfo.files } as P2PMediaLoaderOptions }) - } else { - Object.assign(options, { - webtorrent: { - videoFiles: videoInfo.files - } - }) } this.player = await PeertubePlayerManager.initialize(this.mode, options)