Add previous button

This commit is contained in:
Chocobozzz 2020-08-05 11:02:14 +02:00 committed by Chocobozzz
parent 4572c3d0d9
commit a950e4c82b
7 changed files with 229 additions and 60 deletions

View File

@ -1,4 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="24px" height="24px" viewBox="0 0 36 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg
<path fill="white" d="M 12,24 20.5,18 12,12 V 24 z M 22,12 v 12 h 2 V 12 h -2 z"></path> xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="next.svg"
id="svg4"
version="1.1"
viewBox="0 0 12 12"
height="8"
width="8">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
inkscape:current-layer="svg4"
inkscape:window-maximized="1"
inkscape:window-y="0"
inkscape:window-x="0"
inkscape:cy="-2.5620165"
inkscape:cx="-7.4038126"
inkscape:zoom="29.791667"
fit-margin-bottom="0"
fit-margin-right="0"
fit-margin-left="0"
fit-margin-top="0"
showgrid="false"
id="namedview6"
inkscape:window-height="1037"
inkscape:window-width="1916"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<path
id="path2"
d="M 0,12 8.5,6 0,0 Z M 10,0 v 12 h 2 V 0 Z"
fill="#ffffff" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 283 B

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -6,7 +6,7 @@ import './upnext/end-card'
import './upnext/upnext-plugin' import './upnext/upnext-plugin'
import './bezels/bezels-plugin' import './bezels/bezels-plugin'
import './peertube-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/p2p-info-button'
import './videojs-components/peertube-link-button' import './videojs-components/peertube-link-button'
import './videojs-components/peertube-load-progress-bar' 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 { segmentValidatorFactory } from './p2p-media-loader/segment-validator'
import { getStoredP2PEnabled } from './peertube-player-local-storage' import { getStoredP2PEnabled } from './peertube-player-local-storage'
import { import {
NextPreviousVideoButtonOptions,
P2PMediaLoaderPluginOptions, P2PMediaLoaderPluginOptions,
PlaylistPluginOptions, PlaylistPluginOptions,
UserWatching, UserWatching,
@ -77,7 +78,12 @@ export interface CommonOptions extends CustomizationOptions {
onPlayerElementChange: (element: HTMLVideoElement) => void onPlayerElementChange: (element: HTMLVideoElement) => void
autoplay: boolean autoplay: boolean
nextVideo?: Function
nextVideo?: () => void
hasNextVideo?: () => boolean
previousVideo?: () => void
hasPreviousVideo?: () => boolean
playlist?: PlaylistPluginOptions playlist?: PlaylistPluginOptions
@ -259,7 +265,12 @@ export class PeertubePlayerManager {
captions: commonOptions.captions, captions: commonOptions.captions,
peertubeLink: commonOptions.peertubeLink, peertubeLink: commonOptions.peertubeLink,
theaterButton: commonOptions.theaterButton, theaterButton: commonOptions.theaterButton,
nextVideo: commonOptions.nextVideo
nextVideo: commonOptions.nextVideo,
hasNextVideo: commonOptions.hasNextVideo,
previousVideo: commonOptions.previousVideo,
hasPreviousVideo: commonOptions.hasPreviousVideo
}) as any // FIXME: typings }) as any // FIXME: typings
} }
} }
@ -360,9 +371,14 @@ export class PeertubePlayerManager {
private static getControlBarChildren (mode: PlayerMode, options: { private static getControlBarChildren (mode: PlayerMode, options: {
peertubeLink: boolean peertubeLink: boolean
theaterButton: boolean, theaterButton: boolean
captions: boolean, captions: boolean
nextVideo?: Function nextVideo?: Function
hasNextVideo?: () => boolean
previousVideo?: Function
hasPreviousVideo?: () => boolean
}) { }) {
const settingEntries = [] const settingEntries = []
const loadProgressBar = mode === 'webtorrent' ? 'peerTubeLoadProgressBar' : 'loadProgressBar' const loadProgressBar = mode === 'webtorrent' ? 'peerTubeLoadProgressBar' : 'loadProgressBar'
@ -372,15 +388,39 @@ export class PeertubePlayerManager {
if (options.captions === true) settingEntries.push('captionsButton') if (options.captions === true) settingEntries.push('captionsButton')
settingEntries.push('resolutionMenuButton') settingEntries.push('resolutionMenuButton')
const children = { const children = {}
'playToggle': {}
if (options.previousVideo) {
const buttonOptions: NextPreviousVideoButtonOptions = {
type: 'previous',
handler: options.previousVideo,
isDisabled: () => {
if (!options.hasPreviousVideo) return false
return !options.hasPreviousVideo()
}
} }
if (options.nextVideo) {
Object.assign(children, { Object.assign(children, {
'nextVideoButton': { 'previousVideoButton': buttonOptions
handler: options.nextVideo })
} }
Object.assign(children, { playToggle: {} })
if (options.nextVideo) {
const buttonOptions: NextPreviousVideoButtonOptions = {
type: 'next',
handler: options.nextVideo,
isDisabled: () => {
if (!options.hasNextVideo) return false
return !options.hasNextVideo()
}
}
Object.assign(children, {
'nextVideoButton': buttonOptions
}) })
} }

View File

@ -118,6 +118,12 @@ type PlaylistPluginOptions = {
onItemClicked: (element: VideoPlaylistElement) => void onItemClicked: (element: VideoPlaylistElement) => void
} }
type NextPreviousVideoButtonOptions = {
type: 'next' | 'previous'
handler: Function
isDisabled: () => boolean
}
type WebtorrentPluginOptions = { type WebtorrentPluginOptions = {
playerElement: HTMLVideoElement playerElement: HTMLVideoElement
@ -194,6 +200,7 @@ type PlaylistItemOptions = {
export { export {
PlayerNetworkInfo, PlayerNetworkInfo,
PlaylistItemOptions, PlaylistItemOptions,
NextPreviousVideoButtonOptions,
ResolutionUpdateData, ResolutionUpdateData,
AutoResolutionUpdateData, AutoResolutionUpdateData,
PlaylistPluginOptions, PlaylistPluginOptions,

View File

@ -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)

View File

@ -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)

View File

@ -147,6 +147,10 @@ body {
box-shadow: 0 -15px 40px 10px rgba(0, 0, 0, 0.2); box-shadow: 0 -15px 40px 10px rgba(0, 0, 0, 0.2);
text-shadow: 0 0 2px rgba(0, 0, 0, 0.5); text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
> button:first-child {
margin-left: 1em;
}
.vjs-progress-control, .vjs-progress-control,
.vjs-play-control, .vjs-play-control,
.vjs-playback-rate, .vjs-playback-rate,
@ -230,7 +234,6 @@ body {
cursor: pointer; cursor: pointer;
font-size: $font-size; font-size: $font-size;
margin-left: 1em;
width: 3em; width: 3em;
} }
@ -301,24 +304,32 @@ body {
} }
} }
.vjs-next-video { .vjs-next-video,
.vjs-previous-video {
line-height: $control-bar-height; line-height: $control-bar-height;
text-align: right; text-align: right;
.icon { .icon {
&.icon-next { &.icon-next,
&.icon-previous {
mask-image: url('#{$assets-path}/player/images/next.svg'); mask-image: url('#{$assets-path}/player/images/next.svg');
-webkit-mask-image: url('#{$assets-path}/player/images/next.svg'); -webkit-mask-image: url('#{$assets-path}/player/images/next.svg');
background-color: white; background-color: white;
mask-size: cover; mask-size: cover;
-webkit-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-peertube {
.vjs-next-video {
.icon { .icon {
display: inline-block; display: inline-block;
width: 15px; width: 15px;
@ -650,3 +661,13 @@ body {
display: block; display: block;
} }
} }
.vjs-no-next-in-playlist {
.vjs-next-video {
cursor: default;
.icon {
background-color: rgba(255, 255, 255, 0.5);
}
}
}

View File

@ -309,13 +309,13 @@ export class PeerTubeEmbed {
cancelText: peertubeTranslate('Cancel', translations), cancelText: peertubeTranslate('Cancel', translations),
suspendedText: peertubeTranslate('Autoplay is suspended', translations), suspendedText: peertubeTranslate('Autoplay is suspended', translations),
getTitle: () => this.nextVideoTitle(), getTitle: () => this.nextVideoTitle(),
next: () => this.autoplayNext(), next: () => this.playNextVideo(),
condition: () => !!this.getNextPlaylistElement(), condition: () => !!this.getNextPlaylistElement(),
suspended: () => false suspended: () => false
}) })
} }
private async autoplayNext () { private async playNextVideo () {
const next = this.getNextPlaylistElement() const next = this.getNextPlaylistElement()
if (!next) { if (!next) {
console.log('Next element not found in playlist.') console.log('Next element not found in playlist.')
@ -327,6 +327,18 @@ export class PeerTubeEmbed {
return this.loadVideoAndBuildPlayer(this.currentPlaylistElement.video.uuid) 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) { private async loadVideoAndBuildPlayer (uuid: string) {
const res = await this.loadVideo(uuid) const res = await this.loadVideo(uuid)
if (res === undefined) return if (res === undefined) return
@ -357,6 +369,22 @@ export class PeerTubeEmbed {
return next 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<Response>) { private async buildVideoPlayer (videoResponse: Response, captionsPromise: Promise<Response>) {
let alreadyHadPlayer = false let alreadyHadPlayer = false
@ -418,7 +446,12 @@ export class PeerTubeEmbed {
stopTime: this.stopTime, stopTime: this.stopTime,
subtitle: this.subtitle, 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, playlist: playlistPlugin,
videoCaptions, videoCaptions,