From 3f9c4955af81702591a6eeb2069f99faf0d2814d Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 17 Dec 2019 11:20:24 +0100 Subject: [PATCH] Speedup embed first paint --- .../comment/video-comment-add.component.ts | 8 +-- .../assets/player/peertube-player-manager.ts | 54 ++----------------- .../src/assets/player/translations-manager.ts | 52 ++++++++++++++++++ client/src/standalone/videos/embed-api.ts | 1 - client/src/standalone/videos/embed.html | 2 + client/src/standalone/videos/embed.scss | 10 ++++ client/src/standalone/videos/embed.ts | 54 +++++++++++++++---- scripts/client-report.sh | 2 +- 8 files changed, 117 insertions(+), 66 deletions(-) create mode 100644 client/src/assets/player/translations-manager.ts diff --git a/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts b/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts index 083509b83..1be96ad9e 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts @@ -137,6 +137,10 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { this.router.navigate([ '/login' ]) } + cancelCommentReply () { + this.cancel.emit(null) + } + private addCommentReply (commentCreate: VideoCommentCreate) { return this.videoCommentService .addCommentReply(this.video.id, this.parentComment.id, commentCreate) @@ -146,8 +150,4 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { return this.videoCommentService .addCommentThread(this.video.id, commentCreate) } - - private cancelCommentReply () { - this.cancel.emit(null) - } } diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts index 2f4e0ac1a..b1551185a 100644 --- a/client/src/assets/player/peertube-player-manager.ts +++ b/client/src/assets/player/peertube-player-manager.ts @@ -15,11 +15,12 @@ import './videojs-components/peertube-load-progress-bar' import './videojs-components/theater-button' import { P2PMediaLoaderPluginOptions, UserWatching, VideoJSCaption, VideoJSPluginOptions, videojsUntyped } from './peertube-videojs-typings' import { buildVideoEmbed, buildVideoLink, copyToClipboard, getRtcConfig } from './utils' -import { getCompleteLocale, getShortLocale, is18nLocale, isDefaultLocale } from '../../../../shared/models/i18n/i18n' +import { isDefaultLocale } from '../../../../shared/models/i18n/i18n' import { segmentValidatorFactory } from './p2p-media-loader/segment-validator' import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder' import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager' import { getStoredP2PEnabled } from './peertube-player-local-storage' +import { TranslationsManager } from './translations-manager' // Change 'Playback Rate' to 'Speed' (smaller for our settings menu) videojsUntyped.getComponent('PlaybackRateMenuButton').prototype.controlText_ = 'Speed' @@ -86,24 +87,9 @@ export type PeertubePlayerManagerOptions = { } export class PeertubePlayerManager { - - private static videojsLocaleCache: { [ path: string ]: any } = {} private static playerElementClassName: string private static onPlayerChange: (player: any) => void - static getServerTranslations (serverUrl: string, locale: string) { - const path = PeertubePlayerManager.getLocalePath(serverUrl, locale) - // It is the default locale, nothing to translate - if (!path) return Promise.resolve(undefined) - - return fetch(path + '/server.json') - .then(res => res.json()) - .catch(err => { - console.error('Cannot get server translations', err) - return undefined - }) - } - static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions, onPlayerChange: (player: any) => void) { let p2pMediaLoader: any @@ -120,7 +106,7 @@ export class PeertubePlayerManager { const videojsOptions = this.getVideojsOptions(mode, options, p2pMediaLoader) - await this.loadLocaleInVideoJS(options.common.serverUrl, options.common.language) + await TranslationsManager.loadLocaleInVideoJS(options.common.serverUrl, options.common.language, videojs) const self = this return new Promise(res => { @@ -181,32 +167,6 @@ export class PeertubePlayerManager { }) } - private static loadLocaleInVideoJS (serverUrl: string, locale: string) { - const path = PeertubePlayerManager.getLocalePath(serverUrl, locale) - // It is the default locale, nothing to translate - if (!path) return Promise.resolve(undefined) - - let p: Promise - - if (PeertubePlayerManager.videojsLocaleCache[path]) { - p = Promise.resolve(PeertubePlayerManager.videojsLocaleCache[path]) - } else { - p = fetch(path + '/player.json') - .then(res => res.json()) - .then(json => { - PeertubePlayerManager.videojsLocaleCache[path] = json - return json - }) - .catch(err => { - console.error('Cannot get player translations', err) - return undefined - }) - } - - const completeLocale = getCompleteLocale(locale) - return p.then(json => videojs.addLanguage(getShortLocale(completeLocale), json)) - } - private static getVideojsOptions (mode: PlayerMode, options: PeertubePlayerManagerOptions, p2pMediaLoaderModule?: any) { const commonOptions = options.common @@ -519,14 +479,6 @@ export class PeertubePlayerManager { } }) } - - private static getLocalePath (serverUrl: string, locale: string) { - const completeLocale = getCompleteLocale(locale) - - if (!is18nLocale(completeLocale) || isDefaultLocale(completeLocale)) return undefined - - return serverUrl + '/client/locales/' + completeLocale - } } // ############################################################################ diff --git a/client/src/assets/player/translations-manager.ts b/client/src/assets/player/translations-manager.ts new file mode 100644 index 000000000..e9f300ce7 --- /dev/null +++ b/client/src/assets/player/translations-manager.ts @@ -0,0 +1,52 @@ +import { getCompleteLocale, getShortLocale, is18nLocale, isDefaultLocale } from '../../../../shared/models' + +export class TranslationsManager { + private static videojsLocaleCache: { [ path: string ]: any } = {} + + static getServerTranslations (serverUrl: string, locale: string) { + const path = TranslationsManager.getLocalePath(serverUrl, locale) + // It is the default locale, nothing to translate + if (!path) return Promise.resolve(undefined) + + return fetch(path + '/server.json') + .then(res => res.json()) + .catch(err => { + console.error('Cannot get server translations', err) + return undefined + }) + } + + static loadLocaleInVideoJS (serverUrl: string, locale: string, videojs: any) { + const path = TranslationsManager.getLocalePath(serverUrl, locale) + // It is the default locale, nothing to translate + if (!path) return Promise.resolve(undefined) + + let p: Promise + + if (TranslationsManager.videojsLocaleCache[ path ]) { + p = Promise.resolve(TranslationsManager.videojsLocaleCache[ path ]) + } else { + p = fetch(path + '/player.json') + .then(res => res.json()) + .then(json => { + TranslationsManager.videojsLocaleCache[ path ] = json + return json + }) + .catch(err => { + console.error('Cannot get player translations', err) + return undefined + }) + } + + const completeLocale = getCompleteLocale(locale) + return p.then(json => videojs.addLanguage(getShortLocale(completeLocale), json)) + } + + private static getLocalePath (serverUrl: string, locale: string) { + const completeLocale = getCompleteLocale(locale) + + if (!is18nLocale(completeLocale) || isDefaultLocale(completeLocale)) return undefined + + return serverUrl + '/client/locales/' + completeLocale + } +} diff --git a/client/src/standalone/videos/embed-api.ts b/client/src/standalone/videos/embed-api.ts index 169e371da..259113215 100644 --- a/client/src/standalone/videos/embed-api.ts +++ b/client/src/standalone/videos/embed-api.ts @@ -43,7 +43,6 @@ export class PeerTubeEmbedApi { channel.bind('setPlaybackRate', (txn, playbackRate) => this.embed.player.playbackRate(playbackRate)) channel.bind('getPlaybackRate', (txn, params) => this.embed.player.playbackRate()) channel.bind('getPlaybackRates', (txn, params) => this.embed.playerOptions.playbackRates) - this.channel = channel } diff --git a/client/src/standalone/videos/embed.html b/client/src/standalone/videos/embed.html index 5a15bf552..6edf71f48 100644 --- a/client/src/standalone/videos/embed.html +++ b/client/src/standalone/videos/embed.html @@ -22,5 +22,7 @@ +
+ diff --git a/client/src/standalone/videos/embed.scss b/client/src/standalone/videos/embed.scss index c40ea1208..95573dabe 100644 --- a/client/src/standalone/videos/embed.scss +++ b/client/src/standalone/videos/embed.scss @@ -79,6 +79,16 @@ html, body { } } +#placeholder-preview { + position: absolute; + top: 0; + left: 0; + background-size: 100% auto; + width: 100%; + height: 100%; + background-position: 50% 50%; +} + @media screen and (max-width: 300px) { #error-block { font-size: 36px; diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index bd012f506..f33dd8869 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts @@ -1,16 +1,24 @@ import './embed.scss' -import { peertubeTranslate, ResultList, ServerConfig, VideoDetails } from '../../../../shared' +import { + getCompleteLocale, + is18nLocale, + isDefaultLocale, + peertubeTranslate, + ResultList, + ServerConfig, + VideoDetails +} from '../../../../shared' import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' import { P2PMediaLoaderOptions, - PeertubePlayerManager, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager' import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' import { PeerTubeEmbedApi } from './embed-api' +import { TranslationsManager } from '../../assets/player/translations-manager' export class PeerTubeEmbed { videoElement: HTMLVideoElement @@ -154,20 +162,30 @@ export class PeerTubeEmbed { const urlParts = window.location.pathname.split('/') const videoId = urlParts[ urlParts.length - 1 ] - const [ serverTranslations, videoResponse, captionsResponse, configResponse ] = await Promise.all([ - PeertubePlayerManager.getServerTranslations(window.location.origin, navigator.language), - this.loadVideoInfo(videoId), - this.loadVideoCaptions(videoId), - this.loadConfig() - ]) + const videoPromise = this.loadVideoInfo(videoId) + const captionsPromise = this.loadVideoCaptions(videoId) + const configPromise = this.loadConfig() + + const translationsPromise = TranslationsManager.getServerTranslations(window.location.origin, navigator.language) + const videoResponse = await videoPromise if (!videoResponse.ok) { + const serverTranslations = await translationsPromise + if (videoResponse.status === 404) return this.videoNotFound(serverTranslations) return this.videoFetchError(serverTranslations) } const videoInfo: VideoDetails = await videoResponse.json() + this.loadPlaceholder(videoInfo) + + const PeertubePlayerManagerModulePromise = import('../../assets/player/peertube-player-manager') + + const promises = [ translationsPromise, captionsPromise, configPromise, PeertubePlayerManagerModulePromise ] + const [ serverTranslations, captionsResponse, configResponse, PeertubePlayerManagerModule ] = await Promise.all(promises) + + const PeertubePlayerManager = PeertubePlayerManagerModule.PeertubePlayerManager const videoCaptions = await this.buildCaptions(serverTranslations, captionsResponse) this.loadParams(videoInfo) @@ -220,7 +238,7 @@ export class PeerTubeEmbed { }) } - this.player = await PeertubePlayerManager.initialize(this.mode, options, player => this.player = player) + this.player = await PeertubePlayerManager.initialize(this.mode, options, (player: any) => this.player = player) this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations)) window[ 'videojsPlayer' ] = this.player @@ -230,6 +248,8 @@ export class PeerTubeEmbed { await this.buildDock(videoInfo, configResponse) this.initializeApi() + + this.removePlaceholder() } private handleError (err: Error, translations?: { [ id: string ]: string }) { @@ -282,6 +302,22 @@ export class PeerTubeEmbed { return [] } + + private loadPlaceholder (video: VideoDetails) { + const placeholder = this.getPlaceholderElement() + + const url = window.location.origin + video.previewPath + placeholder.style.backgroundImage = `url("${url}")` + } + + private removePlaceholder () { + const placeholder = this.getPlaceholderElement() + placeholder.parentElement.removeChild(placeholder) + } + + private getPlaceholderElement () { + return document.getElementById('placeholder-preview') + } } PeerTubeEmbed.main() diff --git a/scripts/client-report.sh b/scripts/client-report.sh index a758a211c..76609686b 100755 --- a/scripts/client-report.sh +++ b/scripts/client-report.sh @@ -5,5 +5,5 @@ set -eu gawk -i inplace 'BEGIN { found=0 } { if (found || $0 ~ /^{/) { found=1; print }}' ./client/dist/embed-stats.json npm run concurrently -- -k \ - "cd client && npm run webpack-bundle-analyzer -- -p 8888 ./dist/en_US/stats-es2015.json" \ + "cd client && npm run webpack-bundle-analyzer -- -p 8888 ./dist/en-US/stats-es2015.json" \ "cd client && npm run webpack-bundle-analyzer -- -p 8889 ./dist/embed-stats.json"