diff --git a/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html b/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html index 32e0eeb44..5fb418b67 100644 --- a/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html +++ b/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html @@ -17,7 +17,7 @@ + [courseId]="courseId" [hasDataToSync]="hasOffline" (completionChanged)="onCompletionChange()" /> @@ -162,21 +162,8 @@
- - - - -

{{ errorMessage | translate }}

-
-
- - {{ 'core.openinbrowser' | translate }} - -
- - +

{{ 'addon.mod_scorm.exceededmaxattempts' | translate }}

@@ -184,7 +171,16 @@
- + + + + + + diff --git a/src/addons/mod/scorm/components/index/index.ts b/src/addons/mod/scorm/components/index/index.ts index 93d8d1da3..87e7bb36c 100644 --- a/src/addons/mod/scorm/components/index/index.ts +++ b/src/addons/mod/scorm/components/index/index.ts @@ -13,7 +13,7 @@ // limitations under the License. import { DownloadStatus } from '@/core/constants'; -import { Component, Input, OnInit, Optional } from '@angular/core'; +import { Component, Input, OnDestroy, OnInit, Optional } from '@angular/core'; import { CoreError } from '@classes/errors/error'; import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component'; import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; @@ -23,7 +23,7 @@ import { CoreNavigator } from '@services/navigator'; import { CoreSync } from '@services/sync'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreObject } from '@singletons/object'; -import { Translate } from '@singletons'; +import { NgZone, Translate } from '@singletons'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { AddonModScormPrefetchHandler } from '../../services/handlers/prefetch'; import { @@ -51,6 +51,8 @@ import { } from '../../constants'; import { CoreWait } from '@singletons/wait'; import { CorePromiseUtils } from '@singletons/promise-utils'; +import { CoreNetwork } from '@services/network'; +import { Subscription } from 'rxjs'; /** * Component that displays a SCORM entry page. @@ -60,7 +62,7 @@ import { CorePromiseUtils } from '@singletons/promise-utils'; templateUrl: 'addon-mod-scorm-index.html', styleUrl: 'index.scss', }) -export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit { +export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy { @Input() autoPlayData?: AddonModScormAutoPlayData; // Data to use to play the SCORM automatically. @@ -73,7 +75,6 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom }; // Selected organization. startNewAttempt = false; - errorMessage?: string; // Error message. syncTime?: string; // Last sync time. hasOffline = false; // Whether the SCORM has offline data. attemptToContinue?: number; // The attempt to continue or review. @@ -96,6 +97,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom onlineAttempts: AttemptGrade[] = []; // Grades for online attempts. offlineAttempts: AttemptGrade[] = []; // Grades for offline attempts. gradesExpanded = false; + isOnline: boolean; protected fetchContentDefaultError = 'addon.mod_scorm.errorgetscorm'; // Default error to show when loading contents. protected syncEventName = ADDON_MOD_SCORM_DATA_AUTO_SYNCED; @@ -105,12 +107,22 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom protected hasPlayed = false; // Whether the user has opened the player page. protected dataSentObserver?: CoreEventObserver; // To detect data sent to server. protected dataSent = false; // Whether some data was sent to server while playing the SCORM. + protected useOnlinePlayer = false; // Whether the SCORM needs to be played using an online player. + protected onlineObserver: Subscription; constructor( protected content?: IonContent, @Optional() courseContentsPage?: CoreCourseContentsPage, ) { super('AddonModScormIndexComponent', content, courseContentsPage); + + this.isOnline = CoreNetwork.isOnline(); + this.onlineObserver = CoreNetwork.onChange().subscribe(() => { + // Execute the callback in the Angular zone, so change detection doesn't stop working. + NgZone.run(() => { + this.isOnline = CoreNetwork.isOnline(); + }); + }); } /** @@ -181,19 +193,19 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom this.dataRetrieved.emit(this.scorm); this.description = this.scorm.intro || this.description; - this.errorMessage = AddonModScorm.isScormUnsupported(this.scorm); + this.useOnlinePlayer = AddonModScorm.useOnlinePlayer(this.scorm); if (this.scorm.warningMessage) { return; // SCORM is closed or not open yet, we can't get more data. } - if (sync) { + if (sync && !this.useOnlinePlayer) { // Try to synchronize the SCORM. await CorePromiseUtils.ignoreErrors(this.syncActivity(showErrors)); } const [syncTime, accessInfo] = await Promise.all([ - AddonModScormSync.getReadableSyncTime(this.scorm.id), + this.useOnlinePlayer ? undefined : AddonModScormSync.getReadableSyncTime(this.scorm.id), AddonModScorm.getAccessInformation(this.scorm.id, { cmId: this.module.id }), this.fetchAttemptData(this.scorm), ]); @@ -203,7 +215,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom // Check whether to launch the SCORM immediately. if (this.skip === undefined) { - this.skip = !this.hasOffline && !this.errorMessage && (!this.scorm.lastattemptlock || this.attemptsLeft > 0) && + this.skip = !this.hasOffline && (!this.scorm.lastattemptlock || this.attemptsLeft > 0) && ( !!this.autoPlayData || @@ -268,7 +280,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom * @returns Promise resolved when done. */ protected async loadPackageSize(scorm: AddonModScormScorm): Promise { - if (scorm.packagesize || this.errorMessage) { + if (scorm.packagesize || this.useOnlinePlayer) { return; } @@ -498,6 +510,15 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom event?.preventDefault(); event?.stopPropagation(); + if (this.useOnlinePlayer) { + // No need to download the package, just open it. + if (this.isOnline) { + this.openScorm(scoId, preview); + } + + return; + } + if (this.downloading || !this.scorm) { // Scope is being downloaded, abort. return; @@ -569,8 +590,10 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom } }, this.siteId); + const pageRoute = this.useOnlinePlayer ? 'online-player' : 'player'; + CoreNavigator.navigateToSitePath( - `${ADDON_MOD_SCORM_PAGE_NAME}/${this.courseId}/${this.module.id}/player`, + `${ADDON_MOD_SCORM_PAGE_NAME}/${this.courseId}/${this.module.id}/${pageRoute}`, { params: { mode: autoPlayData?.mode ?? (preview ? AddonModScormMode.BROWSE : AddonModScormMode.NORMAL), @@ -587,6 +610,11 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom * @inheritdoc */ protected async showStatus(status: DownloadStatus): Promise { + if (this.useOnlinePlayer) { + this.statusMessage = ''; + + return; + } if (status === DownloadStatus.OUTDATED && this.scorm) { // Only show the outdated message if the file should be downloaded. @@ -635,6 +663,13 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom return result; } + /** + * @inheritdoc + */ + ngOnDestroy(): void { + this.onlineObserver.unsubscribe(); + } + } /** diff --git a/src/addons/mod/scorm/lang.json b/src/addons/mod/scorm/lang.json index 477497e32..46458c5cb 100644 --- a/src/addons/mod/scorm/lang.json +++ b/src/addons/mod/scorm/lang.json @@ -14,10 +14,7 @@ "errorcreateofflineattempt": "An error occurred while creating a new offline attempt. Please try again.", "errordownloadscorm": "Error downloading SCORM: \"{{name}}\".", "errorgetscorm": "Error getting SCORM data.", - "errorinvalidversion": "Sorry, the application only supports SCORM 1.2.", - "errornotdownloadable": "Your school or learning provider has disabled the download of SCORM packages.", "errornovalidsco": "This SCORM package doesn't have a visible SCO to load.", - "errorpackagefile": "Sorry, the application only supports ZIP packages.", "errorsyncscorm": "An error occurred while synchronising. Please try again.", "exceededmaxattempts": "You have reached the maximum number of attempts.", "failed": "Failed", diff --git a/src/addons/mod/scorm/pages/online-player/online-player.html b/src/addons/mod/scorm/pages/online-player/online-player.html new file mode 100644 index 000000000..97d6682b1 --- /dev/null +++ b/src/addons/mod/scorm/pages/online-player/online-player.html @@ -0,0 +1,23 @@ + + + + + + +

