]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - client/src/assets/player/peertube-player-manager.ts
Add to playlist dropdown
[github/Chocobozzz/PeerTube.git] / client / src / assets / player / peertube-player-manager.ts
index 2e090847ccba2a0355ae824eb85e00ba041dde5b..6cdd543725604d32b08ddbb6aee2e6f3bec5bceb 100644 (file)
@@ -13,9 +13,10 @@ import './videojs-components/p2p-info-button'
 import './videojs-components/peertube-load-progress-bar'
 import './videojs-components/theater-button'
 import { P2PMediaLoaderPluginOptions, UserWatching, VideoJSCaption, VideoJSPluginOptions, videojsUntyped } from './peertube-videojs-typings'
-import { buildVideoEmbed, buildVideoLink, copyToClipboard } from './utils'
+import { buildVideoEmbed, buildVideoLink, copyToClipboard, getRtcConfig } from './utils'
 import { getCompleteLocale, getShortLocale, is18nLocale, isDefaultLocale } from '../../../../shared/models/i18n/i18n'
-import { Engine } from 'p2p-media-loader-hlsjs'
+import { segmentValidatorFactory } from './p2p-media-loader/segment-validator'
+import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder'
 
 // Change 'Playback Rate' to 'Speed' (smaller for our settings menu)
 videojsUntyped.getComponent('PlaybackRateMenuButton').prototype.controlText_ = 'Speed'
