diff options
Diffstat (limited to 'client/src/assets/player/control-bar')
5 files changed, 305 insertions, 0 deletions
diff --git a/client/src/assets/player/control-bar/next-previous-video-button.ts b/client/src/assets/player/control-bar/next-previous-video-button.ts new file mode 100644 index 000000000..fe17ce2ce --- /dev/null +++ b/client/src/assets/player/control-bar/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/control-bar/p2p-info-button.ts b/client/src/assets/player/control-bar/p2p-info-button.ts new file mode 100644 index 000000000..081dee1d3 --- /dev/null +++ b/client/src/assets/player/control-bar/p2p-info-button.ts | |||
@@ -0,0 +1,124 @@ | |||
1 | import videojs from 'video.js' | ||
2 | import { PeerTubeP2PInfoButtonOptions, PlayerNetworkInfo } from '../peertube-videojs-typings' | ||
3 | import { bytes } from '../utils' | ||
4 | |||
5 | const Button = videojs.getComponent('Button') | ||
6 | class P2pInfoButton extends Button { | ||
7 | |||
8 | constructor (player: videojs.Player, options?: PeerTubeP2PInfoButtonOptions) { | ||
9 | super(player, options as any) | ||
10 | } | ||
11 | |||
12 | createEl () { | ||
13 | const div = videojs.dom.createEl('div', { | ||
14 | className: 'vjs-peertube' | ||
15 | }) | ||
16 | const subDivWebtorrent = videojs.dom.createEl('div', { | ||
17 | className: 'vjs-peertube-hidden' // Hide the stats before we get the info | ||
18 | }) as HTMLDivElement | ||
19 | div.appendChild(subDivWebtorrent) | ||
20 | |||
21 | // Stop here if P2P is not enabled | ||
22 | const p2pEnabled = (this.options_ as PeerTubeP2PInfoButtonOptions).p2pEnabled | ||
23 | if (!p2pEnabled) return div as HTMLButtonElement | ||
24 | |||
25 | const downloadIcon = videojs.dom.createEl('span', { | ||
26 | className: 'icon icon-download' | ||
27 | }) | ||
28 | subDivWebtorrent.appendChild(downloadIcon) | ||
29 | |||
30 | const downloadSpeedText = videojs.dom.createEl('span', { | ||
31 | className: 'download-speed-text' | ||
32 | }) | ||
33 | const downloadSpeedNumber = videojs.dom.createEl('span', { | ||
34 | className: 'download-speed-number' | ||
35 | }) | ||
36 | const downloadSpeedUnit = videojs.dom.createEl('span') | ||
37 | downloadSpeedText.appendChild(downloadSpeedNumber) | ||
38 | downloadSpeedText.appendChild(downloadSpeedUnit) | ||
39 | subDivWebtorrent.appendChild(downloadSpeedText) | ||
40 | |||
41 | const uploadIcon = videojs.dom.createEl('span', { | ||
42 | className: 'icon icon-upload' | ||
43 | }) | ||
44 | subDivWebtorrent.appendChild(uploadIcon) | ||
45 | |||
46 | const uploadSpeedText = videojs.dom.createEl('span', { | ||
47 | className: 'upload-speed-text' | ||
48 | }) | ||
49 | const uploadSpeedNumber = videojs.dom.createEl('span', { | ||
50 | className: 'upload-speed-number' | ||
51 | }) | ||
52 | const uploadSpeedUnit = videojs.dom.createEl('span') | ||
53 | uploadSpeedText.appendChild(uploadSpeedNumber) | ||
54 | uploadSpeedText.appendChild(uploadSpeedUnit) | ||
55 | subDivWebtorrent.appendChild(uploadSpeedText) | ||
56 | |||
57 | const peersText = videojs.dom.createEl('span', { | ||
58 | className: 'peers-text' | ||
59 | }) | ||
60 | const peersNumber = videojs.dom.createEl('span', { | ||
61 | className: 'peers-number' | ||
62 | }) | ||
63 | subDivWebtorrent.appendChild(peersNumber) | ||
64 | subDivWebtorrent.appendChild(peersText) | ||
65 | |||
66 | const subDivHttp = videojs.dom.createEl('div', { | ||
67 | className: 'vjs-peertube-hidden' | ||
68 | }) | ||
69 | const subDivHttpText = videojs.dom.createEl('span', { | ||
70 | className: 'http-fallback', | ||
71 | textContent: 'HTTP' | ||
72 | }) | ||
73 | |||
74 | subDivHttp.appendChild(subDivHttpText) | ||
75 | div.appendChild(subDivHttp) | ||
76 | |||
77 | this.player_.on('p2pInfo', (event: any, data: PlayerNetworkInfo) => { | ||
78 | // We are in HTTP fallback | ||
79 | if (!data) { | ||
80 | subDivHttp.className = 'vjs-peertube-displayed' | ||
81 | subDivWebtorrent.className = 'vjs-peertube-hidden' | ||
82 | |||
83 | return | ||
84 | } | ||
85 | |||
86 | const p2pStats = data.p2p | ||
87 | const httpStats = data.http | ||
88 | |||
89 | const downloadSpeed = bytes(p2pStats.downloadSpeed + httpStats.downloadSpeed) | ||
90 | const uploadSpeed = bytes(p2pStats.uploadSpeed + httpStats.uploadSpeed) | ||
91 | const totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded) | ||
92 | const totalUploaded = bytes(p2pStats.uploaded + httpStats.uploaded) | ||
93 | const numPeers = p2pStats.numPeers | ||
94 | |||
95 | subDivWebtorrent.title = this.player().localize('Total downloaded: ') + totalDownloaded.join(' ') + '\n' | ||
96 | |||
97 | if (data.source === 'p2p-media-loader') { | ||
98 | const downloadedFromServer = bytes(httpStats.downloaded).join(' ') | ||
99 | const downloadedFromPeers = bytes(p2pStats.downloaded).join(' ') | ||
100 | |||
101 | subDivWebtorrent.title += | ||
102 | ' * ' + this.player().localize('From servers: ') + downloadedFromServer + '\n' + | ||
103 | ' * ' + this.player().localize('From peers: ') + downloadedFromPeers + '\n' | ||
104 | } | ||
105 | subDivWebtorrent.title += this.player().localize('Total uploaded: ') + totalUploaded.join(' ') | ||
106 | |||
107 | downloadSpeedNumber.textContent = downloadSpeed[0] | ||
108 | downloadSpeedUnit.textContent = ' ' + downloadSpeed[1] | ||
109 | |||
110 | uploadSpeedNumber.textContent = uploadSpeed[0] | ||
111 | uploadSpeedUnit.textContent = ' ' + uploadSpeed[1] | ||
112 | |||
113 | peersNumber.textContent = numPeers.toString() | ||
114 | peersText.textContent = ' ' + (numPeers > 1 ? this.player().localize('peers') : this.player_.localize('peer')) | ||
115 | |||
116 | subDivHttp.className = 'vjs-peertube-hidden' | ||
117 | subDivWebtorrent.className = 'vjs-peertube-displayed' | ||
118 | }) | ||
119 | |||
120 | return div as HTMLButtonElement | ||
121 | } | ||
122 | } | ||
123 | |||
124 | videojs.registerComponent('P2PInfoButton', P2pInfoButton) | ||
diff --git a/client/src/assets/player/control-bar/peertube-link-button.ts b/client/src/assets/player/control-bar/peertube-link-button.ts new file mode 100644 index 000000000..c49cee566 --- /dev/null +++ b/client/src/assets/player/control-bar/peertube-link-button.ts | |||
@@ -0,0 +1,45 @@ | |||
1 | import videojs from 'video.js' | ||
2 | import { buildVideoLink, decorateVideoLink } from '@shared/core-utils' | ||
3 | import { PeerTubeLinkButtonOptions } from '../peertube-videojs-typings' | ||
4 | |||
5 | const Button = videojs.getComponent('Button') | ||
6 | class PeerTubeLinkButton extends Button { | ||
7 | |||
8 | constructor (player: videojs.Player, options?: PeerTubeLinkButtonOptions) { | ||
9 | super(player, options as any) | ||
10 | } | ||
11 | |||
12 | createEl () { | ||
13 | return this.buildElement() | ||
14 | } | ||
15 | |||
16 | updateHref () { | ||
17 | this.el().setAttribute('href', this.buildLink()) | ||
18 | } | ||
19 | |||
20 | handleClick () { | ||
21 | this.player().pause() | ||
22 | } | ||
23 | |||
24 | private buildElement () { | ||
25 | const el = videojs.dom.createEl('a', { | ||
26 | href: this.buildLink(), | ||
27 | innerHTML: 'PeerTube', | ||
28 | title: this.player().localize('Video page (new window)'), | ||
29 | className: 'vjs-peertube-link', | ||
30 | target: '_blank' | ||
31 | }) | ||
32 | |||
33 | el.addEventListener('mouseenter', () => this.updateHref()) | ||
34 | |||
35 | return el as HTMLButtonElement | ||
36 | } | ||
37 | |||
38 | private buildLink () { | ||
39 | const url = buildVideoLink({ shortUUID: (this.options_ as PeerTubeLinkButtonOptions).shortUUID }) | ||
40 | |||
41 | return decorateVideoLink({ url, startTime: this.player().currentTime() }) | ||
42 | } | ||
43 | } | ||
44 | |||
45 | videojs.registerComponent('PeerTubeLinkButton', PeerTubeLinkButton) | ||
diff --git a/client/src/assets/player/control-bar/peertube-load-progress-bar.ts b/client/src/assets/player/control-bar/peertube-load-progress-bar.ts new file mode 100644 index 000000000..623e70eb2 --- /dev/null +++ b/client/src/assets/player/control-bar/peertube-load-progress-bar.ts | |||
@@ -0,0 +1,33 @@ | |||
1 | import videojs from 'video.js' | ||
2 | |||
3 | const Component = videojs.getComponent('Component') | ||
4 | |||
5 | class PeerTubeLoadProgressBar extends Component { | ||
6 | |||
7 | constructor (player: videojs.Player, options?: videojs.ComponentOptions) { | ||
8 | super(player, options) | ||
9 | |||
10 | this.on(player, 'progress', this.update) | ||
11 | } | ||
12 | |||
13 | createEl () { | ||
14 | return super.createEl('div', { | ||
15 | className: 'vjs-load-progress', | ||
16 | innerHTML: `<span class="vjs-control-text"><span>${this.localize('Loaded')}</span>: 0%</span>` | ||
17 | }) | ||
18 | } | ||
19 | |||
20 | dispose () { | ||
21 | super.dispose() | ||
22 | } | ||
23 | |||
24 | update () { | ||
25 | const torrent = this.player().webtorrent().getTorrent() | ||
26 | if (!torrent) return | ||
27 | |||
28 | (this.el() as HTMLElement).style.width = (torrent.progress * 100) + '%' | ||
29 | } | ||
30 | |||
31 | } | ||
32 | |||
33 | Component.registerComponent('PeerTubeLoadProgressBar', PeerTubeLoadProgressBar) | ||
diff --git a/client/src/assets/player/control-bar/theater-button.ts b/client/src/assets/player/control-bar/theater-button.ts new file mode 100644 index 000000000..f862ee224 --- /dev/null +++ b/client/src/assets/player/control-bar/theater-button.ts | |||
@@ -0,0 +1,53 @@ | |||
1 | import videojs from 'video.js' | ||
2 | import { saveTheaterInStore, getStoredTheater } from '../peertube-player-local-storage' | ||
3 | |||
4 | const Button = videojs.getComponent('Button') | ||
5 | class TheaterButton extends Button { | ||
6 | |||
7 | private static readonly THEATER_MODE_CLASS = 'vjs-theater-enabled' | ||
8 | |||
9 | constructor (player: videojs.Player, options: videojs.ComponentOptions) { | ||
10 | super(player, options) | ||
11 | |||
12 | const enabled = getStoredTheater() | ||
13 | if (enabled === true) { | ||
14 | this.player().addClass(TheaterButton.THEATER_MODE_CLASS) | ||
15 | |||
16 | this.handleTheaterChange() | ||
17 | } | ||
18 | |||
19 | this.controlText('Theater mode') | ||
20 | |||
21 | this.player().theaterEnabled = enabled | ||
22 | } | ||
23 | |||
24 | buildCSSClass () { | ||
25 | return `vjs-theater-control ${super.buildCSSClass()}` | ||
26 | } | ||
27 | |||
28 | handleTheaterChange () { | ||
29 | const theaterEnabled = this.isTheaterEnabled() | ||
30 | |||
31 | if (theaterEnabled) { | ||
32 | this.controlText('Normal mode') | ||
33 | } else { | ||
34 | this.controlText('Theater mode') | ||
35 | } | ||
36 | |||
37 | saveTheaterInStore(theaterEnabled) | ||
38 | |||
39 | this.player_.trigger('theaterChange', theaterEnabled) | ||
40 | } | ||
41 | |||
42 | handleClick () { | ||
43 | this.player_.toggleClass(TheaterButton.THEATER_MODE_CLASS) | ||
44 | |||
45 | this.handleTheaterChange() | ||
46 | } | ||
47 | |||
48 | private isTheaterEnabled () { | ||
49 | return this.player_.hasClass(TheaterButton.THEATER_MODE_CLASS) | ||
50 | } | ||
51 | } | ||
52 | |||
53 | videojs.registerComponent('TheaterButton', TheaterButton) | ||