aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/assets/player/videojs-components
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/assets/player/videojs-components')
-rw-r--r--client/src/assets/player/videojs-components/next-previous-video-button.ts50
-rw-r--r--client/src/assets/player/videojs-components/p2p-info-button.ts124
-rw-r--r--client/src/assets/player/videojs-components/peertube-link-button.ts45
-rw-r--r--client/src/assets/player/videojs-components/peertube-load-progress-bar.ts33
-rw-r--r--client/src/assets/player/videojs-components/resolution-menu-button.ts86
-rw-r--r--client/src/assets/player/videojs-components/resolution-menu-item.ts77
-rw-r--r--client/src/assets/player/videojs-components/settings-dialog.ts35
-rw-r--r--client/src/assets/player/videojs-components/settings-menu-button.ts279
-rw-r--r--client/src/assets/player/videojs-components/settings-menu-item.ts378
-rw-r--r--client/src/assets/player/videojs-components/settings-panel-child.ts18
-rw-r--r--client/src/assets/player/videojs-components/settings-panel.ts18
-rw-r--r--client/src/assets/player/videojs-components/theater-button.ts53
12 files changed, 0 insertions, 1196 deletions
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
deleted file mode 100644
index fe17ce2ce..000000000
--- a/client/src/assets/player/videojs-components/next-previous-video-button.ts
+++ /dev/null
@@ -1,50 +0,0 @@
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/p2p-info-button.ts b/client/src/assets/player/videojs-components/p2p-info-button.ts
deleted file mode 100644
index 081dee1d3..000000000
--- a/client/src/assets/player/videojs-components/p2p-info-button.ts
+++ /dev/null
@@ -1,124 +0,0 @@
1import videojs from 'video.js'
2import { PeerTubeP2PInfoButtonOptions, PlayerNetworkInfo } from '../peertube-videojs-typings'
3import { bytes } from '../utils'
4
5const Button = videojs.getComponent('Button')
6class 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
124videojs.registerComponent('P2PInfoButton', P2pInfoButton)
diff --git a/client/src/assets/player/videojs-components/peertube-link-button.ts b/client/src/assets/player/videojs-components/peertube-link-button.ts
deleted file mode 100644
index c49cee566..000000000
--- a/client/src/assets/player/videojs-components/peertube-link-button.ts
+++ /dev/null
@@ -1,45 +0,0 @@
1import videojs from 'video.js'
2import { buildVideoLink, decorateVideoLink } from '@shared/core-utils'
3import { PeerTubeLinkButtonOptions } from '../peertube-videojs-typings'
4
5const Button = videojs.getComponent('Button')
6class 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
45videojs.registerComponent('PeerTubeLinkButton', PeerTubeLinkButton)
diff --git a/client/src/assets/player/videojs-components/peertube-load-progress-bar.ts b/client/src/assets/player/videojs-components/peertube-load-progress-bar.ts
deleted file mode 100644
index 623e70eb2..000000000
--- a/client/src/assets/player/videojs-components/peertube-load-progress-bar.ts
+++ /dev/null
@@ -1,33 +0,0 @@
1import videojs from 'video.js'
2
3const Component = videojs.getComponent('Component')
4
5class 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
33Component.registerComponent('PeerTubeLoadProgressBar', PeerTubeLoadProgressBar)
diff --git a/client/src/assets/player/videojs-components/resolution-menu-button.ts b/client/src/assets/player/videojs-components/resolution-menu-button.ts
deleted file mode 100644
index 8bd5b4f03..000000000
--- a/client/src/assets/player/videojs-components/resolution-menu-button.ts
+++ /dev/null
@@ -1,86 +0,0 @@
1import videojs from 'video.js'
2import { ResolutionMenuItem } from './resolution-menu-item'
3
4const Menu = videojs.getComponent('Menu')
5const MenuButton = videojs.getComponent('MenuButton')
6class ResolutionMenuButton extends MenuButton {
7 labelEl_: HTMLElement
8
9 constructor (player: videojs.Player, options?: videojs.MenuButtonOptions) {
10 super(player, options)
11
12 this.controlText('Quality')
13
14 player.peertubeResolutions().on('resolutionsAdded', () => this.buildQualities())
15
16 // For parent
17 player.peertubeResolutions().on('resolutionChanged', () => {
18 setTimeout(() => this.trigger('labelUpdated'))
19 })
20 }
21
22 createEl () {
23 const el = super.createEl()
24
25 this.labelEl_ = videojs.dom.createEl('div', {
26 className: 'vjs-resolution-value'
27 }) as HTMLElement
28
29 el.appendChild(this.labelEl_)
30
31 return el
32 }
33
34 updateARIAAttributes () {
35 this.el().setAttribute('aria-label', 'Quality')
36 }
37
38 createMenu () {
39 return new Menu(this.player_)
40 }
41
42 buildCSSClass () {
43 return super.buildCSSClass() + ' vjs-resolution-button'
44 }
45
46 buildWrapperCSSClass () {
47 return 'vjs-resolution-control ' + super.buildWrapperCSSClass()
48 }
49
50 private addClickListener (component: any) {
51 component.on('click', () => {
52 const children = this.menu.children()
53
54 for (const child of children) {
55 if (component !== child) {
56 (child as videojs.MenuItem).selected(false)
57 }
58 }
59 })
60 }
61
62 private buildQualities () {
63 for (const d of this.player().peertubeResolutions().getResolutions()) {
64 const label = d.label === '0p'
65 ? this.player().localize('Audio-only')
66 : d.label
67
68 this.menu.addChild(new ResolutionMenuItem(
69 this.player_,
70 {
71 id: d.id,
72 label,
73 selected: d.selected
74 })
75 )
76 }
77
78 for (const m of this.menu.children()) {
79 this.addClickListener(m)
80 }
81
82 this.trigger('menuChanged')
83 }
84}
85
86videojs.registerComponent('ResolutionMenuButton', ResolutionMenuButton)
diff --git a/client/src/assets/player/videojs-components/resolution-menu-item.ts b/client/src/assets/player/videojs-components/resolution-menu-item.ts
deleted file mode 100644
index 6047f52f7..000000000
--- a/client/src/assets/player/videojs-components/resolution-menu-item.ts
+++ /dev/null
@@ -1,77 +0,0 @@
1import videojs from 'video.js'
2
3const MenuItem = videojs.getComponent('MenuItem')
4
5export interface ResolutionMenuItemOptions extends videojs.MenuItemOptions {
6 id: number
7}
8
9class ResolutionMenuItem extends MenuItem {
10 private readonly resolutionId: number
11 private readonly label: string
12
13 private autoResolutionEnabled: boolean
14 private autoResolutionChosen: string
15
16 constructor (player: videojs.Player, options?: ResolutionMenuItemOptions) {
17 options.selectable = true
18
19 super(player, options)
20
21 this.autoResolutionEnabled = true
22 this.autoResolutionChosen = ''
23
24 this.resolutionId = options.id
25 this.label = options.label
26
27 player.peertubeResolutions().on('resolutionChanged', () => this.updateSelection())
28
29 // We only want to disable the "Auto" item
30 if (this.resolutionId === -1) {
31 player.peertubeResolutions().on('autoResolutionEnabledChanged', () => this.updateAutoResolution())
32 }
33 }
34
35 handleClick (event: any) {
36 // Auto button disabled?
37 if (this.autoResolutionEnabled === false && this.resolutionId === -1) return
38
39 super.handleClick(event)
40
41 this.player().peertubeResolutions().select({ id: this.resolutionId, byEngine: false })
42 }
43
44 updateSelection () {
45 const selectedResolution = this.player().peertubeResolutions().getSelected()
46
47 if (this.resolutionId === -1) {
48 this.autoResolutionChosen = this.player().peertubeResolutions().getAutoResolutionChosen()?.label
49 }
50
51 this.selected(this.resolutionId === selectedResolution.id)
52 }
53
54 updateAutoResolution () {
55 const enabled = this.player().peertubeResolutions().isAutoResolutionEnabeld()
56
57 // Check if the auto resolution is enabled or not
58 if (enabled === false) {
59 this.addClass('disabled')
60 } else {
61 this.removeClass('disabled')
62 }
63
64 this.autoResolutionEnabled = enabled
65 }
66
67 getLabel () {
68 if (this.resolutionId === -1) {
69 return this.label + ' <small>' + this.autoResolutionChosen + '</small>'
70 }
71
72 return this.label
73 }
74}
75videojs.registerComponent('ResolutionMenuItem', ResolutionMenuItem)
76
77export { ResolutionMenuItem }
diff --git a/client/src/assets/player/videojs-components/settings-dialog.ts b/client/src/assets/player/videojs-components/settings-dialog.ts
deleted file mode 100644
index 8cd98967f..000000000
--- a/client/src/assets/player/videojs-components/settings-dialog.ts
+++ /dev/null
@@ -1,35 +0,0 @@
1import videojs from 'video.js'
2
3const Component = videojs.getComponent('Component')
4
5class SettingsDialog extends Component {
6 constructor (player: videojs.Player) {
7 super(player)
8
9 this.hide()
10 }
11
12 /**
13 * Create the component's DOM element
14 *
15 */
16 createEl () {
17 const uniqueId = this.id()
18 const dialogLabelId = 'TTsettingsDialogLabel-' + uniqueId
19 const dialogDescriptionId = 'TTsettingsDialogDescription-' + uniqueId
20
21 return super.createEl('div', {
22 className: 'vjs-settings-dialog vjs-modal-overlay',
23 innerHTML: '',
24 tabIndex: -1
25 }, {
26 role: 'dialog',
27 'aria-labelledby': dialogLabelId,
28 'aria-describedby': dialogDescriptionId
29 })
30 }
31}
32
33Component.registerComponent('SettingsDialog', SettingsDialog)
34
35export { SettingsDialog }
diff --git a/client/src/assets/player/videojs-components/settings-menu-button.ts b/client/src/assets/player/videojs-components/settings-menu-button.ts
deleted file mode 100644
index 6de390f4d..000000000
--- a/client/src/assets/player/videojs-components/settings-menu-button.ts
+++ /dev/null
@@ -1,279 +0,0 @@
1// Thanks to Yanko Shterev: https://github.com/yshterev/videojs-settings-menu
2import { SettingsMenuItem } from './settings-menu-item'
3import { toTitleCase } from '../utils'
4import videojs from 'video.js'
5
6import { SettingsDialog } from './settings-dialog'
7import { SettingsPanel } from './settings-panel'
8import { SettingsPanelChild } from './settings-panel-child'
9
10const Button = videojs.getComponent('Button')
11const Menu = videojs.getComponent('Menu')
12const Component = videojs.getComponent('Component')
13
14export interface SettingsButtonOptions extends videojs.ComponentOptions {
15 entries: any[]
16 setup?: {
17 maxHeightOffset: number
18 }
19}
20
21class SettingsButton extends Button {
22 dialog: SettingsDialog
23 dialogEl: HTMLElement
24 menu: videojs.Menu
25 panel: SettingsPanel
26 panelChild: SettingsPanelChild
27
28 addSettingsItemHandler: typeof SettingsButton.prototype.onAddSettingsItem
29 disposeSettingsItemHandler: typeof SettingsButton.prototype.onDisposeSettingsItem
30 documentClickHandler: typeof SettingsButton.prototype.onDocumentClick
31 userInactiveHandler: typeof SettingsButton.prototype.onUserInactive
32
33 private settingsButtonOptions: SettingsButtonOptions
34
35 constructor (player: videojs.Player, options?: SettingsButtonOptions) {
36 super(player, options)
37
38 this.settingsButtonOptions = options
39
40 this.controlText('Settings')
41
42 this.dialog = this.player().addChild('settingsDialog')
43 this.dialogEl = this.dialog.el() as HTMLElement
44 this.menu = null
45 this.panel = this.dialog.addChild('settingsPanel')
46 this.panelChild = this.panel.addChild('settingsPanelChild')
47
48 this.addClass('vjs-settings')
49 this.el().setAttribute('aria-label', 'Settings Button')
50
51 // Event handlers
52 this.addSettingsItemHandler = this.onAddSettingsItem.bind(this)
53 this.disposeSettingsItemHandler = this.onDisposeSettingsItem.bind(this)
54 this.documentClickHandler = this.onDocumentClick.bind(this)
55 this.userInactiveHandler = this.onUserInactive.bind(this)
56
57 this.buildMenu()
58 this.bindEvents()
59
60 // Prepare the dialog
61 this.player().one('play', () => this.hideDialog())
62 }
63
64 onDocumentClick (event: MouseEvent) {
65 const element = event.target as HTMLElement
66
67 if (element?.classList?.contains('vjs-settings') || element?.parentElement?.classList?.contains('vjs-settings')) {
68 return
69 }
70
71 if (!this.dialog.hasClass('vjs-hidden')) {
72 this.hideDialog()
73 }
74 }
75
76 onDisposeSettingsItem (event: any, name: string) {
77 if (name === undefined) {
78 const children = this.menu.children()
79
80 while (children.length > 0) {
81 children[0].dispose()
82 this.menu.removeChild(children[0])
83 }
84
85 this.addClass('vjs-hidden')
86 } else {
87 const item = this.menu.getChild(name)
88
89 if (item) {
90 item.dispose()
91 this.menu.removeChild(item)
92 }
93 }
94
95 this.hideDialog()
96
97 if (this.settingsButtonOptions.entries.length === 0) {
98 this.addClass('vjs-hidden')
99 }
100 }
101
102 dispose () {
103 document.removeEventListener('click', this.documentClickHandler)
104
105 if (this.isInIframe()) {
106 window.removeEventListener('blur', this.documentClickHandler)
107 }
108 }
109
110 onAddSettingsItem (event: any, data: any) {
111 const [ entry, options ] = data
112
113 this.addMenuItem(entry, options)
114 this.removeClass('vjs-hidden')
115 }
116
117 onUserInactive () {
118 if (!this.dialog.hasClass('vjs-hidden')) {
119 this.hideDialog()
120 }
121 }
122
123 bindEvents () {
124 document.addEventListener('click', this.documentClickHandler)
125 if (this.isInIframe()) {
126 window.addEventListener('blur', this.documentClickHandler)
127 }
128
129 this.player().on('addsettingsitem', this.addSettingsItemHandler)
130 this.player().on('disposesettingsitem', this.disposeSettingsItemHandler)
131 this.player().on('userinactive', this.userInactiveHandler)
132 }
133
134 buildCSSClass () {
135 return `vjs-icon-settings ${super.buildCSSClass()}`
136 }
137
138 handleClick () {
139 if (this.dialog.hasClass('vjs-hidden')) {
140 this.showDialog()
141 } else {
142 this.hideDialog()
143 }
144 }
145
146 showDialog () {
147 this.player().peertube().onMenuOpened();
148
149 (this.menu.el() as HTMLElement).style.opacity = '1'
150
151 this.dialog.show()
152 this.el().setAttribute('aria-expanded', 'true')
153
154 this.setDialogSize(this.getComponentSize(this.menu))
155
156 const firstChild = this.menu.children()[0]
157 if (firstChild) firstChild.focus()
158 }
159
160 hideDialog () {
161 this.player_.peertube().onMenuClosed()
162
163 this.dialog.hide()
164 this.el().setAttribute('aria-expanded', 'false')
165
166 this.setDialogSize(this.getComponentSize(this.menu));
167 (this.menu.el() as HTMLElement).style.opacity = '1'
168 this.resetChildren()
169 }
170
171 getComponentSize (element: videojs.Component | HTMLElement) {
172 let width: number = null
173 let height: number = null
174
175 // Could be component or just DOM element
176 if (element instanceof Component) {
177 const el = element.el() as HTMLElement
178
179 width = el.offsetWidth
180 height = el.offsetHeight;
181
182 (element as any).width = width;
183 (element as any).height = height
184 } else {
185 width = element.offsetWidth
186 height = element.offsetHeight
187 }
188
189 return [ width, height ]
190 }
191
192 setDialogSize ([ width, height ]: number[]) {
193 if (typeof height !== 'number') {
194 return
195 }
196
197 const offset = this.settingsButtonOptions.setup.maxHeightOffset
198 const maxHeight = (this.player().el() as HTMLElement).offsetHeight - offset
199
200 const panelEl = this.panel.el() as HTMLElement
201
202 if (height > maxHeight) {
203 height = maxHeight
204 width += 17
205 panelEl.style.maxHeight = `${height}px`
206 } else if (panelEl.style.maxHeight !== '') {
207 panelEl.style.maxHeight = ''
208 }
209
210 this.dialogEl.style.width = `${width}px`
211 this.dialogEl.style.height = `${height}px`
212 }
213
214 buildMenu () {
215 this.menu = new Menu(this.player())
216 this.menu.addClass('vjs-main-menu')
217 const entries = this.settingsButtonOptions.entries
218
219 if (entries.length === 0) {
220 this.addClass('vjs-hidden')
221 this.panelChild.addChild(this.menu)
222 return
223 }
224
225 for (const entry of entries) {
226 this.addMenuItem(entry, this.settingsButtonOptions)
227 }
228
229 this.panelChild.addChild(this.menu)
230 }
231
232 addMenuItem (entry: any, options: any) {
233 const openSubMenu = function (this: any) {
234 if (videojs.dom.hasClass(this.el_, 'open')) {
235 videojs.dom.removeClass(this.el_, 'open')
236 } else {
237 videojs.dom.addClass(this.el_, 'open')
238 }
239 }
240
241 options.name = toTitleCase(entry)
242
243 const newOptions = Object.assign({}, options, { entry, menuButton: this })
244 const settingsMenuItem = new SettingsMenuItem(this.player(), newOptions)
245
246 this.menu.addChild(settingsMenuItem)
247
248 // Hide children to avoid sub menus stacking on top of each other
249 // or having multiple menus open
250 settingsMenuItem.on('click', videojs.bind(this, this.hideChildren))
251
252 // Whether to add or remove selected class on the settings sub menu element
253 settingsMenuItem.on('click', openSubMenu)
254 }
255
256 resetChildren () {
257 for (const menuChild of this.menu.children()) {
258 (menuChild as SettingsMenuItem).reset()
259 }
260 }
261
262 /**
263 * Hide all the sub menus
264 */
265 hideChildren () {
266 for (const menuChild of this.menu.children()) {
267 (menuChild as SettingsMenuItem).hideSubMenu()
268 }
269 }
270
271 isInIframe () {
272 return window.self !== window.top
273 }
274
275}
276
277Component.registerComponent('SettingsButton', SettingsButton)
278
279export { SettingsButton }
diff --git a/client/src/assets/player/videojs-components/settings-menu-item.ts b/client/src/assets/player/videojs-components/settings-menu-item.ts
deleted file mode 100644
index 31d42c456..000000000
--- a/client/src/assets/player/videojs-components/settings-menu-item.ts
+++ /dev/null
@@ -1,378 +0,0 @@
1import videojs from 'video.js'
2// Thanks to Yanko Shterev: https://github.com/yshterev/videojs-settings-menu
3import { toTitleCase } from '../utils'
4import { SettingsDialog } from './settings-dialog'
5import { SettingsButton } from './settings-menu-button'
6import { SettingsPanel } from './settings-panel'
7import { SettingsPanelChild } from './settings-panel-child'
8
9const MenuItem = videojs.getComponent('MenuItem')
10const component = videojs.getComponent('Component')
11
12export interface SettingsMenuItemOptions extends videojs.MenuItemOptions {
13 entry: string
14 menuButton: SettingsButton
15}
16
17class SettingsMenuItem extends MenuItem {
18 settingsButton: SettingsButton
19 dialog: SettingsDialog
20 mainMenu: videojs.Menu
21 panel: SettingsPanel
22 panelChild: SettingsPanelChild
23 panelChildEl: HTMLElement
24 size: number[]
25 menuToLoad: string
26 subMenu: SettingsButton
27
28 submenuClickHandler: typeof SettingsMenuItem.prototype.onSubmenuClick
29 transitionEndHandler: typeof SettingsMenuItem.prototype.onTransitionEnd
30
31 settingsSubMenuTitleEl_: HTMLElement
32 settingsSubMenuValueEl_: HTMLElement
33 settingsSubMenuEl_: HTMLElement
34
35 constructor (player: videojs.Player, options?: SettingsMenuItemOptions) {
36 super(player, options)
37
38 this.settingsButton = options.menuButton
39 this.dialog = this.settingsButton.dialog
40 this.mainMenu = this.settingsButton.menu
41 this.panel = this.dialog.getChild('settingsPanel')
42 this.panelChild = this.panel.getChild('settingsPanelChild')
43 this.panelChildEl = this.panelChild.el() as HTMLElement
44
45 this.size = null
46
47 // keep state of what menu type is loading next
48 this.menuToLoad = 'mainmenu'
49
50 const subMenuName = toTitleCase(options.entry)
51 const SubMenuComponent = videojs.getComponent(subMenuName)
52
53 if (!SubMenuComponent) {
54 throw new Error(`Component ${subMenuName} does not exist`)
55 }
56
57 const newOptions = Object.assign({}, options, { entry: options.menuButton, menuButton: this })
58
59 this.subMenu = new SubMenuComponent(this.player(), newOptions) as SettingsButton
60 const subMenuClass = this.subMenu.buildCSSClass().split(' ')[0]
61 this.settingsSubMenuEl_.className += ' ' + subMenuClass
62
63 this.eventHandlers()
64
65 player.ready(() => {
66 // Voodoo magic for IOS
67 setTimeout(() => {
68 // Player was destroyed
69 if (!this.player_) return
70
71 this.build()
72
73 // Update on rate change
74 player.on('ratechange', this.submenuClickHandler)
75
76 if (subMenuName === 'CaptionsButton') {
77 // Hack to regenerate captions on HTTP fallback
78 player.on('captionsChanged', () => {
79 setTimeout(() => {
80 this.settingsSubMenuEl_.innerHTML = ''
81 this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el())
82 this.update()
83 this.bindClickEvents()
84 }, 0)
85 })
86 }
87
88 this.reset()
89 }, 0)
90 })
91 }
92
93 eventHandlers () {
94 this.submenuClickHandler = this.onSubmenuClick.bind(this)
95 this.transitionEndHandler = this.onTransitionEnd.bind(this)
96 }
97
98 onSubmenuClick (event: any) {
99 let target = null
100
101 if (event.type === 'tap') {
102 target = event.target
103 } else {
104 target = event.currentTarget || event.target
105 }
106
107 if (target?.classList.contains('vjs-back-button')) {
108 this.loadMainMenu()
109 return
110 }
111
112 // To update the sub menu value on click, setTimeout is needed because
113 // updating the value is not instant
114 setTimeout(() => this.update(event), 0)
115
116 // Seems like videojs adds a vjs-hidden class on the caption menu after a click
117 // We don't need it
118 this.subMenu.menu.removeClass('vjs-hidden')
119 }
120
121 /**
122 * Create the component's DOM element
123 *
124 */
125 createEl () {
126 const el = videojs.dom.createEl('li', {
127 className: 'vjs-menu-item',
128 tabIndex: -1
129 })
130
131 this.settingsSubMenuTitleEl_ = videojs.dom.createEl('div', {
132 className: 'vjs-settings-sub-menu-title'
133 }) as HTMLElement
134
135 el.appendChild(this.settingsSubMenuTitleEl_)
136
137 this.settingsSubMenuValueEl_ = videojs.dom.createEl('div', {
138 className: 'vjs-settings-sub-menu-value'
139 }) as HTMLElement
140
141 el.appendChild(this.settingsSubMenuValueEl_)
142
143 this.settingsSubMenuEl_ = videojs.dom.createEl('div', {
144 className: 'vjs-settings-sub-menu'
145 }) as HTMLElement
146
147 return el as HTMLLIElement
148 }
149
150 /**
151 * Handle click on menu item
152 *
153 * @method handleClick
154 */
155 handleClick (event: videojs.EventTarget.Event) {
156 this.menuToLoad = 'submenu'
157 // Remove open class to ensure only the open submenu gets this class
158 videojs.dom.removeClass(this.el(), 'open')
159
160 super.handleClick(event);
161
162 (this.mainMenu.el() as HTMLElement).style.opacity = '0'
163 // Whether to add or remove vjs-hidden class on the settingsSubMenuEl element
164 if (videojs.dom.hasClass(this.settingsSubMenuEl_, 'vjs-hidden')) {
165 videojs.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
166
167 // animation not played without timeout
168 setTimeout(() => {
169 this.settingsSubMenuEl_.style.opacity = '1'
170 this.settingsSubMenuEl_.style.marginRight = '0px'
171 }, 0)
172
173 this.settingsButton.setDialogSize(this.size)
174
175 const firstChild = this.subMenu.menu.children()[0]
176 if (firstChild) firstChild.focus()
177 } else {
178 videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
179 }
180 }
181
182 /**
183 * Create back button
184 *
185 * @method createBackButton
186 */
187 createBackButton () {
188 const button = this.subMenu.menu.addChild('MenuItem', {}, 0)
189
190 button.addClass('vjs-back-button');
191 (button.el() as HTMLElement).innerHTML = this.player().localize(this.subMenu.controlText())
192 }
193
194 /**
195 * Add/remove prefixed event listener for CSS Transition
196 *
197 * @method PrefixedEvent
198 */
199 PrefixedEvent (element: any, type: any, callback: any, action = 'addEvent') {
200 const prefix = [ 'webkit', 'moz', 'MS', 'o', '' ]
201
202 for (let p = 0; p < prefix.length; p++) {
203 if (!prefix[p]) {
204 type = type.toLowerCase()
205 }
206
207 if (action === 'addEvent') {
208 element.addEventListener(prefix[p] + type, callback, false)
209 } else if (action === 'removeEvent') {
210 element.removeEventListener(prefix[p] + type, callback, false)
211 }
212 }
213 }
214
215 onTransitionEnd (event: any) {
216 if (event.propertyName !== 'margin-right') {
217 return
218 }
219
220 if (this.menuToLoad === 'mainmenu') {
221 // hide submenu
222 videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
223
224 // reset opacity to 0
225 this.settingsSubMenuEl_.style.opacity = '0'
226 }
227 }
228
229 reset () {
230 videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
231 this.settingsSubMenuEl_.style.opacity = '0'
232 this.setMargin()
233 }
234
235 loadMainMenu () {
236 const mainMenuEl = this.mainMenu.el() as HTMLElement
237 this.menuToLoad = 'mainmenu'
238 this.mainMenu.show()
239 mainMenuEl.style.opacity = '0'
240
241 // back button will always take you to main menu, so set dialog sizes
242 const mainMenuAny = this.mainMenu as any
243 this.settingsButton.setDialogSize([ mainMenuAny.width, mainMenuAny.height ])
244
245 // animation not triggered without timeout (some async stuff ?!?)
246 setTimeout(() => {
247 // animate margin and opacity before hiding the submenu
248 // this triggers CSS Transition event
249 this.setMargin()
250 mainMenuEl.style.opacity = '1'
251
252 const firstChild = this.mainMenu.children()[0]
253 if (firstChild) firstChild.focus()
254 }, 0)
255 }
256
257 build () {
258 this.subMenu.on('labelUpdated', () => {
259 this.update()
260 })
261 this.subMenu.on('menuChanged', () => {
262 this.bindClickEvents()
263 this.setSize()
264 this.update()
265 })
266
267 this.settingsSubMenuTitleEl_.innerHTML = this.player().localize(this.subMenu.controlText())
268 this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el())
269 this.panelChildEl.appendChild(this.settingsSubMenuEl_)
270 this.update()
271
272 this.createBackButton()
273 this.setSize()
274 this.bindClickEvents()
275
276 // prefixed event listeners for CSS TransitionEnd
277 this.PrefixedEvent(
278 this.settingsSubMenuEl_,
279 'TransitionEnd',
280 this.transitionEndHandler,
281 'addEvent'
282 )
283 }
284
285 update (event?: any) {
286 let target: HTMLElement = null
287 const subMenu = this.subMenu.name()
288
289 if (event && event.type === 'tap') {
290 target = event.target
291 } else if (event) {
292 target = event.currentTarget
293 }
294
295 // Playback rate menu button doesn't get a vjs-selected class
296 // or sets options_['selected'] on the selected playback rate.
297 // Thus we get the submenu value based on the labelEl of playbackRateMenuButton
298 if (subMenu === 'PlaybackRateMenuButton') {
299 const html = (this.subMenu as any).labelEl_.innerHTML
300
301 setTimeout(() => {
302 this.settingsSubMenuValueEl_.innerHTML = html
303 }, 250)
304 } else {
305 // Loop trough the submenu items to find the selected child
306 for (const subMenuItem of this.subMenu.menu.children_) {
307 if (!(subMenuItem instanceof component)) {
308 continue
309 }
310
311 if (subMenuItem.hasClass('vjs-selected')) {
312 const subMenuItemUntyped = subMenuItem as any
313
314 // Prefer to use the function
315 if (typeof subMenuItemUntyped.getLabel === 'function') {
316 this.settingsSubMenuValueEl_.innerHTML = subMenuItemUntyped.getLabel()
317 break
318 }
319
320 this.settingsSubMenuValueEl_.innerHTML = this.player().localize(subMenuItemUntyped.options_.label)
321 }
322 }
323 }
324
325 if (target && !target.classList.contains('vjs-back-button')) {
326 this.settingsButton.hideDialog()
327 }
328 }
329
330 bindClickEvents () {
331 for (const item of this.subMenu.menu.children()) {
332 if (!(item instanceof component)) {
333 continue
334 }
335 item.on([ 'tap', 'click' ], this.submenuClickHandler)
336 }
337 }
338
339 // save size of submenus on first init
340 // if number of submenu items change dynamically more logic will be needed
341 setSize () {
342 this.dialog.removeClass('vjs-hidden')
343 videojs.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
344 this.size = this.settingsButton.getComponentSize(this.settingsSubMenuEl_)
345 this.setMargin()
346 this.dialog.addClass('vjs-hidden')
347 videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
348 }
349
350 setMargin () {
351 if (!this.size) return
352
353 const [ width ] = this.size
354
355 this.settingsSubMenuEl_.style.marginRight = `-${width}px`
356 }
357
358 /**
359 * Hide the sub menu
360 */
361 hideSubMenu () {
362 // after removing settings item this.el_ === null
363 if (!this.el()) {
364 return
365 }
366
367 if (videojs.dom.hasClass(this.el(), 'open')) {
368 videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
369 videojs.dom.removeClass(this.el(), 'open')
370 }
371 }
372
373}
374
375(SettingsMenuItem as any).prototype.contentElType = 'button'
376videojs.registerComponent('SettingsMenuItem', SettingsMenuItem)
377
378export { SettingsMenuItem }
diff --git a/client/src/assets/player/videojs-components/settings-panel-child.ts b/client/src/assets/player/videojs-components/settings-panel-child.ts
deleted file mode 100644
index 161420c38..000000000
--- a/client/src/assets/player/videojs-components/settings-panel-child.ts
+++ /dev/null
@@ -1,18 +0,0 @@
1import videojs from 'video.js'
2
3const Component = videojs.getComponent('Component')
4
5class SettingsPanelChild extends Component {
6
7 createEl () {
8 return super.createEl('div', {
9 className: 'vjs-settings-panel-child',
10 innerHTML: '',
11 tabIndex: -1
12 })
13 }
14}
15
16Component.registerComponent('SettingsPanelChild', SettingsPanelChild)
17
18export { SettingsPanelChild }
diff --git a/client/src/assets/player/videojs-components/settings-panel.ts b/client/src/assets/player/videojs-components/settings-panel.ts
deleted file mode 100644
index 28b579bdd..000000000
--- a/client/src/assets/player/videojs-components/settings-panel.ts
+++ /dev/null
@@ -1,18 +0,0 @@
1import videojs from 'video.js'
2
3const Component = videojs.getComponent('Component')
4
5class SettingsPanel extends Component {
6
7 createEl () {
8 return super.createEl('div', {
9 className: 'vjs-settings-panel',
10 innerHTML: '',
11 tabIndex: -1
12 })
13 }
14}
15
16Component.registerComponent('SettingsPanel', SettingsPanel)
17
18export { SettingsPanel }
diff --git a/client/src/assets/player/videojs-components/theater-button.ts b/client/src/assets/player/videojs-components/theater-button.ts
deleted file mode 100644
index f862ee224..000000000
--- a/client/src/assets/player/videojs-components/theater-button.ts
+++ /dev/null
@@ -1,53 +0,0 @@
1import videojs from 'video.js'
2import { saveTheaterInStore, getStoredTheater } from '../peertube-player-local-storage'
3
4const Button = videojs.getComponent('Button')
5class 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
53videojs.registerComponent('TheaterButton', TheaterButton)