Fallback HLS to webtorrent
authorChocobozzz <me@florianbigard.com>
Wed, 6 Feb 2019 09:39:50 +0000 (10:39 +0100)
committerChocobozzz <chocobozzz@cpy.re>
Mon, 11 Feb 2019 08:13:02 +0000 (09:13 +0100)
client/src/app/videos/+video-watch/video-watch.component.ts
client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts
client/src/assets/player/peertube-player-manager.ts
client/src/assets/player/peertube-plugin.ts
client/src/standalone/videos/embed.ts

index f77316712ee44b2e8c86dee473ba8f8fad534137..e1766255b2ee555b1205403ed93c8b0cf460ca1d 100644 (file)
@@ -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 () => {
index f9a2707fb881f1b71cc1234e2f3649840547cd4c..022a9c16f51753d4e2fbfdbafd43b90ed6aa31f2 100644 (file)
@@ -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
index 3fdba6fdf207f32a93ba5164365ea6c0aa4607db..0ba9bcb1142267422055d96eef196e34defa532f 100644 (file)
@@ -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,
index aacbf5f6efb5b1e09761730dd80423622ddfdb73..7ea4a06d456b6dde0731055fa0bc634e192190a4 100644 (file)
@@ -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_
index 1e58d42d9a1030b254469bb29bc0674adaf7d4e7..c5c46d0c5a74f7c9b2111d81326d220de66e0a6c 100644 (file)
@@ -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)