+ +

+
+ + +
+
+ + + + +

{{ errorMessage | translate }}

+
+
diff --git a/src/addons/mod/scorm/pages/online-player/online-player.ts b/src/addons/mod/scorm/pages/online-player/online-player.ts new file mode 100644 index 000000000..e4c86d574 --- /dev/null +++ b/src/addons/mod/scorm/pages/online-player/online-player.ts @@ -0,0 +1,278 @@ +// (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 { Component, OnInit, OnDestroy } from '@angular/core'; +import { CoreNavigator } from '@services/navigator'; +import { CoreSitesReadingStrategy } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreEvents } from '@singletons/events'; +import { + AddonModScorm, + AddonModScormAttemptCountResult, + AddonModScormGetScormAccessInformationWSResponse, + AddonModScormScorm, + AddonModScormScoWithData, +} from '../../services/scorm'; +import { AddonModScormHelper } from '../../services/scorm-helper'; +import { AddonModScormMode } from '../../constants'; +import { CoreSharedModule } from '@/core/shared.module'; +import { Subscription } from 'rxjs'; +import { CoreNetwork } from '@services/network'; +import { NgZone, Translate } from '@singletons'; +import { CoreError } from '@classes/errors/error'; + +/** + * Page that allows playing a SCORM in online, served from the server. + */ +@Component({ + selector: 'page-addon-mod-scorm-online-player', + templateUrl: 'online-player.html', + standalone: true, + imports: [ + CoreSharedModule, + ], +}) +export default class AddonModScormOnlinePlayerPage implements OnInit, OnDestroy { + + scorm!: AddonModScormScorm; // The SCORM object. + loaded = false; // Whether the data has been loaded. + src?: string; // Iframe src. + errorMessage?: string; // Error message. + scormWidth?: number; // Width applied to scorm iframe. + scormHeight?: number; // Height applied to scorm iframe. + cmId!: number; // Course module ID. + courseId!: number; // Course ID. + + protected mode!: AddonModScormMode; // Mode to play the SCORM. + protected moduleUrl!: string; // Module URL. + protected newAttempt = false; // Whether to start a new attempt. + protected organizationId?: string; // Organization ID to load. + protected attempt = 0; // The attempt number. + protected initialScoId?: number; // Initial SCO ID to load. + protected onlineObserver: Subscription; + protected isDestroyed = false; + + constructor() { + let isOnline = CoreNetwork.isOnline(); + this.onlineObserver = CoreNetwork.onChange().subscribe(() => { + // Execute the callback in the Angular zone, so change detection doesn't stop working. + NgZone.run(() => { + const wasOnline = isOnline; + isOnline = CoreNetwork.isOnline(); + + if (!isOnline && wasOnline) { + // User lost connection while playing an online package. Show an error. + CoreDomUtils.showErrorModal(new CoreError(Translate.instant('core.course.changesofflinemaybelost'))); // , { + // title: Translate.instant('core.youreoffline'), + // })); + + return; + } + }); + }); + } + + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + try { + this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId'); + this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); + this.mode = CoreNavigator.getRouteParam('mode') || AddonModScormMode.NORMAL; + this.moduleUrl = CoreNavigator.getRouteParam('moduleUrl') || ''; + this.newAttempt = !!CoreNavigator.getRouteBooleanParam('newAttempt'); + this.organizationId = CoreNavigator.getRouteParam('organizationId'); + this.initialScoId = CoreNavigator.getRouteNumberParam('scoId'); + } catch (error) { + CoreDomUtils.showErrorModal(error); + CoreNavigator.back(); + + return; + } + + try { + // Fetch the SCORM data. + await this.fetchData(); + + if (!this.src) { + CoreNavigator.back(); + + return; + } + } finally { + this.loaded = true; + } + + } + + /** + * Initialize. + */ + protected async initialize(): Promise { + // Get the SCORM instance. + this.scorm = await AddonModScorm.getScorm(this.courseId, this.cmId, { + moduleUrl: this.moduleUrl, + readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE, + }); + + if (!this.scorm.popup || !this.scorm.width || this.scorm.width <= 100) { + return; + } + + // If we receive a value > 100 we assume it's a fixed pixel size. + this.scormWidth = this.scorm.width; + + // Only get fixed size on height if width is also fixed. + if (this.scorm.height && this.scorm.height > 100) { + this.scormHeight = this.scorm.height; + } + } + + /** + * Determine the attempt to use, the mode (normal/preview) and if it's offline or online. + * + * @param attemptsData Attempts count. + * @param accessInfo Access info. + */ + protected async determineAttemptAndMode( + attemptsData: AddonModScormAttemptCountResult, + accessInfo: AddonModScormGetScormAccessInformationWSResponse, + ): Promise { + const data = await AddonModScormHelper.determineAttemptToContinue(this.scorm, attemptsData); + + let incomplete = false; + this.attempt = data.num; + + // Check if current attempt is incomplete. + if (this.attempt > 0) { + incomplete = await AddonModScorm.isAttemptIncomplete(this.scorm.id, this.attempt, { + cmId: this.cmId, + }); + } + + // Determine mode and attempt to use. + const result = AddonModScorm.determineAttemptAndMode( + this.scorm, + this.mode, + this.attempt, + this.newAttempt, + incomplete, + accessInfo.cansavetrack, + ); + + if (result.attempt > this.attempt) { + // We're creating a new attempt, verify that we can create a new online attempt. We ignore cache. + await AddonModScorm.getScormUserData(this.scorm.id, result.attempt, { + cmId: this.cmId, + readingStrategy: CoreSitesReadingStrategy.ONLY_NETWORK, + }); + } + + this.mode = result.mode; + this.newAttempt = result.newAttempt; + this.attempt = result.attempt; + } + + /** + * Fetch data needed to play the SCORM. + */ + protected async fetchData(): Promise { + await this.initialize(); + + try { + // Get attempts data. + const [attemptsData, accessInfo] = await Promise.all([ + AddonModScorm.getAttemptCount(this.scorm.id, { cmId: this.cmId }), + AddonModScorm.getAccessInformation(this.scorm.id, { + cmId: this.cmId, + }), + ]); + + await this.determineAttemptAndMode(attemptsData, accessInfo); + + const sco = await this.getScoToLoad(); + if (!sco) { + // We couldn't find a SCO to load: they're all inactive or without launch URL. + this.errorMessage = 'addon.mod_scorm.errornovalidsco'; + + return; + } + + // Load SCO. + this.src = await AddonModScorm.getScoSrcForOnlinePlayer(this.scorm, sco, { + mode: this.mode, + organization: this.organizationId, + newAttempt: this.newAttempt, + }); + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'addon.mod_scorm.errorgetscorm', true); + } + } + + /** + * Fetch the TOC. + * + * @returns SCO to load. + */ + protected async getScoToLoad(): Promise { + // We need to check incomplete again: attempt number or status might have changed. + const incomplete = await AddonModScorm.isAttemptIncomplete(this.scorm.id, this.attempt, { + cmId: this.cmId, + }); + + // Get TOC. + const toc = await AddonModScormHelper.getToc(this.scorm.id, this.attempt, incomplete, { + organization: this.organizationId, + cmId: this.cmId, + }); + + if (this.newAttempt) { + // Creating a new attempt, use the first SCO defined by the SCORM. + this.initialScoId = this.scorm.launch; + } + + // Determine current SCO if we received an ID. + let currentSco: AddonModScormScoWithData | undefined; + if (this.initialScoId && this.initialScoId > 0) { + // SCO set by parameter, get it from TOC. + currentSco = AddonModScormHelper.getScoFromToc(toc, this.initialScoId); + } + + if (currentSco) { + return currentSco; + } + + // No SCO defined. Get the first valid one. + return await AddonModScormHelper.getFirstSco(this.scorm.id, this.attempt, { + toc, + organization: this.organizationId, + mode: this.mode, + cmId: this.cmId, + }); + } + + /** + * @inheritdoc + */ + ngOnDestroy(): void { + this.isDestroyed = true; + this.onlineObserver.unsubscribe(); + + // Empty src when leaving the state so unload event is triggered in the iframe. + this.src = ''; + CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'scorm' }); + } + +} diff --git a/src/addons/mod/scorm/pages/player/player.ts b/src/addons/mod/scorm/pages/player/player.ts index 7d6cd920a..7e8987be4 100644 --- a/src/addons/mod/scorm/pages/player/player.ts +++ b/src/addons/mod/scorm/pages/player/player.ts @@ -14,7 +14,6 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; import { CoreNavigationBarItem } from '@components/navigation-bar/navigation-bar'; -import { CoreMainMenuPage } from '@features/mainmenu/pages/menu/menu'; import { CoreNavigator } from '@services/navigator'; import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; import { CoreSync } from '@services/sync'; @@ -89,10 +88,6 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { protected launchPrevObserver?: CoreEventObserver; protected goOfflineObserver?: CoreEventObserver; - constructor( - protected mainMenuPage: CoreMainMenuPage, - ) {} - /** * @inheritdoc */ @@ -311,8 +306,6 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { /** * Fetch data needed to play the SCORM. - * - * @returns Promise resolved when done. */ protected async fetchData(): Promise { if (!this.scorm) { diff --git a/src/addons/mod/scorm/scorm-lazy.module.ts b/src/addons/mod/scorm/scorm-lazy.module.ts index cc234484c..03c8fb0ac 100644 --- a/src/addons/mod/scorm/scorm-lazy.module.ts +++ b/src/addons/mod/scorm/scorm-lazy.module.ts @@ -29,6 +29,10 @@ const routes: Routes = [ path: ':courseId/:cmId/player', component: AddonModScormPlayerPage, }, + { + path: ':courseId/:cmId/online-player', + loadComponent: () => import('./pages/online-player/online-player'), + }, ]; @NgModule({ diff --git a/src/addons/mod/scorm/services/handlers/prefetch.ts b/src/addons/mod/scorm/services/handlers/prefetch.ts index bd31f7e15..7db8d7296 100644 --- a/src/addons/mod/scorm/services/handlers/prefetch.ts +++ b/src/addons/mod/scorm/services/handlers/prefetch.ts @@ -23,7 +23,7 @@ import { CoreFileSizeSum } from '@services/plugin-file-delegate'; import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; import { CorePromiseUtils } from '@singletons/promise-utils'; import { CoreWSFile } from '@services/ws'; -import { makeSingleton, Translate } from '@singletons'; +import { makeSingleton } from '@singletons'; import { AddonModScorm, AddonModScormScorm } from '../scorm'; import { AddonModScormSync } from '../scorm-sync'; import { ADDON_MOD_SCORM_COMPONENT } from '../../constants'; @@ -177,10 +177,9 @@ export class AddonModScormPrefetchHandlerService extends CoreCourseActivityPrefe siteId = siteId || CoreSites.getCurrentSiteId(); - const result = AddonModScorm.isScormUnsupported(scorm); - - if (result) { - throw new CoreError(Translate.instant(result)); + if (AddonModScorm.useOnlinePlayer(scorm)) { + // Shouldn't happen, if scorm uses online player it shouldn't be downloaded. + throw new CoreError('This SCORM cannot be downloaded.'); } // First verify that the file needs to be downloaded. @@ -270,7 +269,7 @@ export class AddonModScormPrefetchHandlerService extends CoreCourseActivityPrefe async getDownloadSize(module: CoreCourseAnyModuleData, courseId: number): Promise { const scorm = await this.getScorm(module, courseId); - if (AddonModScorm.isScormUnsupported(scorm)) { + if (AddonModScorm.useOnlinePlayer(scorm)) { return { size: -1, total: false }; } else if (!scorm.packagesize) { // We don't have package size, try to calculate it. @@ -353,7 +352,7 @@ export class AddonModScormPrefetchHandlerService extends CoreCourseActivityPrefe return false; } - if (AddonModScorm.isScormUnsupported(scorm)) { + if (AddonModScorm.useOnlinePlayer(scorm)) { return false; } diff --git a/src/addons/mod/scorm/services/scorm.ts b/src/addons/mod/scorm/services/scorm.ts index 585c5f1e8..81bd0355e 100644 --- a/src/addons/mod/scorm/services/scorm.ts +++ b/src/addons/mod/scorm/services/scorm.ts @@ -962,6 +962,32 @@ export class AddonModScormProvider { return CorePath.concatenatePaths(dirPath, launchUrl); } + /** + * Given a SCORM and a SCO, returns the full launch URL for the SCO to be used in an online player. + * + * @param scorm SCORM. + * @param sco SCO. + * @param options Other options. + * @returns The URL. + */ + async getScoSrcForOnlinePlayer( + scorm: AddonModScormScorm, + sco: AddonModScormWSSco, + options: AddonModScormGetScoSrcForOnlinePlayerOptions = {}, + ): Promise { + const site = await CoreSites.getSite(options.siteId); + + // Use online player. + return CoreUrl.addParamsToUrl(CorePath.concatenatePaths(site.getURL(), '/mod/scorm/player.php'), { + a: scorm.id, + scoid: sco.id, + display: 'popup', + mode: options.mode, + currentorg: options.organization, + newattempt: options.newAttempt ? 'on' : 'off', + }); + } + /** * Get the path to the folder where a SCORM is downloaded. * @@ -985,7 +1011,7 @@ export class AddonModScormProvider { getScormFileList(scorm: AddonModScormScorm): CoreWSFile[] { const files: CoreWSFile[] = []; - if (!this.isScormUnsupported(scorm) && !scorm.warningMessage) { + if (!this.useOnlinePlayer(scorm) && !scorm.warningMessage) { files.push({ fileurl: this.getPackageUrl(scorm), filepath: '/', @@ -1359,22 +1385,6 @@ export class AddonModScormProvider { return !!(scorm.timeopen && scorm.timeopen > CoreTimeUtils.timestamp()); } - /** - * Check if a SCORM is unsupported in the app. If it's not, returns the error code to show. - * - * @param scorm SCORM to check. - * @returns String with error code if unsupported, undefined if supported. - */ - isScormUnsupported(scorm: AddonModScormScorm): string | undefined { - if (!this.isScormValidVersion(scorm)) { - return 'addon.mod_scorm.errorinvalidversion'; - } else if (!this.isScormDownloadable(scorm)) { - return 'addon.mod_scorm.errornotdownloadable'; - } else if (!this.isValidPackageUrl(this.getPackageUrl(scorm))) { - return 'addon.mod_scorm.errorpackagefile'; - } - } - /** * Check if it's a valid SCORM 1.2. * @@ -1698,6 +1708,17 @@ export class AddonModScormProvider { }); } + /** + * Check if a SCORM should use an online player. + * + * @param scorm SCORM to check. + * @returns True if it should use an online player. + */ + useOnlinePlayer(scorm: AddonModScormScorm): boolean { + return !this.isScormValidVersion(scorm) || !this.isScormDownloadable(scorm) || + !this.isValidPackageUrl(this.getPackageUrl(scorm)); + } + } export const AddonModScorm = makeSingleton(AddonModScormProvider); @@ -2066,6 +2087,16 @@ export type AddonModScormScoIcon = { description: string; }; +/** + * Options to pass to getScoSrcForOnlinePlayer. + */ +export type AddonModScormGetScoSrcForOnlinePlayerOptions = { + siteId?: string; + mode?: string; // Navigation mode. + organization?: string; // Organization ID. + newAttempt?: boolean; // Whether to start a new attempt. +}; + declare module '@singletons/events' { /**