Add previous button
This commit is contained in:
parent
4572c3d0d9
commit
a950e4c82b
|
@ -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 |
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue