forked from EVOgeek/Vmeda.Online
		
	MOBILE-4166 core: Fix VideoJS in books and destroy players
This commit is contained in:
		
							parent
							
								
									9419db02a1
								
							
						
					
					
						commit
						9b011ba350
					
				| @ -22,6 +22,7 @@ import { CoreUrlUtils } from '@services/utils/url'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { CoreDirectivesRegistry } from '@singletons/directives-registry'; | ||||
| import { CoreDom } from '@singletons/dom'; | ||||
| import { CoreEvents } from '@singletons/events'; | ||||
| import videojs from 'video.js'; | ||||
| import { VideoJSOptions } from '../../classes/videojs-ogvjs'; | ||||
| 
 | ||||
| @ -88,7 +89,7 @@ export class AddonFilterMediaPluginHandlerService extends CoreFilterDefaultHandl | ||||
|         const dataSetupString = mediaElement.getAttribute('data-setup') || mediaElement.getAttribute('data-setup-lazy') || '{}'; | ||||
|         const data = CoreTextUtils.parseJSON<VideoJSOptions>(dataSetupString, {}); | ||||
| 
 | ||||
|         videojs(mediaElement, { | ||||
|         const player = videojs(mediaElement, { | ||||
|             controls: true, | ||||
|             techOrder: ['OgvJS'], | ||||
|             language: lang, | ||||
| @ -98,6 +99,13 @@ export class AddonFilterMediaPluginHandlerService extends CoreFilterDefaultHandl | ||||
|             }, | ||||
|             aspectRatio: data.aspectRatio, | ||||
|         }); | ||||
| 
 | ||||
|         CoreEvents.trigger(CoreEvents.JS_PLAYER_CREATED, { | ||||
|             id: mediaElement.id, | ||||
|             element: mediaElement, | ||||
|             player, | ||||
|         }); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -18,6 +18,7 @@ | ||||
| export abstract class ElementController { | ||||
| 
 | ||||
|     protected enabled: boolean; | ||||
|     protected destroyed = false; | ||||
| 
 | ||||
|     constructor(enabled: boolean) { | ||||
|         this.enabled = enabled; | ||||
| @ -49,6 +50,19 @@ export abstract class ElementController { | ||||
|         this.onDisabled(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Destroy the element. | ||||
|      */ | ||||
|     destroy(): void { | ||||
|         if (this.destroyed) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.destroyed = true; | ||||
| 
 | ||||
|         this.onDestroy(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update underlying element to enable interactivity. | ||||
|      */ | ||||
| @ -59,4 +73,11 @@ export abstract class ElementController { | ||||
|      */ | ||||
|     abstract onDisabled(): void; | ||||
| 
 | ||||
|     /** | ||||
|      * Destroy/dispose pertinent data. | ||||
|      */ | ||||
|     onDestroy(): void { | ||||
|         // By default, nothing to destroy.
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -14,6 +14,10 @@ | ||||
| 
 | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { ElementController } from './ElementController'; | ||||
| import videojs from 'video.js'; | ||||
| import { CoreDom } from '@singletons/dom'; | ||||
| import { CorePromisedValue } from '@classes/promised-value'; | ||||
| import { CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||
| 
 | ||||
| /** | ||||
|  * Wrapper class to control the interactivity of a media element. | ||||
| @ -25,6 +29,10 @@ export class MediaElementController extends ElementController { | ||||
|     private playing?: boolean; | ||||
|     private playListener?: () => void; | ||||
|     private pauseListener?: () => void; | ||||
|     private jsPlayer = new CorePromisedValue<VideoJSPlayer | null>(); | ||||
|     private jsPlayerListener?: CoreEventObserver; | ||||
|     private shouldEnable = false; | ||||
|     private shouldDisable = false; | ||||
| 
 | ||||
|     constructor(media: HTMLMediaElement, enabled: boolean) { | ||||
|         super(enabled); | ||||
| @ -34,48 +42,127 @@ export class MediaElementController extends ElementController { | ||||
| 
 | ||||
|         media.autoplay = false; | ||||
| 
 | ||||
|         if (CoreDom.mediaUsesJavascriptPlayer(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 as VideoJSPlayer); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         } else { | ||||
|             this.jsPlayer.resolve(null); | ||||
|         } | ||||
| 
 | ||||
|         enabled && this.onEnabled(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     onEnabled(): void { | ||||
|     async onEnabled(): Promise<void> { | ||||
|         this.shouldEnable = true; | ||||
|         this.shouldDisable = false; | ||||
| 
 | ||||
|         const jsPlayer = await this.jsPlayer; | ||||
| 
 | ||||
|         if (!this.shouldEnable || this.destroyed) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const ready = this.playing ?? this.autoplay | ||||
|             ? this.media.play() | ||||
|             ? (jsPlayer ?? this.media).play() | ||||
|             : Promise.resolve(); | ||||
| 
 | ||||
|         ready | ||||
|             .then(() => this.addPlaybackEventListeners()) | ||||
|             .catch(error => CoreUtils.logUnhandledError('Error enabling media element', error)); | ||||
|         try { | ||||
|             await ready; | ||||
| 
 | ||||
|             this.addPlaybackEventListeners(jsPlayer); | ||||
|         } catch (error) { | ||||
|             CoreUtils.logUnhandledError('Error enabling media element', error); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async onDisabled(): Promise<void> { | ||||
|         this.removePlaybackEventListeners(); | ||||
|         this.shouldDisable = true; | ||||
|         this.shouldEnable = false; | ||||
| 
 | ||||
|         this.media.pause(); | ||||
|         const jsPlayer = await this.jsPlayer; | ||||
| 
 | ||||
|         if (!this.shouldDisable || this.destroyed) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.removePlaybackEventListeners(jsPlayer); | ||||
| 
 | ||||
|         (jsPlayer ?? this.media).pause(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async onDestroy(): Promise<void> { | ||||
|         const jsPlayer = await this.jsPlayer; | ||||
| 
 | ||||
|         this.removePlaybackEventListeners(jsPlayer); | ||||
|         jsPlayer?.dispose(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Start listening playback events. | ||||
|      * | ||||
|      * @param jsPlayer Javascript player instance (if any). | ||||
|      */ | ||||
|     private addPlaybackEventListeners(): void { | ||||
|         this.media.addEventListener('play', this.playListener = () => this.playing = true); | ||||
|         this.media.addEventListener('pause', this.pauseListener = () => this.playing = false); | ||||
|     private addPlaybackEventListeners(jsPlayer: VideoJSPlayer | null): void { | ||||
|         if (jsPlayer) { | ||||
|             jsPlayer.on('play', this.playListener = () => this.playing = true); | ||||
|             jsPlayer.on('pause', this.pauseListener = () => this.playing = false); | ||||
|         } else { | ||||
|             this.media.addEventListener('play', this.playListener = () => this.playing = true); | ||||
|             this.media.addEventListener('pause', this.pauseListener = () => this.playing = false); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Stop listening playback events. | ||||
|      * | ||||
|      * @param jsPlayer Javascript player instance (if any). | ||||
|      */ | ||||
|     private removePlaybackEventListeners(): void { | ||||
|         this.playListener && this.media.removeEventListener('play', this.playListener); | ||||
|         this.pauseListener && this.media.removeEventListener('pause', this.pauseListener); | ||||
|     private removePlaybackEventListeners(jsPlayer: VideoJSPlayer | null): void { | ||||
|         if (jsPlayer) { | ||||
|             this.playListener && jsPlayer.off('play', this.playListener); | ||||
|             this.pauseListener && jsPlayer.off('pause', this.pauseListener); | ||||
|         } else { | ||||
|             this.playListener && this.media.removeEventListener('play', this.playListener); | ||||
|             this.pauseListener && this.media.removeEventListener('pause', this.pauseListener); | ||||
|         } | ||||
| 
 | ||||
|         delete this.playListener; | ||||
|         delete this.pauseListener; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Search JS player instance. | ||||
|      * | ||||
|      * @returns Player instance if found. | ||||
|      */ | ||||
|     private searchJSPlayer(): VideoJSPlayer | undefined { | ||||
|         return videojs.getPlayer(this.media.id) || videojs.getPlayer(this.media.id.replace('_html5_api', '')); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| type VideoJSPlayer = { | ||||
|     play: () => Promise<void>; | ||||
|     pause: () => Promise<void>; | ||||
|     on: (name: string, callback: (ev: Event) => void) => void; | ||||
|     off: (name: string, callback: (ev: Event) => void) => void; | ||||
|     dispose: () => void; | ||||
| }; | ||||
|  | ||||
| @ -149,6 +149,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec | ||||
|     ngOnDestroy(): void { | ||||
|         this.domElementPromise?.cancel(); | ||||
|         this.domPromises.forEach((promise) => { promise.cancel();}); | ||||
|         this.elementControllers.forEach(controller => controller.destroy()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -365,6 +366,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec | ||||
|         // Move the children to the current element to be able to calculate the height.
 | ||||
|         CoreDomUtils.moveChildren(result.div, this.element); | ||||
| 
 | ||||
|         this.elementControllers.forEach(controller => controller.destroy()); | ||||
|         this.elementControllers = result.elementControllers; | ||||
| 
 | ||||
|         await CoreUtils.nextTick(); | ||||
|  | ||||
| @ -64,6 +64,7 @@ export interface CoreEventsData { | ||||
|     [CoreEvents.ORIENTATION_CHANGE]: CoreEventOrientationData; | ||||
|     [CoreEvents.COURSE_MODULE_VIEWED]: CoreEventCourseModuleViewed; | ||||
|     [CoreEvents.COMPLETE_REQUIRED_PROFILE_DATA_FINISHED]: CoreEventCompleteRequiredProfileDataFinished; | ||||
|     [CoreEvents.JS_PLAYER_CREATED]: CoreEventJSVideoPlayerCreated; | ||||
| } | ||||
| 
 | ||||
| /* | ||||
| @ -123,6 +124,7 @@ export class CoreEvents { | ||||
|     static readonly COMPLETE_REQUIRED_PROFILE_DATA_FINISHED = 'complete_required_profile_data_finished'; | ||||
|     static readonly MAIN_HOME_LOADED = 'main_home_loaded'; | ||||
|     static readonly FULL_SCREEN_CHANGED = 'full_screen_changed'; | ||||
|     static readonly JS_PLAYER_CREATED = 'js_player_created'; | ||||
| 
 | ||||
|     protected static logger = CoreLogger.getInstance('CoreEvents'); | ||||
|     protected static observables: { [eventName: string]: Subject<unknown> } = {}; | ||||
| @ -490,3 +492,12 @@ export type CoreEventCourseModuleViewed = { | ||||
| export type CoreEventCompleteRequiredProfileDataFinished = { | ||||
|     path: string; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data passed to JS_PLAYER_CREATED event. | ||||
|  */ | ||||
| export type CoreEventJSVideoPlayerCreated = { | ||||
|     id: string; | ||||
|     element: HTMLAudioElement | HTMLVideoElement; | ||||
|     player: unknown; | ||||
| }; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user