Merge pull request #3559 from NoelDeMartin/MOBILE-4239
MOBILE-4239 mediaplugin: Lazy load videojs
This commit is contained in:
		
						commit
						b7db2507cd
					
				| @ -16,7 +16,7 @@ import { CorePlatform } from '@services/platform'; | |||||||
| import { OGVPlayer, OGVCompat, OGVLoader } from 'ogv'; | import { OGVPlayer, OGVCompat, OGVLoader } from 'ogv'; | ||||||
| import videojs, { PreloadOption, TechSourceObject, VideoJSOptions } from 'video.js'; | import videojs, { PreloadOption, TechSourceObject, VideoJSOptions } from 'video.js'; | ||||||
| 
 | 
 | ||||||
| export const Tech = videojs.getComponent('Tech'); | const Tech = videojs.getComponent('Tech'); | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Object.defineProperty but "lazy", which means that the value is only set after |  * Object.defineProperty but "lazy", which means that the value is only set after | ||||||
| @ -728,13 +728,6 @@ export class VideoJSOgvJS extends Tech { | |||||||
| ].forEach(([key, fn]) => { | ].forEach(([key, fn]) => { | ||||||
|     defineLazyProperty(VideoJSOgvJS.prototype, key, () => VideoJSOgvJS[fn](), true); |     defineLazyProperty(VideoJSOgvJS.prototype, key, () => VideoJSOgvJS[fn](), true); | ||||||
| }); | }); | ||||||
| /** |  | ||||||
|  * Initialize the controller. |  | ||||||
|  */ |  | ||||||
| export const initializeVideoJSOgvJS = (): void => { |  | ||||||
|     OGVLoader.base = 'assets/lib/ogv'; |  | ||||||
|     Tech.registerTech('OgvJS', VideoJSOgvJS); |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| type OGVPlayerEl = (HTMLAudioElement | HTMLVideoElement) & { | type OGVPlayerEl = (HTMLAudioElement | HTMLVideoElement) & { | ||||||
|     stop: () => void; |     stop: () => void; | ||||||
|  | |||||||
| @ -15,7 +15,6 @@ | |||||||
| import { APP_INITIALIZER, NgModule } from '@angular/core'; | import { APP_INITIALIZER, NgModule } from '@angular/core'; | ||||||
| 
 | 
 | ||||||
| import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; | import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; | ||||||
| import { initializeVideoJSOgvJS } from './classes/videojs-ogvjs'; |  | ||||||
| import { AddonFilterMediaPluginHandler } from './services/handlers/mediaplugin'; | import { AddonFilterMediaPluginHandler } from './services/handlers/mediaplugin'; | ||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
| @ -29,8 +28,6 @@ import { AddonFilterMediaPluginHandler } from './services/handlers/mediaplugin'; | |||||||
|             multi: true, |             multi: true, | ||||||
|             useValue: () => { |             useValue: () => { | ||||||
|                 CoreFilterDelegate.registerHandler(AddonFilterMediaPluginHandler.instance); |                 CoreFilterDelegate.registerHandler(AddonFilterMediaPluginHandler.instance); | ||||||
| 
 |  | ||||||
|                 initializeVideoJSOgvJS(); |  | ||||||
|             }, |             }, | ||||||
|         }, |         }, | ||||||
|     ], |     ], | ||||||
|  | |||||||
| @ -12,18 +12,12 @@ | |||||||
| // See the License for the specific language governing permissions and
 | // See the License for the specific language governing permissions and
 | ||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
|  | import { AddonFilterMediaPluginVideoJS } from '@addons/filter/mediaplugin/services/videojs'; | ||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { CoreExternalContentDirective } from '@directives/external-content'; |  | ||||||
| 
 | 
 | ||||||
