aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/assets/player/images/next.svg63
-rw-r--r--client/src/assets/player/peertube-player-manager.ts60
-rw-r--r--client/src/assets/player/peertube-videojs-typings.ts7
-rw-r--r--client/src/assets/player/videojs-components/next-previous-video-button.ts50
-rw-r--r--client/src/assets/player/videojs-components/next-video-button.ts37
-rw-r--r--client/src/sass/player/peertube-skin.scss33
-rw-r--r--client/src/standalone/videos/embed.ts39
7 files changed, 229 insertions, 60 deletions
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 @@
1<?xml version="1.0" encoding="UTF-8"?> 1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<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"> 2<svg
3 <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> 3 xmlns:dc="http://purl.org/dc/elements/1.1/"
4</svg> \ No newline at end of file 4 xmlns:cc="http://creativecommons.org/ns#"
5 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6 xmlns:svg="http://www.w3.org/2000/svg"
7 xmlns="http://www.w3.org/2000/svg"
8 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10 inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
11 sodipodi:docname="next.svg"
12 id="svg4"
13 version="1.1"
14 viewBox="0 0 12 12"
15 height="8"
16 width="8">
17 <metadata
18 id="metadata10">
19 <rdf:RDF>
20 <cc:Work
21 rdf:about="">
22 <dc:format>image/svg+xml</dc:format>
23 <dc:type
24 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
25 <dc:title></dc:title>
26 </cc:Work>
27 </rdf:RDF>
28 </metadata>
29 <defs
30 id="defs8" />
31 <sodipodi:namedview
32 inkscape:current-layer="svg4"
33 inkscape:window-maximized="1"
34 inkscape:window-y="0"
35 inkscape:window-x="0"
36 inkscape:cy="-2.5620165"
37 inkscape:cx="-7.4038126"
38 inkscape:zoom="29.791667"
39 fit-margin-bottom="0"
40 fit-margin-right="0"
41 fit-margin-left="0"
42 fit-margin-top="0"
43 showgrid="false"
44 id="namedview6"
45 inkscape:window-height="1037"
46 inkscape:window-width="1916"
47 inkscape:pageshadow="2"
48 inkscape:pageopacity="0"
49 guidetolerance="10"
50 gridtolerance="10"
51 objecttolerance="10"
52 borderopacity="1"
53 bordercolor="#666666"
54 pagecolor="#ffffff" />
55 <path
56 id="path2"
57 d="M 0,12 8.5,6 0,0 Z M 10,0 v 12 h 2 V 0 Z"
58 fill="#ffffff" />
59</svg>
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'
6import './upnext/upnext-plugin' 6import './upnext/upnext-plugin'
7import './bezels/bezels-plugin' 7import './bezels/bezels-plugin'
8import './peertube-plugin' 8import './peertube-plugin'
9import './videojs-components/next-video-button' 9import './videojs-components/next-previous-video-button'
10import './videojs-components/p2p-info-button' 10import './videojs-components/p2p-info-button'
11import './videojs-components/peertube-link-button' 11import './videojs-components/peertube-link-button'
12import './videojs-components/peertube-load-progress-bar' 12import './videojs-components/peertube-load-progress-bar'
@@ -27,6 +27,7 @@ import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder
27import { segmentValidatorFactory } from './p2p-media-loader/segment-validator' 27import { segmentValidatorFactory } from './p2p-media-loader/segment-validator'
28import { getStoredP2PEnabled } from './peertube-player-local-storage' 28import { getStoredP2PEnabled } from './peertube-player-local-storage'
29import { 29import {
30 NextPreviousVideoButtonOptions,
30 P2PMediaLoaderPluginOptions, 31 P2PMediaLoaderPluginOptions,
31 PlaylistPluginOptions, 32 PlaylistPluginOptions,
32 UserWatching, 33 UserWatching,
@@ -77,7 +78,12 @@ export interface CommonOptions extends CustomizationOptions {
77 onPlayerElementChange: (element: HTMLVideoElement) => void 78 onPlayerElementChange: (element: HTMLVideoElement) => void
78 79
79 autoplay: boolean 80 autoplay: boolean
80 nextVideo?: Function 81
82 nextVideo?: () => void
83 hasNextVideo?: () => boolean
84
85 previousVideo?: () => void
86 hasPreviousVideo?: () => boolean
81 87
82 playlist?: PlaylistPluginOptions 88 playlist?: PlaylistPluginOptions
83 89
@@ -259,7 +265,12 @@ export class PeertubePlayerManager {
259 captions: commonOptions.captions, 265 captions: commonOptions.captions,
260 peertubeLink: commonOptions.peertubeLink, 266 peertubeLink: commonOptions.peertubeLink,
261 theaterButton: commonOptions.theaterButton, 267 theaterButton: commonOptions.theaterButton,
262 nextVideo: commonOptions.nextVideo 268
269 nextVideo: commonOptions.nextVideo,
270 hasNextVideo: commonOptions.hasNextVideo,
271
272 previousVideo: commonOptions.previousVideo,
273 hasPreviousVideo: commonOptions.hasPreviousVideo
263 }) as any // FIXME: typings 274 }) as any // FIXME: typings
264 } 275 }
265 } 276 }
@@ -360,9 +371,14 @@ export class PeertubePlayerManager {
360 371
361 private static getControlBarChildren (mode: PlayerMode, options: { 372 private static getControlBarChildren (mode: PlayerMode, options: {
362 peertubeLink: boolean 373 peertubeLink: boolean
363 theaterButton: boolean, 374 theaterButton: boolean
364 captions: boolean, 375 captions: boolean
376
365 nextVideo?: Function 377 nextVideo?: Function
378 hasNextVideo?: () => boolean
379
380 previousVideo?: Function
381 hasPreviousVideo?: () => boolean
366 }) { 382 }) {
367 const settingEntries = [] 383 const settingEntries = []
368 const loadProgressBar = mode === 'webtorrent' ? 'peerTubeLoadProgressBar' : 'loadProgressBar' 384 const loadProgressBar = mode === 'webtorrent' ? 'peerTubeLoadProgressBar' : 'loadProgressBar'
@@ -372,15 +388,39 @@ export class PeertubePlayerManager {
372 if (options.captions === true) settingEntries.push('captionsButton') 388 if (options.captions === true) settingEntries.push('captionsButton')
373 settingEntries.push('resolutionMenuButton') 389 settingEntries.push('resolutionMenuButton')
374 390
375 const children = { 391 const children = {}
376 'playToggle': {} 392
393 if (options.previousVideo) {
394 const buttonOptions: NextPreviousVideoButtonOptions = {
395 type: 'previous',
396 handler: options.previousVideo,
397 isDisabled: () => {
398 if (!options.hasPreviousVideo) return false
399
400 return !options.hasPreviousVideo()
401 }
402 }
403
404 Object.assign(children, {
405 'previousVideoButton': buttonOptions
406 })
377 } 407 }
378 408
409 Object.assign(children, { playToggle: {} })
410
379 if (options.nextVideo) { 411 if (options.nextVideo) {
380 Object.assign(children, { 412 const buttonOptions: NextPreviousVideoButtonOptions = {
381 'nextVideoButton': { 413 type: 'next',
382 handler: options.nextVideo 414 handler: options.nextVideo,
415 isDisabled: () => {
416 if (!options.hasNextVideo) return false
417
418 return !options.hasNextVideo()
383 } 419 }
420 }
421
422 Object.assign(children, {
423 'nextVideoButton': buttonOptions
384 }) 424 })
385 } 425 }
386 426
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 = {
118 onItemClicked: (element: VideoPlaylistElement) => void 118 onItemClicked: (element: VideoPlaylistElement) => void
119} 119}
120 120
121type NextPreviousVideoButtonOptions = {
122 type: 'next' | 'previous'
123 handler: Function
124 isDisabled: () => boolean
125}
126
121type WebtorrentPluginOptions = { 127type WebtorrentPluginOptions = {
122 playerElement: HTMLVideoElement 128 playerElement: HTMLVideoElement
123 129
@@ -194,6 +200,7 @@ type PlaylistItemOptions = {
194export { 200export {
195 PlayerNetworkInfo, 201 PlayerNetworkInfo,
196 PlaylistItemOptions, 202 PlaylistItemOptions,
203 NextPreviousVideoButtonOptions,
197 ResolutionUpdateData, 204 ResolutionUpdateData,
198 AutoResolutionUpdateData, 205 AutoResolutionUpdateData,
199 PlaylistPluginOptions, 206 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 @@
1import videojs from 'video.js'
2import { NextPreviousVideoButtonOptions } from '../peertube-videojs-typings'
3
4const Button = videojs.getComponent('Button')
5
6class NextPreviousVideoButton extends Button {
7 private readonly nextPreviousVideoButtonOptions: NextPreviousVideoButtonOptions
8
9 constructor (player: videojs.Player, options?: NextPreviousVideoButtonOptions) {
10 super(player, options as any)
11
12 this.nextPreviousVideoButtonOptions = options
13
14 this.update()
15 }
16
17 createEl () {
18 const type = (this.options_ as NextPreviousVideoButtonOptions).type
19
20 const button = videojs.dom.createEl('button', {
21 className: 'vjs-' + type + '-video'
22 }) as HTMLButtonElement
23 const nextIcon = videojs.dom.createEl('span', {
24 className: 'icon icon-' + type
25 })
26 button.appendChild(nextIcon)
27
28 if (type === 'next') {
29 button.title = this.player_.localize('Next video')
30 } else {
31 button.title = this.player_.localize('Previous video')
32 }
33
34 return button
35 }
36
37 handleClick () {
38 this.nextPreviousVideoButtonOptions.handler()
39 }
40
41 update () {
42 const disabled = this.nextPreviousVideoButtonOptions.isDisabled()
43
44 if (disabled) this.addClass('vjs-disabled')
45 else this.removeClass('vjs-disabled')
46 }
47}
48
49videojs.registerComponent('NextVideoButton', NextPreviousVideoButton)
50videojs.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 @@
1import videojs from 'video.js'
2
3const Button = videojs.getComponent('Button')
4
5export interface NextVideoButtonOptions extends videojs.ComponentOptions {
6 handler: Function
7}
8
9class NextVideoButton extends Button {
10 private readonly nextVideoButtonOptions: NextVideoButtonOptions
11
12 constructor (player: videojs.Player, options?: NextVideoButtonOptions) {
13 super(player, options)
14
15 this.nextVideoButtonOptions = options
16 }
17
18 createEl () {
19 const button = videojs.dom.createEl('button', {
20 className: 'vjs-next-video'
21 }) as HTMLButtonElement
22 const nextIcon = videojs.dom.createEl('span', {
23 className: 'icon icon-next'
24 })
25 button.appendChild(nextIcon)
26
27 button.title = this.player_.localize('Next video')
28
29 return button
30 }
31
32 handleClick () {
33 this.nextVideoButtonOptions.handler()
34 }
35}
36
37videojs.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 {
147 box-shadow: 0 -15px 40px 10px rgba(0, 0, 0, 0.2); 147 box-shadow: 0 -15px 40px 10px rgba(0, 0, 0, 0.2);
148 text-shadow: 0 0 2px rgba(0, 0, 0, 0.5); 148 text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
149 149
150 > button:first-child {
151 margin-left: 1em;
152 }
153
150 .vjs-progress-control, 154 .vjs-progress-control,
151 .vjs-play-control, 155 .vjs-play-control,
152 .vjs-playback-rate, 156 .vjs-playback-rate,
@@ -230,7 +234,6 @@ body {
230 234
231 cursor: pointer; 235 cursor: pointer;
232 font-size: $font-size; 236 font-size: $font-size;
233 margin-left: 1em;
234 width: 3em; 237 width: 3em;
235 } 238 }
236 239
@@ -301,24 +304,32 @@ body {
301 } 304 }
302 } 305 }
303 306
304 .vjs-next-video { 307 .vjs-next-video,
308 .vjs-previous-video {
305 line-height: $control-bar-height; 309 line-height: $control-bar-height;
306 text-align: right; 310 text-align: right;
307 311
308 .icon { 312 .icon {
309 &.icon-next { 313 &.icon-next,
314 &.icon-previous {
310 mask-image: url('#{$assets-path}/player/images/next.svg'); 315 mask-image: url('#{$assets-path}/player/images/next.svg');
311 -webkit-mask-image: url('#{$assets-path}/player/images/next.svg'); 316 -webkit-mask-image: url('#{$assets-path}/player/images/next.svg');
312 background-color: white; 317 background-color: white;
313 mask-size: cover; 318 mask-size: cover;
314 -webkit-mask-size: cover; 319 -webkit-mask-size: cover;
315 transform: scale(2.2); 320 width: 11px;
321 height: 11px;
322 margin-top: -2px;
323 display: inline-block;
324 }
325
326 &.icon-previous {
327 transform: rotate(180deg);
316 } 328 }
317 } 329 }
318 } 330 }
319 331
320 .vjs-peertube, 332 .vjs-peertube {
321 .vjs-next-video {
322 .icon { 333 .icon {
323 display: inline-block; 334 display: inline-block;
324 width: 15px; 335 width: 15px;
@@ -650,3 +661,13 @@ body {
650 display: block; 661 display: block;
651 } 662 }
652} 663}
664
665.vjs-no-next-in-playlist {
666 .vjs-next-video {
667 cursor: default;
668
669 .icon {
670 background-color: rgba(255, 255, 255, 0.5);
671 }
672 }
673}
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 {
309 cancelText: peertubeTranslate('Cancel', translations), 309 cancelText: peertubeTranslate('Cancel', translations),
310 suspendedText: peertubeTranslate('Autoplay is suspended', translations), 310 suspendedText: peertubeTranslate('Autoplay is suspended', translations),
311 getTitle: () => this.nextVideoTitle(), 311 getTitle: () => this.nextVideoTitle(),
312 next: () => this.autoplayNext(), 312 next: () => this.playNextVideo(),
313 condition: () => !!this.getNextPlaylistElement(), 313 condition: () => !!this.getNextPlaylistElement(),
314 suspended: () => false 314 suspended: () => false
315 }) 315 })
316 } 316 }
317 317
318 private async autoplayNext () { 318 private async playNextVideo () {
319 const next = this.getNextPlaylistElement() 319 const next = this.getNextPlaylistElement()
320 if (!next) { 320 if (!next) {
321 console.log('Next element not found in playlist.') 321 console.log('Next element not found in playlist.')
@@ -327,6 +327,18 @@ export class PeerTubeEmbed {
327 return this.loadVideoAndBuildPlayer(this.currentPlaylistElement.video.uuid) 327 return this.loadVideoAndBuildPlayer(this.currentPlaylistElement.video.uuid)
328 } 328 }
329 329
330 private async playPreviousVideo () {
331 const previous = this.getPreviousPlaylistElement()
332 if (!previous) {
333 console.log('Previous element not found in playlist.')
334 return
335 }
336
337 this.currentPlaylistElement = previous
338
339 return this.loadVideoAndBuildPlayer(this.currentPlaylistElement.video.uuid)
340 }
341
330 private async loadVideoAndBuildPlayer (uuid: string) { 342 private async loadVideoAndBuildPlayer (uuid: string) {
331 const res = await this.loadVideo(uuid) 343 const res = await this.loadVideo(uuid)
332 if (res === undefined) return 344 if (res === undefined) return
@@ -357,6 +369,22 @@ export class PeerTubeEmbed {
357 return next 369 return next
358 } 370 }
359 371
372 private getPreviousPlaylistElement (position?: number): VideoPlaylistElement {
373 if (!position) position = this.currentPlaylistElement.position -1
374
375 if (position < 1) {
376 return undefined
377 }
378
379 const prev = this.playlistElements.find(e => e.position === position)
380
381 if (!prev || !prev.video) {
382 return this.getNextPlaylistElement(position - 1)
383 }
384
385 return prev
386 }
387
360 private async buildVideoPlayer (videoResponse: Response, captionsPromise: Promise<Response>) { 388 private async buildVideoPlayer (videoResponse: Response, captionsPromise: Promise<Response>) {
361 let alreadyHadPlayer = false 389 let alreadyHadPlayer = false
362 390
@@ -418,7 +446,12 @@ export class PeerTubeEmbed {
418 stopTime: this.stopTime, 446 stopTime: this.stopTime,
419 subtitle: this.subtitle, 447 subtitle: this.subtitle,
420 448
421 nextVideo: () => this.autoplayNext(), 449 nextVideo: this.playlist ? () => this.playNextVideo() : undefined,
450 hasNextVideo: this.playlist ? () => !!this.getNextPlaylistElement() : undefined,
451
452 previousVideo: this.playlist ? () => this.playPreviousVideo() : undefined,
453 hasPreviousVideo: this.playlist ? () => !!this.getPreviousPlaylistElement() : undefined,
454
422 playlist: playlistPlugin, 455 playlist: playlistPlugin,
423 456
424 videoCaptions, 457 videoCaptions,