aboutsummaryrefslogblamecommitdiffhomepage
path: root/client/src/assets/player/shared/player-options-builder/hls-options-builder.ts
blob: 10df2db5d0d4214fb9f527f79f8ffa1925df6a2a (plain) (tree)
1
2
3
4
5
6
7
8
9

                                                                      
                                             
                                                     
                                                                                
                                                                                                                                     
                                                      

                                                                                  





                                                                                                                



                                
                                        




                                      
                             








                                                                                              
 

                                                                           
                                                                               
     


                                                                                                                   

                                                      
 

                                    

                                        



                   

                                                


                                                                        
                                                                                          






                                              


       
                                    



                                                                                





                                                              

                                                              
                                                                        


                        

                                                            
 
                                                        




                                           





                                       
                                 
                                                                     


                                                             
                                                                
 
                                                                                                               
 
                                                                                        

          
                                                                           
 
                                                                          
 
                                        




                                   
                                              
                                                                                  








                                                                          
                                                            






                                              
                                      












                                                                         
                                    














                                                                                
                                                        


















                                                                    
                           






                                
                                                            
























                                              
import { HybridLoaderSettings } from '@peertube/p2p-media-loader-core'
import { HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs'
import { logger } from '@root-helpers/logger'
import { LiveVideoLatencyMode } from '@shared/models'
import { getAverageBandwidthInStore } from '../../peertube-player-local-storage'
import { P2PMediaLoader, P2PMediaLoaderPluginOptions, PeerTubePlayerContructorOptions, PeerTubePlayerLoadOptions } from '../../types'
import { getRtcConfig, isSameOrigin } from '../common'
import { RedundancyUrlManager } from '../p2p-media-loader/redundancy-url-manager'
import { segmentUrlBuilderFactory } from '../p2p-media-loader/segment-url-builder'
import { SegmentValidator } from '../p2p-media-loader/segment-validator'

type ConstructorOptions =
  Pick<PeerTubePlayerContructorOptions, 'pluginsManager' | 'serverUrl' | 'authorizationHeader'> &
  Pick<PeerTubePlayerLoadOptions, 'videoPassword' | 'requiresUserAuth' | 'videoFileToken' | 'requiresPassword' |
  'isLive' | 'liveOptions' | 'p2pEnabled' | 'hls'>

export class HLSOptionsBuilder {

  constructor (
    private options: ConstructorOptions,
    private p2pMediaLoaderModule?: any
  ) {

  }

  async getPluginOptions () {
    const redundancyUrlManager = new RedundancyUrlManager(this.options.hls.redundancyBaseUrls)
    const segmentValidator = new SegmentValidator({
      segmentsSha256Url: this.options.hls.segmentsSha256Url,
      authorizationHeader: this.options.authorizationHeader,
      requiresUserAuth: this.options.requiresUserAuth,
      serverUrl: this.options.serverUrl,
      requiresPassword: this.options.requiresPassword,
      videoPassword: this.options.videoPassword
    })

    const p2pMediaLoaderConfig = await this.options.pluginsManager.runHook(
      'filter:internal.player.p2p-media-loader.options.result',
      this.getP2PMediaLoaderOptions({ redundancyUrlManager, segmentValidator })
    )
    const loader = new this.p2pMediaLoaderModule.Engine(p2pMediaLoaderConfig).createLoaderClass() as P2PMediaLoader

    const p2pMediaLoader: P2PMediaLoaderPluginOptions = {
      requiresUserAuth: this.options.requiresUserAuth,
      videoFileToken: this.options.videoFileToken,

      redundancyUrlManager,
      type: 'application/x-mpegURL',
      src: this.options.hls.playlistUrl,
      segmentValidator,
      loader
    }

    const hlsjs = {
      hlsjsConfig: this.getHLSJSOptions(loader),

      levelLabelHandler: (level: { height: number, width: number }) => {
        const resolution = Math.min(level.height || 0, level.width || 0)

        const file = this.options.hls.videoFiles.find(f => f.resolution.id === resolution)
        // We don't have files for live videos
        if (!file) return level.height

        let label = file.resolution.label
        if (file.fps >= 50) label += file.fps

        return label
      }
    }

    return { p2pMediaLoader, hlsjs }
  }

  // ---------------------------------------------------------------------------

  private getP2PMediaLoaderOptions (options: {
    redundancyUrlManager: RedundancyUrlManager
    segmentValidator: SegmentValidator
  }): HlsJsEngineSettings {
    const { redundancyUrlManager, segmentValidator } = options

    let consumeOnly = false
    if ((navigator as any)?.connection?.type === 'cellular') {
      logger.info('We are on a cellular connection: disabling seeding.')
      consumeOnly = true
    }

    const trackerAnnounce = this.options.hls.trackerAnnounce
      .filter(t => t.startsWith('ws'))

    const specificLiveOrVODOptions = this.options.isLive
      ? this.getP2PMediaLoaderLiveOptions()
      : this.getP2PMediaLoaderVODOptions()

    return {
      loader: {
        trackerAnnounce,
        rtcConfig: getRtcConfig(),

        simultaneousHttpDownloads: 1,
        httpFailedSegmentTimeout: 1000,

        xhrSetup: (xhr, url) => {
          const { requiresUserAuth, requiresPassword } = this.options

          if (!(requiresUserAuth || requiresPassword)) return

          if (!isSameOrigin(this.options.serverUrl, url)) return

          if (requiresPassword) xhr.setRequestHeader('x-peertube-video-password', this.options.videoPassword())

          else xhr.setRequestHeader('Authorization', this.options.authorizationHeader())
        },

        segmentValidator: segmentValidator.validate.bind(segmentValidator),

        segmentUrlBuilder: segmentUrlBuilderFactory(redundancyUrlManager),

        useP2P: this.options.p2pEnabled,
        consumeOnly,

        ...specificLiveOrVODOptions
      },
      segments: {
        swarmId: this.options.hls.playlistUrl,
        forwardSegmentCount: specificLiveOrVODOptions.p2pDownloadMaxPriority ?? 20
      }
    }
  }

  private getP2PMediaLoaderLiveOptions (): Partial<HybridLoaderSettings> {
    const base = {
      requiredSegmentsPriority: 1
    }

    const latencyMode = this.options.liveOptions.latencyMode

    switch (latencyMode) {
      case LiveVideoLatencyMode.SMALL_LATENCY:
        return {
          ...base,

          useP2P: false,
          requiredSegmentsPriority: 10
        }

      case LiveVideoLatencyMode.HIGH_LATENCY:
        return base

      default:
        return base
    }
  }

  private getP2PMediaLoaderVODOptions (): Partial<HybridLoaderSettings> {
    return {
      requiredSegmentsPriority: 3,
      skipSegmentBuilderPriority: 1,

      cachedSegmentExpiration: 86400000,
      cachedSegmentsCount: 100,

      httpDownloadMaxPriority: 9,
      httpDownloadProbability: 0.06,
      httpDownloadProbabilitySkipIfNoPeers: true,

      p2pDownloadMaxPriority: 50
    }
  }

  // ---------------------------------------------------------------------------

  private getHLSJSOptions (loader: P2PMediaLoader) {
    const specificLiveOrVODOptions = this.options.isLive
      ? this.getHLSLiveOptions()
      : this.getHLSVODOptions()

    const base = {
      capLevelToPlayerSize: true,
      autoStartLoad: false,

      loader,

      ...specificLiveOrVODOptions
    }

    const averageBandwidth = getAverageBandwidthInStore()
    if (!averageBandwidth) return base

    return {
      ...base,

      abrEwmaDefaultEstimate: averageBandwidth * 8, // We want bit/s
      backBufferLength: 90,
      startLevel: -1,
      testBandwidth: false,
      debug: false
    }
  }

  private getHLSLiveOptions () {
    const latencyMode = this.options.liveOptions.latencyMode

    switch (latencyMode) {
      case LiveVideoLatencyMode.SMALL_LATENCY:
        return {
          liveSyncDurationCount: 2
        }

      case LiveVideoLatencyMode.HIGH_LATENCY:
        return {
          liveSyncDurationCount: 10
        }

      default:
        return {
          liveSyncDurationCount: 5
        }
    }
  }

  private getHLSVODOptions () {
    return {
      liveSyncDurationCount: 5
    }
  }
}