diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss index d1f840937..00e776a69 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ b/client/src/app/videos/+video-watch/video-watch.component.scss @@ -21,6 +21,11 @@ position: relative !important; } } + + /deep/ .video-js.vjs-theater-enabled { + width: 100%; + height: calc(100vh - #{$header-height} - #{$theater-bottom-space}); + } } #video-not-found { diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index 0f4f5ce89..eefa43a73 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts @@ -368,7 +368,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { enableHotkeys: true, peertubeLink: false, poster: this.video.previewUrl, - startTime + startTime, + theaterMode: true }) if (this.videojsLocaleLoaded === false) { diff --git a/client/src/assets/player/images/theater.svg b/client/src/assets/player/images/theater.svg new file mode 100644 index 000000000..d7086c214 --- /dev/null +++ b/client/src/assets/player/images/theater.svg @@ -0,0 +1,13 @@ + + + + theater + + + + + + + + + diff --git a/client/src/assets/player/peertube-player.ts b/client/src/assets/player/peertube-player.ts index eb75091de..afc8e0881 100644 --- a/client/src/assets/player/peertube-player.ts +++ b/client/src/assets/player/peertube-player.ts @@ -10,6 +10,7 @@ import './settings-menu-button' import './webtorrent-info-button' import './peertube-videojs-plugin' import './peertube-load-progress-bar' +import './theater-button' import { videojsUntyped } from './peertube-videojs-typings' import { buildVideoEmbed, buildVideoLink, copyToClipboard } from './utils' import { getCompleteLocale, getShortLocale, is18nLocale, isDefaultLocale } from '../../../../shared/models/i18n/i18n' @@ -28,6 +29,7 @@ function getVideojsOptions (options: { peertubeLink: boolean, poster: string, startTime: number + theaterMode: boolean }) { const videojsOptions = { controls: true, @@ -63,6 +65,7 @@ function getVideojsOptions (options: { function getControlBarChildren (options: { peertubeLink: boolean + theaterMode: boolean }) { const children = { 'playToggle': {}, @@ -105,6 +108,12 @@ function getControlBarChildren (options: { }) } + if (options.theaterMode === true) { + Object.assign(children, { + 'theaterButton': {} + }) + } + Object.assign(children, { 'fullscreenToggle': {} }) diff --git a/client/src/assets/player/peertube-videojs-plugin.ts b/client/src/assets/player/peertube-videojs-plugin.ts index 47288c842..d3ae7b137 100644 --- a/client/src/assets/player/peertube-videojs-plugin.ts +++ b/client/src/assets/player/peertube-videojs-plugin.ts @@ -55,6 +55,7 @@ class PeerTubePlugin extends Plugin { private player: any private currentVideoFile: VideoFile private torrent: WebTorrent.Torrent + private fakeRenderer private autoResolution = true private isAutoResolutionObservation = false @@ -123,6 +124,8 @@ class PeerTubePlugin extends Plugin { // Don't need to destroy renderer, video player will be destroyed this.flushVideoFile(this.currentVideoFile, false) + + this.destroyFakeRenderer() } getCurrentResolutionId () { @@ -185,7 +188,6 @@ class PeerTubePlugin extends Plugin { console.log('Adding ' + magnetOrTorrentUrl + '.') const oldTorrent = this.torrent - let fakeRenderer const torrentOptions = { store: (chunkLength, storeOpts) => new CacheChunkStore(new PeertubeChunkStore(chunkLength, storeOpts), { max: 100 @@ -205,7 +207,7 @@ class PeerTubePlugin extends Plugin { if (options.delay) { const fakeVideoElem = document.createElement('video') renderVideo(torrent.files[0], fakeVideoElem, { autoplay: false, controls: false }, (err, renderer) => { - fakeRenderer = renderer + this.fakeRenderer = renderer if (err) console.error('Cannot render new torrent in fake video element.', err) @@ -217,16 +219,7 @@ class PeerTubePlugin extends Plugin { // Render the video in a few seconds? (on resolution change for example, we wait some seconds of the new video resolution) this.addTorrentDelay = setTimeout(() => { - if (fakeRenderer) { - if (fakeRenderer.destroy) { - try { - fakeRenderer.destroy() - } catch (err) { - console.log('Cannot destroy correctly fake renderer.', err) - } - } - fakeRenderer = undefined - } + this.destroyFakeRenderer() const paused = this.player.paused() @@ -567,6 +560,19 @@ class PeerTubePlugin extends Plugin { return this.videoFiles[Math.floor(this.videoFiles.length / 2)] } + private destroyFakeRenderer () { + if (this.fakeRenderer) { + if (this.fakeRenderer.destroy) { + try { + this.fakeRenderer.destroy() + } catch (err) { + console.log('Cannot destroy correctly fake renderer.', err) + } + } + this.fakeRenderer = undefined + } + } + // Thanks: https://github.com/videojs/video.js/issues/4460#issuecomment-312861657 private initSmoothProgressBar () { const SeekBar = videojsUntyped.getComponent('SeekBar') diff --git a/client/src/assets/player/theater-button.ts b/client/src/assets/player/theater-button.ts new file mode 100644 index 000000000..1d330e08f --- /dev/null +++ b/client/src/assets/player/theater-button.ts @@ -0,0 +1,46 @@ +import { VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings' +import { getStoredTheater, saveTheaterInStore } from './utils' + +const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button') +class TheaterButton extends Button { + + private static readonly THEATER_MODE_CLASS = 'vjs-theater-enabled' + + constructor (player, options) { + super(player, options) + + const enabled = getStoredTheater() + if (enabled === true) { + this.player_.addClass(TheaterButton.THEATER_MODE_CLASS) + this.handleTheaterChange() + } + } + + buildCSSClass () { + return `vjs-theater-control ${super.buildCSSClass()}` + } + + handleTheaterChange () { + if (this.isTheaterEnabled()) { + this.controlText('Normal mode') + } else { + this.controlText('Theater mode') + } + + saveTheaterInStore(this.isTheaterEnabled()) + } + + handleClick () { + this.player_.toggleClass(TheaterButton.THEATER_MODE_CLASS) + + this.handleTheaterChange() + } + + private isTheaterEnabled () { + return this.player_.hasClass(TheaterButton.THEATER_MODE_CLASS) + } +} + +TheaterButton.prototype.controlText_ = 'Theater mode' + +TheaterButton.registerComponent('TheaterButton', TheaterButton) diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts index 4eaf53720..b7cd40aa2 100644 --- a/client/src/assets/player/utils.ts +++ b/client/src/assets/player/utils.ts @@ -51,6 +51,13 @@ function getAverageBandwidth () { return undefined } +function getStoredTheater () { + const value = getLocalStorage('theater-enabled') + if (value !== null && value !== undefined) return value === 'true' + + return undefined +} + function saveVolumeInStore (value: number) { return setLocalStorage('volume', value.toString()) } @@ -59,6 +66,10 @@ function saveMuteInStore (value: boolean) { return setLocalStorage('mute', value.toString()) } +function saveTheaterInStore (enabled: boolean) { + return setLocalStorage('theater-enabled', enabled.toString()) +} + function saveAverageBandwidth (value: number) { return setLocalStorage('average-bandwidth', value.toString()) } @@ -133,6 +144,8 @@ export { videoFileMaxByResolution, videoFileMinByResolution, copyToClipboard, + getStoredTheater, + saveTheaterInStore, isMobile, bytes } diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss index 95b4b166e..092f8ed24 100644 --- a/client/src/sass/include/_variables.scss +++ b/client/src/sass/include/_variables.scss @@ -31,3 +31,5 @@ $footer-border-color: $header-border-color; $video-thumbnail-height: 110px; $video-thumbnail-width: 200px; + +$theater-bottom-space: 85px; diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss index de6501962..e60a854d4 100644 --- a/client/src/sass/player/peertube-skin.scss +++ b/client/src/sass/player/peertube-skin.scss @@ -135,6 +135,7 @@ .vjs-resolution-control, .vjs-fullscreen-control, .vjs-peertube-link, + .vjs-theater-control, .vjs-settings { color: $primary-foreground-color !important; @@ -394,6 +395,26 @@ padding: 0 5px; } + .vjs-theater-control { + @include disable-outline; + + width: 37px; + margin-right: 1px; + + .vjs-icon-placeholder { + display: inline-block; + width: 22px; + height: 22px; + vertical-align: middle; + background: url('#{$assets-path}/player/images/theater.svg') no-repeat; + background-size: contain; + + &::before { + content: ''; + } + } + } + .vjs-fullscreen-control { @include disable-outline; @@ -440,6 +461,10 @@ } @media screen and (max-width: 750px) { + .vjs-theater-control { + display: none; + } + .vjs-dock-text { font-size: 16px; } diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index e28d964de..bf0eb484e 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts @@ -99,7 +99,8 @@ loadLocale(window.location.origin, videojs, navigator.language) enableHotkeys: true, peertubeLink: true, poster: window.location.origin + videoInfo.previewPath, - startTime + startTime, + theaterMode: false }) videojs(videoContainerId, videojsOptions, function () { const player = this