mirror of https://github.com/Chocobozzz/PeerTube
441 lines
12 KiB
TypeScript
441 lines
12 KiB
TypeScript
![]() |
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)
|
||
|
}
|
||
|
}
|