]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - client/src/assets/player/p2p-media-loader/hls-plugin.ts
Merge remote-tracking branch 'weblate/develop' into develop
[github/Chocobozzz/PeerTube.git] / client / src / assets / player / p2p-media-loader / hls-plugin.ts
index d78e1ab9021ec498f8afd6dc38a577010530ac54..672a85d0b0525bfd1d54bf9ed0982d8ca81f6610 100644 (file)
@@ -1,8 +1,8 @@
 // Thanks https://github.com/streamroot/videojs-hlsjs-plugin
 // We duplicated this plugin to choose the hls.js version we want, because streamroot only provide a bundled file
 
-import * as Hlsjs from 'hls.js'
-import videojs, { VideoJsPlayer } from 'video.js'
+import * as Hlsjs from 'hls.js/dist/hls.light.js'
+import videojs from 'video.js'
 import { HlsjsConfigHandlerOptions, QualityLevelRepresentation, QualityLevels, VideoJSTechHLS } from '../peertube-videojs-typings'
 
 type ErrorCounts = {
@@ -13,6 +13,8 @@ type Metadata = {
   levels: Hlsjs.Level[]
 }
 
+type CustomAudioTrack = Hlsjs.HlsAudioTrack & { name?: string, lang?: string }
+
 const registerSourceHandler = function (vjs: typeof videojs) {
   if (!Hlsjs.isSupported()) {
     console.warn('Hls.js is not supported in this browser!')
@@ -53,7 +55,7 @@ const registerSourceHandler = function (vjs: typeof videojs) {
   (vjs as any).Html5Hlsjs = Html5Hlsjs
 }
 
-function hlsjsConfigHandler (this: VideoJsPlayer, options: HlsjsConfigHandlerOptions) {
+function hlsjsConfigHandler (this: videojs.Player, options: HlsjsConfigHandlerOptions) {
   const player = this
 
   if (!options) return
@@ -86,12 +88,12 @@ class Html5Hlsjs {
 
   private readonly videoElement: HTMLVideoElement
   private readonly errorCounts: ErrorCounts = {}
-  private readonly player: VideoJsPlayer
+  private readonly player: videojs.Player
   private readonly tech: videojs.Tech
   private readonly source: videojs.Tech.SourceObject
   private readonly vjs: typeof videojs
 
-  private hls: Hlsjs & { manualLevel?: number } // FIXME: typings
+  private hls: Hlsjs & { manualLevel?: number, audioTrack?: any, audioTracks?: CustomAudioTrack[] } // FIXME: typings
   private hlsjsConfig: Partial<Hlsjs.Config & { cueHandler: any }> = null
 
   private _duration: number = null
@@ -100,9 +102,8 @@ class Html5Hlsjs {
   private dvrDuration: number = null
   private edgeMargin: number = null
 
-  private handlers: { [ id in 'play' | 'addtrack' | 'playing' | 'textTracksChange' | 'audioTracksChange' ]: EventListener } = {
+  private handlers: { [ id in 'play' | 'playing' | 'textTracksChange' | 'audioTracksChange' ]: EventListener } = {
     play: null,
-    addtrack: null,
     playing: null,
     textTracksChange: null,
     audioTracksChange: null
@@ -122,8 +123,11 @@ class Html5Hlsjs {
 
     this.videoElement.addEventListener('error', event => {
       let errorTxt: string
-      const mediaError = (event.currentTarget as HTMLVideoElement).error
+      const mediaError = ((event.currentTarget || event.target) as HTMLVideoElement).error
+
+      if (!mediaError) return
 
+      console.log(mediaError)
       switch (mediaError.code) {
         case mediaError.MEDIA_ERR_ABORTED:
           errorTxt = 'You aborted the video playback'
@@ -172,14 +176,11 @@ class Html5Hlsjs {
   // See comment for `initialize` method.
   dispose () {
     this.videoElement.removeEventListener('play', this.handlers.play)
-    this.videoElement.textTracks.removeEventListener('addtrack', this.handlers.addtrack)
     this.videoElement.removeEventListener('playing', this.handlers.playing)
 
     this.player.textTracks().removeEventListener('change', this.handlers.textTracksChange)
     this.uiTextTrackHandled = false
 
-    this.player.audioTracks().removeEventListener('change', this.handlers.audioTracksChange)
-
     this.hls.destroy()
   }
 
@@ -226,12 +227,34 @@ class Html5Hlsjs {
 
     if (this.errorCounts[ Hlsjs.ErrorTypes.MEDIA_ERROR ] > 2) {
       console.info('bubbling media error up to VIDEOJS')
+      this.hls.destroy()
       this.tech.error = () => error
       this.tech.trigger('error')
       return
     }
   }
 
+  private _handleNetworkError (error: any) {
+    if (this.errorCounts[ Hlsjs.ErrorTypes.NETWORK_ERROR] <= 5) {
+      console.info('trying to recover network error')
+
+      // Wait 1 second and retry
+      setTimeout(() => this.hls.startLoad(), 1000)
+
+      // Reset error count on success
+      this.hls.once(Hlsjs.Events.FRAG_LOADED, () => {
+        this.errorCounts[ Hlsjs.ErrorTypes.NETWORK_ERROR] = 0
+      })
+
+      return
+    }
+
+    console.info('bubbling network error up to VIDEOJS')
+    this.hls.destroy()
+    this.tech.error = () => error
+    this.tech.trigger('error')
+  }
+
   private _onError (_event: any, data: Hlsjs.errorData) {
     const error: { message: string, code?: number } = {
       message: `HLS.js error: ${data.type} - fatal: ${data.fatal} - ${data.details}`
@@ -242,30 +265,19 @@ class Html5Hlsjs {
     if (this.errorCounts[ data.type ]) this.errorCounts[ data.type ] += 1
     else this.errorCounts[ data.type ] = 1
 
-    // Implement simple error handling based on hls.js documentation
-    // https://github.com/dailymotion/hls.js/blob/master/API.md#fifth-step-error-handling
-    if (data.fatal) {
-      switch (data.type) {
-        case Hlsjs.ErrorTypes.NETWORK_ERROR:
-          console.info('bubbling network error up to VIDEOJS')
-          error.code = 2
-          this.tech.error = () => error as any
-          this.tech.trigger('error')
-          break
-
-        case Hlsjs.ErrorTypes.MEDIA_ERROR:
-          error.code = 3
-          this._handleMediaError(error)
-          break
-
-        default:
-          // cannot recover
-          this.hls.destroy()
-          console.info('bubbling error up to VIDEOJS')
-          this.tech.error = () => error as any
-          this.tech.trigger('error')
-          break
-      }
+    if (!data.fatal) return
+
+    if (data.type === Hlsjs.ErrorTypes.NETWORK_ERROR) {
+      error.code = 2
+      this._handleNetworkError(error)
+    } else if (data.type === Hlsjs.ErrorTypes.MEDIA_ERROR && data.details !== 'manifestIncompatibleCodecsError') {
+      error.code = 3
+      this._handleMediaError(error)
+    } else {
+      this.hls.destroy()
+      console.info('bubbling error up to VIDEOJS')
+      this.tech.error = () => error as any
+      this.tech.trigger('error')
     }
   }
 
@@ -393,7 +405,7 @@ class Html5Hlsjs {
   }
 
   private _onAudioTracks () {
-    const hlsAudioTracks = this.hls.audioTracks as (AudioTrack & { name?: string, lang?: string })[] // FIXME typings
+    const hlsAudioTracks = this.hls.audioTracks
     const playerAudioTracks = this.tech.audioTracks()
 
     if (hlsAudioTracks.length > 1 && playerAudioTracks.length === 0) {
@@ -608,9 +620,6 @@ class Html5Hlsjs {
 
     this.hls.attachMedia(this.videoElement)
 
-    this.handlers.addtrack = this._updateTextTrackList.bind(this)
-    this.videoElement.textTracks.addEventListener('addtrack', this.handlers.addtrack)
-
     this.hls.loadSource(this.source.src)
   }