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



                                                                         
             



                       
             

                                     
                                                                                                                                           




                   
         
                       
                       
                       






                                                    
                                   










                                        
                                       







































































                                                                                



                                                                                                       















                                                                                       
                                                                







                                                                                    
                                    

                                                                                                                            
                                    

                   
                                                      




                                                                                








































                                                                      

                              


                                 

                    
                          

                          

                                


                               

                              






                                          


                       
                     

                       
                   
                    
                    
               
           
                         

               



                                                             
 

                      
 

                                                                
 
                                  
 
                              
 
                 
 





                                                                     
 

                                                                   
 

                                      
 
                               
 
                                                         
 

                                                         
 

                                                     
 

                       
 
                                            
 
                                             
 
                                         
 
                 


                               
                                      
     












                                                                   











                                                                                             




                                           
      












                                         
 
                                                                              














                                                                        











                                                                                   
 




                                 


     
                                                             
                                                                                                     
                                      

            




                                                                       




















                                                                                                                   




























                                                                                                                          








                                                                     
import { peertubeTranslate } from '../../../../../shared/core-utils/i18n'
import {
  HTMLServerConfig,
  LiveVideo,
  Storyboard,
  Video,
  VideoCaption,
  VideoDetails,
  VideoPlaylistElement,
  VideoState,
  VideoStreamingPlaylistType
} from '../../../../../shared/models'
import { HLSOptions, PeerTubePlayerContructorOptions, PeerTubePlayerLoadOptions, PlayerMode, VideoJSCaption } from '../../../assets/player'
import {
  getBoolOrDefault,
  getParamString,
  getParamToggle,
  isP2PEnabled,
  logger,
  peertubeLocalStorage,
  UserLocalStorageKeys,
  videoRequiresUserAuth
} from '../../../root-helpers'
import { PeerTubePlugin } from './peertube-plugin'
import { PlayerHTML } from './player-html'
import { PlaylistTracker } from './playlist-tracker'
import { Translations } from './translations'
import { VideoFetcher } from './video-fetcher'

export class PlayerOptionsBuilder {
  private autoplay: boolean

  private controls: boolean
  private controlBar: boolean

  private muted: boolean
  private loop: boolean
  private subtitle: string
  private enableApi = false
  private startTime: number | string = 0
  private stopTime: number | string
  private playbackRate: number | string

  private title: boolean
  private warningTitle: boolean
  private peertubeLink: boolean
  private p2pEnabled: boolean
  private bigPlayBackgroundColor: string
  private foregroundColor: string

  private mode: PlayerMode
  private scope = 'peertube'

  constructor (
    private readonly playerHTML: PlayerHTML,
    private readonly videoFetcher: VideoFetcher,
    private readonly peertubePlugin: PeerTubePlugin
  ) {}

  hasAPIEnabled () {
    return this.enableApi
  }

  hasAutoplay () {
    return this.autoplay
  }

  hasControls () {
    return this.controls
  }

  hasTitle () {
    return this.title
  }

  hasWarningTitle () {
    return this.warningTitle
  }

  hasP2PEnabled () {
    return !!this.p2pEnabled
  }

  hasBigPlayBackgroundColor () {
    return !!this.bigPlayBackgroundColor
  }

  getBigPlayBackgroundColor () {
    return this.bigPlayBackgroundColor
  }

  hasForegroundColor () {
    return !!this.foregroundColor
  }

  getForegroundColor () {
    return this.foregroundColor
  }

  getMode () {
    return this.mode
  }

  getScope () {
    return this.scope
  }

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

