]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - client/src/assets/player/peertube-videojs-plugin.ts
Localize player
[github/Chocobozzz/PeerTube.git] / client / src / assets / player / peertube-videojs-plugin.ts
index 91a3993a34cbd1ecf6df1164fd2c354686f008ac..68e98f17048bdea322966f4691f38bbf3190eda8 100644 (file)
@@ -4,16 +4,11 @@ import { VideoFile } from '../../../../shared/models/videos/video.model'
 import { renderVideo } from './video-renderer'
 import './settings-menu-button'
 import { PeertubePluginOptions, VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings'
-import {
-  getAverageBandwidth,
-  getStoredMute,
-  getStoredVolume,
-  saveAverageBandwidth,
-  saveMuteInStore,
-  saveVolumeInStore
-} from './utils'
+import { getAverageBandwidth, getStoredMute, getStoredVolume, saveAverageBandwidth, saveMuteInStore, saveVolumeInStore } from './utils'
 import minBy from 'lodash-es/minBy'
 import maxBy from 'lodash-es/maxBy'
+import * as CacheChunkStore from 'cache-chunk-store'
+import { PeertubeChunkStore } from './peertube-chunk-store'
 
 const webtorrent = new WebTorrent({
   tracker: {
@@ -36,6 +31,7 @@ class PeerTubePlugin extends Plugin {
   private readonly playerElement: HTMLVideoElement
 
   private readonly autoplay: boolean = false
+  private readonly startTime: number = 0
   private readonly savePlayerSrcFunction: Function
   private readonly videoFiles: VideoFile[]
   private readonly videoViewUrl: string
@@ -44,29 +40,33 @@ class PeerTubePlugin extends Plugin {
     INFO_SCHEDULER: 1000, // Don't change this
     AUTO_QUALITY_SCHEDULER: 3000, // Check quality every 3 seconds
     AUTO_QUALITY_THRESHOLD_PERCENT: 30, // Bandwidth should be 30% more important than a resolution bitrate to change to it
-    AUTO_QUALITY_OBSERVATION_TIME: 10000, // Wait 10 seconds before potentially changing the definition
-    AUTO_QUALITY_UPPER_RESOLUTION_DELAY: 5000, // Buffer upper resolution during 5 seconds
+    AUTO_QUALITY_OBSERVATION_TIME: 10000, // Wait 10 seconds after having change the resolution before another check
+    AUTO_QUALITY_HIGHER_RESOLUTION_DELAY: 5000, // Buffering higher resolution during 5 seconds
     BANDWIDTH_AVERAGE_NUMBER_OF_VALUES: 5 // Last 5 seconds to build average bandwidth
   }
 
   private player: any
   private currentVideoFile: VideoFile
   private torrent: WebTorrent.Torrent
+  private autoResolution = true
+  private isAutoResolutionObservation = false
+
   private videoViewInterval
   private torrentInfoInterval
   private autoQualityInterval
-  private autoResolution = true
-  private isAutoResolutionObservation = false
+  private addTorrentDelay
+  private qualityObservationTimer
+  private runAutoQualitySchedulerTimer
 
   private downloadSpeeds: number[] = []
 
   constructor (player: videojs.Player, options: PeertubePluginOptions) {
     super(player, options)
 
-    // Fix canplay event on google chrome by disabling default videojs autoplay
-    this.autoplay = this.player.options_.autoplay
-    this.player.options_.autoplay = false
+    // Disable auto play on iOS
+    this.autoplay = options.autoplay && this.isIOS() === false
 
+    this.startTime = options.startTime
     this.videoFiles = options.videoFiles
     this.videoViewUrl = options.videoViewUrl
     this.videoDuration = options.videoDuration
@@ -79,6 +79,8 @@ class PeerTubePlugin extends Plugin {
 
     this.playerElement = options.playerElement
 
+    if (this.autoplay === true) this.player.addClass('vjs-has-autoplay')
+
     this.player.ready(() => {
       const volume = getStoredVolume()
       if (volume !== undefined) this.player.volume(volume)
@@ -90,8 +92,10 @@ class PeerTubePlugin extends Plugin {
       this.runViewAdd()
 
       this.player.one('play', () => {
-        // Don't run immediately scheduler, wait some seconds the TCP connections are maid
-        setTimeout(() => this.runAutoQualityScheduler(), this.CONSTANTS.AUTO_QUALITY_SCHEDULER)
+        // Don't run immediately scheduler, wait some seconds the TCP connections are made
+        this.runAutoQualitySchedulerTimer = setTimeout(() => {
+          this.runAutoQualityScheduler()
+        }, this.CONSTANTS.AUTO_QUALITY_SCHEDULER)
       })
     })
 
@@ -102,6 +106,10 @@ class PeerTubePlugin extends Plugin {
   }
 
   dispose () {
+    clearTimeout(this.addTorrentDelay)
+    clearTimeout(this.qualityObservationTimer)
+    clearTimeout(this.runAutoQualitySchedulerTimer)
+
     clearInterval(this.videoViewInterval)
     clearInterval(this.torrentInfoInterval)
     clearInterval(this.autoQualityInterval)
@@ -157,7 +165,13 @@ class PeerTubePlugin extends Plugin {
     console.log('Adding ' + magnetOrTorrentUrl + '.')
 
     const oldTorrent = this.torrent
-    this.torrent = webtorrent.add(magnetOrTorrentUrl, torrent => {
+    const options = {
+      store: (chunkLength, storeOpts) => new CacheChunkStore(new PeertubeChunkStore(chunkLength, storeOpts), {
+        max: 100
+      })
+    }
+
+    this.torrent = webtorrent.add(magnetOrTorrentUrl, options, torrent => {
       console.log('Added ' + magnetOrTorrentUrl + '.')
 
       // Pause the old torrent
@@ -167,7 +181,8 @@ class PeerTubePlugin extends Plugin {
         oldTorrent.removePeer(oldTorrent['ws'])
       }
 
-      setTimeout(() => {
+      // Render the video in a few seconds? (on resolution change for example, we wait some seconds of the new video resolution)
+      this.addTorrentDelay = setTimeout(() => {
         this.flushVideoFile(previousVideoFile)
 
         const options = { autoplay: true, controls: true }
@@ -176,12 +191,7 @@ class PeerTubePlugin extends Plugin {
 
           if (err) return this.fallbackToHttp(done)
 
-          if (!this.player.paused()) {
-            const playPromise = this.player.play()
-            if (playPromise !== undefined) return playPromise.then(done)
-
-            return done()
-          }
+          if (!this.player.paused()) return this.tryToPlay(done)
 
           return done()
         })
@@ -224,10 +234,7 @@ class PeerTubePlugin extends Plugin {
     }
 
     const newVideoFile = this.videoFiles.find(f => f.resolution.id === resolutionId)
-    this.updateVideoFile(newVideoFile, delay, () => {
-      this.player.currentTime(currentTime)
-      this.player.handleTechSeeked_()
-    })
+    this.updateVideoFile(newVideoFile, delay, () => this.seek(currentTime))
   }
 
   flushVideoFile (videoFile: VideoFile, destroyRenderer = true) {
@@ -253,19 +260,53 @@ class PeerTubePlugin extends Plugin {
     this.trigger('autoResolutionUpdate')
   }
 
+  getCurrentVideoFile () {
+    return this.currentVideoFile
+  }
+
+  getTorrent () {
+    return this.torrent
+  }
+
+  private tryToPlay (done?: Function) {
+    if (!done) done = function () { /* empty */ }
+
+    const playPromise = this.player.play()
+    if (playPromise !== undefined) {
+      return playPromise.then(done)
+                        .catch(err => {
+                          console.error(err)
+                          this.player.pause()
+                          this.player.posterImage.show()
+                          this.player.removeClass('vjs-has-autoplay')
+
+                          return done()
+                        })
+    }
+
+    return done()
+  }
+
+  private seek (time: number) {
+    this.player.currentTime(time)
+    this.player.handleTechSeeked_()
+  }
+
   private getAppropriateFile (averageDownloadSpeed?: number): VideoFile {
     if (this.videoFiles === undefined || this.videoFiles.length === 0) return undefined
     if (this.videoFiles.length === 1) return this.videoFiles[0]
-    if (this.torrent && this.torrent.progress === 1) return this.currentVideoFile
 
-    if (!averageDownloadSpeed) averageDownloadSpeed = this.getActualDownloadSpeed()
+    // Don't change the torrent is the play was ended
+    if (this.torrent && this.torrent.progress === 1 && this.player.ended()) return this.currentVideoFile
+
+    if (!averageDownloadSpeed) averageDownloadSpeed = this.getAndSaveActualDownloadSpeed()
 
     // Filter videos we can play according to our bandwidth
     const filteredFiles = this.videoFiles.filter(f => {
       const fileBitrate = (f.size / this.videoDuration)
       let threshold = fileBitrate
 
-      // If this is for a higher resolution, or an initial load -> add a upper margin
+      // If this is for a higher resolution or an initial load: add a margin
       if (!this.currentVideoFile || f.resolution.id > this.currentVideoFile.resolution.id) {
         threshold += ((fileBitrate * this.CONSTANTS.AUTO_QUALITY_THRESHOLD_PERCENT) / 100)
       }
@@ -279,7 +320,7 @@ class PeerTubePlugin extends Plugin {
     return maxBy(filteredFiles, 'resolution.id')
   }
 
-  private getActualDownloadSpeed () {
+  private getAndSaveActualDownloadSpeed () {
     const start = Math.max(this.downloadSpeeds.length - this.CONSTANTS.BANDWIDTH_AVERAGE_NUMBER_OF_VALUES, 0)
     const lastDownloadSpeeds = this.downloadSpeeds.slice(start, this.downloadSpeeds.length)
     if (lastDownloadSpeeds.length === 0) return -1
@@ -300,20 +341,34 @@ class PeerTubePlugin extends Plugin {
 
     if (this.autoplay === true) {
       this.player.posterImage.hide()
-      this.updateVideoFile(undefined, 0, () => this.player.play())
+
+      this.updateVideoFile(undefined, 0, () => {
+        this.seek(this.startTime)
+        this.tryToPlay()
+      })
     } else {
+      // Don't try on iOS that does not support MediaSource
+      if (this.isIOS()) {
+        this.currentVideoFile = this.videoFiles[0]
+        return this.fallbackToHttp(undefined, false)
+      }
+
       // Proxy first play
       const oldPlay = this.player.play.bind(this.player)
       this.player.play = () => {
-        this.updateVideoFile(undefined, 0, () => oldPlay)
+        this.player.addClass('vjs-has-big-play-button-clicked')
         this.player.play = oldPlay
+
+        this.updateVideoFile(undefined, 0, () => this.seek(this.startTime))
       }
     }
   }
 
   private runAutoQualityScheduler () {
     this.autoQualityInterval = setInterval(() => {
-      if (this.torrent === undefined) return
+
+      // Not initialized or in HTTP fallback
+      if (this.torrent === undefined || this.torrent === null) return
       if (this.isAutoResolutionOn() === false) return
       if (this.isAutoResolutionObservation === true) return
 
@@ -325,10 +380,10 @@ class PeerTubePlugin extends Plugin {
       if (this.isPlayerWaiting() && file.resolution.id < this.currentVideoFile.resolution.id) {
         console.log('Downgrading automatically the resolution to: %s', file.resolution.label)
         changeResolution = true
-      } else if (file.resolution.id > this.currentVideoFile.resolution.id) { // Greater resolution
+      } else if (file.resolution.id > this.currentVideoFile.resolution.id) { // Higher resolution
         console.log('Upgrading automatically the resolution to: %s', file.resolution.label)
         changeResolution = true
-        changeResolutionDelay = this.CONSTANTS.AUTO_QUALITY_UPPER_RESOLUTION_DELAY
+        changeResolutionDelay = this.CONSTANTS.AUTO_QUALITY_HIGHER_RESOLUTION_DELAY
       }
 
       if (changeResolution === true) {
@@ -336,13 +391,16 @@ class PeerTubePlugin extends Plugin {
 
         // Wait some seconds in observation of our new resolution
         this.isAutoResolutionObservation = true
-        setTimeout(() => this.isAutoResolutionObservation = false, this.CONSTANTS.AUTO_QUALITY_OBSERVATION_TIME)
+
+        this.qualityObservationTimer = setTimeout(() => {
+          this.isAutoResolutionObservation = false
+        }, this.CONSTANTS.AUTO_QUALITY_OBSERVATION_TIME)
       }
     }, this.CONSTANTS.AUTO_QUALITY_SCHEDULER)
   }
 
   private isPlayerWaiting () {
-    return this.player.hasClass('vjs-waiting')
+    return this.player && this.player.hasClass('vjs-waiting')
   }
 
   private runTorrentInfoScheduler () {
@@ -397,7 +455,7 @@ class PeerTubePlugin extends Plugin {
     return fetch(this.videoViewUrl, { method: 'POST' })
   }
 
-  private fallbackToHttp (done: Function) {
+  private fallbackToHttp (done?: Function, play = true) {
     this.flushVideoFile(this.currentVideoFile, true)
     this.torrent = null
 
@@ -407,9 +465,9 @@ class PeerTubePlugin extends Plugin {
     const httpUrl = this.currentVideoFile.fileUrl
     this.player.src = this.savePlayerSrcFunction
     this.player.src(httpUrl)
-    this.player.play()
+    if (play) this.tryToPlay()
 
-    return done()
+    if (done) return done()
   }
 
   private handleError (err: Error | string) {
@@ -424,6 +482,10 @@ class PeerTubePlugin extends Plugin {
     this.player.removeClass('vjs-error-display-enabled')
   }
 
+  private isIOS () {
+    return !!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform)
+  }
+
   private alterInactivity () {
     let saveInactivityTimeout: number