aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/assets/player/manager-options
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-03-14 11:16:54 +0100
committerChocobozzz <me@florianbigard.com>2022-03-14 11:36:38 +0100
commit9597920ee3d4ac99803e7107983ddf98a9dfb3c4 (patch)
tree62c98909b4eb30ece3c0f58c26c8555e6bef1e31 /client/src/assets/player/manager-options
parent9af2accee68082e4e1160a4e4a7036451262be02 (diff)
downloadPeerTube-9597920ee3d4ac99803e7107983ddf98a9dfb3c4.tar.gz
PeerTube-9597920ee3d4ac99803e7107983ddf98a9dfb3c4.tar.zst
PeerTube-9597920ee3d4ac99803e7107983ddf98a9dfb3c4.zip
Reorganize player manager options builder
Diffstat (limited to 'client/src/assets/player/manager-options')
-rw-r--r--client/src/assets/player/manager-options/control-bar-options-builder.ts132
-rw-r--r--client/src/assets/player/manager-options/hls-options-builder.ts192
-rw-r--r--client/src/assets/player/manager-options/manager-options-builder.ts168
-rw-r--r--client/src/assets/player/manager-options/manager-options.model.ts84
-rw-r--r--client/src/assets/player/manager-options/webtorrent-options-builder.ts36
5 files changed, 612 insertions, 0 deletions
diff --git a/client/src/assets/player/manager-options/control-bar-options-builder.ts b/client/src/assets/player/manager-options/control-bar-options-builder.ts
new file mode 100644
index 000000000..54e61c5d0
--- /dev/null
+++ b/client/src/assets/player/manager-options/control-bar-options-builder.ts
@@ -0,0 +1,132 @@
1import { NextPreviousVideoButtonOptions, PeerTubeLinkButtonOptions } from '../peertube-videojs-typings'
2import { CommonOptions, PeertubePlayerManagerOptions, PlayerMode } from './manager-options.model'
3
4export class ControlBarOptionsBuilder {
5 private options: CommonOptions
6
7 constructor (
8 globalOptions: PeertubePlayerManagerOptions,
9 private mode: PlayerMode
10 ) {
11 this.options = globalOptions.common
12 }
13
14 getChildrenOptions () {
15 const children = {}
16
17 if (this.options.previousVideo) {
18 Object.assign(children, this.getPreviousVideo())
19 }
20
21 Object.assign(children, { playToggle: {} })
22
23 if (this.options.nextVideo) {
24 Object.assign(children, this.getNextVideo())
25 }
26
27 Object.assign(children, {
28 currentTimeDisplay: {},
29 timeDivider: {},
30 durationDisplay: {},
31 liveDisplay: {},
32
33 flexibleWidthSpacer: {},
34
35 ...this.getProgressControl(),
36
37 p2PInfoButton: {
38 p2pEnabled: this.options.p2pEnabled
39 },
40
41 muteToggle: {},
42 volumeControl: {},
43
44 settingsButton: this.getSettingsButton()
45 })
46
47 if (this.options.peertubeLink === true) {
48 Object.assign(children, {
49 peerTubeLinkButton: { shortUUID: this.options.videoShortUUID } as PeerTubeLinkButtonOptions
50 })
51 }
52
53 if (this.options.theaterButton === true) {
54 Object.assign(children, {
55 theaterButton: {}
56 })
57 }
58
59 Object.assign(children, {
60 fullscreenToggle: {}
61 })
62
63 return children
64 }
65
66 private getSettingsButton () {
67 const settingEntries: string[] = []
68
69 settingEntries.push('playbackRateMenuButton')
70
71 if (this.options.captions === true) settingEntries.push('captionsButton')
72
73 settingEntries.push('resolutionMenuButton')
74
75 return {
76 settingsButton: {
77 setup: {
78 maxHeightOffset: 40
79 },
80 entries: settingEntries
81 }
82 }
83 }
84
85 private getProgressControl () {
86 const loadProgressBar = this.mode === 'webtorrent'
87 ? 'peerTubeLoadProgressBar'
88 : 'loadProgressBar'
89
90 return {
91 progressControl: {
92 children: {
93 seekBar: {
94 children: {
95 [loadProgressBar]: {},
96 mouseTimeDisplay: {},
97 playProgressBar: {}
98 }
99 }
100 }
101 }
102 }
103 }
104
105 private getPreviousVideo () {
106 const buttonOptions: NextPreviousVideoButtonOptions = {
107 type: 'previous',
108 handler: this.options.previousVideo,
109 isDisabled: () => {
110 if (!this.options.hasPreviousVideo) return false
111
112 return !this.options.hasPreviousVideo()
113 }
114 }
115
116 return { previousVideoButton: buttonOptions }
117 }
118
119 private getNextVideo () {
120 const buttonOptions: NextPreviousVideoButtonOptions = {
121 type: 'next',
122 handler: this.options.nextVideo,
123 isDisabled: () => {
124 if (!this.options.hasNextVideo) return false
125
126 return !this.options.hasNextVideo()
127 }
128 }
129
130 return { nextVideoButton: buttonOptions }
131 }
132}
diff --git a/client/src/assets/player/manager-options/hls-options-builder.ts b/client/src/assets/player/manager-options/hls-options-builder.ts
new file mode 100644
index 000000000..9de23561b
--- /dev/null
+++ b/client/src/assets/player/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 { RedundancyUrlManager } from '../p2p-media-loader/redundancy-url-manager'
5import { segmentUrlBuilderFactory } from '../p2p-media-loader/segment-url-builder'
6import { segmentValidatorFactory } from '../p2p-media-loader/segment-validator'
7import { getAverageBandwidthInStore } from '../peertube-player-local-storage'
8import { P2PMediaLoader, P2PMediaLoaderPluginOptions } from '../peertube-videojs-typings'
9import { getRtcConfig } from '../utils'
10import { PeertubePlayerManagerOptions } from './manager-options.model'
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/manager-options/manager-options-builder.ts b/client/src/assets/player/manager-options/manager-options-builder.ts
new file mode 100644
index 000000000..14bdb5d96
--- /dev/null
+++ b/client/src/assets/player/manager-options/manager-options-builder.ts
@@ -0,0 +1,168 @@
1import videojs from 'video.js'
2import { buildVideoLink, decorateVideoLink } from '@shared/core-utils'
3import { isDefaultLocale } from '@shared/core-utils/i18n'
4import { copyToClipboard } from '../../../root-helpers/utils'
5import { VideoJSPluginOptions } from '../peertube-videojs-typings'
6import { buildVideoOrPlaylistEmbed, isIOS, isSafari } from '../utils'
7import { ControlBarOptionsBuilder } from './control-bar-options-builder'
8import { HLSOptionsBuilder } from './hls-options-builder'
9import { CommonOptions, PeertubePlayerManagerOptions, PlayerMode } from './manager-options.model'
10import { WebTorrentOptionsBuilder } from './webtorrent-options-builder'
11
12export class ManagerOptionsBuilder {
13
14 constructor (
15 private mode: PlayerMode,
16 private options: PeertubePlayerManagerOptions,
17 private p2pMediaLoaderModule?: any
18 ) {
19
20 }
21
22 getVideojsOptions (alreadyPlayed: boolean): videojs.PlayerOptions {
23 const commonOptions = this.options.common
24
25 let autoplay = this.getAutoPlayValue(commonOptions.autoplay, alreadyPlayed)
26 const html5 = {
27 preloadTextTracks: false
28 }
29
30 const plugins: VideoJSPluginOptions = {
31 peertube: {
32 mode: this.mode,
33 autoplay, // Use peertube plugin autoplay because we could get the file by webtorrent
34 videoViewUrl: commonOptions.videoViewUrl,
35 videoDuration: commonOptions.videoDuration,
36 userWatching: commonOptions.userWatching,
37 subtitle: commonOptions.subtitle,
38 videoCaptions: commonOptions.videoCaptions,
39 stopTime: commonOptions.stopTime,
40 isLive: commonOptions.isLive,
41 videoUUID: commonOptions.videoUUID
42 }
43 }
44
45 if (commonOptions.playlist) {
46 plugins.playlist = commonOptions.playlist
47 }
48
49 if (this.mode === 'p2p-media-loader') {
50 const hlsOptionsBuilder = new HLSOptionsBuilder(this.options, this.p2pMediaLoaderModule)
51
52 Object.assign(plugins, hlsOptionsBuilder.getPluginOptions())
53 } else if (this.mode === 'webtorrent') {
54 const webtorrentOptionsBuilder = new WebTorrentOptionsBuilder(this.options, this.getAutoPlayValue(autoplay, alreadyPlayed))
55
56 Object.assign(plugins, webtorrentOptionsBuilder.getPluginOptions())
57
58 // WebTorrent plugin handles autoplay, because we do some hackish stuff in there
59 autoplay = false
60 }
61
62 const controlBarOptionsBuilder = new ControlBarOptionsBuilder(this.options, this.mode)
63
64 const videojsOptions = {
65 html5,
66
67 // We don't use text track settings for now
68 textTrackSettings: false as any, // FIXME: typings
69 controls: commonOptions.controls !== undefined ? commonOptions.controls : true,
70 loop: commonOptions.loop !== undefined ? commonOptions.loop : false,
71
72 muted: commonOptions.muted !== undefined
73 ? commonOptions.muted
74 : undefined, // Undefined so the player knows it has to check the local storage
75
76 autoplay: this.getAutoPlayValue(autoplay, alreadyPlayed),
77
78 poster: commonOptions.poster,
79 inactivityTimeout: commonOptions.inactivityTimeout,
80 playbackRates: [ 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2 ],
81
82 plugins,
83
84 controlBar: {
85 children: controlBarOptionsBuilder.getChildrenOptions() as any // FIXME: typings
86 }
87 }
88
89 if (commonOptions.language && !isDefaultLocale(commonOptions.language)) {
90 Object.assign(videojsOptions, { language: commonOptions.language })
91 }
92
93 return videojsOptions
94 }
95
96 private getAutoPlayValue (autoplay: any, alreadyPlayed: boolean) {
97 if (autoplay !== true) return autoplay
98
99 // On first play, disable autoplay to avoid issues
100 // But if the player already played videos, we can safely autoplay next ones
101 if (isIOS() || isSafari()) {
102 return alreadyPlayed ? 'play' : false
103 }
104
105 return 'play'
106 }
107
108 getContextMenuOptions (player: videojs.Player, commonOptions: CommonOptions) {
109 const content = () => {
110 const isLoopEnabled = player.options_['loop']
111
112 const items = [
113 {
114 icon: 'repeat',
115 label: player.localize('Play in loop') + (isLoopEnabled ? '<span class="vjs-icon-tick-white"></span>' : ''),
116 listener: function () {
117 player.options_['loop'] = !isLoopEnabled
118 }
119 },
120 {
121 label: player.localize('Copy the video URL'),
122 listener: function () {
123 copyToClipboard(buildVideoLink({ shortUUID: commonOptions.videoShortUUID }))
124 }
125 },
126 {
127 label: player.localize('Copy the video URL at the current time'),
128 listener: function (this: videojs.Player) {
129 const url = buildVideoLink({ shortUUID: commonOptions.videoShortUUID })
130
131 copyToClipboard(decorateVideoLink({ url, startTime: this.currentTime() }))
132 }
133 },
134 {
135 icon: 'code',
136 label: player.localize('Copy embed code'),
137 listener: () => {
138 copyToClipboard(buildVideoOrPlaylistEmbed(commonOptions.embedUrl, commonOptions.embedTitle))
139 }
140 }
141 ]
142
143 if (this.mode === 'webtorrent') {
144 items.push({
145 label: player.localize('Copy magnet URI'),
146 listener: function (this: videojs.Player) {
147 copyToClipboard(this.webtorrent().getCurrentVideoFile().magnetUri)
148 }
149 })
150 }
151
152 items.push({
153 icon: 'info',
154 label: player.localize('Stats for nerds'),
155 listener: () => {
156 player.stats().show()
157 }
158 })
159
160 return items.map(i => ({
161 ...i,
162 label: `<span class="vjs-icon-${i.icon || 'link-2'}"></span>` + i.label
163 }))
164 }
165
166 return { content }
167 }
168}
diff --git a/client/src/assets/player/manager-options/manager-options.model.ts b/client/src/assets/player/manager-options/manager-options.model.ts
new file mode 100644
index 000000000..0b0f8b435
--- /dev/null
+++ b/client/src/assets/player/manager-options/manager-options.model.ts
@@ -0,0 +1,84 @@
1import { PluginsManager } from '@root-helpers/plugins-manager'
2import { LiveVideoLatencyMode, VideoFile } from '@shared/models'
3import { PlaylistPluginOptions, UserWatching, VideoJSCaption } from '../peertube-videojs-typings'
4
5export type PlayerMode = 'webtorrent' | 'p2p-media-loader'
6
7export type WebtorrentOptions = {
8 videoFiles: VideoFile[]
9}
10
11export type P2PMediaLoaderOptions = {
12 playlistUrl: string
13 segmentsSha256Url: string
14 trackerAnnounce: string[]
15 redundancyBaseUrls: string[]
16 videoFiles: VideoFile[]
17}
18
19export interface CustomizationOptions {
20 startTime: number | string
21 stopTime: number | string
22
23 controls?: boolean
24 muted?: boolean
25 loop?: boolean
26 subtitle?: string
27 resume?: string
28
29 peertubeLink: boolean
30}
31
32export interface CommonOptions extends CustomizationOptions {
33 playerElement: HTMLVideoElement
34 onPlayerElementChange: (element: HTMLVideoElement) => void
35
36 autoplay: boolean
37 p2pEnabled: boolean
38
39 nextVideo?: () => void
40 hasNextVideo?: () => boolean
41
42 previousVideo?: () => void
43 hasPreviousVideo?: () => boolean
44
45 playlist?: PlaylistPluginOptions
46
47 videoDuration: number
48 enableHotkeys: boolean
49 inactivityTimeout: number
50 poster: string
51
52 theaterButton: boolean
53 captions: boolean
54
55 videoViewUrl: string
56 embedUrl: string
57 embedTitle: string
58
59 isLive: boolean
60 liveOptions?: {
61 latencyMode: LiveVideoLatencyMode
62 }
63
64 language?: string
65
66 videoCaptions: VideoJSCaption[]
67
68 videoUUID: string
69 videoShortUUID: string
70
71 userWatching?: UserWatching
72
73 serverUrl: string
74
75 errorNotifier: (message: string) => void
76}
77
78export type PeertubePlayerManagerOptions = {
79 common: CommonOptions
80 webtorrent: WebtorrentOptions
81 p2pMediaLoader?: P2PMediaLoaderOptions
82
83 pluginsManager: PluginsManager
84}
diff --git a/client/src/assets/player/manager-options/webtorrent-options-builder.ts b/client/src/assets/player/manager-options/webtorrent-options-builder.ts
new file mode 100644
index 000000000..303940b29
--- /dev/null
+++ b/client/src/assets/player/manager-options/webtorrent-options-builder.ts
@@ -0,0 +1,36 @@
1import { PeertubePlayerManagerOptions } from './manager-options.model'
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}