PeerTube/client/src/standalone/videos/embed.ts

350 lines
11 KiB
TypeScript
Raw Normal View History

2017-07-23 14:49:52 +02:00
import './embed.scss'
2022-03-14 14:28:20 +01:00
import '../../assets/player/shared/dock/peertube-dock-component'
import '../../assets/player/shared/dock/peertube-dock-plugin'
2020-06-26 08:37:26 +02:00
import videojs from 'video.js'
2020-08-06 14:58:01 +02:00
import { peertubeTranslate } from '../../../../shared/core-utils/i18n'
2022-05-31 08:59:30 +02:00
import { HTMLServerConfig, LiveVideo, ResultList, VideoDetails, VideoPlaylist, VideoPlaylistElement } from '../../../../shared/models'
2022-05-31 14:18:41 +02:00
import { PeertubePlayerManager } from '../../assets/player'
2020-06-26 08:37:26 +02:00
import { TranslationsManager } from '../../assets/player/translations-manager'
import { getParamString, logger } from '../../root-helpers'
2021-05-14 12:04:44 +02:00
import { PeerTubeEmbedApi } from './embed-api'
2022-05-31 14:18:41 +02:00
import { AuthHTTP, LiveManager, PeerTubePlugin, PlayerManagerOptions, PlaylistFetcher, PlaylistTracker, VideoFetcher } from './shared'
2022-05-31 08:59:30 +02:00
import { PlayerHTML } from './shared/player-html'
2017-07-23 14:49:52 +02:00
2019-06-11 15:59:10 +02:00
export class PeerTubeEmbed {
2020-04-17 11:20:12 +02:00
player: videojs.Player
2018-07-10 18:02:30 +02:00
api: PeerTubeEmbedApi = null
2020-08-04 11:42:06 +02:00
2021-05-14 12:04:44 +02:00
config: HTMLServerConfig
2020-08-04 11:42:06 +02:00
private translationsPromise: Promise<{ [id: string]: string }>
private PeertubePlayerManagerModulePromise: Promise<any>
2022-05-31 08:59:30 +02:00
private readonly http: AuthHTTP
private readonly videoFetcher: VideoFetcher
private readonly playlistFetcher: PlaylistFetcher
private readonly peertubePlugin: PeerTubePlugin
private readonly playerHTML: PlayerHTML
private readonly playerManagerOptions: PlayerManagerOptions
2022-05-31 14:18:41 +02:00
private readonly liveManager: LiveManager
2020-08-04 11:42:06 +02:00
2022-05-31 08:59:30 +02:00
private playlistTracker: PlaylistTracker
2020-08-04 11:42:06 +02:00
2022-05-31 08:59:30 +02:00
constructor (videoWrapperId: string) {
logger.registerServerSending(window.location.origin)
2022-05-31 08:59:30 +02:00
this.http = new AuthHTTP()
2020-08-20 11:46:25 +02:00
2022-05-31 08:59:30 +02:00
this.videoFetcher = new VideoFetcher(this.http)
this.playlistFetcher = new PlaylistFetcher(this.http)
this.peertubePlugin = new PeerTubePlugin(this.http)
this.playerHTML = new PlayerHTML(videoWrapperId)
this.playerManagerOptions = new PlayerManagerOptions(this.playerHTML, this.videoFetcher, this.peertubePlugin)
2022-05-31 14:18:41 +02:00
this.liveManager = new LiveManager(this.playerHTML)
2021-05-14 12:04:44 +02:00
try {
this.config = JSON.parse(window['PeerTubeServerConfig'])
} catch (err) {
logger.error('Cannot parse HTML config.', err)
2021-05-14 12:04:44 +02:00
}
2018-07-10 18:02:30 +02:00
}
2021-08-17 14:42:53 +02:00
static async main () {
const videoContainerId = 'video-wrapper'
const embed = new PeerTubeEmbed(videoContainerId)
await embed.init()
}
2022-05-31 08:59:30 +02:00
getPlayerElement () {
return this.playerHTML.getPlayerElement()
2020-08-04 11:42:06 +02:00
}
2022-05-31 08:59:30 +02:00
getScope () {
return this.playerManagerOptions.getScope()
}
2018-04-19 18:06:59 +02:00
2022-05-31 08:59:30 +02:00
// ---------------------------------------------------------------------------
2018-07-13 18:21:19 +02:00
2022-05-31 08:59:30 +02:00
async init () {
this.translationsPromise = TranslationsManager.getServerTranslations(window.location.origin, navigator.language)
this.PeertubePlayerManagerModulePromise = import('../../assets/player/peertube-player-manager')
2022-03-04 13:40:02 +01:00
2022-05-31 08:59:30 +02:00
// Issue when we parsed config from HTML, fallback to API
if (!this.config) {
this.config = await this.http.fetch('/api/v1/config', { optionalAuth: false })
.then(res => res.json())
}
2020-08-04 11:42:06 +02:00
2022-05-31 08:59:30 +02:00
const videoId = this.isPlaylistEmbed()
? await this.initPlaylist()
: this.getResourceId()
2020-08-05 13:46:01 +02:00
2022-05-31 08:59:30 +02:00
if (!videoId) return
2020-08-04 11:42:06 +02:00
2022-05-31 08:59:30 +02:00
return this.loadVideoAndBuildPlayer(videoId)
}
2018-04-19 18:06:59 +02:00
2022-05-31 08:59:30 +02:00
private async initPlaylist () {
const playlistId = this.getResourceId()
2022-05-31 08:59:30 +02:00
try {
const res = await this.playlistFetcher.loadPlaylist(playlistId)
2022-05-31 08:59:30 +02:00
const [ playlist, playlistElementResult ] = await Promise.all([
res.playlistResponse.json() as Promise<VideoPlaylist>,
res.videosResponse.json() as Promise<ResultList<VideoPlaylistElement>>
])
2022-05-31 08:59:30 +02:00
const allPlaylistElements = await this.playlistFetcher.loadAllPlaylistVideos(playlistId, playlistElementResult)
2022-05-31 08:59:30 +02:00
this.playlistTracker = new PlaylistTracker(playlist, allPlaylistElements)
2020-08-05 14:00:36 +02:00
2022-05-31 08:59:30 +02:00
const params = new URL(window.location.toString()).searchParams
const playlistPositionParam = getParamString(params, 'playlistPosition')
2022-05-31 08:59:30 +02:00
const position = playlistPositionParam
? parseInt(playlistPositionParam + '', 10)
: 1
2022-05-31 08:59:30 +02:00
this.playlistTracker.setPosition(position)
} catch (err) {
this.playerHTML.displayError(err.message, await this.translationsPromise)
return undefined
}
2020-08-04 11:42:06 +02:00
2022-05-31 08:59:30 +02:00
return this.playlistTracker.getCurrentElement().video.uuid
2020-08-04 11:42:06 +02:00
}
2022-05-31 08:59:30 +02:00
private initializeApi () {
if (this.playerManagerOptions.hasAPIEnabled()) {
this.api = new PeerTubeEmbedApi(this)
this.api.initialize()
}
}
2018-04-19 18:06:59 +02:00
2022-05-31 08:59:30 +02:00
// ---------------------------------------------------------------------------
2018-03-27 10:34:40 +02:00
2022-05-31 08:59:30 +02:00
async playNextPlaylistVideo () {
const next = this.playlistTracker.getNextPlaylistElement()
2020-08-05 14:16:39 +02:00
if (!next) {
logger.info('Next element not found in playlist.')
2020-08-05 14:16:39 +02:00
return
}
2022-05-31 08:59:30 +02:00
this.playlistTracker.setCurrentElement(next)
2020-08-05 14:16:39 +02:00
2022-05-31 08:59:30 +02:00
return this.loadVideoAndBuildPlayer(next.video.uuid)
2020-08-05 14:16:39 +02:00
}
2022-05-31 08:59:30 +02:00
async playPreviousPlaylistVideo () {
const previous = this.playlistTracker.getPreviousPlaylistElement()
2020-08-05 14:16:39 +02:00
if (!previous) {
logger.info('Previous element not found in playlist.')
2020-08-05 14:16:39 +02:00
return
}
2022-05-31 08:59:30 +02:00
this.playlistTracker.setCurrentElement(previous)
2020-08-05 14:16:39 +02:00
2022-05-31 08:59:30 +02:00
await this.loadVideoAndBuildPlayer(previous.video.uuid)
2020-08-05 14:16:39 +02:00
}
2022-05-31 08:59:30 +02:00
getCurrentPlaylistPosition () {
return this.playlistTracker.getCurrentPosition()
2018-07-10 18:02:30 +02:00
}
2022-05-31 08:59:30 +02:00
// ---------------------------------------------------------------------------
2020-08-04 11:42:06 +02:00
2022-05-31 08:59:30 +02:00
private async loadVideoAndBuildPlayer (uuid: string) {
try {
2022-05-31 08:59:30 +02:00
const { videoResponse, captionsPromise } = await this.videoFetcher.loadVideo(uuid)
2020-08-04 11:42:06 +02:00
2022-05-31 08:59:30 +02:00
return this.buildVideoPlayer(videoResponse, captionsPromise)
} catch (err) {
2022-05-31 08:59:30 +02:00
this.playerHTML.displayError(err.message, await this.translationsPromise)
}
2020-08-05 11:02:14 +02:00
}
2020-08-04 11:42:06 +02:00
private async buildVideoPlayer (videoResponse: Response, captionsPromise: Promise<Response>) {
2022-05-31 08:59:30 +02:00
const alreadyHadPlayer = this.resetPlayerElement()
2021-05-14 12:04:44 +02:00
2022-03-04 13:40:02 +01:00
const videoInfoPromise: Promise<{ video: VideoDetails, live?: LiveVideo }> = videoResponse.json()
2020-08-04 11:42:06 +02:00
.then((videoInfo: VideoDetails) => {
2022-05-31 08:59:30 +02:00
this.playerManagerOptions.loadParams(this.config, videoInfo)
2021-08-02 11:46:11 +02:00
2022-05-31 08:59:30 +02:00
if (!alreadyHadPlayer && !this.playerManagerOptions.hasAutoplay()) {
this.playerHTML.buildPlaceholder(videoInfo)
}
2020-08-04 11:42:06 +02:00
2022-05-31 08:59:30 +02:00
if (!videoInfo.isLive) {
return { video: videoInfo }
}
2022-03-04 13:40:02 +01:00
2022-05-31 08:59:30 +02:00
return this.videoFetcher.loadVideoWithLive(videoInfo)
2020-08-04 11:42:06 +02:00
})
2022-05-31 08:59:30 +02:00
const [ { video, live }, translations, captionsResponse, PeertubePlayerManagerModule ] = await Promise.all([
2020-08-04 11:42:06 +02:00
videoInfoPromise,
this.translationsPromise,
captionsPromise,
this.PeertubePlayerManagerModulePromise
])
2019-12-17 11:20:24 +01:00
2022-05-31 08:59:30 +02:00
await this.peertubePlugin.loadPlugins(this.config, translations)
2022-05-31 08:59:30 +02:00
const PlayerManager: typeof PeertubePlayerManager = PeertubePlayerManagerModule.PeertubePlayerManager
2022-05-31 08:59:30 +02:00
const options = await this.playerManagerOptions.getPlayerOptions({
video,
captionsResponse,
alreadyHadPlayer,
translations,
2022-06-28 14:04:03 +02:00
serverConfig: this.config,
2022-05-31 08:59:30 +02:00
onVideoUpdate: (uuid: string) => this.loadVideoAndBuildPlayer(uuid),
2022-05-31 08:59:30 +02:00
playlistTracker: this.playlistTracker,
playNextPlaylistVideo: () => this.playNextPlaylistVideo(),
playPreviousPlaylistVideo: () => this.playPreviousPlaylistVideo(),
2022-05-31 08:59:30 +02:00
live
})
2017-07-23 14:49:52 +02:00
2022-05-31 08:59:30 +02:00
this.player = await PlayerManager.initialize(this.playerManagerOptions.getMode(), options, (player: videojs.Player) => {
2021-08-17 14:42:53 +02:00
this.player = player
})
2022-05-31 08:59:30 +02:00
this.player.on('customError', (event: any, data: any) => {
const message = data?.err?.message || ''
if (!message.includes('from xs param')) return
this.player.dispose()
this.playerHTML.removePlayerElement()
this.playerHTML.displayError('This video is not available because the remote instance is not responding.', translations)
})
2021-08-17 14:42:53 +02:00
window['videojsPlayer'] = this.player
2018-07-10 18:02:30 +02:00
2019-06-11 15:59:10 +02:00
this.buildCSS()
2022-05-31 08:59:30 +02:00
this.buildPlayerDock(video)
2019-06-11 15:59:10 +02:00
this.initializeApi()
2019-12-17 11:20:24 +01:00
2022-05-31 08:59:30 +02:00
this.playerHTML.removePlaceholder()
2020-08-04 11:42:06 +02:00
if (this.isPlaylistEmbed()) {
2022-05-31 08:59:30 +02:00
await this.buildPlayerPlaylistUpnext()
2020-08-05 09:44:58 +02:00
this.player.playlist().updateSelected()
this.player.on('stopped', () => {
2022-05-31 08:59:30 +02:00
this.playNextPlaylistVideo()
})
2020-08-04 11:42:06 +02:00
}
2020-08-20 11:46:25 +02:00
2022-05-31 08:59:30 +02:00
this.peertubePlugin.getPluginsManager().runHook('action:embed.player.loaded', undefined, { player: this.player, videojs, video })
2022-05-31 14:18:41 +02:00
if (video.isLive) {
this.liveManager.displayInfoAndListenForChanges({
video,
translations,
onPublishedVideo: () => {
this.liveManager.stopListeningForChanges(video)
this.loadVideoAndBuildPlayer(video.uuid)
}
})
}
2020-08-04 11:42:06 +02:00
}
2022-05-31 08:59:30 +02:00
private resetPlayerElement () {
let alreadyHadPlayer = false
2020-08-04 11:42:06 +02:00
2022-05-31 08:59:30 +02:00
if (this.player) {
this.player.dispose()
alreadyHadPlayer = true
}
2020-08-04 11:42:06 +02:00
2022-05-31 08:59:30 +02:00
const playerElement = document.createElement('video')
playerElement.className = 'video-js vjs-peertube-skin'
playerElement.setAttribute('playsinline', 'true')
2020-08-04 11:42:06 +02:00
2022-05-31 08:59:30 +02:00
this.playerHTML.setPlayerElement(playerElement)
this.playerHTML.addPlayerElementToDOM()
2020-08-04 11:42:06 +02:00
2022-05-31 08:59:30 +02:00
return alreadyHadPlayer
2019-06-11 15:59:10 +02:00
}
2022-05-31 08:59:30 +02:00
private async buildPlayerPlaylistUpnext () {
const translations = await this.translationsPromise
this.player.upnext({
timeout: 10000, // 10s
headText: peertubeTranslate('Up Next', translations),
cancelText: peertubeTranslate('Cancel', translations),
suspendedText: peertubeTranslate('Autoplay is suspended', translations),
getTitle: () => this.playlistTracker.nextVideoTitle(),
next: () => this.playNextPlaylistVideo(),
condition: () => !!this.playlistTracker.getNextPlaylistElement(),
suspended: () => false
})
2019-06-11 15:59:10 +02:00
}
2022-05-31 08:59:30 +02:00
private buildPlayerDock (videoInfo: VideoDetails) {
if (!this.playerManagerOptions.hasControls()) return
2019-06-11 15:59:10 +02:00
// On webtorrent fallback, player may have been disposed
if (!this.player.player_) return
2019-06-11 15:59:10 +02:00
2022-05-31 08:59:30 +02:00
const title = this.playerManagerOptions.hasTitle()
? videoInfo.name
: undefined
const description = this.playerManagerOptions.hasWarningTitle() && this.playerManagerOptions.hasP2PEnabled()
2020-02-03 13:33:42 +01:00
? '<span class="text">' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '</span>'
: undefined
2022-05-31 08:59:30 +02:00
if (!title && !description) return
2022-03-08 16:26:30 +01:00
const availableAvatars = videoInfo.channel.avatars.filter(a => a.width < 50)
const avatar = availableAvatars.length !== 0
? availableAvatars[0]
: undefined
2022-05-31 08:59:30 +02:00
this.player.peertubeDock({
title,
description,
avatarUrl: title && avatar
? avatar.path
: undefined
})
2019-06-11 15:59:10 +02:00
}
2018-07-13 18:21:19 +02:00
2019-06-11 15:59:10 +02:00
private buildCSS () {
const body = document.getElementById('custom-css')
2022-05-31 08:59:30 +02:00
if (this.playerManagerOptions.hasBigPlayBackgroundColor()) {
body.style.setProperty('--embedBigPlayBackgroundColor', this.playerManagerOptions.getBigPlayBackgroundColor())
2019-06-11 15:59:10 +02:00
}
2022-05-31 08:59:30 +02:00
if (this.playerManagerOptions.hasForegroundColor()) {
body.style.setProperty('--embedForegroundColor', this.playerManagerOptions.getForegroundColor())
2019-06-11 15:59:10 +02:00
}
}
2022-05-31 08:59:30 +02:00
// ---------------------------------------------------------------------------
2020-08-04 11:42:06 +02:00
private getResourceId () {
const urlParts = window.location.pathname.split('/')
2021-08-17 14:42:53 +02:00
return urlParts[urlParts.length - 1]
2020-08-04 11:42:06 +02:00
}
private isPlaylistEmbed () {
return window.location.pathname.split('/')[1] === 'video-playlists'
}
}
PeerTubeEmbed.main()
.catch(err => {
(window as any).displayIncompatibleBrowser()
logger.error('Cannot init embed.', err)
})