From b13460a10aea1c8e00d24b8b1e07631be209d69a Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 23 Nov 2023 08:14:54 +0100 Subject: [PATCH] Add ability to set password from embed API --- client/src/assets/player/utils.ts | 15 ----- .../src/standalone/embed-player-api/player.ts | 9 +++ client/src/standalone/videos/embed-api.ts | 14 +++-- client/src/standalone/videos/embed.ts | 60 ++++++++++++++++--- .../videos/shared/player-options-builder.ts | 32 +++++++--- .../standalone/videos/shared/video-fetcher.ts | 2 +- client/src/standalone/videos/test-embed.ts | 16 +++-- support/doc/api/embeds.md | 24 ++++++++ 8 files changed, 127 insertions(+), 45 deletions(-) diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts index b56e3df8f..1926d45c0 100644 --- a/client/src/assets/player/utils.ts +++ b/client/src/assets/player/utils.ts @@ -49,20 +49,6 @@ function isMobile () { return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) } -function buildVideoOrPlaylistEmbed (embedUrl: string, embedTitle: string) { - const iframe = document.createElement('iframe') - - iframe.title = embedTitle - iframe.width = '560' - iframe.height = '315' - iframe.src = embedUrl - iframe.frameBorder = '0' - iframe.allowFullscreen = true - iframe.sandbox.add('allow-same-origin', 'allow-scripts', 'allow-popups') - - return iframe.outerHTML -} - function videoFileMaxByResolution (files: VideoFile[]) { let max = files[0] @@ -106,7 +92,6 @@ export { isWebRTCDisabled, isP2PEnabled, - buildVideoOrPlaylistEmbed, videoFileMaxByResolution, videoFileMinByResolution, isMobile, diff --git a/client/src/standalone/embed-player-api/player.ts b/client/src/standalone/embed-player-api/player.ts index 75487258b..803a7fdce 100644 --- a/client/src/standalone/embed-player-api/player.ts +++ b/client/src/standalone/embed-player-api/player.ts @@ -195,6 +195,15 @@ export class PeerTubePlayer { return this.sendMessage('getCurrentPosition') } + /** + * Set video password to so the user doesn't have to manually fill it + * + * @param password + */ + async setVideoPassword (password: string): Promise { + await this.sendMessage('setVideoPassword', password) + } + private constructChannel () { this.channel = Channel.build({ window: this.embedElement.contentWindow, diff --git a/client/src/standalone/videos/embed-api.ts b/client/src/standalone/videos/embed-api.ts index 6227c378e..0e8489b6d 100644 --- a/client/src/standalone/videos/embed-api.ts +++ b/client/src/standalone/videos/embed-api.ts @@ -25,16 +25,15 @@ export class PeerTubeEmbedApi { initialize () { this.constructChannel() - this.setupStateTracking() - - // We're ready! - - this.notifyReady() } - reInit () { + initWithVideo () { this.disposeStateTracking() this.setupStateTracking() + + if (!this.isReady) { + this.notifyReady() + } } private get element () { @@ -44,6 +43,8 @@ export class PeerTubeEmbedApi { private constructChannel () { const channel = Channel.build({ window: window.parent, origin: '*', scope: this.embed.getScope() }) + channel.bind('setVideoPassword', (txn, value) => this.embed.setVideoPasswordByAPI(value)) + channel.bind('play', (txn, params) => this.embed.player.play()) channel.bind('pause', (txn, params) => this.embed.player.pause()) channel.bind('seek', (txn, time) => this.embed.player.currentTime(time)) @@ -66,6 +67,7 @@ export class PeerTubeEmbedApi { channel.bind('playNextVideo', (txn, params) => this.embed.playNextPlaylistVideo()) channel.bind('playPreviousVideo', (txn, params) => this.embed.playPreviousPlaylistVideo()) channel.bind('getCurrentPosition', (txn, params) => this.embed.getCurrentPlaylistPosition()) + this.channel = channel } diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index 78c5e5592..b61a665e3 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts @@ -53,6 +53,8 @@ export class PeerTubeEmbed { private alreadyPlayed = false private videoPassword: string + private videoPasswordFromAPI: string + private onVideoPasswordFromAPIResolver: (value: string) => void private requiresPassword: boolean constructor (videoWrapperId: string) { @@ -142,17 +144,33 @@ export class PeerTubeEmbed { } private initializeApi () { - if (this.playerOptionsBuilder.hasAPIEnabled()) { - if (this.api) { - this.api.reInit() - return - } + if (!this.playerOptionsBuilder.hasAPIEnabled()) return + if (this.api) return - this.api = new PeerTubeEmbedApi(this) - this.api.initialize() + this.api = new PeerTubeEmbedApi(this) + this.api.initialize() + } + + // --------------------------------------------------------------------------- + + setVideoPasswordByAPI (password: string) { + logger.info('Setting password from API') + + this.videoPasswordFromAPI = password + + if (this.onVideoPasswordFromAPIResolver) { + this.onVideoPasswordFromAPIResolver(password) } } + private getPasswordByAPI () { + if (this.videoPasswordFromAPI) return Promise.resolve(this.videoPasswordFromAPI) + + return new Promise(res => { + this.onVideoPasswordFromAPIResolver = res + }) + } + // --------------------------------------------------------------------------- async playNextPlaylistVideo () { @@ -191,6 +209,9 @@ export class PeerTubeEmbed { }) { const { uuid, forceAutoplay } = options + this.playerOptionsBuilder.loadCommonParams() + this.initializeApi() + try { const { videoResponse, @@ -218,7 +239,7 @@ export class PeerTubeEmbed { const videoInfoPromise = videoResponse.json() .then(async (videoInfo: VideoDetails) => { - this.playerOptionsBuilder.loadParams(this.config, videoInfo) + this.playerOptionsBuilder.loadVideoParams(this.config, videoInfo) const live = videoInfo.isLive ? await this.videoFetcher.loadLive(videoInfo) @@ -287,7 +308,8 @@ export class PeerTubeEmbed { (window as any)['videojsPlayer'] = this.player this.buildCSS() - this.initializeApi() + + if (this.api) this.api.initWithVideo() } this.alreadyInitialized = true @@ -360,10 +382,30 @@ export class PeerTubeEmbed { if (incorrectPassword === null) return false this.requiresPassword = true + + if (this.playerOptionsBuilder.mustWaitPasswordFromEmbedAPI()) { + logger.info('Waiting for password from Embed API') + + const videoPasswordFromAPI = await this.getPasswordByAPI() + + if (videoPasswordFromAPI && this.videoPassword !== videoPasswordFromAPI) { + logger.info('Using video password from API') + + this.videoPassword = videoPasswordFromAPI + + return true + } + + logger.error('Password from embed API is not valid') + + return false + } + this.videoPassword = await this.playerHTML.askVideoPassword({ incorrectPassword, translations: await this.translationsPromise }) + return true } diff --git a/client/src/standalone/videos/shared/player-options-builder.ts b/client/src/standalone/videos/shared/player-options-builder.ts index dec859409..a38895cd6 100644 --- a/client/src/standalone/videos/shared/player-options-builder.ts +++ b/client/src/standalone/videos/shared/player-options-builder.ts @@ -49,6 +49,8 @@ export class PlayerOptionsBuilder { private bigPlayBackgroundColor: string private foregroundColor: string + private waitPasswordFromEmbedAPI = false + private mode: PlayerMode private scope = 'peertube' @@ -106,18 +108,16 @@ export class PlayerOptionsBuilder { return this.scope } + mustWaitPasswordFromEmbedAPI () { + return this.waitPasswordFromEmbedAPI + } + // --------------------------------------------------------------------------- - loadParams (config: HTMLServerConfig, video: VideoDetails) { + loadCommonParams () { 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) @@ -125,9 +125,9 @@ export class PlayerOptionsBuilder { this.loop = getParamToggle(params, 'loop', false) this.title = getParamToggle(params, 'title', true) this.enableApi = getParamToggle(params, 'api', this.enableApi) + this.waitPasswordFromEmbedAPI = getParamToggle(params, 'waitPasswordFromEmbedAPI', this.waitPasswordFromEmbedAPI) 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') @@ -137,6 +137,22 @@ export class PlayerOptionsBuilder { this.bigPlayBackgroundColor = getParamString(params, 'bigPlayBackgroundColor') this.foregroundColor = getParamString(params, 'foregroundColor') + } catch (err) { + logger.error('Cannot get params from URL.', err) + } + } + + loadVideoParams (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.p2pEnabled = getParamToggle(params, 'p2p', this.isP2PEnabled(config, video)) const modeParam = getParamString(params, 'mode') diff --git a/client/src/standalone/videos/shared/video-fetcher.ts b/client/src/standalone/videos/shared/video-fetcher.ts index c52861189..c85d2bce4 100644 --- a/client/src/standalone/videos/shared/video-fetcher.ts +++ b/client/src/standalone/videos/shared/video-fetcher.ts @@ -30,7 +30,7 @@ export class VideoFetcher { } if (videoResponse?.status === HttpStatusCode.FORBIDDEN_403) { const res = await videoResponse.json() - throw new PeerTubeServerError(res.message, res.code) + throw new PeerTubeServerError(res.message || res.detail, res.code) } throw new Error('We cannot fetch the video. Please try again later.') } diff --git a/client/src/standalone/videos/test-embed.ts b/client/src/standalone/videos/test-embed.ts index b7a283c4d..70376c383 100644 --- a/client/src/standalone/videos/test-embed.ts +++ b/client/src/standalone/videos/test-embed.ts @@ -14,9 +14,9 @@ window.addEventListener('load', async () => { const iframe = document.createElement('iframe') iframe.src = isPlaylist ? `/video-playlists/embed/${elementId}?api=1` - : `/videos/embed/${elementId}?api=1` + : `/videos/embed/${elementId}?api=1&waitPasswordFromEmbedAPI=1` - iframe.sandbox.add('allow-same-origin', 'allow-scripts', 'allow-popups') + iframe.sandbox.add('allow-same-origin', 'allow-scripts', 'allow-popups', 'allow-forms') const mainElement = document.querySelector('#host') mainElement.appendChild(iframe) @@ -27,6 +27,8 @@ window.addEventListener('load', async () => { (window as any)['player'] = player logger.info('Awaiting player ready...') + await player.setVideoPassword('toto') + await player.ready logger.info('Player is ready.') @@ -41,10 +43,12 @@ window.addEventListener('load', async () => { player.addEventListener(e as PlayerEventType, (param) => logger.info(`PLAYER: event '${e}' received`, { param })) logger.info(`PLAYER: now listening for event '${e}'`) - player.getCurrentPosition() - .then(position => { - document.getElementById('playlist-position').innerHTML = position + '' - }) + if (isPlaylist) { + player.getCurrentPosition() + .then(position => { + document.getElementById('playlist-position').innerHTML = position + '' + }) + } }) let playbackRates: number[] = [] diff --git a/support/doc/api/embeds.md b/support/doc/api/embeds.md index 989c8e98e..7721c3862 100644 --- a/support/doc/api/embeds.md +++ b/support/doc/api/embeds.md @@ -154,6 +154,21 @@ Enable embed JavaScript API (see methods below). Value must be `0` or `1`. +### waitPasswordFromEmbedAPI + +**PeerTube >= 6.0** + +If the video requires a password, PeerTube will wait a password provided by `setVideoPassword` method before loading the video. + +Until you provide a password, `player.ready` is not resolved. + + +## Embed attributes + +### `ready: Promise` + +This promise is resolved when the video is loaded an the player is ready. + ## Embed methods @@ -237,6 +252,15 @@ Play previous video in playlist. Get current position in playlist (starts from 1). + +### `setVideoPassword(): Promise` + +**PeerTube >= 6.0** + +Set the video password so the user doesn't have to manually fill it. +`waitPasswordFromEmbedAPI=1` is required in embed URL. + + ## Embed events You can subscribe to events by using `addEventListener()`. See above for details.