Add previous button
This commit is contained in:
parent
4572c3d0d9
commit
a950e4c82b
|
@ -1,4 +1,59 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<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>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
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>
|
||||
|
|
Before Width: | Height: | Size: 283 B After Width: | Height: | Size: 1.6 KiB |
|
@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Response>) {
|
||||
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,
|
||||
|
|
Loading…
Reference in New Issue