Skip to content

Commit 9b011ba

Browse files
committed
MOBILE-4166 core: Fix VideoJS in books and destroy players
1 parent 9419db0 commit 9b011ba

File tree

5 files changed

+143
-14
lines changed

5 files changed

+143
-14
lines changed

src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { CoreUrlUtils } from '@services/utils/url';
2222
import { makeSingleton } from '@singletons';
2323
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
2424
import { CoreDom } from '@singletons/dom';
25+
import { CoreEvents } from '@singletons/events';
2526
import videojs from 'video.js';
2627
import { VideoJSOptions } from '../../classes/videojs-ogvjs';
2728

@@ -88,7 +89,7 @@ export class AddonFilterMediaPluginHandlerService extends CoreFilterDefaultHandl
8889
const dataSetupString = mediaElement.getAttribute('data-setup') || mediaElement.getAttribute('data-setup-lazy') || '{}';
8990
const data = CoreTextUtils.parseJSON<VideoJSOptions>(dataSetupString, {});
9091

91-
videojs(mediaElement, {
92+
const player = videojs(mediaElement, {
9293
controls: true,
9394
techOrder: ['OgvJS'],
9495
language: lang,
@@ -98,6 +99,13 @@ export class AddonFilterMediaPluginHandlerService extends CoreFilterDefaultHandl
9899
},
99100
aspectRatio: data.aspectRatio,
100101
});
102+
103+
CoreEvents.trigger(CoreEvents.JS_PLAYER_CREATED, {
104+
id: mediaElement.id,
105+
element: mediaElement,
106+
player,
107+
});
108+
101109
}
102110

103111
/**

src/core/classes/element-controllers/ElementController.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
export abstract class ElementController {
1919

2020
protected enabled: boolean;
21+
protected destroyed = false;
2122

2223
constructor(enabled: boolean) {
2324
this.enabled = enabled;
@@ -49,6 +50,19 @@ export abstract class ElementController {
4950
this.onDisabled();
5051
}
5152

53+
/**
54+
* Destroy the element.
55+
*/
56+
destroy(): void {
57+
if (this.destroyed) {
58+
return;
59+
}
60+
61+
this.destroyed = true;
62+
63+
this.onDestroy();
64+
}
65+
5266
/**
5367
* Update underlying element to enable interactivity.
5468
*/
@@ -59,4 +73,11 @@ export abstract class ElementController {
5973
*/
6074
abstract onDisabled(): void;
6175

76+
/**
77+
* Destroy/dispose pertinent data.
78+
*/
79+
onDestroy(): void {
80+
// By default, nothing to destroy.
81+
}
82+
6283
}

src/core/classes/element-controllers/MediaElementController.ts

Lines changed: 100 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
import { CoreUtils } from '@services/utils/utils';
1616
import { ElementController } from './ElementController';
17+
import videojs from 'video.js';
18+
import { CoreDom } from '@singletons/dom';
19+
import { CorePromisedValue } from '@classes/promised-value';
20+
import { CoreEventObserver, CoreEvents } from '@singletons/events';
1721