@@ -32,10 +33,15 @@ export type WebtorrentOptions = {
 
 export type P2PMediaLoaderOptions = {
   playlistUrl: string
+  segmentsSha256Url: string
+  trackerAnnounce: string[]
+  redundancyBaseUrls: string[]
+  videoFiles: VideoFile[]
 }
 
 export type CommonOptions = {
   playerElement: HTMLVideoElement
+  onPlayerElementChange: (element: HTMLVideoElement) => void
 
   autoplay: boolean
   videoDuration: number
@@ -43,6 +49,7 @@ export type CommonOptions = {
   inactivityTimeout: number
   poster: string
   startTime: number | string
+  stopTime: number | string
 
   theaterMode: boolean
   captions: boolean
@@ -66,13 +73,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)
@@ -88,10 +96,19 @@ export class PeertubePlayerManager {
   }
 
   static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions) {
-    if (mode === 'webtorrent') await import('./webtorrent-plugin')
-    if (mode === 'p2p-media-loader') await import('./p2p-media-loader-plugin')
+    let p2pMediaLoader: any
 
-    const videojsOptions = this.getVideojsOptions(mode, options)
+    this.playerElementClassName = options.common.playerElement.className
+
+    if (mode === 'webtorrent') await import('./webtorrent/webtorrent-plugin')
+    if (mode === 'p2p-media-loader') {
+      [ p2pMediaLoader ] = await Promise.all([
+        import('p2p-media-loader-hlsjs'),
+        import('./p2p-media-loader/p2p-media-loader-plugin')
+      ])
+    }
+
+    const videojsOptions = this.getVideojsOptions(mode, options, p2pMediaLoader)
 
     await this.loadLocaleInVideoJS(options.common.serverUrl, options.common.language)
 
@@ -100,6 +117,9 @@ export class PeertubePlayerManager {
       videojs(options.common.playerElement, videojsOptions, function (this: any) {
         const player = this
 
+        player.tech_.one('error', () => self.maybeFallbackToWebTorrent(mode, player, options))
+        player.one('error', () => self.maybeFallbackToWebTorrent(mode, player, options))
+
         self.addContextMenu(mode, player, options.common.embedUrl)
 
         return res(player)
@@ -107,6 +127,39 @@ export class PeertubePlayerManager {
     })
   }
 
+  private static async maybeFallbackToWebTorrent (currentMode: PlayerMode, player: any, options: PeertubePlayerManagerOptions) {
+    if (currentMode === 'webtorrent') return
+
+    console.log('Fallback to webtorrent.')
+
+    const newVideoElement = document.createElement('video')
+    newVideoElement.className = this.playerElementClassName
+
+    // VideoJS wraps our video element inside a div
+    let currentParentPlayerElement = options.common.playerElement.parentNode
+    // Fix on IOS, don't ask me why
+    if (!currentParentPlayerElement) currentParentPlayerElement = document.getElementById(options.common.playerElement.id).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
@@ -133,40 +186,63 @@ export class PeertubePlayerManager {
     return p.then(json => videojs.addLanguage(getShortLocale(completeLocale), json))
   }
 
-  private static getVideojsOptions (mode: PlayerMode, options: PeertubePlayerManagerOptions) {
+  private static getVideojsOptions (mode: PlayerMode, options: PeertubePlayerManagerOptions, p2pMediaLoaderModule?: any) {
     const commonOptions = options.common
     const webtorrentOptions = options.webtorrent
     const p2pMediaLoaderOptions = options.p2pMediaLoader
+
+    let autoplay = options.common.autoplay
     let html5 = {}
 
     const plugins: VideoJSPluginOptions = {
       peertube: {
-        autoplay: commonOptions.autoplay, // Use peertube plugin autoplay because we get the file by webtorrent
+        mode,
+        autoplay, // Use peertube plugin autoplay because we get the file by webtorrent
         videoViewUrl: commonOptions.videoViewUrl,
         videoDuration: commonOptions.videoDuration,
-        startTime: commonOptions.startTime,
         userWatching: commonOptions.userWatching,
         subtitle: commonOptions.subtitle,
-        videoCaptions: commonOptions.videoCaptions
+        videoCaptions: commonOptions.videoCaptions,
+        stopTime: commonOptions.stopTime
       }
     }
 
-    if (p2pMediaLoaderOptions) {
+    if (mode === 'p2p-media-loader') {
       const p2pMediaLoader: P2PMediaLoaderPluginOptions = {
+        redundancyBaseUrls: options.p2pMediaLoader.redundancyBaseUrls,
         type: 'application/x-mpegURL',
+        startTime: commonOptions.startTime,
         src: p2pMediaLoaderOptions.playlistUrl
       }
 
-      const config = {
+      const trackerAnnounce = p2pMediaLoaderOptions.trackerAnnounce
+        .filter(t => t.startsWith('ws'))
+
+      const p2pMediaLoaderConfig = {
+        loader: {
+          trackerAnnounce,
+          segmentValidator: segmentValidatorFactory(options.p2pMediaLoader.segmentsSha256Url),
+          rtcConfig: getRtcConfig(),
+          requiredSegmentsPriority: 5,
+          segmentUrlBuilder: segmentUrlBuilderFactory(options.p2pMediaLoader.redundancyBaseUrls)
+        },
         segments: {
-          swarmId: 'swarm' // TODO: choose swarm id
+          swarmId: p2pMediaLoaderOptions.playlistUrl
         }
       }
       const streamrootHls = {
+        levelLabelHandler: (level: { height: number, width: number }) => {
+          const file = p2pMediaLoaderOptions.videoFiles.find(f => f.resolution.id === level.height)
+
+          let label = file.resolution.label
+          if (file.fps >= 50) label += file.fps
+
+          return label
+        },
         html5: {
           hlsjsConfig: {
             liveSyncDurationCount: 7,
-            loader: new Engine(config).createLoaderClass()
+            loader: new p2pMediaLoaderModule.Engine(p2pMediaLoaderConfig).createLoaderClass()
           }
         }
       }
@@ -175,14 +251,18 @@ export class PeertubePlayerManager {
       html5 = streamrootHls.html5
     }
 
-    if (webtorrentOptions) {
+    if (mode === 'webtorrent') {
       const webtorrent = {
-        autoplay: commonOptions.autoplay,
+        autoplay,
         videoDuration: commonOptions.videoDuration,
         playerElement: commonOptions.playerElement,
-        videoFiles: webtorrentOptions.videoFiles
+        videoFiles: webtorrentOptions.videoFiles,
+        startTime: commonOptions.startTime
       }
       Object.assign(plugins, { webtorrent })
+
+      // WebTorrent plugin handles autoplay, because we do some hackish stuff in there
+      autoplay = false
     }
 
     const videojsOptions = {
@@ -198,7 +278,7 @@ export class PeertubePlayerManager {
         : undefined, // Undefined so the player knows it has to check the local storage
 
       poster: commonOptions.poster,
-      autoplay: false,
+      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,