  loadParams (config: HTMLServerConfig, video: VideoDetails) {
    try {
      const params = new URL(window.location.toString()).searchParams

      this.autoplay = getParamToggle(params, 'autoplay', false)
      // Disable auto play on live videos that are not streamed
      if (video.state.id === VideoState.LIVE_ENDED || video.state.id === VideoState.WAITING_FOR_LIVE) {
        this.autoplay = false
      }

      this.controls = getParamToggle(params, 'controls', true)
      this.controlBar = getParamToggle(params, 'controlBar', true)

      this.muted = getParamToggle(params, 'muted', undefined)
      this.loop = getParamToggle(params, 'loop', false)
      this.title = getParamToggle(params, 'title', true)
      this.enableApi = getParamToggle(params, 'api', this.enableApi)
      this.warningTitle = getParamToggle(params, 'warningTitle', true)
      this.peertubeLink = getParamToggle(params, 'peertubeLink', true)
      this.p2pEnabled = getParamToggle(params, 'p2p', this.isP2PEnabled(config, video))

      this.scope = getParamString(params, 'scope', this.scope)
      this.subtitle = getParamString(params, 'subtitle')
      this.startTime = getParamString(params, 'start')
      this.stopTime = getParamString(params, 'stop')
      this.playbackRate = getParamString(params, 'playbackRate')

      this.bigPlayBackgroundColor = getParamString(params, 'bigPlayBackgroundColor')
      this.foregroundColor = getParamString(params, 'foregroundColor')

      const modeParam = getParamString(params, 'mode')

      if (modeParam) {
        if (modeParam === 'p2p-media-loader') this.mode = 'p2p-media-loader'
        else this.mode = 'web-video'
      } else {
        if (Array.isArray(video.streamingPlaylists) && video.streamingPlaylists.length !== 0) this.mode = 'p2p-media-loader'
        else this.mode = 'web-video'
      }
    } catch (err) {
      logger.error('Cannot get params from URL.', err)
    }
  }

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

  getPlayerConstructorOptions (options: {
    serverConfig: HTMLServerConfig
    authorizationHeader: () => string
  }): PeerTubePlayerContructorOptions {
    const { serverConfig, authorizationHeader } = options

    return {
      controls: this.controls,
      controlBar: this.controlBar,

      muted: this.muted,
      loop: this.loop,

      playbackRate: this.playbackRate,

      inactivityTimeout: 2500,
      videoViewIntervalMs: 5000,
      metricsUrl: window.location.origin + '/api/v1/metrics/playback',

      authorizationHeader,

      playerElement: () => this.playerHTML.getPlayerElement(),
      enableHotkeys: true,

      peertubeLink: () => this.peertubeLink,
      instanceName: serverConfig.instance.name,

      theaterButton: false,

      serverUrl: window.location.origin,
      language: navigator.language,

      pluginsManager: this.peertubePlugin.getPluginsManager(),

      errorNotifier: () => {
        // Empty, we don't have a notifier in the embed
      }
    }
  }

  async getPlayerLoadOptions (options: {
    video: VideoDetails
    captionsResponse: Response

    storyboardsResponse: Response

    live?: LiveVideo

    alreadyPlayed: boolean
    forceAutoplay: boolean

    videoFileToken: () => string

    videoPassword: () => string
    requiresPassword: boolean

    translations: Translations

    playlist?: {
      playlistTracker: PlaylistTracker
      playNext: () => any
      playPrevious: () => any
      onVideoUpdate: (uuid: string) => any
    }
  }): Promise<PeerTubePlayerLoadOptions> {
    const {
      video,
      captionsResponse,
      videoFileToken,
      videoPassword,
      requiresPassword,
      translations,
      alreadyPlayed,
      forceAutoplay,
      playlist,
      live,
      storyboardsResponse
    } = options

    const [ videoCaptions, storyboard ] = await Promise.all([
      this.buildCaptions(captionsResponse, translations),
      this.buildStoryboard(storyboardsResponse)
    ])

    return {
      mode: this.mode,

      autoplay: forceAutoplay || alreadyPlayed || this.autoplay,
      forceAutoplay,

      p2pEnabled: this.p2pEnabled,

      subtitle: this.subtitle,

      storyboard,

      startTime: playlist
        ? playlist.playlistTracker.getCurrentElement().startTimestamp
        : this.startTime,
      stopTime: playlist
        ? playlist.playlistTracker.getCurrentElement().stopTimestamp
        : this.stopTime,

      videoCaptions,
      videoViewUrl: this.videoFetcher.getVideoViewsUrl(video.uuid),

      videoShortUUID: video.shortUUID,
      videoUUID: video.uuid,

      duration: video.duration,

      poster: window.location.origin + video.previewPath,

      embedUrl: window.location.origin + video.embedPath,
      embedTitle: video.name,

      requiresUserAuth: videoRequiresUserAuth(video),
      videoFileToken,

      requiresPassword,
      videoPassword,

      ...this.buildLiveOptions(video, live),

      ...this.buildPlaylistOptions(playlist),

      dock: this.buildDockOptions(video),

      webVideo: {
        videoFiles: video.files
      },

      hls: this.buildHLSOptions(video)
    }
  }