1822
/**
1923
* Wrapper class to control the interactivity of a media element.
@@ -25,6 +29,10 @@ export class MediaElementController extends ElementController {
2529
private playing?: boolean;
2630
private playListener?: () => void;
2731
private pauseListener?: () => void;
32+
private jsPlayer = new CorePromisedValue<VideoJSPlayer | null>();
33+
private jsPlayerListener?: CoreEventObserver;
34+
private shouldEnable = false;
35+
private shouldDisable = false;
2836

2937
constructor(media: HTMLMediaElement, enabled: boolean) {
3038
super(enabled);
@@ -34,48 +42,127 @@ export class MediaElementController extends ElementController {
3442

3543
media.autoplay = false;
3644

45+
if (CoreDom.mediaUsesJavascriptPlayer(media)) {
46+
const player = this.searchJSPlayer();
47+
if (player) {
48+
this.jsPlayer.resolve(player);
49+
} else {
50+
this.jsPlayerListener = CoreEvents.on(CoreEvents.JS_PLAYER_CREATED, data => {
51+
if (data.element === media) {
52+
this.jsPlayerListener?.off();
53+
this.jsPlayer.resolve(data.player as VideoJSPlayer);
54+
}
55+
});
56+
}
57+
} else {
58+
this.jsPlayer.resolve(null);
59+
}
60+
3761
enabled && this.onEnabled();
3862
}
3963

4064
/**
4165
* @inheritdoc
4266
*/
43-
onEnabled(): void {
67+
async onEnabled(): Promise<void> {
68+
this.shouldEnable = true;
69+
this.shouldDisable = false;
70+
71+
const jsPlayer = await this.jsPlayer;
72+
73+
if (!this.shouldEnable || this.destroyed) {
74+
return;
75+
}
76+
4477
const ready = this.playing ?? this.autoplay
45-
? this.media.play()
78+
? (jsPlayer ?? this.media).play()
4679
: Promise.resolve();
4780

48-
ready
49-
.then(() => this.addPlaybackEventListeners())
50-
.catch(error => CoreUtils.logUnhandledError('Error enabling media element', error));
81+
try {
82+
await ready;
83+
84+
this.addPlaybackEventListeners(jsPlayer);
85+
} catch (error) {
86+
CoreUtils.logUnhandledError('Error enabling media element', error);
87+
}
5188
}
5289

5390
/**
5491
* @inheritdoc
5592
*/
5693
async onDisabled(): Promise<void> {
57-
this.removePlaybackEventListeners();
94+
this.shouldDisable = true;
95+
this.shouldEnable = false;
5896

59-
this.media.pause();
97+
const jsPlayer = await this.jsPlayer;
98+
99+
if (!this.shouldDisable || this.destroyed) {
100+
return;
101+
}
102+
103+
this.removePlaybackEventListeners(jsPlayer);
104+
105+
(jsPlayer ?? this.media).pause();
106+
}
107+
108+
/**
109+
* @inheritdoc
110+
*/
111+
async onDestroy(): Promise<void> {
112+
const jsPlayer = await this.jsPlayer;
113+
114+
this.removePlaybackEventListeners(jsPlayer);
115+
jsPlayer?.dispose();
60116
}
61117

62118
/**
63119
* Start listening playback events.
120+
*
121+
* @param jsPlayer Javascript player instance (if any).
64122
*/
65-
private addPlaybackEventListeners(): void {
66-
this.media.addEventListener('play', this.playListener = () => this.playing = true);
67-
this.media.addEventListener('pause', this.pauseListener = () => this.playing = false);
123+
private addPlaybackEventListeners(jsPlayer: VideoJSPlayer | null): void {
124+
if (jsPlayer) {
125+
jsPlayer.on('play', this.playListener = () => this.playing = true);
126+
jsPlayer.on('pause', this.pauseListener = () => this.playing = false);
127+
} else {
128+
this.media.addEventListener('play', this.playListener = () => this.playing = true);
129+
this.media.addEventListener('pause', this.pauseListener = () => this.playing = false);
130+
}
68131
}
69132

70133
/**
71134
* Stop listening playback events.
135+
*
136+
* @param jsPlayer Javascript player instance (if any).
72137
*/
73-
private removePlaybackEventListeners(): void {
74-
this.playListener && this.media.removeEventListener('play', this.playListener);
75-
this.pauseListener && this.media.removeEventListener('pause', this.pauseListener);
138+
private removePlaybackEventListeners(jsPlayer: VideoJSPlayer | null): void {
139+
if (jsPlayer) {
140+
this.playListener && jsPlayer.off('play', this.playListener);
141+
this.pauseListener && jsPlayer.off('pause', this.pauseListener);
142+
} else {
143+
this.playListener && this.media.removeEventListener('play', this.playListener);
144+
this.pauseListener && this.media.removeEventListener('pause', this.pauseListener);
145+
}
76146

77147
delete this.playListener;
78148
delete this.pauseListener;
79149
}
80150

151+
/**
152+
* Search JS player instance.
153+
*
154+
* @returns Player instance if found.
155+
*/
156+
private searchJSPlayer(): VideoJSPlayer | undefined {
157+
return videojs.getPlayer(this.media.id) || videojs.getPlayer(this.media.id.replace('_html5_api', ''));
158+
}
159+
81160
}
161+
162+
type VideoJSPlayer = {
163+
play: () => Promise<void>;
164+
pause: () => Promise<void>;
165+
on: (name: string, callback: (ev: Event) => void) => void;
166+
off: (name: string, callback: (ev: Event) => void) => void;
167+
dispose: () => void;
168+
};

src/core/directives/format-text.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
149149
ngOnDestroy(): void {
150150
this.domElementPromise?.cancel();
151151
this.domPromises.forEach((promise) => { promise.cancel();});
152+
this.elementControllers.forEach(controller => controller.destroy());
152153
}
153154

154155
/**
@@ -365,6 +366,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
365366
// Move the children to the current element to be able to calculate the height.
366367
CoreDomUtils.moveChildren(result.div, this.element);
367368

369+
this.elementControllers.forEach(controller => controller.destroy());
368370
this.elementControllers = result.elementControllers;
369371

370372
await CoreUtils.nextTick();

src/core/singletons/events.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export interface CoreEventsData {
6464
[CoreEvents.ORIENTATION_CHANGE]: CoreEventOrientationData;
6565
[CoreEvents.COURSE_MODULE_VIEWED]: CoreEventCourseModuleViewed;
6666
[CoreEvents.COMPLETE_REQUIRED_PROFILE_DATA_FINISHED]: CoreEventCompleteRequiredProfileDataFinished;
67+
[CoreEvents.JS_PLAYER_CREATED]: CoreEventJSVideoPlayerCreated;
6768
}
6869

6970
/*
@@ -123,6 +124,7 @@ export class CoreEvents {
123124
static readonly COMPLETE_REQUIRED_PROFILE_DATA_FINISHED = 'complete_required_profile_data_finished';
124125
static readonly MAIN_HOME_LOADED = 'main_home_loaded';
125126
static readonly FULL_SCREEN_CHANGED = 'full_screen_changed';
127+
static readonly JS_PLAYER_CREATED = 'js_player_created';
126128

127129
protected static logger = CoreLogger.getInstance('CoreEvents');
128130
protected static observables: { [eventName: string]: Subject<unknown> } = {};
@@ -490,3 +492,12 @@ export type CoreEventCourseModuleViewed = {
490492
export type CoreEventCompleteRequiredProfileDataFinished = {
491493
path: string;
492494
};
495+
496+
/**
497+
* Data passed to JS_PLAYER_CREATED event.
498+
*/
499+
export type CoreEventJSVideoPlayerCreated = {
500+
id: string;
501+
element: HTMLAudioElement | HTMLVideoElement;
502+
player: unknown;
503+
};

0 commit comments

Comments
 (0)