aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/assets/player/shared/manager-options
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/assets/player/shared/manager-options')
-rw-r--r--client/src/assets/player/shared/manager-options/control-bar-options-builder.ts137
-rw-r--r--client/src/assets/player/shared/manager-options/hls-options-builder.ts192
-rw-r--r--client/src/assets/player/shared/manager-options/index.ts1
-rw-r--r--client/src/assets/player/shared/manager-options/manager-options-builder.ts169
-rw-r--r--client/src/assets/player/shared/manager-options/webtorrent-options-builder.ts36
5 files changed, 535 insertions, 0 deletions
diff --git a/client/src/assets/player/shared/manager-options/control-bar-options-builder.ts b/client/src/assets/player/shared/manager-options/control-bar-options-builder.ts
new file mode 100644
index 000000000..72a10eb26
--- /dev/null
+++ b/client/src/assets/player/shared/manager-options/control-bar-options-builder.ts
@@ -0,0 +1,137 @@
1import {
2 CommonOptions,
3 NextPreviousVideoButtonOptions,
4 PeerTubeLinkButtonOptions,
5 PeertubePlayerManagerOptions,
6 PlayerMode
7} from '../../types'
8
9export class ControlBarOptionsBuilder {
10 private options: CommonOptions
11
12 constructor (
13 globalOptions: PeertubePlayerManagerOptions,
14 private mode: PlayerMode
15 ) {
16 this.options = globalOptions.common
17 }
18
19 getChildrenOptions () {
20 const children = {}
21
22 if (this.options.previousVideo) {
23 Object.assign(children, this.getPreviousVideo())
24 }
25
26 Object.assign(children, { playToggle: {} })
27
28 if (this.options.nextVideo) {
29 Object.assign(children, this.getNextVideo())
30 }
31
32 Object.assign(children, {
33 currentTimeDisplay: {},
34 timeDivider: {},
35 durationDisplay: {},
36 liveDisplay: {},
37
38 flexibleWidthSpacer: {},
39
40 ...this.getProgressControl(),
41
42 p2PInfoButton: {
43 p2pEnabled: this.options.p2pEnabled
44 },
45
46 muteToggle: {},
47 volumeControl: {},
48
49 ...this.getSettingsButton()
50 })
51
52 if (this.options.peertubeLink === true) {
53 Object.assign(children, {
54 peerTubeLinkButton: { shortUUID: this.options.videoShortUUID } as PeerTubeLinkButtonOptions
55 })
56 }
57
58 if (this.options.theaterButton === true) {
59 Object.assign(children, {
60 theaterButton: {}
61 })
62 }
63
64 Object.assign(children, {
65 fullscreenToggle: {}
66 })
67
68 return children
69 }
70
71 private getSettingsButton () {
72 const settingEntries: string[] = []
73
74 settingEntries.push('playbackRateMenuButton')
75
76 if (this.options.captions === true) settingEntries.push('captionsButton')
77
78 settingEntries.push('resolutionMenuButton')
79
80 return {
81 settingsButton: {
82 setup: {
83 maxHeightOffset: 40
84 },
85 entries: settingEntries
86 }
87 }
88 }
89
90 private getProgressControl () {
91 const loadProgressBar = this.mode === 'webtorrent'
92 ? 'peerTubeLoadProgressBar'
93 : 'loadProgressBar'
94
95 return {
96 progressControl: {
97 children: {
98 seekBar: {
99 children: {
100 [loadProgressBar]: {},
101 mouseTimeDisplay: {},
102 playProgressBar: {}
103 }
104 }
105 }
106 }
107 }
108 }
109
110 private getPreviousVideo () {
111 const buttonOptions: NextPreviousVideoButtonOptions = {
112 type: 'previous',
113 handler: this.options.previousVideo,
114 isDisabled: () => {
115 if (!this.options.hasPreviousVideo) return false
116
117 return !this.options.hasPreviousVideo()
118 }
119 }
120
121 return { previousVideoButton: buttonOptions }
122 }
123
124 private getNextVideo () {
125 const buttonOptions: NextPreviousVideoButtonOptions = {
126 type: 'next',
127 handler: this.options.nextVideo,
128 isDisabled: () => {
129 if (!this.options.hasNextVideo) return false
130
131 return !this.options.hasNextVideo()
132 }
133 }
134
135 return { nextVideoButton: buttonOptions }
136 }
137}
diff --git a/client/src/assets/player/shared/manager-options/hls-options-builder.ts b/client/src/assets/player/shared/manager-options/hls-options-builder.ts
new file mode 100644
index 000000000..e7f664fd4
--- /dev/null
+++ b/client/src/assets/player/shared/manager-options/hls-options-builder.ts
@@ -0,0 +1,192 @@
1import { HybridLoaderSettings } from '@peertube/p2p-media-loader-core'
2import { HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs'
3import { LiveVideoLatencyMode } from '@shared/models'
4import { getAverageBandwidthInStore } from '../../peertube-player-local-storage'
5import { P2PMediaLoader, P2PMediaLoaderPluginOptions } from '../../types'
6import { PeertubePlayerManagerOptions } from '../../types/manager-options'
7import { getRtcConfig } from '../common'
8import { RedundancyUrlManager } from '../p2p-media-loader/redundancy-url-manager'
9import { segmentUrlBuilderFactory } from '../p2p-media-loader/segment-url-builder'
10import { segmentValidatorFactory } from '../p2p-media-loader/segment-validator'
11
12export class HLSOptionsBuilder {
13
14 constructor (
15 private options: PeertubePlayerManagerOptions,
16 private p2pMediaLoaderModule?: any
17 ) {
18
19 }
20
21 getPluginOptions () {
22 const commonOptions = this.options.common
23
24 const redundancyUrlManager = new RedundancyUrlManager(this.options.p2pMediaLoader.redundancyBaseUrls)
25
26 const p2pMediaLoaderConfig = this.getP2PMediaLoaderOptions(redundancyUrlManager)
27 const loader = new this.p2pMediaLoaderModule.Engine(p2pMediaLoaderConfig).createLoaderClass() as P2PMediaLoader
28
29 const p2pMediaLoader: P2PMediaLoaderPluginOptions = {
30 redundancyUrlManager,
31 type: 'application/x-mpegURL',
32 startTime: commonOptions.startTime,
33 src: this.options.p2pMediaLoader.playlistUrl,
34 loader
35 }
36
37 const hlsjs = {
38 levelLabelHandler: (level: { height: number, width: number }) => {
39 const resolution = Math.min(level.height || 0, level.width || 0)
40
41 const file = this.options.p2pMediaLoader.videoFiles.find(f => f.resolution.id === resolution)
42 // We don't have files for live videos
43 if (!file) return level.height
44
45 let label = file.resolution.label
46 if (file.fps >= 50) label += file.fps
47
48 return label
49 },
50 html5: {
51 hlsjsConfig: this.getHLSJSOptions(loader)
52 }
53 }
54
55 return { p2pMediaLoader, hlsjs }
56 }
57
58 // ---------------------------------------------------------------------------
59
60 private getP2PMediaLoaderOptions (redundancyUrlManager: RedundancyUrlManager): HlsJsEngineSettings {
61 let consumeOnly = false
62 if ((navigator as any)?.connection?.type === 'cellular') {
63 console.log('We are on a cellular connection: disabling seeding.')
64 consumeOnly = true
65 }
66
67 const trackerAnnounce = this.options.p2pMediaLoader.trackerAnnounce
68 .filter(t => t.startsWith('ws'))
69
70 const specificLiveOrVODOptions = this.options.common.isLive
71 ? this.getP2PMediaLoaderLiveOptions()
72 : this.getP2PMediaLoaderVODOptions()
73
74 return {
75 loader: {
76
77 trackerAnnounce,
78 rtcConfig: getRtcConfig(),
79
80 simultaneousHttpDownloads: 1,
81 httpFailedSegmentTimeout: 1000,
82
83 segmentValidator: segmentValidatorFactory(this.options.p2pMediaLoader.segmentsSha256Url, this.options.common.isLive),
84 segmentUrlBuilder: segmentUrlBuilderFactory(redundancyUrlManager, 1),
85
86 useP2P: this.options.common.p2pEnabled,
87 consumeOnly,
88
89 ...specificLiveOrVODOptions
90 },
91 segments: {
92 swarmId: this.options.p2pMediaLoader.playlistUrl,
93 forwardSegmentCount: specificLiveOrVODOptions.p2pDownloadMaxPriority
94 }
95 }
96 }
97
98 private getP2PMediaLoaderLiveOptions (): Partial<HybridLoaderSettings> {
99 const base = {
100 requiredSegmentsPriority: 1
101 }
102
103 const latencyMode = this.options.common.liveOptions.latencyMode
104
105 switch (latencyMode) {
106 case LiveVideoLatencyMode.SMALL_LATENCY:
107 return {
108 ...base,
109
110 useP2P: false,
111 httpDownloadProbability: 1
112 }
113
114 case LiveVideoLatencyMode.HIGH_LATENCY:
115 return base
116
117 default:
118 return base
119 }
120 }
121
122 private getP2PMediaLoaderVODOptions (): Partial<HybridLoaderSettings> {
123 return {
124 requiredSegmentsPriority: 3,
125
126 cachedSegmentExpiration: 86400000,
127 cachedSegmentsCount: 100,
128
129 httpDownloadMaxPriority: 9,
130 httpDownloadProbability: 0.06,
131 httpDownloadProbabilitySkipIfNoPeers: true,
132
133 p2pDownloadMaxPriority: 50
134 }
135 }
136
137 // ---------------------------------------------------------------------------
138
139 private getHLSJSOptions (loader: P2PMediaLoader) {
140 const specificLiveOrVODOptions = this.options.common.isLive
141 ? this.getHLSLiveOptions()
142 : this.getHLSVODOptions()
143
144 const base = {
145 capLevelToPlayerSize: true,
146 autoStartLoad: false,
147
148 loader,
149
150 ...specificLiveOrVODOptions
151 }
152
153 const averageBandwidth = getAverageBandwidthInStore()
154 if (!averageBandwidth) return base
155
156 return {
157 ...base,
158
159 abrEwmaDefaultEstimate: averageBandwidth * 8, // We want bit/s
160 startLevel: -1,
161 testBandwidth: false,
162 debug: false
163 }
164 }
165
166 private getHLSLiveOptions () {
167 const latencyMode = this.options.common.liveOptions.latencyMode
168
169 switch (latencyMode) {
170 case LiveVideoLatencyMode.SMALL_LATENCY:
171 return {
172 liveSyncDurationCount: 2
173 }
174
175 case LiveVideoLatencyMode.HIGH_LATENCY:
176 return {
177 liveSyncDurationCount: 10
178 }
179
180 default:
181 return {
182 liveSyncDurationCount: 5
183 }
184 }
185 }
186
187 private getHLSVODOptions () {
188 return {
189 liveSyncDurationCount: 5
190 }
191 }
192}
diff --git a/client/src/assets/player/shared/manager-options/index.ts b/client/src/assets/player/shared/manager-options/index.ts
new file mode 100644
index 000000000..4934d8302
--- /dev/null
+++ b/client/src/assets/player/shared/manager-options/index.ts
@@ -0,0 +1 @@
export * from './manager-options-builder'
diff --git a/client/src/assets/player/shared/manager-options/manager-options-builder.ts b/client/src/assets/player/shared/manager-options/manager-options-builder.ts
new file mode 100644
index 000000000..5dab1f7a9
--- /dev/null
+++ b/client/src/assets/player/shared/manager-options/manager-options-builder.ts
@@ -0,0 +1,169 @@
1import videojs from 'video.js'
2import { copyToClipboard } from '@root-helpers/utils'
3import { buildVideoOrPlaylistEmbed } from '@root-helpers/video'
4import { isIOS, isSafari } from '@root-helpers/web-browser'
5import { buildVideoLink, decorateVideoLink } from '@shared/core-utils'
6import { isDefaultLocale } from '@shared/core-utils/i18n'
7import { VideoJSPluginOptions } from '../../types'
8import { CommonOptions, PeertubePlayerManagerOptions, PlayerMode } from '../../types/manager-options'
9import { ControlBarOptionsBuilder } from './control-bar-options-builder'
10import { HLSOptionsBuilder } from './hls-options-builder'
11import { WebTorrentOptionsBuilder } from './webtorrent-options-builder'
12
13export class ManagerOptionsBuilder {
14
15 constructor (
16 private mode: PlayerMode,
17 private options: PeertubePlayerManagerOptions,
18 private p2pMediaLoaderModule?: any
19 ) {
20
21 }
22
23 getVideojsOptions (alreadyPlayed: boolean): videojs.PlayerOptions {
24 const commonOptions = this.options.common
25
26 let autoplay = this.getAutoPlayValue(commonOptions.autoplay, alreadyPlayed)
27 const html5 = {
28 preloadTextTracks: false
29 }
30
31 const plugins: VideoJSPluginOptions = {
32 peertube: {
33 mode: this.mode,
34 autoplay, // Use peertube plugin autoplay because we could get the file by webtorrent
35 videoViewUrl: commonOptions.videoViewUrl,
36 videoDuration: commonOptions.videoDuration,
37 userWatching: commonOptions.userWatching,
38 subtitle: commonOptions.subtitle,
39 videoCaptions: commonOptions.videoCaptions,
40 stopTime: commonOptions.stopTime,
41 isLive: commonOptions.isLive,
42 videoUUID: commonOptions.videoUUID
43 }
44 }
45
46 if (commonOptions.playlist) {
47 plugins.playlist = commonOptions.playlist
48 }
49
50 if (this.mode === 'p2p-media-loader') {
51 const hlsOptionsBuilder = new HLSOptionsBuilder(this.options, this.p2pMediaLoaderModule)
52
53 Object.assign(plugins, hlsOptionsBuilder.getPluginOptions())
54 } else if (this.mode === 'webtorrent') {
55 const webtorrentOptionsBuilder = new WebTorrentOptionsBuilder(this.options, this.getAutoPlayValue(autoplay, alreadyPlayed))
56
57 Object.assign(plugins, webtorrentOptionsBuilder.getPluginOptions())
58
59 // WebTorrent plugin handles autoplay, because we do some hackish stuff in there
60 autoplay = false
61 }
62
63 const controlBarOptionsBuilder = new ControlBarOptionsBuilder(this.options, this.mode)
64
65 const videojsOptions = {
66 html5,
67
68 // We don't use text track settings for now
69 textTrackSettings: false as any, // FIXME: typings
70 controls: commonOptions.controls !== undefined ? commonOptions.controls : true,
71 loop: commonOptions.loop !== undefined ? commonOptions.loop : false,
72
73 muted: commonOptions.muted !== undefined
74 ? commonOptions.muted
75 : undefined, // Undefined so the player knows it has to check the local storage
76
77 autoplay: this.getAutoPlayValue(autoplay, alreadyPlayed),
78
79 poster: commonOptions.poster,
80 inactivityTimeout: commonOptions.inactivityTimeout,
81 playbackRates: [ 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2 ],
82
83 plugins,
84
85 controlBar: {
86 children: controlBarOptionsBuilder.getChildrenOptions() as any // FIXME: typings
87 }
88 }
89
90 if (commonOptions.language && !isDefaultLocale(commonOptions.language)) {
91 Object.assign(videojsOptions, { language: commonOptions.language })
92 }
93
94 return videojsOptions
95 }
96
97 private getAutoPlayValue (autoplay: any, alreadyPlayed: boolean) {
98 if (autoplay !== true) return autoplay
99
100 // On first play, disable autoplay to avoid issues
101 // But if the player already played videos, we can safely autoplay next ones
102 if (isIOS() || isSafari()) {
103 return alreadyPlayed ? 'play' : false
104 }
105
106 return 'play'
107 }
108
109 getContextMenuOptions (player: videojs.Player, commonOptions: CommonOptions) {
110 const content = () => {
111 const isLoopEnabled = player.options_['loop']
112
113 const items = [
114 {
115 icon: 'repeat',
116 label: player.localize('Play in loop') + (isLoopEnabled ? '<span class="vjs-icon-tick-white"></span>' : ''),
117 listener: function () {
118 player.options_['loop'] = !isLoopEnabled
119 }
120 },
121 {
122 label: player.localize('Copy the video URL'),
123 listener: function () {
124 copyToClipboard(buildVideoLink({ shortUUID: commonOptions.videoShortUUID }))
125 }
126 },
127 {
128 label: player.localize('Copy the video URL at the current time'),
129 listener: function (this: videojs.Player) {
130 const url = buildVideoLink({ shortUUID: commonOptions.videoShortUUID })
131
132 copyToClipboard(decorateVideoLink({ url, startTime: this.currentTime() }))
133 }
134 },
135 {
136 icon: 'code',
137 label: player.localize('Copy embed code'),
138 listener: () => {
139 copyToClipboard(buildVideoOrPlaylistEmbed(commonOptions.embedUrl, commonOptions.embedTitle))
140 }
141 }
142 ]
143
144 if (this.mode === 'webtorrent') {
145 items.push({
146 label: player.localize('Copy magnet URI'),
147 listener: function (this: videojs.Player) {
148 copyToClipboard(this.webtorrent().getCurrentVideoFile().magnetUri)
149 }
150 })
151 }
152
153 items.push({
154 icon: 'info',
155 label: player.localize('Stats for nerds'),
156 listener: () => {
157 player.stats().show()
158 }
159 })
160
161 return items.map(i => ({
162 ...i,
163 label: `<span class="vjs-icon-${i.icon || 'link-2'}"></span>` + i.label
164 }))
165 }
166
167 return { content }
168 }
169}
diff --git a/client/src/assets/player/shared/manager-options/webtorrent-options-builder.ts b/client/src/assets/player/shared/manager-options/webtorrent-options-builder.ts
new file mode 100644
index 000000000..257cf1e05
--- /dev/null
+++ b/client/src/assets/player/shared/manager-options/webtorrent-options-builder.ts
@@ -0,0 +1,36 @@
1import { PeertubePlayerManagerOptions } from '../../types'
2
3export class WebTorrentOptionsBuilder {
4
5 constructor (
6 private options: PeertubePlayerManagerOptions,
7 private autoPlayValue: any
8 ) {
9
10 }
11
12 getPluginOptions () {
13 const commonOptions = this.options.common
14 const webtorrentOptions = this.options.webtorrent
15 const p2pMediaLoaderOptions = this.options.p2pMediaLoader
16
17 const autoplay = this.autoPlayValue === 'play'
18
19 const webtorrent = {
20 autoplay,
21
22 playerRefusedP2P: commonOptions.p2pEnabled === false,
23 videoDuration: commonOptions.videoDuration,
24 playerElement: commonOptions.playerElement,
25
26 videoFiles: webtorrentOptions.videoFiles.length !== 0
27 ? webtorrentOptions.videoFiles
28 // The WebTorrent plugin won't be able to play these files, but it will fallback to HTTP mode
29 : p2pMediaLoaderOptions?.videoFiles || [],
30
31 startTime: commonOptions.startTime
32 }
33
34 return { webtorrent }
35 }
36}