diff options
-rw-r--r-- | client/src/app/+videos/+video-watch/video-watch.component.ts | 6 | ||||
-rw-r--r-- | client/src/app/core/plugins/plugin.service.ts | 184 | ||||
-rw-r--r-- | client/src/app/core/theme/theme.service.ts | 1 | ||||
-rw-r--r-- | client/src/assets/player/peertube-player-manager.ts | 26 | ||||
-rw-r--r-- | client/src/root-helpers/index.ts | 1 | ||||
-rw-r--r-- | client/src/root-helpers/plugins-manager.ts | 251 | ||||
-rw-r--r-- | client/src/root-helpers/plugins.ts | 126 | ||||
-rw-r--r-- | client/src/standalone/videos/embed.ts | 53 | ||||
-rw-r--r-- | shared/models/plugins/client/client-hook.model.ts | 5 |
9 files changed, 336 insertions, 317 deletions
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.ts b/client/src/app/+videos/+video-watch/video-watch.component.ts index 3fdbc0184..a444dc51f 100644 --- a/client/src/app/+videos/+video-watch/video-watch.component.ts +++ b/client/src/app/+videos/+video-watch/video-watch.component.ts | |||
@@ -12,6 +12,7 @@ import { | |||
12 | MetaService, | 12 | MetaService, |
13 | Notifier, | 13 | Notifier, |
14 | PeerTubeSocket, | 14 | PeerTubeSocket, |
15 | PluginService, | ||
15 | RestExtractor, | 16 | RestExtractor, |
16 | ScreenService, | 17 | ScreenService, |
17 | ServerService, | 18 | ServerService, |
@@ -146,6 +147,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
146 | private videoCaptionService: VideoCaptionService, | 147 | private videoCaptionService: VideoCaptionService, |
147 | private hotkeysService: HotkeysService, | 148 | private hotkeysService: HotkeysService, |
148 | private hooks: HooksService, | 149 | private hooks: HooksService, |
150 | private pluginService: PluginService, | ||
149 | private peertubeSocket: PeerTubeSocket, | 151 | private peertubeSocket: PeerTubeSocket, |
150 | private screenService: ScreenService, | 152 | private screenService: ScreenService, |
151 | private location: PlatformLocation, | 153 | private location: PlatformLocation, |
@@ -859,7 +861,9 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
859 | 861 | ||
860 | webtorrent: { | 862 | webtorrent: { |
861 | videoFiles: video.files | 863 | videoFiles: video.files |
862 | } | 864 | }, |
865 | |||
866 | pluginsManager: this.pluginService.getPluginsManager() | ||
863 | } | 867 | } |
864 | 868 | ||
865 | // Only set this if we're in a playlist | 869 | // Only set this if we're in a playlist |
diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts index ccbfd3e4d..bfd5ba4cc 100644 --- a/client/src/app/core/plugins/plugin.service.ts +++ b/client/src/app/core/plugins/plugin.service.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import * as debug from 'debug' | 1 | import { Observable, of } from 'rxjs' |
2 | import { Observable, of, ReplaySubject } from 'rxjs' | 2 | import { catchError, map, shareReplay } from 'rxjs/operators' |
3 | import { catchError, first, map, shareReplay } from 'rxjs/operators' | ||
4 | import { HttpClient } from '@angular/common/http' | 3 | import { HttpClient } from '@angular/common/http' |
5 | import { Inject, Injectable, LOCALE_ID, NgZone } from '@angular/core' | 4 | import { Inject, Injectable, LOCALE_ID, NgZone } from '@angular/core' |
6 | import { VideoEditType } from '@app/+videos/+video-edit/shared/video-edit.type' | 5 | import { VideoEditType } from '@app/+videos/+video-edit/shared/video-edit.type' |
@@ -11,7 +10,7 @@ import { RestExtractor } from '@app/core/rest' | |||
11 | import { ServerService } from '@app/core/server/server.service' | 10 | import { ServerService } from '@app/core/server/server.service' |
12 | import { getDevLocale, isOnDevLocale } from '@app/helpers' | 11 | import { getDevLocale, isOnDevLocale } from '@app/helpers' |
13 | import { CustomModalComponent } from '@app/modal/custom-modal.component' | 12 | import { CustomModalComponent } from '@app/modal/custom-modal.component' |
14 | import { FormFields, Hooks, loadPlugin, PluginInfo, runHook } from '@root-helpers/plugins' | 13 | import { PluginInfo, PluginsManager } from '@root-helpers/plugins-manager' |
15 | import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' | 14 | import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' |
16 | import { | 15 | import { |
17 | ClientHook, | 16 | ClientHook, |
@@ -20,49 +19,39 @@ import { | |||
20 | PluginTranslation, | 19 | PluginTranslation, |
21 | PluginType, | 20 | PluginType, |
22 | PublicServerSetting, | 21 | PublicServerSetting, |
22 | RegisterClientFormFieldOptions, | ||
23 | RegisterClientSettingsScript, | 23 | RegisterClientSettingsScript, |
24 | RegisterClientVideoFieldOptions, | ||
24 | ServerConfigPlugin | 25 | ServerConfigPlugin |
25 | } from '@shared/models' | 26 | } from '@shared/models' |
26 | import { environment } from '../../../environments/environment' | 27 | import { environment } from '../../../environments/environment' |
27 | import { RegisterClientHelpers } from '../../../types/register-client-option.model' | 28 | import { RegisterClientHelpers } from '../../../types/register-client-option.model' |
28 | 29 | ||
29 | const logger = debug('peertube:plugins') | 30 | type FormFields = { |
31 | video: { | ||
32 | commonOptions: RegisterClientFormFieldOptions | ||
33 | videoFormOptions: RegisterClientVideoFieldOptions | ||
34 | }[] | ||
35 | } | ||
30 | 36 | ||
31 | @Injectable() | 37 | @Injectable() |
32 | export class PluginService implements ClientHook { | 38 | export class PluginService implements ClientHook { |
33 | private static BASE_PLUGIN_API_URL = environment.apiUrl + '/api/v1/plugins' | 39 | private static BASE_PLUGIN_API_URL = environment.apiUrl + '/api/v1/plugins' |
34 | private static BASE_PLUGIN_URL = environment.apiUrl + '/plugins' | 40 | private static BASE_PLUGIN_URL = environment.apiUrl + '/plugins' |
35 | 41 | ||
36 | pluginsLoaded: { [ scope in PluginClientScope ]: ReplaySubject<boolean> } = { | ||
37 | common: new ReplaySubject<boolean>(1), | ||
38 | 'admin-plugin': new ReplaySubject<boolean>(1), | ||
39 | search: new ReplaySubject<boolean>(1), | ||
40 | 'video-watch': new ReplaySubject<boolean>(1), | ||
41 | signup: new ReplaySubject<boolean>(1), | ||
42 | login: new ReplaySubject<boolean>(1), | ||
43 | 'video-edit': new ReplaySubject<boolean>(1), | ||
44 | embed: new ReplaySubject<boolean>(1) | ||
45 | } | ||
46 | |||
47 | translationsObservable: Observable<PluginTranslation> | 42 | translationsObservable: Observable<PluginTranslation> |
48 | 43 | ||
49 | customModal: CustomModalComponent | 44 | customModal: CustomModalComponent |
50 | 45 | ||
51 | private plugins: ServerConfigPlugin[] = [] | ||
52 | private helpers: { [ npmName: string ]: RegisterClientHelpers } = {} | 46 | private helpers: { [ npmName: string ]: RegisterClientHelpers } = {} |
53 | 47 | ||
54 | private scopes: { [ scopeName: string ]: PluginInfo[] } = {} | ||
55 | |||
56 | private loadedScripts: { [ script: string ]: boolean } = {} | ||
57 | private loadedScopes: PluginClientScope[] = [] | ||
58 | private loadingScopes: { [id in PluginClientScope]?: boolean } = {} | ||
59 | |||
60 | private hooks: Hooks = {} | ||
61 | private formFields: FormFields = { | 48 | private formFields: FormFields = { |
62 | video: [] | 49 | video: [] |
63 | } | 50 | } |
64 | private settingsScripts: { [ npmName: string ]: RegisterClientSettingsScript } = {} | 51 | private settingsScripts: { [ npmName: string ]: RegisterClientSettingsScript } = {} |
65 | 52 | ||
53 | private pluginsManager: PluginsManager | ||
54 | |||
66 | constructor ( | 55 | constructor ( |
67 | private authService: AuthService, | 56 | private authService: AuthService, |
68 | private notifier: Notifier, | 57 | private notifier: Notifier, |
@@ -74,111 +63,48 @@ export class PluginService implements ClientHook { | |||
74 | @Inject(LOCALE_ID) private localeId: string | 63 | @Inject(LOCALE_ID) private localeId: string |
75 | ) { | 64 | ) { |
76 | this.loadTranslations() | 65 | this.loadTranslations() |
66 | |||
67 | this.pluginsManager = new PluginsManager({ | ||
68 | peertubeHelpersFactory: this.buildPeerTubeHelpers.bind(this), | ||
69 | onFormFields: this.onFormFields.bind(this), | ||
70 | onSettingsScripts: this.onSettingsScripts.bind(this) | ||
71 | }) | ||
77 | } | 72 | } |
78 | 73 | ||
79 | initializePlugins () { | 74 | initializePlugins () { |
80 | const config = this.server.getHTMLConfig() | 75 | this.pluginsManager.loadPluginsList(this.server.getHTMLConfig()) |
81 | this.plugins = config.plugin.registered | ||
82 | |||
83 | this.buildScopeStruct() | ||
84 | 76 | ||
85 | this.ensurePluginsAreLoaded('common') | 77 | this.pluginsManager.ensurePluginsAreLoaded('common') |
86 | } | 78 | } |
87 | 79 | ||
88 | initializeCustomModal (customModal: CustomModalComponent) { | 80 | initializeCustomModal (customModal: CustomModalComponent) { |
89 | this.customModal = customModal | 81 | this.customModal = customModal |
90 | } | 82 | } |
91 | 83 | ||
92 | ensurePluginsAreLoaded (scope: PluginClientScope) { | 84 | runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> { |
93 | this.loadPluginsByScope(scope) | 85 | return this.zone.runOutsideAngular(() => { |
94 | 86 | return this.pluginsManager.runHook(hookName, result, params) | |
95 | return this.pluginsLoaded[scope].asObservable() | 87 | }) |
96 | .pipe(first(), shareReplay()) | ||
97 | .toPromise() | ||
98 | } | 88 | } |
99 | 89 | ||
100 | addPlugin (plugin: ServerConfigPlugin, isTheme = false) { | 90 | ensurePluginsAreLoaded (scope: PluginClientScope) { |
101 | const pathPrefix = this.getPluginPathPrefix(isTheme) | 91 | return this.pluginsManager.ensurePluginsAreLoaded(scope) |
102 | |||
103 | for (const key of Object.keys(plugin.clientScripts)) { | ||
104 | const clientScript = plugin.clientScripts[key] | ||
105 | |||
106 | for (const scope of clientScript.scopes) { | ||
107 | if (!this.scopes[scope]) this.scopes[scope] = [] | ||
108 | |||
109 | this.scopes[scope].push({ | ||
110 | plugin, | ||
111 | clientScript: { | ||
112 | script: `${pathPrefix}/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`, | ||
113 | scopes: clientScript.scopes | ||
114 | }, | ||
115 | pluginType: isTheme ? PluginType.THEME : PluginType.PLUGIN, | ||
116 | isTheme | ||
117 | }) | ||
118 | |||
119 | this.loadedScripts[clientScript.script] = false | ||
120 | } | ||
121 | } | ||
122 | } | 92 | } |
123 | 93 | ||
124 | removePlugin (plugin: ServerConfigPlugin) { | 94 | reloadLoadedScopes () { |
125 | for (const key of Object.keys(this.scopes)) { | 95 | return this.pluginsManager.reloadLoadedScopes() |
126 | this.scopes[key] = this.scopes[key].filter(o => o.plugin.name !== plugin.name) | ||
127 | } | ||
128 | } | 96 | } |
129 | 97 | ||
130 | async reloadLoadedScopes () { | 98 | getPluginsManager () { |
131 | for (const scope of this.loadedScopes) { | 99 | return this.pluginsManager |
132 | await this.loadPluginsByScope(scope, true) | ||
133 | } | ||
134 | } | 100 | } |
135 | 101 | ||
136 | async loadPluginsByScope (scope: PluginClientScope, isReload = false) { | 102 | addPlugin (plugin: ServerConfigPlugin, isTheme = false) { |
137 | if (this.loadingScopes[scope]) return | 103 | return this.pluginsManager.addPlugin(plugin, isTheme) |
138 | if (!isReload && this.loadedScopes.includes(scope)) return | ||
139 | |||
140 | this.loadingScopes[scope] = true | ||
141 | |||
142 | logger('Loading scope %s', scope) | ||
143 | |||
144 | try { | ||
145 | if (!isReload) this.loadedScopes.push(scope) | ||
146 | |||
147 | const toLoad = this.scopes[ scope ] | ||
148 | if (!Array.isArray(toLoad)) { | ||
149 | this.loadingScopes[scope] = false | ||
150 | this.pluginsLoaded[scope].next(true) | ||
151 | |||
152 | logger('Nothing to load for scope %s', scope) | ||
153 | return | ||
154 | } | ||
155 | |||
156 | const promises: Promise<any>[] = [] | ||
157 | for (const pluginInfo of toLoad) { | ||
158 | const clientScript = pluginInfo.clientScript | ||
159 | |||
160 | if (this.loadedScripts[ clientScript.script ]) continue | ||
161 | |||
162 | promises.push(this.loadPlugin(pluginInfo)) | ||
163 | |||
164 | this.loadedScripts[ clientScript.script ] = true | ||
165 | } | ||
166 | |||
167 | await Promise.all(promises) | ||
168 | |||
169 | this.pluginsLoaded[scope].next(true) | ||
170 | this.loadingScopes[scope] = false | ||
171 | |||
172 | logger('Scope %s loaded', scope) | ||
173 | } catch (err) { | ||
174 | console.error('Cannot load plugins by scope %s.', scope, err) | ||
175 | } | ||
176 | } | 104 | } |
177 | 105 | ||
178 | runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> { | 106 | removePlugin (plugin: ServerConfigPlugin) { |
179 | return this.zone.runOutsideAngular(() => { | 107 | return this.pluginsManager.removePlugin(plugin) |
180 | return runHook(this.hooks, hookName, result, params) | ||
181 | }) | ||
182 | } | 108 | } |
183 | 109 | ||
184 | nameToNpmName (name: string, type: PluginType) { | 110 | nameToNpmName (name: string, type: PluginType) { |
@@ -189,12 +115,6 @@ export class PluginService implements ClientHook { | |||
189 | return prefix + name | 115 | return prefix + name |
190 | } | 116 | } |
191 | 117 | ||
192 | pluginTypeFromNpmName (npmName: string) { | ||
193 | return npmName.startsWith('peertube-plugin-') | ||
194 | ? PluginType.PLUGIN | ||
195 | : PluginType.THEME | ||
196 | } | ||
197 | |||
198 | getRegisteredVideoFormFields (type: VideoEditType) { | 118 | getRegisteredVideoFormFields (type: VideoEditType) { |
199 | return this.formFields.video.filter(f => f.videoFormOptions.type === type) | 119 | return this.formFields.video.filter(f => f.videoFormOptions.type === type) |
200 | } | 120 | } |
@@ -213,27 +133,17 @@ export class PluginService implements ClientHook { | |||
213 | return helpers.translate(toTranslate) | 133 | return helpers.translate(toTranslate) |
214 | } | 134 | } |
215 | 135 | ||
216 | private loadPlugin (pluginInfo: PluginInfo) { | 136 | private onFormFields (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) { |
217 | return this.zone.runOutsideAngular(() => { | 137 | this.formFields.video.push({ |
218 | const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) | 138 | commonOptions, |
219 | 139 | videoFormOptions | |
220 | const helpers = this.buildPeerTubeHelpers(pluginInfo) | ||
221 | this.helpers[npmName] = helpers | ||
222 | |||
223 | return loadPlugin({ | ||
224 | hooks: this.hooks, | ||
225 | formFields: this.formFields, | ||
226 | onSettingsScripts: options => this.settingsScripts[npmName] = options, | ||
227 | pluginInfo, | ||
228 | peertubeHelpersFactory: () => helpers | ||
229 | }) | ||
230 | }) | 140 | }) |
231 | } | 141 | } |
232 | 142 | ||
233 | private buildScopeStruct () { | 143 | private onSettingsScripts (pluginInfo: PluginInfo, options: RegisterClientSettingsScript) { |
234 | for (const plugin of this.plugins) { | 144 | const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) |
235 | this.addPlugin(plugin) | 145 | |
236 | } | 146 | this.settingsScripts[npmName] = options |
237 | } | 147 | } |
238 | 148 | ||
239 | private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers { | 149 | private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers { |
@@ -242,12 +152,12 @@ export class PluginService implements ClientHook { | |||
242 | 152 | ||
243 | return { | 153 | return { |
244 | getBaseStaticRoute: () => { | 154 | getBaseStaticRoute: () => { |
245 | const pathPrefix = this.getPluginPathPrefix(pluginInfo.isTheme) | 155 | const pathPrefix = PluginsManager.getPluginPathPrefix(pluginInfo.isTheme) |
246 | return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/static` | 156 | return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/static` |
247 | }, | 157 | }, |
248 | 158 | ||
249 | getBaseRouterRoute: () => { | 159 | getBaseRouterRoute: () => { |
250 | const pathPrefix = this.getPluginPathPrefix(pluginInfo.isTheme) | 160 | const pathPrefix = PluginsManager.getPluginPathPrefix(pluginInfo.isTheme) |
251 | return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/router` | 161 | return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/router` |
252 | }, | 162 | }, |
253 | 163 | ||
@@ -324,8 +234,4 @@ export class PluginService implements ClientHook { | |||
324 | .get<PluginTranslation>(PluginService.BASE_PLUGIN_URL + '/translations/' + completeLocale + '.json') | 234 | .get<PluginTranslation>(PluginService.BASE_PLUGIN_URL + '/translations/' + completeLocale + '.json') |
325 | .pipe(shareReplay()) | 235 | .pipe(shareReplay()) |
326 | } | 236 | } |
327 | |||
328 | private getPluginPathPrefix (isTheme: boolean) { | ||
329 | return isTheme ? '/themes' : '/plugins' | ||
330 | } | ||
331 | } | 237 | } |
diff --git a/client/src/app/core/theme/theme.service.ts b/client/src/app/core/theme/theme.service.ts index 0c7dec0a1..c35548798 100644 --- a/client/src/app/core/theme/theme.service.ts +++ b/client/src/app/core/theme/theme.service.ts | |||
@@ -114,6 +114,7 @@ export class ThemeService { | |||
114 | const theme = this.getTheme(currentTheme) | 114 | const theme = this.getTheme(currentTheme) |
115 | if (theme) { | 115 | if (theme) { |
116 | console.log('Adding scripts of theme %s.', currentTheme) | 116 | console.log('Adding scripts of theme %s.', currentTheme) |
117 | |||
117 | this.pluginService.addPlugin(theme, true) | 118 | this.pluginService.addPlugin(theme, true) |
118 | 119 | ||
119 | this.pluginService.reloadLoadedScopes() | 120 | this.pluginService.reloadLoadedScopes() |
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts index 62dff8285..814253188 100644 --- a/client/src/assets/player/peertube-player-manager.ts +++ b/client/src/assets/player/peertube-player-manager.ts | |||
@@ -22,8 +22,10 @@ import './videojs-components/settings-panel-child' | |||
22 | import './videojs-components/theater-button' | 22 | import './videojs-components/theater-button' |
23 | import './playlist/playlist-plugin' | 23 | import './playlist/playlist-plugin' |
24 | import videojs from 'video.js' | 24 | import videojs from 'video.js' |
25 | import { PluginsManager } from '@root-helpers/plugins-manager' | ||
25 | import { isDefaultLocale } from '@shared/core-utils/i18n' | 26 | import { isDefaultLocale } from '@shared/core-utils/i18n' |
26 | import { VideoFile } from '@shared/models' | 27 | import { VideoFile } from '@shared/models' |
28 | import { copyToClipboard } from '../../root-helpers/utils' | ||
27 | import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager' | 29 | import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager' |
28 | import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder' | 30 | import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder' |
29 | import { segmentValidatorFactory } from './p2p-media-loader/segment-validator' | 31 | import { segmentValidatorFactory } from './p2p-media-loader/segment-validator' |
@@ -37,8 +39,7 @@ import { | |||
37 | VideoJSPluginOptions | 39 | VideoJSPluginOptions |
38 | } from './peertube-videojs-typings' | 40 | } from './peertube-videojs-typings' |
39 | import { TranslationsManager } from './translations-manager' | 41 | import { TranslationsManager } from './translations-manager' |
40 | import { buildVideoOrPlaylistEmbed, buildVideoLink, getRtcConfig, isSafari, isIOS } from './utils' | 42 | import { buildVideoLink, buildVideoOrPlaylistEmbed, getRtcConfig, isIOS, isSafari } from './utils' |
41 | import { copyToClipboard } from '../../root-helpers/utils' | ||
42 | 43 | ||
43 | // Change 'Playback Rate' to 'Speed' (smaller for our settings menu) | 44 | // Change 'Playback Rate' to 'Speed' (smaller for our settings menu) |
44 | (videojs.getComponent('PlaybackRateMenuButton') as any).prototype.controlText_ = 'Speed' | 45 | (videojs.getComponent('PlaybackRateMenuButton') as any).prototype.controlText_ = 'Speed' |
@@ -116,21 +117,26 @@ export interface CommonOptions extends CustomizationOptions { | |||
116 | } | 117 | } |
117 | 118 | ||
118 | export type PeertubePlayerManagerOptions = { | 119 | export type PeertubePlayerManagerOptions = { |
119 | common: CommonOptions, | 120 | common: CommonOptions |
120 | webtorrent: WebtorrentOptions, | 121 | webtorrent: WebtorrentOptions |
121 | p2pMediaLoader?: P2PMediaLoaderOptions | 122 | p2pMediaLoader?: P2PMediaLoaderOptions |
123 | |||
124 | pluginsManager: PluginsManager | ||
122 | } | 125 | } |
123 | 126 | ||
124 | export class PeertubePlayerManager { | 127 | export class PeertubePlayerManager { |
125 | private static playerElementClassName: string | 128 | private static playerElementClassName: string |
126 | private static onPlayerChange: (player: videojs.Player) => void | 129 | private static onPlayerChange: (player: videojs.Player) => void |
127 | private static alreadyPlayed = false | 130 | private static alreadyPlayed = false |
131 | private static pluginsManager: PluginsManager | ||
128 | 132 | ||
129 | static initState () { | 133 | static initState () { |
130 | PeertubePlayerManager.alreadyPlayed = false | 134 | PeertubePlayerManager.alreadyPlayed = false |
131 | } | 135 | } |
132 | 136 | ||
133 | static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions, onPlayerChange: (player: videojs.Player) => void) { | 137 | static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions, onPlayerChange: (player: videojs.Player) => void) { |
138 | this.pluginsManager = options.pluginsManager | ||
139 | |||
134 | let p2pMediaLoader: any | 140 | let p2pMediaLoader: any |
135 | 141 | ||
136 | this.onPlayerChange = onPlayerChange | 142 | this.onPlayerChange = onPlayerChange |
@@ -144,7 +150,7 @@ export class PeertubePlayerManager { | |||
144 | ]) | 150 | ]) |
145 | } | 151 | } |
146 | 152 | ||
147 | const videojsOptions = this.getVideojsOptions(mode, options, p2pMediaLoader) | 153 | const videojsOptions = await this.getVideojsOptions(mode, options, p2pMediaLoader) |
148 | 154 | ||
149 | await TranslationsManager.loadLocaleInVideoJS(options.common.serverUrl, options.common.language, videojs) | 155 | await TranslationsManager.loadLocaleInVideoJS(options.common.serverUrl, options.common.language, videojs) |
150 | 156 | ||
@@ -206,7 +212,7 @@ export class PeertubePlayerManager { | |||
206 | await import('./webtorrent/webtorrent-plugin') | 212 | await import('./webtorrent/webtorrent-plugin') |
207 | 213 | ||
208 | const mode = 'webtorrent' | 214 | const mode = 'webtorrent' |
209 | const videojsOptions = this.getVideojsOptions(mode, options) | 215 | const videojsOptions = await this.getVideojsOptions(mode, options) |
210 | 216 | ||
211 | const self = this | 217 | const self = this |
212 | videojs(newVideoElement, videojsOptions, function (this: videojs.Player) { | 218 | videojs(newVideoElement, videojsOptions, function (this: videojs.Player) { |
@@ -218,16 +224,16 @@ export class PeertubePlayerManager { | |||
218 | }) | 224 | }) |
219 | } | 225 | } |
220 | 226 | ||
221 | private static getVideojsOptions ( | 227 | private static async getVideojsOptions ( |
222 | mode: PlayerMode, | 228 | mode: PlayerMode, |
223 | options: PeertubePlayerManagerOptions, | 229 | options: PeertubePlayerManagerOptions, |
224 | p2pMediaLoaderModule?: any | 230 | p2pMediaLoaderModule?: any |
225 | ): videojs.PlayerOptions { | 231 | ): Promise<videojs.PlayerOptions> { |
226 | const commonOptions = options.common | 232 | const commonOptions = options.common |
227 | const isHLS = mode === 'p2p-media-loader' | 233 | const isHLS = mode === 'p2p-media-loader' |
228 | 234 | ||
229 | let autoplay = this.getAutoPlayValue(commonOptions.autoplay) | 235 | let autoplay = this.getAutoPlayValue(commonOptions.autoplay) |
230 | let html5 = { | 236 | const html5 = { |
231 | preloadTextTracks: false | 237 | preloadTextTracks: false |
232 | } | 238 | } |
233 | 239 | ||
@@ -306,7 +312,7 @@ export class PeertubePlayerManager { | |||
306 | Object.assign(videojsOptions, { language: commonOptions.language }) | 312 | Object.assign(videojsOptions, { language: commonOptions.language }) |
307 | } | 313 | } |
308 | 314 | ||
309 | return videojsOptions | 315 | return this.pluginsManager.runHook('filter:internal.player.videojs.options.result', videojsOptions) |
310 | } | 316 | } |
311 | 317 | ||
312 | private static addP2PMediaLoaderOptions ( | 318 | private static addP2PMediaLoaderOptions ( |
diff --git a/client/src/root-helpers/index.ts b/client/src/root-helpers/index.ts index 036a7677d..d62f07f9e 100644 --- a/client/src/root-helpers/index.ts +++ b/client/src/root-helpers/index.ts | |||
@@ -2,3 +2,4 @@ export * from './users' | |||
2 | export * from './bytes' | 2 | export * from './bytes' |
3 | export * from './peertube-web-storage' | 3 | export * from './peertube-web-storage' |
4 | export * from './utils' | 4 | export * from './utils' |
5 | export * from './plugins-manager' | ||
diff --git a/client/src/root-helpers/plugins-manager.ts b/client/src/root-helpers/plugins-manager.ts new file mode 100644 index 000000000..f919db8af --- /dev/null +++ b/client/src/root-helpers/plugins-manager.ts | |||
@@ -0,0 +1,251 @@ | |||
1 | import * as debug from 'debug' | ||
2 | import { ReplaySubject } from 'rxjs' | ||
3 | import { first, shareReplay } from 'rxjs/operators' | ||
4 | import { RegisterClientHelpers } from 'src/types/register-client-option.model' | ||
5 | import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks' | ||
6 | import { | ||
7 | ClientHookName, | ||
8 | clientHookObject, | ||
9 | ClientScript, | ||
10 | HTMLServerConfig, | ||
11 | PluginClientScope, | ||
12 | PluginType, | ||
13 | RegisterClientFormFieldOptions, | ||
14 | RegisterClientHookOptions, | ||
15 | RegisterClientSettingsScript, | ||
16 | RegisterClientVideoFieldOptions, | ||
17 | ServerConfigPlugin | ||
18 | } from '../../../shared/models' | ||
19 | import { environment } from '../environments/environment' | ||
20 | import { ClientScript as ClientScriptModule } from '../types/client-script.model' | ||
21 | |||
22 | interface HookStructValue extends RegisterClientHookOptions { | ||
23 | plugin: ServerConfigPlugin | ||
24 | clientScript: ClientScript | ||
25 | } | ||
26 | |||
27 | type Hooks = { [ name: string ]: HookStructValue[] } | ||
28 | |||
29 | type PluginInfo = { | ||
30 | plugin: ServerConfigPlugin | ||
31 | clientScript: ClientScript | ||
32 | pluginType: PluginType | ||
33 | isTheme: boolean | ||
34 | } | ||
35 | |||
36 | type PeertubeHelpersFactory = (pluginInfo: PluginInfo) => RegisterClientHelpers | ||
37 | type OnFormFields = (options: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void | ||
38 | type OnSettingsScripts = (pluginInfo: PluginInfo, options: RegisterClientSettingsScript) => void | ||
39 | |||
40 | const logger = debug('peertube:plugins') | ||
41 | |||
42 | class PluginsManager { | ||
43 | private hooks: Hooks = {} | ||
44 | |||
45 | private scopes: { [ scopeName: string ]: PluginInfo[] } = {} | ||
46 | |||
47 | private loadedScripts: { [ script: string ]: boolean } = {} | ||
48 | private loadedScopes: PluginClientScope[] = [] | ||
49 | private loadingScopes: { [id in PluginClientScope]?: boolean } = {} | ||
50 | |||
51 | private pluginsLoaded: { [ scope in PluginClientScope ]: ReplaySubject<boolean> } = { | ||
52 | common: new ReplaySubject<boolean>(1), | ||
53 | 'admin-plugin': new ReplaySubject<boolean>(1), | ||
54 | search: new ReplaySubject<boolean>(1), | ||
55 | 'video-watch': new ReplaySubject<boolean>(1), | ||
56 | signup: new ReplaySubject<boolean>(1), | ||
57 | login: new ReplaySubject<boolean>(1), | ||
58 | 'video-edit': new ReplaySubject<boolean>(1), | ||
59 | embed: new ReplaySubject<boolean>(1) | ||
60 | } | ||
61 | |||
62 | private readonly peertubeHelpersFactory: PeertubeHelpersFactory | ||
63 | private readonly onFormFields: OnFormFields | ||
64 | private readonly onSettingsScripts: OnSettingsScripts | ||
65 | |||
66 | constructor (options: { | ||
67 | peertubeHelpersFactory: PeertubeHelpersFactory | ||
68 | onFormFields?: OnFormFields | ||
69 | onSettingsScripts?: OnSettingsScripts | ||
70 | }) { | ||
71 | this.peertubeHelpersFactory = options.peertubeHelpersFactory | ||
72 | this.onFormFields = options.onFormFields | ||
73 | this.onSettingsScripts = options.onSettingsScripts | ||
74 | } | ||
75 | |||
76 | static getPluginPathPrefix (isTheme: boolean) { | ||
77 | return isTheme ? '/themes' : '/plugins' | ||
78 | } | ||
79 | |||
80 | loadPluginsList (config: HTMLServerConfig) { | ||
81 | for (const plugin of config.plugin.registered) { | ||
82 | this.addPlugin(plugin) | ||
83 | } | ||
84 | } | ||
85 | |||
86 | async runHook<T> (hookName: ClientHookName, result?: T, params?: any) { | ||
87 | if (!this.hooks[hookName]) return result | ||
88 | |||
89 | const hookType = getHookType(hookName) | ||
90 | |||
91 | for (const hook of this.hooks[hookName]) { | ||
92 | console.log('Running hook %s of plugin %s.', hookName, hook.plugin.name) | ||
93 | |||
94 | result = await internalRunHook(hook.handler, hookType, result, params, err => { | ||
95 | console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.clientScript.script, hook.plugin.name, err) | ||
96 | }) | ||
97 | } | ||
98 | |||
99 | return result | ||
100 | } | ||
101 | |||
102 | ensurePluginsAreLoaded (scope: PluginClientScope) { | ||
103 | this.loadPluginsByScope(scope) | ||
104 | |||
105 | return this.pluginsLoaded[scope].asObservable() | ||
106 | .pipe(first(), shareReplay()) | ||
107 | .toPromise() | ||
108 | } | ||
109 | |||
110 | async reloadLoadedScopes () { | ||
111 | for (const scope of this.loadedScopes) { | ||
112 | await this.loadPluginsByScope(scope, true) | ||
113 | } | ||
114 | } | ||
115 | |||
116 | addPlugin (plugin: ServerConfigPlugin, isTheme = false) { | ||
117 | const pathPrefix = PluginsManager.getPluginPathPrefix(isTheme) | ||
118 | |||
119 | for (const key of Object.keys(plugin.clientScripts)) { | ||
120 | const clientScript = plugin.clientScripts[key] | ||
121 | |||
122 | for (const scope of clientScript.scopes) { | ||
123 | if (!this.scopes[scope]) this.scopes[scope] = [] | ||
124 | |||
125 | this.scopes[scope].push({ | ||
126 | plugin, | ||
127 | clientScript: { | ||
128 | script: `${pathPrefix}/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`, | ||
129 | scopes: clientScript.scopes | ||
130 | }, | ||
131 | pluginType: isTheme ? PluginType.THEME : PluginType.PLUGIN, | ||
132 | isTheme | ||
133 | }) | ||
134 | |||
135 | this.loadedScripts[clientScript.script] = false | ||
136 | } | ||
137 | } | ||
138 | } | ||
139 | |||
140 | removePlugin (plugin: ServerConfigPlugin) { | ||
141 | for (const key of Object.keys(this.scopes)) { | ||
142 | this.scopes[key] = this.scopes[key].filter(o => o.plugin.name !== plugin.name) | ||
143 | } | ||
144 | } | ||
145 | |||
146 | async loadPluginsByScope (scope: PluginClientScope, isReload = false) { | ||
147 | if (this.loadingScopes[scope]) return | ||
148 | if (!isReload && this.loadedScopes.includes(scope)) return | ||
149 | |||
150 | this.loadingScopes[scope] = true | ||
151 | |||
152 | logger('Loading scope %s', scope) | ||
153 | |||
154 | try { | ||
155 | if (!isReload) this.loadedScopes.push(scope) | ||
156 | |||
157 | const toLoad = this.scopes[ scope ] | ||
158 | if (!Array.isArray(toLoad)) { | ||
159 | this.loadingScopes[scope] = false | ||
160 | this.pluginsLoaded[scope].next(true) | ||
161 | |||
162 | logger('Nothing to load for scope %s', scope) | ||
163 | return | ||
164 | } | ||
165 | |||
166 | const promises: Promise<any>[] = [] | ||
167 | for (const pluginInfo of toLoad) { | ||
168 | const clientScript = pluginInfo.clientScript | ||
169 | |||
170 | if (this.loadedScripts[ clientScript.script ]) continue | ||
171 | |||
172 | promises.push(this.loadPlugin(pluginInfo)) | ||
173 | |||
174 | this.loadedScripts[ clientScript.script ] = true | ||
175 | } | ||
176 | |||
177 | await Promise.all(promises) | ||
178 | |||
179 | this.pluginsLoaded[scope].next(true) | ||
180 | this.loadingScopes[scope] = false | ||
181 | |||
182 | logger('Scope %s loaded', scope) | ||
183 | } catch (err) { | ||
184 | console.error('Cannot load plugins by scope %s.', scope, err) | ||
185 | } | ||
186 | } | ||
187 | |||
188 | private loadPlugin (pluginInfo: PluginInfo) { | ||
189 | const { plugin, clientScript } = pluginInfo | ||
190 | |||
191 | const registerHook = (options: RegisterClientHookOptions) => { | ||
192 | if (clientHookObject[options.target] !== true) { | ||
193 | console.error('Unknown hook %s of plugin %s. Skipping.', options.target, plugin.name) | ||
194 | return | ||
195 | } | ||
196 | |||
197 | if (!this.hooks[options.target]) this.hooks[options.target] = [] | ||
198 | |||
199 | this.hooks[options.target].push({ | ||
200 | plugin, | ||
201 | clientScript, | ||
202 | target: options.target, | ||
203 | handler: options.handler, | ||
204 | priority: options.priority || 0 | ||
205 | }) | ||
206 | } | ||
207 | |||
208 | const registerVideoField = (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => { | ||
209 | if (!this.onFormFields) { | ||
210 | throw new Error('Video field registration is not supported') | ||
211 | } | ||
212 | |||
213 | return this.onFormFields(commonOptions, videoFormOptions) | ||
214 | } | ||
215 | |||
216 | const registerSettingsScript = (options: RegisterClientSettingsScript) => { | ||
217 | if (!this.onSettingsScripts) { | ||
218 | throw new Error('Registering settings script is not supported') | ||
219 | } | ||
220 | |||
221 | return this.onSettingsScripts(pluginInfo, options) | ||
222 | } | ||
223 | |||
224 | const peertubeHelpers = this.peertubeHelpersFactory(pluginInfo) | ||
225 | |||
226 | console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name) | ||
227 | |||
228 | const absURL = (environment.apiUrl || window.location.origin) + clientScript.script | ||
229 | return import(/* webpackIgnore: true */ absURL) | ||
230 | .then((script: ClientScriptModule) => script.register({ registerHook, registerVideoField, registerSettingsScript, peertubeHelpers })) | ||
231 | .then(() => this.sortHooksByPriority()) | ||
232 | .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) | ||
233 | } | ||
234 | |||
235 | private sortHooksByPriority () { | ||
236 | for (const hookName of Object.keys(this.hooks)) { | ||
237 | this.hooks[hookName].sort((a, b) => { | ||
238 | return b.priority - a.priority | ||
239 | }) | ||
240 | } | ||
241 | } | ||
242 | } | ||
243 | |||
244 | export { | ||
245 | PluginsManager, | ||
246 | |||
247 | PluginInfo, | ||
248 | PeertubeHelpersFactory, | ||
249 | OnFormFields, | ||
250 | OnSettingsScripts | ||
251 | } | ||
diff --git a/client/src/root-helpers/plugins.ts b/client/src/root-helpers/plugins.ts deleted file mode 100644 index 10c111a8c..000000000 --- a/client/src/root-helpers/plugins.ts +++ /dev/null | |||
@@ -1,126 +0,0 @@ | |||
1 | import { RegisterClientHelpers } from 'src/types/register-client-option.model' | ||
2 | import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks' | ||
3 | import { | ||
4 | ClientHookName, | ||
5 | clientHookObject, | ||
6 | ClientScript, | ||
7 | PluginType, | ||
8 | RegisterClientFormFieldOptions, | ||
9 | RegisterClientHookOptions, | ||
10 | RegisterClientSettingsScript, | ||
11 | RegisterClientVideoFieldOptions, | ||
12 | ServerConfigPlugin | ||
13 | } from '../../../shared/models' | ||
14 | import { environment } from '../environments/environment' | ||
15 | import { ClientScript as ClientScriptModule } from '../types/client-script.model' | ||
16 | |||
17 | interface HookStructValue extends RegisterClientHookOptions { | ||
18 | plugin: ServerConfigPlugin | ||
19 | clientScript: ClientScript | ||
20 | } | ||
21 | |||
22 | type Hooks = { [ name: string ]: HookStructValue[] } | ||
23 | |||
24 | type PluginInfo = { | ||
25 | plugin: ServerConfigPlugin | ||
26 | clientScript: ClientScript | ||
27 | pluginType: PluginType | ||
28 | isTheme: boolean | ||
29 | } | ||
30 | |||
31 | type FormFields = { | ||
32 | video: { | ||
33 | commonOptions: RegisterClientFormFieldOptions | ||
34 | videoFormOptions: RegisterClientVideoFieldOptions | ||
35 | }[] | ||
36 | } | ||
37 | |||
38 | async function runHook<T> (hooks: Hooks, hookName: ClientHookName, result?: T, params?: any) { | ||
39 | if (!hooks[hookName]) return result | ||
40 | |||
41 | const hookType = getHookType(hookName) | ||
42 | |||
43 | for (const hook of hooks[hookName]) { | ||
44 | console.log('Running hook %s of plugin %s.', hookName, hook.plugin.name) | ||
45 | |||
46 | result = await internalRunHook(hook.handler, hookType, result, params, err => { | ||
47 | console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.clientScript.script, hook.plugin.name, err) | ||
48 | }) | ||
49 | } | ||
50 | |||
51 | return result | ||
52 | } | ||
53 | |||
54 | function loadPlugin (options: { | ||
55 | hooks: Hooks | ||
56 | pluginInfo: PluginInfo | ||
57 | peertubeHelpersFactory: (pluginInfo: PluginInfo) => RegisterClientHelpers | ||
58 | formFields?: FormFields | ||
59 | onSettingsScripts?: (options: RegisterClientSettingsScript) => void | ||
60 | }) { | ||
61 | const { hooks, pluginInfo, peertubeHelpersFactory, formFields, onSettingsScripts } = options | ||
62 | const { plugin, clientScript } = pluginInfo | ||
63 | |||
64 | const registerHook = (options: RegisterClientHookOptions) => { | ||
65 | if (clientHookObject[options.target] !== true) { | ||
66 | console.error('Unknown hook %s of plugin %s. Skipping.', options.target, plugin.name) | ||
67 | return | ||
68 | } | ||
69 | |||
70 | if (!hooks[options.target]) hooks[options.target] = [] | ||
71 | |||
72 | hooks[options.target].push({ | ||
73 | plugin, | ||
74 | clientScript, | ||
75 | target: options.target, | ||
76 | handler: options.handler, | ||
77 | priority: options.priority || 0 | ||
78 | }) | ||
79 | } | ||
80 | |||
81 | const registerVideoField = (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => { | ||
82 | if (!formFields) { | ||
83 | throw new Error('Video field registration is not supported') | ||
84 | } | ||
85 | |||
86 | formFields.video.push({ | ||
87 | commonOptions, | ||
88 | videoFormOptions | ||
89 | }) | ||
90 | } | ||
91 | |||
92 | const registerSettingsScript = (options: RegisterClientSettingsScript) => { | ||
93 | if (!onSettingsScripts) { | ||
94 | throw new Error('Registering settings script is not supported') | ||
95 | } | ||
96 | |||
97 | return onSettingsScripts(options) | ||
98 | } | ||
99 | |||
100 | const peertubeHelpers = peertubeHelpersFactory(pluginInfo) | ||
101 | |||
102 | console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name) | ||
103 | |||
104 | const absURL = (environment.apiUrl || window.location.origin) + clientScript.script | ||
105 | return import(/* webpackIgnore: true */ absURL) | ||
106 | .then((script: ClientScriptModule) => script.register({ registerHook, registerVideoField, registerSettingsScript, peertubeHelpers })) | ||
107 | .then(() => sortHooksByPriority(hooks)) | ||
108 | .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) | ||
109 | } | ||
110 | |||
111 | export { | ||
112 | HookStructValue, | ||
113 | Hooks, | ||
114 | PluginInfo, | ||
115 | FormFields, | ||
116 | loadPlugin, | ||
117 | runHook | ||
118 | } | ||
119 | |||
120 | function sortHooksByPriority (hooks: Hooks) { | ||
121 | for (const hookName of Object.keys(hooks)) { | ||
122 | hooks[hookName].sort((a, b) => { | ||
123 | return b.priority - a.priority | ||
124 | }) | ||
125 | } | ||
126 | } | ||
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index a367feb8e..dc9727049 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -3,7 +3,6 @@ import videojs from 'video.js' | |||
3 | import { peertubeTranslate } from '../../../../shared/core-utils/i18n' | 3 | import { peertubeTranslate } from '../../../../shared/core-utils/i18n' |
4 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | 4 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
5 | import { | 5 | import { |
6 | ClientHookName, | ||
7 | HTMLServerConfig, | 6 | HTMLServerConfig, |
8 | OAuth2ErrorCode, | 7 | OAuth2ErrorCode, |
9 | PluginType, | 8 | PluginType, |
@@ -19,7 +18,7 @@ import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode } from | |||
19 | import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' | 18 | import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' |
20 | import { TranslationsManager } from '../../assets/player/translations-manager' | 19 | import { TranslationsManager } from '../../assets/player/translations-manager' |
21 | import { peertubeLocalStorage } from '../../root-helpers/peertube-web-storage' | 20 | import { peertubeLocalStorage } from '../../root-helpers/peertube-web-storage' |
22 | import { Hooks, loadPlugin, runHook } from '../../root-helpers/plugins' | 21 | import { PluginsManager } from '../../root-helpers/plugins-manager' |
23 | import { Tokens } from '../../root-helpers/users' | 22 | import { Tokens } from '../../root-helpers/users' |
24 | import { objectToUrlEncoded } from '../../root-helpers/utils' | 23 | import { objectToUrlEncoded } from '../../root-helpers/utils' |
25 | import { RegisterClientHelpers } from '../../types/register-client-option.model' | 24 | import { RegisterClientHelpers } from '../../types/register-client-option.model' |
@@ -68,8 +67,7 @@ export class PeerTubeEmbed { | |||
68 | 67 | ||
69 | private wrapperElement: HTMLElement | 68 | private wrapperElement: HTMLElement |
70 | 69 | ||
71 | private peertubeHooks: Hooks = {} | 70 | private pluginsManager: PluginsManager |
72 | private loadedScripts = new Set<string>() | ||
73 | 71 | ||
74 | static async main () { | 72 | static async main () { |
75 | const videoContainerId = 'video-wrapper' | 73 | const videoContainerId = 'video-wrapper' |
@@ -489,7 +487,7 @@ export class PeerTubeEmbed { | |||
489 | this.PeertubePlayerManagerModulePromise | 487 | this.PeertubePlayerManagerModulePromise |
490 | ]) | 488 | ]) |
491 | 489 | ||
492 | await this.ensurePluginsAreLoaded(serverTranslations) | 490 | await this.loadPlugins(serverTranslations) |
493 | 491 | ||
494 | const videoInfo: VideoDetails = videoInfoTmp | 492 | const videoInfo: VideoDetails = videoInfoTmp |
495 | 493 | ||
@@ -560,7 +558,9 @@ export class PeerTubeEmbed { | |||
560 | 558 | ||
561 | webtorrent: { | 559 | webtorrent: { |
562 | videoFiles: videoInfo.files | 560 | videoFiles: videoInfo.files |
563 | } | 561 | }, |
562 | |||
563 | pluginsManager: this.pluginsManager | ||
564 | } | 564 | } |
565 | 565 | ||
566 | if (this.mode === 'p2p-media-loader') { | 566 | if (this.mode === 'p2p-media-loader') { |
@@ -600,7 +600,7 @@ export class PeerTubeEmbed { | |||
600 | }) | 600 | }) |
601 | } | 601 | } |
602 | 602 | ||
603 | this.runHook('action:embed.player.loaded', undefined, { player: this.player, videojs, video: videoInfo }) | 603 | this.pluginsManager.runHook('action:embed.player.loaded', undefined, { player: this.player, videojs, video: videoInfo }) |
604 | } | 604 | } |
605 | 605 | ||
606 | private async initCore () { | 606 | private async initCore () { |
@@ -740,37 +740,14 @@ export class PeerTubeEmbed { | |||
740 | return window.location.pathname.split('/')[1] === 'video-playlists' | 740 | return window.location.pathname.split('/')[1] === 'video-playlists' |
741 | } | 741 | } |
742 | 742 | ||
743 | private async ensurePluginsAreLoaded (translations?: { [ id: string ]: string }) { | 743 | private loadPlugins (translations?: { [ id: string ]: string }) { |
744 | if (this.config.plugin.registered.length === 0) return | 744 | this.pluginsManager = new PluginsManager({ |
745 | 745 | peertubeHelpersFactory: _ => this.buildPeerTubeHelpers(translations) | |
746 | for (const plugin of this.config.plugin.registered) { | 746 | }) |
747 | for (const key of Object.keys(plugin.clientScripts)) { | ||
748 | const clientScript = plugin.clientScripts[key] | ||
749 | |||
750 | if (clientScript.scopes.includes('embed') === false) continue | ||
751 | |||
752 | const script = `/plugins/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}` | ||
753 | |||
754 | if (this.loadedScripts.has(script)) continue | ||
755 | 747 | ||
756 | const pluginInfo = { | 748 | this.pluginsManager.loadPluginsList(this.config) |
757 | plugin, | ||
758 | clientScript: { | ||
759 | script, | ||
760 | scopes: clientScript.scopes | ||
761 | }, | ||
762 | pluginType: PluginType.PLUGIN, | ||
763 | isTheme: false | ||
764 | } | ||
765 | 749 | ||
766 | await loadPlugin({ | 750 | return this.pluginsManager.ensurePluginsAreLoaded('embed') |
767 | hooks: this.peertubeHooks, | ||
768 | pluginInfo, | ||
769 | onSettingsScripts: () => undefined, | ||
770 | peertubeHelpersFactory: _ => this.buildPeerTubeHelpers(translations) | ||
771 | }) | ||
772 | } | ||
773 | } | ||
774 | } | 751 | } |
775 | 752 | ||
776 | private buildPeerTubeHelpers (translations?: { [ id: string ]: string }): RegisterClientHelpers { | 753 | private buildPeerTubeHelpers (translations?: { [ id: string ]: string }): RegisterClientHelpers { |
@@ -808,10 +785,6 @@ export class PeerTubeEmbed { | |||
808 | } | 785 | } |
809 | } | 786 | } |
810 | } | 787 | } |
811 | |||
812 | private runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> { | ||
813 | return runHook(this.peertubeHooks, hookName, result, params) | ||
814 | } | ||
815 | } | 788 | } |
816 | 789 | ||
817 | PeerTubeEmbed.main() | 790 | PeerTubeEmbed.main() |
diff --git a/shared/models/plugins/client/client-hook.model.ts b/shared/models/plugins/client/client-hook.model.ts index 3eedd670b..546866845 100644 --- a/shared/models/plugins/client/client-hook.model.ts +++ b/shared/models/plugins/client/client-hook.model.ts | |||
@@ -53,7 +53,10 @@ export const clientFilterHookObject = { | |||
53 | 'filter:internal.common.svg-icons.get-content.result': true, | 53 | 'filter:internal.common.svg-icons.get-content.result': true, |
54 | 54 | ||
55 | // Filter left menu links | 55 | // Filter left menu links |
56 | 'filter:left-menu.links.create.result': true | 56 | 'filter:left-menu.links.create.result': true, |
57 | |||
58 | // Filter videojs options built for PeerTube player | ||
59 | 'filter:internal.player.videojs.options.result': true | ||
57 | } | 60 | } |
58 | 61 | ||
59 | export type ClientFilterHookName = keyof typeof clientFilterHookObject | 62 | export type ClientFilterHookName = keyof typeof clientFilterHookObject |