diff options
Diffstat (limited to 'client/src/assets/player')
5 files changed, 166 insertions, 51 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' | |||
6 | import './upnext/upnext-plugin' | 6 | import './upnext/upnext-plugin' |
7 | import './bezels/bezels-plugin' | 7 | import './bezels/bezels-plugin' |
8 | import './peertube-plugin' | 8 | import './peertube-plugin' |
9 | import './videojs-components/next-video-button' | 9 | import './videojs-components/next-previous-video-button' |
10 | import './videojs-components/p2p-info-button' | 10 | import './videojs-components/p2p-info-button' |
11 | import './videojs-components/peertube-link-button' | 11 | import './videojs-components/peertube-link-button' |
12 | import './videojs-components/peertube-load-progress-bar' | 12 | import './videojs-components/peertube-load-progress-bar' |
@@ -27,6 +27,7 @@ import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder | |||
27 | import { segmentValidatorFactory } from './p2p-media-loader/segment-validator' | 27 | import { segmentValidatorFactory } from './p2p-media-loader/segment-validator' |
28 | import { getStoredP2PEnabled } from './peertube-player-local-storage' | 28 | import { getStoredP2PEnabled } from './peertube-player-local-storage' |
29 | import { | 29 | import { |
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 | ||
121 | type NextPreviousVideoButtonOptions = { | ||
122 | type: 'next' | 'previous' | ||
123 | handler: Function | ||
124 | isDisabled: () => boolean | ||
125 | } | ||
126 | |||
121 | type WebtorrentPluginOptions = { | 127 | type WebtorrentPluginOptions = { |
122 | playerElement: HTMLVideoElement | 128 | playerElement: HTMLVideoElement |
123 | 129 | ||
@@ -194,6 +200,7 @@ type PlaylistItemOptions = { | |||
194 | export { | 200 | export { |
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 @@ | |||
1 | import videojs from 'video.js' | ||
2 | import { NextPreviousVideoButtonOptions } from '../peertube-videojs-typings' | ||
3 | |||
4 | const Button = videojs.getComponent('Button') | ||
5 | |||
6 | class 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 | |||
49 | videojs.registerComponent('NextVideoButton', NextPreviousVideoButton) | ||
50 | 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 @@ | |||
1 | import videojs from 'video.js' | ||
2 | |||
3 | const Button = videojs.getComponent('Button') | ||
4 | |||
5 | export interface NextVideoButtonOptions extends videojs.ComponentOptions { | ||
6 | handler: Function | ||
7 | } | ||
8 | |||
9 | class 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 | |||
37 | videojs.registerComponent('NextVideoButton', NextVideoButton) | ||