| import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; | import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; | ||||||
| import { CoreLang } from '@services/lang'; |  | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreUrlUtils } from '@services/utils/url'; |  | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { CoreDirectivesRegistry } from '@singletons/directives-registry'; |  | ||||||
| import { CoreEvents } from '@singletons/events'; |  | ||||||
| import { CoreMedia } from '@singletons/media'; | import { CoreMedia } from '@singletons/media'; | ||||||
| import videojs, { VideoJSOptions, VideoJSPlayer } from 'video.js'; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handler to support the Multimedia filter. |  * Handler to support the Multimedia filter. | ||||||
| @ -39,15 +33,13 @@ export class AddonFilterMediaPluginHandlerService extends CoreFilterDefaultHandl | |||||||
|     /** |     /** | ||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|     filter( |     filter(text: string): string | Promise<string> { | ||||||
|         text: string, |  | ||||||
|     ): string | Promise<string> { |  | ||||||
|         this.template.innerHTML = text; |         this.template.innerHTML = text; | ||||||
| 
 | 
 | ||||||
|         const videos = Array.from(this.template.content.querySelectorAll('video')); |         const videos = Array.from(this.template.content.querySelectorAll('video')); | ||||||
| 
 | 
 | ||||||
|         videos.forEach((video) => { |         videos.forEach((video) => { | ||||||
|             this.treatYoutubeVideos(video); |             AddonFilterMediaPluginVideoJS.treatYoutubeVideos(video); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         return this.template.innerHTML; |         return this.template.innerHTML; | ||||||
| @ -61,104 +53,18 @@ export class AddonFilterMediaPluginHandlerService extends CoreFilterDefaultHandl | |||||||
| 
 | 
 | ||||||
|         mediaElements.forEach((mediaElement) => { |         mediaElements.forEach((mediaElement) => { | ||||||
|             if (CoreMedia.mediaUsesJavascriptPlayer(mediaElement)) { |             if (CoreMedia.mediaUsesJavascriptPlayer(mediaElement)) { | ||||||
|                 this.useVideoJS(mediaElement); |                 AddonFilterMediaPluginVideoJS.createPlayer(mediaElement); | ||||||
|             } else { | 
 | ||||||
|                 // Remove the VideoJS classes and data if present.
 |                 return; | ||||||
|                 mediaElement.classList.remove('video-js'); |  | ||||||
|                 mediaElement.removeAttribute('data-setup'); |  | ||||||
|                 mediaElement.removeAttribute('data-setup-lazy'); |  | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             // Remove the VideoJS classes and data if present.
 | ||||||
|  |             mediaElement.classList.remove('video-js'); | ||||||
|  |             mediaElement.removeAttribute('data-setup'); | ||||||
|  |             mediaElement.removeAttribute('data-setup-lazy'); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Use video JS in a certain video or audio. |  | ||||||
|      * |  | ||||||
|      * @param mediaElement Media element. |  | ||||||
|      */ |  | ||||||
|     protected async useVideoJS(mediaElement: HTMLVideoElement | HTMLAudioElement): Promise<void> { |  | ||||||
|         const lang = await CoreLang.getCurrentLanguage(); |  | ||||||
| 
 |  | ||||||
|         // Wait for external-content to finish in the element and its sources.
 |  | ||||||
|         await Promise.all([ |  | ||||||
|             CoreDirectivesRegistry.waitDirectivesReady(mediaElement, undefined, CoreExternalContentDirective), |  | ||||||
|             CoreDirectivesRegistry.waitDirectivesReady(mediaElement, 'source', CoreExternalContentDirective), |  | ||||||
|         ]); |  | ||||||
| 
 |  | ||||||
|         const dataSetupString = mediaElement.getAttribute('data-setup') || mediaElement.getAttribute('data-setup-lazy') || '{}'; |  | ||||||
|         const data = CoreTextUtils.parseJSON<VideoJSOptions>(dataSetupString, {}); |  | ||||||
| 
 |  | ||||||
|         const player = videojs(mediaElement, { |  | ||||||
|             controls: true, |  | ||||||
|             techOrder: ['OgvJS'], |  | ||||||
|             language: lang, |  | ||||||
|             controlBar: { |  | ||||||
|                 pictureInPictureToggle: false, |  | ||||||
|             }, |  | ||||||
|             aspectRatio: data.aspectRatio, |  | ||||||
|         }, () => { |  | ||||||
|             if (mediaElement.tagName === 'VIDEO') { |  | ||||||
|                 this.fixVideoJSPlayerSize(player); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         CoreEvents.trigger(CoreEvents.JS_PLAYER_CREATED, { |  | ||||||
|             id: mediaElement.id, |  | ||||||
|             element: mediaElement, |  | ||||||
|             player, |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Fix VideoJS player size. |  | ||||||
|      * If video width is wider than available width, video is cut off. Fix the dimensions in this case. |  | ||||||
|      * |  | ||||||
|      * @param player Player instance. |  | ||||||
|      */ |  | ||||||
|     protected fixVideoJSPlayerSize(player: VideoJSPlayer): void { |  | ||||||
|         const videoWidth = player.videoWidth(); |  | ||||||
|         const videoHeight = player.videoHeight(); |  | ||||||
|         const playerDimensions = player.currentDimensions(); |  | ||||||
|         if (!videoWidth || !videoHeight || !playerDimensions.width || videoWidth === playerDimensions.width) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const candidateHeight = playerDimensions.width * videoHeight / videoWidth; |  | ||||||
|         if (!playerDimensions.height || Math.abs(candidateHeight - playerDimensions.height) > 1) { |  | ||||||
|             player.dimension('height', candidateHeight); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Treat Video JS Youtube video links and translate them to iframes. |  | ||||||
|      * |  | ||||||
|      * @param video Video element. |  | ||||||
|      */ |  | ||||||
|     protected treatYoutubeVideos(video: HTMLElement): void { |  | ||||||
|         if (!video.classList.contains('video-js')) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const dataSetupString = video.getAttribute('data-setup') || video.getAttribute('data-setup-lazy') || '{}'; |  | ||||||
|         const data = CoreTextUtils.parseJSON<VideoJSOptions>(dataSetupString, {}); |  | ||||||
|         const youtubeUrl = data.techOrder?.[0] == 'youtube' && CoreUrlUtils.getYoutubeEmbedUrl(data.sources?.[0]?.src); |  | ||||||
| 
 |  | ||||||
|         if (!youtubeUrl) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const iframe = document.createElement('iframe'); |  | ||||||
|         iframe.id = video.id; |  | ||||||
|         iframe.src = youtubeUrl; |  | ||||||
|         iframe.setAttribute('frameborder', '0'); |  | ||||||
|         iframe.setAttribute('allowfullscreen', '1'); |  | ||||||
|         iframe.width = '100%'; |  | ||||||
|         iframe.height = '300'; |  | ||||||
| 
 |  | ||||||
|         // Replace video tag by the iframe.
 |  | ||||||
|         video.parentNode?.replaceChild(iframe, video); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const AddonFilterMediaPluginHandler = makeSingleton(AddonFilterMediaPluginHandlerService); | export const AddonFilterMediaPluginHandler = makeSingleton(AddonFilterMediaPluginHandlerService); | ||||||
|  | |||||||
							
								
								
									
										188
									
								
								src/addons/filter/mediaplugin/services/videojs.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								src/addons/filter/mediaplugin/services/videojs.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,188 @@ | |||||||
|  | // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||||
|  | //
 | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||||
|  | // you may not use this file except in compliance with the License.
 | ||||||
|  | // You may obtain a copy of the License at
 | ||||||
|  | //
 | ||||||
|  | //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | //
 | ||||||
|  | // Unless required by applicable law or agreed to in writing, software
 | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||||
|  | // See the License for the specific language governing permissions and
 | ||||||
|  | // limitations under the License.
 | ||||||
|  | 
 | ||||||
|  | import { Injectable } from '@angular/core'; | ||||||
|  | import { CorePromisedValue } from '@classes/promised-value'; | ||||||
|  | import { CoreExternalContentDirective } from '@directives/external-content'; | ||||||
|  | import { CoreLang } from '@services/lang'; | ||||||
|  | import { CoreTextUtils } from '@services/utils/text'; | ||||||
|  | import { CoreUrlUtils } from '@services/utils/url'; | ||||||
|  | import { makeSingleton } from '@singletons'; | ||||||
|  | import { CoreDirectivesRegistry } from '@singletons/directives-registry'; | ||||||
|  | import { CoreEvents } from '@singletons/events'; | ||||||
|  | import type videojs from 'video.js'; | ||||||
|  | 
 | ||||||
|  | // eslint-disable-next-line no-duplicate-imports
 | ||||||
|  | import type { VideoJSOptions, VideoJSPlayer } from 'video.js'; | ||||||
|  | 
 | ||||||
|  | declare module '@singletons/events' { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Augment CoreEventsData interface with events specific to this service. | ||||||
|  |      * | ||||||
|  |      * @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
 | ||||||
|  |      */ | ||||||
|  |     export interface CoreEventsData { | ||||||
|  |         [VIDEO_JS_PLAYER_CREATED]: CoreEventJSVideoPlayerCreated; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const VIDEO_JS_PLAYER_CREATED = 'video_js_player_created'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Wrapper encapsulating videojs functionality. | ||||||
|  |  */ | ||||||
|  | @Injectable({ providedIn: 'root' }) | ||||||
|  | export class AddonFilterMediaPluginVideoJSService { | ||||||
|  | 
 | ||||||
|  |     protected videojs?: CorePromisedValue<typeof videojs>; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Create a VideoJS player. | ||||||
|  |      * | ||||||
|  |      * @param element Media element. | ||||||
|  |      */ | ||||||
|  |     async createPlayer(element: HTMLVideoElement | HTMLAudioElement): Promise<void> { | ||||||
|  |         // Wait for external-content to finish in the element and its sources.
 | ||||||
|  |         await Promise.all([ | ||||||
|  |             CoreDirectivesRegistry.waitDirectivesReady(element, undefined, CoreExternalContentDirective), | ||||||
|  |             CoreDirectivesRegistry.waitDirectivesReady(element, 'source', CoreExternalContentDirective), | ||||||
|  |         ]); | ||||||
|  | 
 | ||||||
|  |         // Create player.
 | ||||||
|  |         const videojs = await this.getVideoJS(); | ||||||
|  |         const dataSetupString = element.getAttribute('data-setup') || element.getAttribute('data-setup-lazy') || '{}'; | ||||||
|  |         const data = CoreTextUtils.parseJSON<VideoJSOptions>(dataSetupString, {}); | ||||||
|  |         const player = videojs( | ||||||
|  |             element, | ||||||
|  |             { | ||||||
|  |                 controls: true, | ||||||
|  |                 techOrder: ['OgvJS'], | ||||||
|  |                 language: await CoreLang.getCurrentLanguage(), | ||||||
|  |                 controlBar: { pictureInPictureToggle: false }, | ||||||
|  |                 aspectRatio: data.aspectRatio, | ||||||
|  |             }, | ||||||
|  |             () => element.tagName === 'VIDEO' && this.fixVideoJSPlayerSize(player), | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         CoreEvents.trigger(VIDEO_JS_PLAYER_CREATED, { | ||||||
|  |             element, | ||||||
|  |             player, | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Find a VideoJS player by id. | ||||||
|  |      * | ||||||
|  |      * @param id Element id. | ||||||
|  |      * @returns VideoJS player. | ||||||
|  |      */ | ||||||
|  |     async findPlayer(id: string): Promise<VideoJSPlayer | null> { | ||||||
|  |         const videojs = await this.getVideoJS(); | ||||||
|  | 
 | ||||||
|  |         return videojs.getPlayer(id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Treat Video JS Youtube video links and translate them to iframes. | ||||||
|  |      * | ||||||
|  |      * @param video Video element. | ||||||
|  |      */ | ||||||
|  |     treatYoutubeVideos(video: HTMLElement): void { | ||||||
|  |         if (!video.classList.contains('video-js')) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const dataSetupString = video.getAttribute('data-setup') || video.getAttribute('data-setup-lazy') || '{}'; | ||||||
|  |         const data = CoreTextUtils.parseJSON<VideoJSOptions>(dataSetupString, {}); | ||||||
|  |         const youtubeUrl = data.techOrder?.[0] == 'youtube' && CoreUrlUtils.getYoutubeEmbedUrl(data.sources?.[0]?.src); | ||||||
|  | 
 | ||||||
|  |         if (!youtubeUrl) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const iframe = document.createElement('iframe'); | ||||||
|  |         iframe.id = video.id; | ||||||
|  |         iframe.src = youtubeUrl; | ||||||
|  |         iframe.setAttribute('frameborder', '0'); | ||||||
|  |         iframe.setAttribute('allowfullscreen', '1'); | ||||||
|  |         iframe.width = '100%'; | ||||||
|  |         iframe.height = '300'; | ||||||
|  | 
 | ||||||
|  |         // Replace video tag by the iframe.
 | ||||||
|  |         video.parentNode?.replaceChild(iframe, video); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Gets videojs instance. | ||||||
|  |      * | ||||||
|  |      * @returns VideoJS. | ||||||
|  |      */ | ||||||
|  |     protected async getVideoJS(): Promise<typeof videojs> { | ||||||
|  |         if (!this.videojs) { | ||||||
|  |             this.videojs = new CorePromisedValue(); | ||||||
|  | 
 | ||||||
|  |             // Inject CSS.
 | ||||||
|  |             const link = document.createElement('link'); | ||||||
|  | 
 | ||||||
|  |             link.rel = 'stylesheet'; | ||||||
|  |             link.href = 'assets/lib/video.js/video-js.min.css'; | ||||||
|  | 
 | ||||||
|  |             document.head.appendChild(link); | ||||||
|  | 
 | ||||||
|  |             // Load library.
 | ||||||
|  |             return import('@addons/filter/mediaplugin/utils/videojs').then(({ initializeVideoJSOgvJS, videojs }) => { | ||||||
|  |                 initializeVideoJSOgvJS(); | ||||||
|  | 
 | ||||||
|  |                 this.videojs?.resolve(videojs); | ||||||
|  | 
 | ||||||
|  |                 return videojs; | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return this.videojs; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Fix VideoJS player size. | ||||||
|  |      * If video width is wider than available width, video is cut off. Fix the dimensions in this case. | ||||||
|  |      * | ||||||
|  |      * @param player Player instance. | ||||||
|  |      */ | ||||||
|  |     protected fixVideoJSPlayerSize(player: VideoJSPlayer): void { | ||||||
|  |         const videoWidth = player.videoWidth(); | ||||||
|  |         const videoHeight = player.videoHeight(); | ||||||
|  |         const playerDimensions = player.currentDimensions(); | ||||||
|  |         if (!videoWidth || !videoHeight || !playerDimensions.width || videoWidth === playerDimensions.width) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const candidateHeight = playerDimensions.width * videoHeight / videoWidth; | ||||||
|  |         if (!playerDimensions.height || Math.abs(candidateHeight - playerDimensions.height) > 1) { | ||||||
|  |             player.dimension('height', candidateHeight); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const AddonFilterMediaPluginVideoJS = makeSingleton(AddonFilterMediaPluginVideoJSService); | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data passed to VIDEO_JS_PLAYER_CREATED event. | ||||||
|  |  */ | ||||||
|  | export type CoreEventJSVideoPlayerCreated = { | ||||||
|  |     element: HTMLAudioElement | HTMLVideoElement; | ||||||
|  |     player: VideoJSPlayer; | ||||||
|  | }; | ||||||
							
								
								
									
										28
									
								
								src/addons/filter/mediaplugin/utils/videojs.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/addons/filter/mediaplugin/utils/videojs.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | |||||||
|  | // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||||
|  | //
 | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||||
|  | // you may not use this file except in compliance with the License.
 | ||||||
|  | // You may obtain a copy of the License at
 | ||||||
|  | //
 | ||||||
|  | //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | //
 | ||||||
|  | // Unless required by applicable law or agreed to in writing, software
 | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||||
|  | // See the License for the specific language governing permissions and
 | ||||||
|  | // limitations under the License.
 | ||||||
|  | 
 | ||||||
|  | import { VideoJSOgvJS } from '@addons/filter/mediaplugin/classes/videojs-ogvjs'; | ||||||
|  | import { OGVLoader } from 'ogv'; | ||||||
|  | import videojs from 'video.js'; | ||||||
|  | 
 | ||||||
|  | export { videojs }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Initialize the controller. | ||||||
|  |  */ | ||||||
|  | export function initializeVideoJSOgvJS(): void { | ||||||
|  |     OGVLoader.base = 'assets/lib/ogv'; | ||||||
|  | 
 | ||||||
|  |     videojs.getComponent('Tech').registerTech('OgvJS', VideoJSOgvJS); | ||||||
|  | } | ||||||
| @ -14,10 +14,11 @@ | |||||||
| 
 | 
 | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { ElementController } from './ElementController'; | import { ElementController } from './ElementController'; | ||||||
| import videojs, { VideoJSPlayer } from 'video.js'; |  | ||||||
| import { CorePromisedValue } from '@classes/promised-value'; | import { CorePromisedValue } from '@classes/promised-value'; | ||||||
| import { CoreEventObserver, CoreEvents } from '@singletons/events'; | import { CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||||
| import { CoreMedia } from '@singletons/media'; | import { CoreMedia } from '@singletons/media'; | ||||||
|  | import { AddonFilterMediaPluginVideoJS, VIDEO_JS_PLAYER_CREATED } from '@addons/filter/mediaplugin/services/videojs'; | ||||||
|  | import type { VideoJSPlayer } from 'video.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Wrapper class to control the interactivity of a media element. |  * Wrapper class to control the interactivity of a media element. | ||||||
| @ -42,21 +43,7 @@ export class MediaElementController extends ElementController { | |||||||
| 
 | 
 | ||||||
|         media.autoplay = false; |         media.autoplay = false; | ||||||
| 
 | 
 | ||||||
|         if (CoreMedia.mediaUsesJavascriptPlayer(media)) { |         this.initJSPlayer(media); | ||||||
|             const player = this.searchJSPlayer(); |  | ||||||
|             if (player) { |  | ||||||
|                 this.jsPlayer.resolve(player); |  | ||||||
|             } else { |  | ||||||
|                 this.jsPlayerListener = CoreEvents.on(CoreEvents.JS_PLAYER_CREATED, data => { |  | ||||||
|                     if (data.element === media) { |  | ||||||
|                         this.jsPlayerListener?.off(); |  | ||||||
|                         this.jsPlayer.resolve(data.player); |  | ||||||
|                     } |  | ||||||
|                 }); |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             this.jsPlayer.resolve(null); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         enabled && this.onEnabled(); |         enabled && this.onEnabled(); | ||||||
|     } |     } | ||||||
| @ -115,6 +102,36 @@ export class MediaElementController extends ElementController { | |||||||
|         jsPlayer?.dispose(); |         jsPlayer?.dispose(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Init JS Player instance. | ||||||
|  |      * | ||||||
|  |      * @param media Media element. | ||||||
|  |      */ | ||||||
|  |     private async initJSPlayer(media: HTMLMediaElement): Promise<void> { | ||||||
|  |         if (!CoreMedia.mediaUsesJavascriptPlayer(media)) { | ||||||
|  |             this.jsPlayer.resolve(null); | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const player = await this.searchJSPlayer(); | ||||||
|  | 
 | ||||||
|  |         if (!player) { | ||||||
|  |             this.jsPlayerListener = CoreEvents.on(VIDEO_JS_PLAYER_CREATED, ({ element, player }) => { | ||||||
|  |                 if (element !== media) { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 this.jsPlayerListener?.off(); | ||||||
|  |                 this.jsPlayer.resolve(player); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.jsPlayer.resolve(player); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Start listening playback events. |      * Start listening playback events. | ||||||
|      * |      * | ||||||
| @ -153,8 +170,9 @@ export class MediaElementController extends ElementController { | |||||||
|      * |      * | ||||||
|      * @returns Player instance if found. |      * @returns Player instance if found. | ||||||
|      */ |      */ | ||||||
|     private searchJSPlayer(): VideoJSPlayer | null { |     private async searchJSPlayer(): Promise<VideoJSPlayer | null> { | ||||||
|         return videojs.getPlayer(this.media.id) || videojs.getPlayer(this.media.id.replace('_html5_api', '')); |         return AddonFilterMediaPluginVideoJS.findPlayer(this.media.id) | ||||||
|  |             ?? AddonFilterMediaPluginVideoJS.findPlayer(this.media.id.replace('_html5_api', '')); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -20,7 +20,6 @@ import { CoreFilepoolComponentFileEventData } from '@services/filepool'; | |||||||
| import { CoreRedirectPayload } from '@services/navigator'; | import { CoreRedirectPayload } from '@services/navigator'; | ||||||
| import { CoreCourseModuleCompletionData } from '@features/course/services/course-helper'; | import { CoreCourseModuleCompletionData } from '@features/course/services/course-helper'; | ||||||
| import { CoreScreenOrientation } from '@services/screen'; | import { CoreScreenOrientation } from '@services/screen'; | ||||||
| import { VideoJSPlayer } from 'video.js'; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Observer instance to stop listening to an event. |  * Observer instance to stop listening to an event. | ||||||
| @ -65,7 +64,6 @@ export interface CoreEventsData { | |||||||
|     [CoreEvents.ORIENTATION_CHANGE]: CoreEventOrientationData; |     [CoreEvents.ORIENTATION_CHANGE]: CoreEventOrientationData; | ||||||
|     [CoreEvents.COURSE_MODULE_VIEWED]: CoreEventCourseModuleViewed; |     [CoreEvents.COURSE_MODULE_VIEWED]: CoreEventCourseModuleViewed; | ||||||
|     [CoreEvents.COMPLETE_REQUIRED_PROFILE_DATA_FINISHED]: CoreEventCompleteRequiredProfileDataFinished; |     [CoreEvents.COMPLETE_REQUIRED_PROFILE_DATA_FINISHED]: CoreEventCompleteRequiredProfileDataFinished; | ||||||
|     [CoreEvents.JS_PLAYER_CREATED]: CoreEventJSVideoPlayerCreated; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
| @ -125,7 +123,6 @@ export class CoreEvents { | |||||||
|     static readonly COMPLETE_REQUIRED_PROFILE_DATA_FINISHED = 'complete_required_profile_data_finished'; |     static readonly COMPLETE_REQUIRED_PROFILE_DATA_FINISHED = 'complete_required_profile_data_finished'; | ||||||
|     static readonly MAIN_HOME_LOADED = 'main_home_loaded'; |     static readonly MAIN_HOME_LOADED = 'main_home_loaded'; | ||||||
|     static readonly FULL_SCREEN_CHANGED = 'full_screen_changed'; |     static readonly FULL_SCREEN_CHANGED = 'full_screen_changed'; | ||||||
|     static readonly JS_PLAYER_CREATED = 'js_player_created'; |  | ||||||
| 
 | 
 | ||||||
|     protected static logger = CoreLogger.getInstance('CoreEvents'); |     protected static logger = CoreLogger.getInstance('CoreEvents'); | ||||||
|     protected static observables: { [eventName: string]: Subject<unknown> } = {}; |     protected static observables: { [eventName: string]: Subject<unknown> } = {}; | ||||||
| @ -493,12 +490,3 @@ export type CoreEventCourseModuleViewed = { | |||||||
| export type CoreEventCompleteRequiredProfileDataFinished = { | export type CoreEventCompleteRequiredProfileDataFinished = { | ||||||
|     path: string; |     path: string; | ||||||
| }; | }; | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Data passed to JS_PLAYER_CREATED event. |  | ||||||
|  */ |  | ||||||
| export type CoreEventJSVideoPlayerCreated = { |  | ||||||
|     id: string; |  | ||||||
|     element: HTMLAudioElement | HTMLVideoElement; |  | ||||||
|     player: VideoJSPlayer; |  | ||||||
| }; |  | ||||||
|  | |||||||
| @ -16,19 +16,12 @@ | |||||||
|     <meta name="msapplication-tap-highlight" content="no" /> |     <meta name="msapplication-tap-highlight" content="no" /> | ||||||
| 
 | 
 | ||||||
|     <link rel="icon" type="image/png" href="assets/icon/favicon.png" /> |     <link rel="icon" type="image/png" href="assets/icon/favicon.png" /> | ||||||
|     <link rel="stylesheet" href="assets/lib/video.js/video-js.min.css"> |  | ||||||
| 
 | 
 | ||||||
|     <!-- add to homescreen for ios --> |     <!-- add to homescreen for ios --> | ||||||
|     <meta name="apple-mobile-web-app-capable" content="yes" /> |     <meta name="apple-mobile-web-app-capable" content="yes" /> | ||||||
|     <meta name="apple-mobile-web-app-status-bar-style" content="black" /> |     <meta name="apple-mobile-web-app-status-bar-style" content="black" /> | ||||||
| 
 | 
 | ||||||
|     <script src="assets/lib/mathjax/MathJax.js?delayStartupUntil=configured"></script> |     <script src="assets/lib/mathjax/MathJax.js?delayStartupUntil=configured"></script> | ||||||
|     <script type="text/javascript"> |  | ||||||
|         if (globalThis === undefined) { |  | ||||||
|             // Define globalThis in environments where it's not supported to avoid errors with ogv.js. |  | ||||||
|             var globalThis = window; |  | ||||||
|         } |  | ||||||
|     </script> |  | ||||||
| </head> | </head> | ||||||
| 
 | 
 | ||||||
| <body> | <body> | ||||||
|  | |||||||
| @ -20,6 +20,7 @@ import 'zone.js/dist/zone'; | |||||||
| 
 | 
 | ||||||
| // Platform polyfills
 | // Platform polyfills
 | ||||||
| import 'core-js/es/array/includes'; | import 'core-js/es/array/includes'; | ||||||
|  | import 'core-js/es/global-this'; | ||||||
| import 'core-js/es/promise/finally'; | import 'core-js/es/promise/finally'; | ||||||
| import 'core-js/es/string/match-all'; | import 'core-js/es/string/match-all'; | ||||||
| import 'core-js/es/string/trim-right'; | import 'core-js/es/string/trim-right'; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user