From a950e4c82bd458e924b00698a77c06275a64a46c Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 5 Aug 2020 11:02:14 +0200 Subject: Add previous button --- client/src/assets/player/images/next.svg | 63 ++++++++++++++++++++-- .../src/assets/player/peertube-player-manager.ts | 60 +++++++++++++++++---- .../src/assets/player/peertube-videojs-typings.ts | 7 +++ .../next-previous-video-button.ts | 50 +++++++++++++++++ .../player/videojs-components/next-video-button.ts | 37 ------------- client/src/sass/player/peertube-skin.scss | 33 +++++++++--- client/src/standalone/videos/embed.ts | 39 ++++++++++++-- 7 files changed, 229 insertions(+), 60 deletions(-) create mode 100644 client/src/assets/player/videojs-components/next-previous-video-button.ts delete mode 100644 client/src/assets/player/videojs-components/next-video-button.ts diff --git a/client/src/assets/player/images/next.svg b/client/src/assets/player/images/next.svg index af42dd270..0441f93c8 100644 --- a/client/src/assets/player/images/next.svg +++ b/client/src/assets/player/images/next.svg @@ -1,4 +1,59 @@ - - - - \ No newline at end of file + + + + + + image/svg+xml + + + + + + + + + diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts index dcfa3a593..c71b43415 100644 --- a/client/src/assets/player/peertube-player-manager.ts +++ b/client/src/assets/player/peertube-player-manager.ts @@ -6,7 +6,7 @@ import './upnext/end-card' import './upnext/upnext-plugin' import './bezels/bezels-plugin' import './peertube-plugin' -import './videojs-components/next-video-button' +import './videojs-components/next-previous-video-button' import './videojs-components/p2p-info-button' import './videojs-components/peertube-link-button' import './videojs-components/peertube-load-progress-bar' @@ -27,6 +27,7 @@ import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder import { segmentValidatorFactory } from './p2p-media-loader/segment-validator' import { getStoredP2PEnabled } from './peertube-player-local-storage' import { + NextPreviousVideoButtonOptions, P2PMediaLoaderPluginOptions, PlaylistPluginOptions, UserWatching, @@ -77,7 +78,12 @@ export interface CommonOptions extends CustomizationOptions { onPlayerElementChange: (element: HTMLVideoElement) => void autoplay: boolean - nextVideo?: Function + + nextVideo?: () => void + hasNextVideo?: () => boolean + + previousVideo?: () => void + hasPreviousVideo?: () => boolean playlist?: PlaylistPluginOptions @@ -259,7 +265,12 @@ export class PeertubePlayerManager { captions: commonOptions.captions, peertubeLink: commonOptions.peertubeLink, theaterButton: commonOptions.theaterButton, - nextVideo: commonOptions.nextVideo + + nextVideo: commonOptions.nextVideo, + hasNextVideo: commonOptions.hasNextVideo, + + previousVideo: commonOptions.previousVideo, + hasPreviousVideo: commonOptions.hasPreviousVideo }) as any // FIXME: typings } } @@ -360,9 +371,14 @@ export class PeertubePlayerManager { private static getControlBarChildren (mode: PlayerMode, options: { peertubeLink: boolean - theaterButton: boolean, - captions: boolean, + theaterButton: boolean + captions: boolean + nextVideo?: Function + hasNextVideo?: () => boolean + + previousVideo?: Function + hasPreviousVideo?: () => boolean }) { const settingEntries = [] const loadProgressBar = mode === 'webtorrent' ? 'peerTubeLoadProgressBar' : 'loadProgressBar' @@ -372,15 +388,39 @@ export class PeertubePlayerManager { if (options.captions === true) settingEntries.push('captionsButton') settingEntries.push('resolutionMenuButton') - const children = { - 'playToggle': {} + const children = {} + + if (options.previousVideo) { + const buttonOptions: NextPreviousVideoButtonOptions = { + type: 'previous', + handler: options.previousVideo, + isDisabled: () => { + if (!options.hasPreviousVideo) return false + + return !options.hasPreviousVideo() + } + } + + Object.assign(children, { + 'previousVideoButton': buttonOptions + }) } + Object.assign(children, { playToggle: {} }) + if (options.nextVideo) { - Object.assign(children, { - 'nextVideoButton': { - handler: options.nextVideo + const buttonOptions: NextPreviousVideoButtonOptions = { + type: 'next', + handler: options.nextVideo, + isDisabled: () => { + if (!options.hasNextVideo) return false + + return !options.hasNextVideo() } + } + + Object.assign(children, { + 'nextVideoButton': buttonOptions }) } diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts index b72c4b0f9..a359b8595 100644 --- a/client/src/assets/player/peertube-videojs-typings.ts +++ b/client/src/assets/player/peertube-videojs-typings.ts @@ -118,6 +118,12 @@ type PlaylistPluginOptions = { onItemClicked: (element: VideoPlaylistElement) => void } +type NextPreviousVideoButtonOptions = { + type: 'next' | 'previous' + handler: Function + isDisabled: () => boolean +} + type WebtorrentPluginOptions = { playerElement: HTMLVideoElement @@ -194,6 +200,7 @@ type PlaylistItemOptions = { export { PlayerNetworkInfo, PlaylistItemOptions, + NextPreviousVideoButtonOptions, ResolutionUpdateData, AutoResolutionUpdateData, PlaylistPluginOptions, diff --git a/client/src/assets/player/videojs-components/next-previous-video-button.ts b/client/src/assets/player/videojs-components/next-previous-video-button.ts new file mode 100644 index 000000000..fe17ce2ce --- /dev/null +++ b/client/src/assets/player/videojs-components/next-previous-video-button.ts @@ -0,0 +1,50 @@ +import videojs from 'video.js' +import { NextPreviousVideoButtonOptions } from '../peertube-videojs-typings' + +const Button = videojs.getComponent('Button') + +class NextPreviousVideoButton extends Button { + private readonly nextPreviousVideoButtonOptions: NextPreviousVideoButtonOptions + + constructor (player: videojs.Player, options?: NextPreviousVideoButtonOptions) { + super(player, options as any) + + this.nextPreviousVideoButtonOptions = options + + this.update() + } + + createEl () { + const type = (this.options_ as NextPreviousVideoButtonOptions).type + + const button = videojs.dom.createEl('button', { + className: 'vjs-' + type + '-video' + }) as HTMLButtonElement + const nextIcon = videojs.dom.createEl('span', { + className: 'icon icon-' + type + }) + button.appendChild(nextIcon) + + if (type === 'next') { + button.title = this.player_.localize('Next video') + } else { + button.title = this.player_.localize('Previous video') + } + + return button + } + + handleClick () { + this.nextPreviousVideoButtonOptions.handler() + } + + update () { + const disabled = this.nextPreviousVideoButtonOptions.isDisabled() + + if (disabled) this.addClass('vjs-disabled') + else this.removeClass('vjs-disabled') + } +} + +videojs.registerComponent('NextVideoButton', NextPreviousVideoButton) +videojs.registerComponent('PreviousVideoButton', NextPreviousVideoButton) diff --git a/client/src/assets/player/videojs-components/next-video-button.ts b/client/src/assets/player/videojs-components/next-video-button.ts deleted file mode 100644 index 22b32f06b..000000000 --- a/client/src/assets/player/videojs-components/next-video-button.ts +++ /dev/null @@ -1,37 +0,0 @@ -import videojs from 'video.js' - -const Button = videojs.getComponent('Button') - -export interface NextVideoButtonOptions extends videojs.ComponentOptions { - handler: Function -} - -class NextVideoButton extends Button { - private readonly nextVideoButtonOptions: NextVideoButtonOptions - - constructor (player: videojs.Player, options?: NextVideoButtonOptions) { - super(player, options) - - this.nextVideoButtonOptions = options - } - - createEl () { - const button = videojs.dom.createEl('button', { - className: 'vjs-next-video' - }) as HTMLButtonElement - const nextIcon = videojs.dom.createEl('span', { - className: 'icon icon-next' - }) - button.appendChild(nextIcon) - - button.title = this.player_.localize('Next video') - - return button - } - - handleClick () { - this.nextVideoButtonOptions.handler() - } -} - -videojs.registerComponent('NextVideoButton', NextVideoButton) diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss index 2c22239a0..994936f81 100644 --- a/client/src/sass/player/peertube-skin.scss +++ b/client/src/sass/player/peertube-skin.scss @@ -147,6 +147,10 @@ body { box-shadow: 0 -15px 40px 10px rgba(0, 0, 0, 0.2); text-shadow: 0 0 2px rgba(0, 0, 0, 0.5); + > button:first-child { + margin-left: 1em; + } + .vjs-progress-control, .vjs-play-control, .vjs-playback-rate, @@ -230,7 +234,6 @@ body { cursor: pointer; font-size: $font-size; - margin-left: 1em; width: 3em; } @@ -301,24 +304,32 @@ body { } } - .vjs-next-video { + .vjs-next-video, + .vjs-previous-video { line-height: $control-bar-height; text-align: right; .icon { - &.icon-next { + &.icon-next, + &.icon-previous { mask-image: url('#{$assets-path}/player/images/next.svg'); -webkit-mask-image: url('#{$assets-path}/player/images/next.svg'); background-color: white; mask-size: cover; -webkit-mask-size: cover; - transform: scale(2.2); + width: 11px; + height: 11px; + margin-top: -2px; + display: inline-block; + } + + &.icon-previous { + transform: rotate(180deg); } } } - .vjs-peertube, - .vjs-next-video { + .vjs-peertube { .icon { display: inline-block; width: 15px; @@ -650,3 +661,13 @@ body { display: block; } } + +.vjs-no-next-in-playlist { + .vjs-next-video { + cursor: default; + + .icon { + background-color: rgba(255, 255, 255, 0.5); + } + } +} diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index 17b0ee9ef..786d749a4 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts @@ -309,13 +309,13 @@ export class PeerTubeEmbed { cancelText: peertubeTranslate('Cancel', translations), suspendedText: peertubeTranslate('Autoplay is suspended', translations), getTitle: () => this.nextVideoTitle(), - next: () => this.autoplayNext(), + next: () => this.playNextVideo(), condition: () => !!this.getNextPlaylistElement(), suspended: () => false }) } - private async autoplayNext () { + private async playNextVideo () { const next = this.getNextPlaylistElement() if (!next) { console.log('Next element not found in playlist.') @@ -327,6 +327,18 @@ export class PeerTubeEmbed { return this.loadVideoAndBuildPlayer(this.currentPlaylistElement.video.uuid) } + private async playPreviousVideo () { + const previous = this.getPreviousPlaylistElement() + if (!previous) { + console.log('Previous element not found in playlist.') + return + } + + this.currentPlaylistElement = previous + + return this.loadVideoAndBuildPlayer(this.currentPlaylistElement.video.uuid) + } + private async loadVideoAndBuildPlayer (uuid: string) { const res = await this.loadVideo(uuid) if (res === undefined) return @@ -357,6 +369,22 @@ export class PeerTubeEmbed { return next } + private getPreviousPlaylistElement (position?: number): VideoPlaylistElement { + if (!position) position = this.currentPlaylistElement.position -1 + + if (position < 1) { + return undefined + } + + const prev = this.playlistElements.find(e => e.position === position) + + if (!prev || !prev.video) { + return this.getNextPlaylistElement(position - 1) + } + + return prev + } + private async buildVideoPlayer (videoResponse: Response, captionsPromise: Promise) { let alreadyHadPlayer = false @@ -418,7 +446,12 @@ export class PeerTubeEmbed { stopTime: this.stopTime, subtitle: this.subtitle, - nextVideo: () => this.autoplayNext(), + nextVideo: this.playlist ? () => this.playNextVideo() : undefined, + hasNextVideo: this.playlist ? () => !!this.getNextPlaylistElement() : undefined, + + previousVideo: this.playlist ? () => this.playPreviousVideo() : undefined, + hasPreviousVideo: this.playlist ? () => !!this.getPreviousPlaylistElement() : undefined, + playlist: playlistPlugin, videoCaptions, -- cgit v1.2.3