]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add fallback to HTTP
authorChocobozzz <me@florianbigard.com>
Mon, 26 Feb 2018 08:55:23 +0000 (09:55 +0100)
committerChocobozzz <me@florianbigard.com>
Mon, 26 Feb 2018 09:49:27 +0000 (10:49 +0100)
client/src/app/videos/+video-watch/video-watch.component.ts
client/src/assets/player/peertube-videojs-plugin.ts
client/src/assets/player/video-renderer.ts
client/src/sass/video-js-custom.scss

index d04d503109a4cd83d7406d4d17e18e0b1369b48b..c9bfa7ffbe27a795fc43f05774e93075117fb929 100644 (file)
@@ -272,6 +272,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
 
   private handleError (err: any) {
     const errorMessage: string = typeof err === 'string' ? err : err.message
+    if (!errorMessage) return
+
     let message = ''
 
     if (errorMessage.indexOf('http error') !== -1) {
@@ -353,9 +355,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
           this.zone.runOutsideAngular(() => {
             videojs(this.playerElement, videojsOptions, function () {
               self.player = this
-              this.on('customError', (event, data) => {
-                self.handleError(data.err)
-              })
+              this.on('customError', (event, data) => self.handleError(data.err))
             })
           })
         } else {
index 01a630cb62cc4c12951b29f4078fa5eb38397b7c..618d77cbed6c373a8e0b1ae61c04fb053c456f8a 100644 (file)
@@ -1,6 +1,5 @@
 // Big thanks to: https://github.com/kmoskwiak/videojs-resolution-switcher
 
-import { VideoService } from '@app/shared/video/video.service'
 import * as videojs from 'video.js'
 import * as WebTorrent from 'webtorrent'
 import { VideoFile } from '../../../../shared/models/videos/video.model'
@@ -147,12 +146,12 @@ Button.registerComponent('PeerTubeLinkButton', PeertubeLinkButton)
 class WebTorrentButton extends Button {
   createEl () {
     const div = document.createElement('div')
-    const subDiv = document.createElement('div')
-    div.appendChild(subDiv)
+    const subDivWebtorrent = document.createElement('div')
+    div.appendChild(subDivWebtorrent)
 
     const downloadIcon = document.createElement('span')
     downloadIcon.classList.add('icon', 'icon-download')
-    subDiv.appendChild(downloadIcon)
+    subDivWebtorrent.appendChild(downloadIcon)
 
     const downloadSpeedText = document.createElement('span')
     downloadSpeedText.classList.add('download-speed-text')
@@ -161,11 +160,11 @@ class WebTorrentButton extends Button {
     const downloadSpeedUnit = document.createElement('span')
     downloadSpeedText.appendChild(downloadSpeedNumber)
     downloadSpeedText.appendChild(downloadSpeedUnit)
-    subDiv.appendChild(downloadSpeedText)
+    subDivWebtorrent.appendChild(downloadSpeedText)
 
     const uploadIcon = document.createElement('span')
     uploadIcon.classList.add('icon', 'icon-upload')
-    subDiv.appendChild(uploadIcon)
+    subDivWebtorrent.appendChild(uploadIcon)
 
     const uploadSpeedText = document.createElement('span')
     uploadSpeedText.classList.add('upload-speed-text')
@@ -174,34 +173,56 @@ class WebTorrentButton extends Button {
     const uploadSpeedUnit = document.createElement('span')
     uploadSpeedText.appendChild(uploadSpeedNumber)
     uploadSpeedText.appendChild(uploadSpeedUnit)
-    subDiv.appendChild(uploadSpeedText)
+    subDivWebtorrent.appendChild(uploadSpeedText)
 
     const peersText = document.createElement('span')
-    peersText.textContent = ' peers'
     peersText.classList.add('peers-text')
     const peersNumber = document.createElement('span')
     peersNumber.classList.add('peers-number')
-    subDiv.appendChild(peersNumber)
-    subDiv.appendChild(peersText)
+    subDivWebtorrent.appendChild(peersNumber)
+    subDivWebtorrent.appendChild(peersText)
 
-    div.className = 'vjs-webtorrent'
+    div.className = 'vjs-peertube'
     // Hide the stats before we get the info
-    subDiv.className = 'vjs-webtorrent-hidden'
+    subDivWebtorrent.className = 'vjs-peertube-hidden'
+
+    const subDivHttp = document.createElement('div')
+    subDivHttp.className = 'vjs-peertube-hidden'
+    const subDivHttpText = document.createElement('span')
+    subDivHttpText.classList.add('peers-number')
+    subDivHttpText.textContent = 'HTTP'
+    const subDivFallbackText = document.createElement('span')
+    subDivFallbackText.classList.add('peers-text')
+    subDivFallbackText.textContent = ' fallback'
+
+    subDivHttp.appendChild(subDivHttpText)
+    subDivHttp.appendChild(subDivFallbackText)
+    div.appendChild(subDivHttp)
 
     this.player_.peertube().on('torrentInfo', (event, data) => {
+      // We are in HTTP fallback
+      if (!data) {
+        subDivHttp.className = 'vjs-peertube-displayed'
+        subDivWebtorrent.className = 'vjs-peertube-hidden'
+
+        return
+      }
+
       const downloadSpeed = bytes(data.downloadSpeed)
       const uploadSpeed = bytes(data.uploadSpeed)
       const numPeers = data.numPeers
 
-      downloadSpeedNumber.textContent = downloadSpeed[0]
-      downloadSpeedUnit.textContent = ' ' + downloadSpeed[1]
+      downloadSpeedNumber.textContent = downloadSpeed[ 0 ]
+      downloadSpeedUnit.textContent = ' ' + downloadSpeed[ 1 ]
 
-      uploadSpeedNumber.textContent = uploadSpeed[0]
-      uploadSpeedUnit.textContent = ' ' + uploadSpeed[1]
+      uploadSpeedNumber.textContent = uploadSpeed[ 0 ]
+      uploadSpeedUnit.textContent = ' ' + uploadSpeed[ 1 ]
 
       peersNumber.textContent = numPeers
+      peersText.textContent = ' peers'
 
-      subDiv.className = 'vjs-webtorrent-displayed'
+      subDivHttp.className = 'vjs-peertube-hidden'
+      subDivWebtorrent.className = 'vjs-peertube-displayed'
     })
 
     return div
@@ -225,6 +246,7 @@ class PeerTubePlugin extends Plugin {
   private videoDuration: number
   private videoViewInterval
   private torrentInfoInterval
+  private savePlayerSrcFunction: Function
 
   constructor (player: videojs.Player, options: PeertubePluginOptions) {
     super(player, options)
@@ -237,12 +259,11 @@ class PeerTubePlugin extends Plugin {
     this.videoViewUrl = options.videoViewUrl
     this.videoDuration = options.videoDuration
 
+    this.savePlayerSrcFunction = this.player.src
     // Hack to "simulate" src link in video.js >= 6
     // Without this, we can't play the video after pausing it
     // https://github.com/videojs/video.js/blob/master/src/js/player.js#L1633
-    this.player.src = function () {
-      return true
-    }
+    this.player.src = () => true
 
     this.playerElement = options.playerElement
 
@@ -284,6 +305,10 @@ class PeerTubePlugin extends Plugin {
       return
     }
 
+    // Do not display error to user because we will have multiple fallbacks
+    this.disableErrorDisplay()
+    this.player.src = () => true
+
     const previousVideoFile = this.currentVideoFile
     this.currentVideoFile = videoFile
 
@@ -295,7 +320,7 @@ class PeerTubePlugin extends Plugin {
 
       const options = { autoplay: true, controls: true }
       renderVideo(torrent.files[0], this.playerElement, options,(err, renderer) => {
-        if (err) return this.handleError(err)
+        if (err) return this.fallbackToHttp()
 
         this.renderer = renderer
         if (!this.player.paused()) {
@@ -347,7 +372,8 @@ class PeerTubePlugin extends Plugin {
 
   flushVideoFile (videoFile: VideoFile, destroyRenderer = true) {
     if (videoFile !== undefined && webtorrent.get(videoFile.magnetUri)) {
-      if (destroyRenderer === true) this.renderer.destroy()
+      if (destroyRenderer === true && this.renderer && this.renderer.destroy) this.renderer.destroy()
+
       webtorrent.remove(videoFile.magnetUri)
       console.log('Removed ' + videoFile.magnetUri)
     }
@@ -390,13 +416,17 @@ class PeerTubePlugin extends Plugin {
 
   private runTorrentInfoScheduler () {
     this.torrentInfoInterval = setInterval(() => {
-      if (this.torrent !== undefined) {
-        this.trigger('torrentInfo', {
-          downloadSpeed: this.torrent.downloadSpeed,
-          numPeers: this.torrent.numPeers,
-          uploadSpeed: this.torrent.uploadSpeed
-        })
-      }
+      // Not initialized yet
+      if (this.torrent === undefined) return
+
+      // Http fallback
+      if (this.torrent === null) return this.trigger('torrentInfo', false)
+
+      return this.trigger('torrentInfo', {
+        downloadSpeed: this.torrent.downloadSpeed,
+        numPeers: this.torrent.numPeers,
+        uploadSpeed: this.torrent.uploadSpeed
+      })
     }, 1000)
   }
 
@@ -433,8 +463,29 @@ class PeerTubePlugin extends Plugin {
     return fetch(this.videoViewUrl, { method: 'POST' })
   }
 
+  private fallbackToHttp () {
+    this.flushVideoFile(this.currentVideoFile, true)
+    this.torrent = null
+
+    // Enable error display now this is our last fallback
+    this.player.one('error', () => this.enableErrorDisplay())
+
+    const httpUrl = this.currentVideoFile.fileUrl
+    this.player.src = this.savePlayerSrcFunction
+    this.player.src(httpUrl)
+    this.player.play()
+  }
+
   private handleError (err: Error | string) {
     return this.player.trigger('customError', { err })
   }
+
+  private enableErrorDisplay () {
+    this.player.addClass('vjs-error-display-enabled')
+  }
+
+  private disableErrorDisplay () {
+    this.player.removeClass('vjs-error-display-enabled')
+  }
 }
 videojsUntyped.registerPlugin('peertube', PeerTubePlugin)
index bda40b11d0ceaac7dda380c1bc860679e1915273..174676ffafe5ebafe9bcee95e3c6542de4919c43 100644 (file)
@@ -1,8 +1,8 @@
 // Thanks: https://github.com/feross/render-media
 // TODO: use render-media once https://github.com/feross/render-media/issues/32 is fixed
 
-import { extname } from 'path'
 import * as MediaElementWrapper from 'mediasource'
+import { extname } from 'path'
 import * as videostream from 'videostream'
 
 const VIDEOSTREAM_EXTS = [
@@ -27,7 +27,7 @@ function renderVideo (
   return renderMedia(file, elem, opts, callback)
 }
 
-function renderMedia (file, elem: HTMLVideoElement, opts: RenderMediaOptions, callback: (err: Error, renderer: any) => void) {
+function renderMedia (file, elem: HTMLVideoElement, opts: RenderMediaOptions, callback: (err: Error, renderer?: any) => void) {
   const extension = extname(file.name).toLowerCase()
   let preparedElem = undefined
   let currentTime = 0
@@ -41,18 +41,33 @@ function renderMedia (file, elem: HTMLVideoElement, opts: RenderMediaOptions, ca
 
   function useVideostream () {
     prepareElem()
-    preparedElem.addEventListener('error', fallbackToMediaSource)
+    preparedElem.addEventListener('error', function onError () {
+      preparedElem.removeEventListener('error', onError)
+
+      return fallbackToMediaSource()
+    })
     preparedElem.addEventListener('loadstart', onLoadStart)
     return videostream(file, preparedElem)
   }
 
-  function useMediaSource () {
+  function useMediaSource (useVP9 = false) {
+    const codecs = getCodec(file.name, useVP9)
+
     prepareElem()
-    preparedElem.addEventListener('error', callback)
+    preparedElem.addEventListener('error', function onError(err) {
+      // Try with vp9 before returning an error
+      if (codecs.indexOf('vp8') !== -1) {
+        preparedElem.removeEventListener('error', onError)
+
+        return fallbackToMediaSource(true)
+      }
+
+      return callback(err)
+    })
     preparedElem.addEventListener('loadstart', onLoadStart)
 
     const wrapper = new MediaElementWrapper(preparedElem)
-    const writable = wrapper.createWriteStream(getCodec(file.name))
+    const writable = wrapper.createWriteStream(codecs)
     file.createReadStream().pipe(writable)
 
     if (currentTime) preparedElem.currentTime = currentTime
@@ -60,10 +75,11 @@ function renderMedia (file, elem: HTMLVideoElement, opts: RenderMediaOptions, ca
     return wrapper
   }
 
-  function fallbackToMediaSource () {
-    preparedElem.removeEventListener('error', fallbackToMediaSource)
+  function fallbackToMediaSource (useVP9 = false) {
+    if (useVP9 === true) console.log('Falling back to media source with VP9 enabled.')
+    else console.log('Falling back to media source..')
 
-    useMediaSource()
+    useMediaSource(useVP9)
   }
 
   function prepareElem () {
@@ -96,16 +112,19 @@ function validateFile (file) {
   }
 }
 
-function getCodec (name: string) {
+function getCodec (name: string, useVP9 = false) {
   const ext = extname(name).toLowerCase()
-  return {
-    '.m4a': 'audio/mp4; codecs="mp4a.40.5"',
-    '.m4v': 'video/mp4; codecs="avc1.640029, mp4a.40.5"',
-    '.mkv': 'video/webm; codecs="avc1.640029, mp4a.40.5"',
-    '.mp3': 'audio/mpeg',
-    '.mp4': 'video/mp4; codecs="avc1.640029, mp4a.40.5"',
-    '.webm': 'video/webm; codecs="opus, vorbis, vp8"'
-  }[ext]
+  if (ext === '.mp4') {
+    return 'video/mp4; codecs="avc1.640029, mp4a.40.5"'
+  }
+
+  if (ext === '.webm') {
+    if (useVP9 === true) return 'video/webm; codecs="vp9, opus"'
+
+    return 'video/webm; codecs="vp8, vorbis"'
+  }
+
+  return undefined
 }
 
 export {
index ee6b9219bc36a02693a011d968e700e726b58fe1..ee8f2594935ffbea1b526f387287b8dd0528f9de 100644 (file)
@@ -154,17 +154,17 @@ $control-bar-height: 34px;
       }
     }
 
-    .vjs-webtorrent {
+    .vjs-peertube {
       width: 100%;
       line-height: $control-bar-height;
       text-align: right;
       padding-right: 60px;
 
-      .vjs-webtorrent-displayed {
+      .vjs-peertube-displayed {
         display: block;
       }
 
-      .vjs-webtorrent-hidden {
+      .vjs-peertube-hidden {
         display: none;
       }
 
@@ -424,3 +424,20 @@ $control-bar-height: 34px;
   }
 }
 
+// Error display disabled
+.vjs-error:not(.vjs-error-display-enabled) {
+  .vjs-error-display {
+    display: none;
+  }
+
+  .vjs-loading-spinner {
+    display: block;
+  }
+}
+
+// Error display enabled
+.vjs-error.vjs-error-display-enabled {
+  .vjs-error-display {
+    display: block;
+  }
+}