MOBILE-4239 mediaplugin: Lazy load videojs
parent
4cb9a6640c
commit
8ee614a60a
|
@ -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 {
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the VideoJS classes and data if present.
|
// Remove the VideoJS classes and data if present.
|
||||||
mediaElement.classList.remove('video-js');
|
mediaElement.classList.remove('video-js');
|
||||||
mediaElement.removeAttribute('data-setup');
|
mediaElement.removeAttribute('data-setup');
|
||||||
mediaElement.removeAttribute('data-setup-lazy');
|
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);
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
|
@ -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…
Reference in New Issue