  private buildLiveOptions (video: VideoDetails, live: LiveVideo) {
    if (!video.isLive) return { isLive: false }

    return {
      isLive: true,
      liveOptions: {
        latencyMode: live.latencyMode
      }
    }
  }

  private async buildStoryboard (storyboardsResponse: Response) {
    const { storyboards } = await storyboardsResponse.json() as { storyboards: Storyboard[] }
    if (!storyboards || storyboards.length === 0) return undefined

    return {
      url: window.location.origin + storyboards[0].storyboardPath,
      height: storyboards[0].spriteHeight,
      width: storyboards[0].spriteWidth,
      interval: storyboards[0].spriteDuration
    }
  }

  private buildPlaylistOptions (options?: {
    playlistTracker: PlaylistTracker
    playNext: () => any
    playPrevious: () => any
    onVideoUpdate: (uuid: string) => any
  }) {
    if (!options) {
      return {
        nextVideo: {
          enabled: false,
          displayControlBarButton: false,
          getVideoTitle: () => ''
        },
        previousVideo: {
          enabled: false,
          displayControlBarButton: false
        }
      }
    }

    const { playlistTracker, playNext, playPrevious, onVideoUpdate } = options

    return {
      playlist: {
        elements: playlistTracker.getPlaylistElements(),
        playlist: playlistTracker.getPlaylist(),

        getCurrentPosition: () => playlistTracker.getCurrentPosition(),

        onItemClicked: (videoPlaylistElement: VideoPlaylistElement) => {
          playlistTracker.setCurrentElement(videoPlaylistElement)

          onVideoUpdate(videoPlaylistElement.video.uuid)
        }
      },

      previousVideo: {
        enabled: playlistTracker.hasPreviousPlaylistElement(),
        handler: () => playPrevious(),
        displayControlBarButton: true
      },

      nextVideo: {
        enabled: playlistTracker.hasNextPlaylistElement(),
        handler: () => playNext(),
        getVideoTitle: () => playlistTracker.getNextPlaylistElement()?.video?.name,
        displayControlBarButton: true
      },

      upnext: {
        isEnabled: () => true,
        isSuspended: () => false,
        timeout: 0
      }
    }
  }

  private buildHLSOptions (video: VideoDetails): HLSOptions {
    const hlsPlaylist = video.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
    if (!hlsPlaylist) return undefined

    return {
      playlistUrl: hlsPlaylist.playlistUrl,
      segmentsSha256Url: hlsPlaylist.segmentsSha256Url,
      redundancyBaseUrls: hlsPlaylist.redundancies.map(r => r.baseUrl),
      trackerAnnounce: video.trackerUrls,
      videoFiles: hlsPlaylist.files
    }
  }

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

  private async buildCaptions (captionsResponse: Response, translations: Translations): Promise<VideoJSCaption[]> {
    if (captionsResponse.ok) {
      const { data } = await captionsResponse.json()

      return data.map((c: VideoCaption) => ({
        label: peertubeTranslate(c.language.label, translations),
        language: c.language.id,
        src: window.location.origin + c.captionPath
      }))
    }

    return []
  }

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

  private buildDockOptions (videoInfo: VideoDetails) {
    if (!this.hasControls()) return undefined

    const title = this.hasTitle()
      ? videoInfo.name
      : undefined

    const description = this.hasWarningTitle() && this.hasP2PEnabled()
      ? '<span class="text">' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '</span>'
      : undefined

    if (!title && !description) return

    const availableAvatars = videoInfo.channel.avatars.filter(a => a.width < 50)
    const avatar = availableAvatars.length !== 0
      ? availableAvatars[0]
      : undefined

    return {
      title,
      description,
      avatarUrl: title && avatar
        ? avatar.path
        : undefined
    }
  }

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

  private isP2PEnabled (config: HTMLServerConfig, video: Video) {
    const userP2PEnabled = getBoolOrDefault(
      peertubeLocalStorage.getItem(UserLocalStorageKeys.P2P_ENABLED),
      config.defaults.p2p.embed.enabled
    )

    return isP2PEnabled(video, config, userP2PEnabled)
  }
}