MOBILE-4669 scorm: Fix height problems in online player
parent
bab16335d0
commit
20a39bbcb5
|
@ -980,10 +980,7 @@
|
||||||
"addon.mod_scorm.errorcreateofflineattempt": "local_moodlemobileapp",
|
"addon.mod_scorm.errorcreateofflineattempt": "local_moodlemobileapp",
|
||||||
"addon.mod_scorm.errordownloadscorm": "local_moodlemobileapp",
|
"addon.mod_scorm.errordownloadscorm": "local_moodlemobileapp",
|
||||||
"addon.mod_scorm.errorgetscorm": "local_moodlemobileapp",
|
"addon.mod_scorm.errorgetscorm": "local_moodlemobileapp",
|
||||||
"addon.mod_scorm.errorinvalidversion": "local_moodlemobileapp",
|
|
||||||
"addon.mod_scorm.errornotdownloadable": "local_moodlemobileapp",
|
|
||||||
"addon.mod_scorm.errornovalidsco": "local_moodlemobileapp",
|
"addon.mod_scorm.errornovalidsco": "local_moodlemobileapp",
|
||||||
"addon.mod_scorm.errorpackagefile": "local_moodlemobileapp",
|
|
||||||
"addon.mod_scorm.errorsyncscorm": "local_moodlemobileapp",
|
"addon.mod_scorm.errorsyncscorm": "local_moodlemobileapp",
|
||||||
"addon.mod_scorm.exceededmaxattempts": "scorm",
|
"addon.mod_scorm.exceededmaxattempts": "scorm",
|
||||||
"addon.mod_scorm.failed": "scorm",
|
"addon.mod_scorm.failed": "scorm",
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<core-loading [hideUntil]="loaded">
|
<core-loading [hideUntil]="loaded">
|
||||||
<core-iframe *ngIf="loaded" id="scorm_object" [src]="src" [iframeWidth]="scormWidth" [iframeHeight]="scormHeight"
|
<core-iframe *ngIf="loaded" id="scorm_object" [src]="src" [iframeWidth]="scormWidth" [iframeHeight]="scormHeight"
|
||||||
[showFullscreenOnToolbar]="true" [autoFullscreenOnRotate]="true" />
|
[showFullscreenOnToolbar]="true" [autoFullscreenOnRotate]="enableFullScreenOnRotate" (loaded)="iframeLoaded()" />
|
||||||
|
|
||||||
<p *ngIf="!src && errorMessage">{{ errorMessage | translate }}</p>
|
<p *ngIf="!src && errorMessage">{{ errorMessage | translate }}</p>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// 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 { Component, OnInit, OnDestroy } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreSitesReadingStrategy } from '@services/sites';
|
import { CoreSitesReadingStrategy } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
@ -31,6 +31,8 @@ import { Subscription } from 'rxjs';
|
||||||
import { CoreNetwork } from '@services/network';
|
import { CoreNetwork } from '@services/network';
|
||||||
import { NgZone, Translate } from '@singletons';
|
import { NgZone, Translate } from '@singletons';
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
|
import { CoreWait } from '@singletons/wait';
|
||||||
|
import { CoreIframeComponent } from '@components/iframe/iframe';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that allows playing a SCORM in online, served from the server.
|
* Page that allows playing a SCORM in online, served from the server.
|
||||||
|
@ -45,6 +47,8 @@ import { CoreError } from '@classes/errors/error';
|
||||||
})
|
})
|
||||||
export default class AddonModScormOnlinePlayerPage implements OnInit, OnDestroy {
|
export default class AddonModScormOnlinePlayerPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
@ViewChild(CoreIframeComponent) iframe?: CoreIframeComponent;
|
||||||
|
|
||||||
scorm!: AddonModScormScorm; // The SCORM object.
|
scorm!: AddonModScormScorm; // The SCORM object.
|
||||||
loaded = false; // Whether the data has been loaded.
|
loaded = false; // Whether the data has been loaded.
|
||||||
src?: string; // Iframe src.
|
src?: string; // Iframe src.
|
||||||
|
@ -53,6 +57,7 @@ export default class AddonModScormOnlinePlayerPage implements OnInit, OnDestroy
|
||||||
scormHeight?: number; // Height applied to scorm iframe.
|
scormHeight?: number; // Height applied to scorm iframe.
|
||||||
cmId!: number; // Course module ID.
|
cmId!: number; // Course module ID.
|
||||||
courseId!: number; // Course ID.
|
courseId!: number; // Course ID.
|
||||||
|
enableFullScreenOnRotate = false;
|
||||||
|
|
||||||
protected mode!: AddonModScormMode; // Mode to play the SCORM.
|
protected mode!: AddonModScormMode; // Mode to play the SCORM.
|
||||||
protected moduleUrl!: string; // Module URL.
|
protected moduleUrl!: string; // Module URL.
|
||||||
|
@ -73,9 +78,9 @@ export default class AddonModScormOnlinePlayerPage implements OnInit, OnDestroy
|
||||||
|
|
||||||
if (!isOnline && wasOnline) {
|
if (!isOnline && wasOnline) {
|
||||||
// User lost connection while playing an online package. Show an error.
|
// User lost connection while playing an online package. Show an error.
|
||||||
CoreDomUtils.showErrorModal(new CoreError(Translate.instant('core.course.changesofflinemaybelost'))); // , {
|
CoreDomUtils.showErrorModal(new CoreError(Translate.instant('core.course.changesofflinemaybelost'), {
|
||||||
// title: Translate.instant('core.youreoffline'),
|
title: Translate.instant('core.youreoffline'),
|
||||||
// }));
|
}));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -114,7 +119,6 @@ export default class AddonModScormOnlinePlayerPage implements OnInit, OnDestroy
|
||||||
} finally {
|
} finally {
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -275,4 +279,21 @@ export default class AddonModScormOnlinePlayerPage implements OnInit, OnDestroy
|
||||||
CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'scorm' });
|
CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'scorm' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SCORM iframe has been loaded.
|
||||||
|
*/
|
||||||
|
async iframeLoaded(): Promise<void> {
|
||||||
|
// When using online player, some packages don't calculate the right height. Sending a 'resize' event doesn't fix it, but
|
||||||
|
// changing the iframe size makes the SCORM recalculate the size.
|
||||||
|
// Wait 1 second (to let inner iframes load) and then force full screen to make the SCORM recalculate the size.
|
||||||
|
await CoreWait.wait(1000);
|
||||||
|
|
||||||
|
if (this.isDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.iframe?.toggleFullscreen(true);
|
||||||
|
this.enableFullScreenOnRotate = true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,13 +159,27 @@ Feature: Test basic usage of SCORM activity in app
|
||||||
# And I go back in the app
|
# And I go back in the app
|
||||||
# Then I should find "1" within "Number of attempts you have made" "ion-item" in the app
|
# Then I should find "1" within "Number of attempts you have made" "ion-item" in the app
|
||||||
|
|
||||||
Scenario: Unsupported SCORM
|
Scenario: SCORM 2004 works online
|
||||||
Given the following "activities" exist:
|
Given the following "activities" exist:
|
||||||
| activity | name | course | idnumber | packagefilepath |
|
| activity | name | course | idnumber | packagefilepath |
|
||||||
| scorm | SCORM 1.2 | C1 | scorm | mod/scorm/tests/packages/RuntimeBasicCalls_SCORM20043rdEdition.zip |
|
| scorm | SCORM 2004 | C1 | scorm | mod/scorm/tests/packages/RuntimeBasicCalls_SCORM20043rdEdition.zip |
|
||||||
And I entered the course "Course 1" as "student1" in the app
|
And I entered the course "Course 1" as "student1" in the app
|
||||||
When I press "SCORM 1.2" in the app
|
When I press "SCORM 2004" in the app
|
||||||
Then I should find "Sorry, the application only supports SCORM 1.2." in the app
|
Then I should be able to press "Enter" in the app
|
||||||
|
|
||||||
|
When I switch network connection to offline
|
||||||
|
Then I should find "This activity is not available offline" in the app
|
||||||
|
And I should not be able to press "Enter" in the app
|
||||||
|
|
||||||
|
When I switch network connection to wifi
|
||||||
|
And I press "Enter" in the app
|
||||||
|
And I press "Disable fullscreen" in the app
|
||||||
|
Then I should not be able to press "TOC" in the app
|
||||||
|
|
||||||
|
When I switch network connection to offline
|
||||||
|
Then I should find "Any changes you make to this activity while offline may not be saved" in the app
|
||||||
|
|
||||||
|
# TODO: When iframes are fixed, test that the iframe actually works. However, the Golf 2004 SCORM has some issues.
|
||||||
|
|
||||||
Scenario: Hidden SCOs not displayed in TOC
|
Scenario: Hidden SCOs not displayed in TOC
|
||||||
Given the following "activities" exist:
|
Given the following "activities" exist:
|
||||||
|
|
|
@ -64,6 +64,7 @@ export class CoreIframeComponent implements OnChanges, OnDestroy {
|
||||||
|
|
||||||
initialized = false;
|
initialized = false;
|
||||||
|
|
||||||
|
protected fullScreenInitialized = false;
|
||||||
protected iframe?: HTMLIFrameElement;
|
protected iframe?: HTMLIFrameElement;
|
||||||
protected style?: HTMLStyleElement;
|
protected style?: HTMLStyleElement;
|
||||||
protected orientationObs?: CoreEventObserver;
|
protected orientationObs?: CoreEventObserver;
|
||||||
|
@ -87,36 +88,6 @@ export class CoreIframeComponent implements OnChanges, OnDestroy {
|
||||||
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
|
|
||||||
if (this.showFullscreenOnToolbar || this.autoFullscreenOnRotate) {
|
|
||||||
// Leave fullscreen when navigating.
|
|
||||||
this.navSubscription = Router.events
|
|
||||||
.pipe(filter(event => event instanceof NavigationStart))
|
|
||||||
.subscribe(async () => {
|
|
||||||
if (this.fullscreen) {
|
|
||||||
this.toggleFullscreen(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const shadow =
|
|
||||||
this.elementRef.nativeElement.closest('.ion-page')?.querySelector('ion-header ion-toolbar')?.shadowRoot;
|
|
||||||
if (shadow) {
|
|
||||||
this.style = document.createElement('style');
|
|
||||||
shadow.appendChild(this.style);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.autoFullscreenOnRotate) {
|
|
||||||
this.toggleFullscreen(CoreScreen.isLandscape);
|
|
||||||
|
|
||||||
this.orientationObs = CoreEvents.on(CoreEvents.ORIENTATION_CHANGE, (data) => {
|
|
||||||
if (this.isInHiddenPage()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.toggleFullscreen(data.orientation == CoreScreenOrientation.LANDSCAPE);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show loading only with external URLs.
|
// Show loading only with external URLs.
|
||||||
this.loading = !this.src || !CoreUrl.isLocalFileUrl(this.src);
|
this.loading = !this.src || !CoreUrl.isLocalFileUrl(this.src);
|
||||||
|
|
||||||
|
@ -127,6 +98,72 @@ export class CoreIframeComponent implements OnChanges, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure fullscreen based on the inputs.
|
||||||
|
*/
|
||||||
|
protected configureFullScreen(): void {
|
||||||
|
if (!this.showFullscreenOnToolbar && !this.autoFullscreenOnRotate) {
|
||||||
|
// Full screen disabled, stop watchers if enabled.
|
||||||
|
this.navSubscription?.unsubscribe();
|
||||||
|
this.orientationObs?.off();
|
||||||
|
this.style?.remove();
|
||||||
|
this.navSubscription = undefined;
|
||||||
|
this.orientationObs = undefined;
|
||||||
|
this.style = undefined;
|
||||||
|
this.fullScreenInitialized = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.navSubscription) {
|
||||||
|
// Leave fullscreen when navigating.
|
||||||
|
this.navSubscription = Router.events
|
||||||
|
.pipe(filter(event => event instanceof NavigationStart))
|
||||||
|
.subscribe(async () => {
|
||||||
|
if (this.fullscreen) {
|
||||||
|
this.toggleFullscreen(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.style) {
|
||||||
|
const shadow = this.elementRef.nativeElement.closest('.ion-page')?.querySelector('ion-header ion-toolbar')?.shadowRoot;
|
||||||
|
if (shadow) {
|
||||||
|
this.style = document.createElement('style');
|
||||||
|
shadow.appendChild(this.style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.autoFullscreenOnRotate) {
|
||||||
|
this.orientationObs?.off();
|
||||||
|
this.orientationObs = undefined;
|
||||||
|
this.fullScreenInitialized = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.orientationObs) {
|
||||||
|
this.fullScreenInitialized = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.fullScreenInitialized) {
|
||||||
|
// Only change full screen value if it's being initialized.
|
||||||
|
this.toggleFullscreen(CoreScreen.isLandscape);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.orientationObs = CoreEvents.on(CoreEvents.ORIENTATION_CHANGE, (data) => {
|
||||||
|
if (this.isInHiddenPage()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggleFullscreen(data.orientation == CoreScreenOrientation.LANDSCAPE);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fullScreenInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize things related to the iframe element.
|
* Initialize things related to the iframe element.
|
||||||
*/
|
*/
|
||||||
|
@ -170,6 +207,10 @@ export class CoreIframeComponent implements OnChanges, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!changes.src) {
|
if (!changes.src) {
|
||||||
|
if (changes.showFullscreenOnToolbar || changes.autoFullscreenOnRotate) {
|
||||||
|
this.configureFullScreen();
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,6 +253,9 @@ export class CoreIframeComponent implements OnChanges, OnDestroy {
|
||||||
// Now that the URL has been set, initialize the iframe. Wait for the iframe to the added to the DOM.
|
// Now that the URL has been set, initialize the iframe. Wait for the iframe to the added to the DOM.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.init();
|
this.init();
|
||||||
|
if (changes.showFullscreenOnToolbar || changes.autoFullscreenOnRotate) {
|
||||||
|
this.configureFullScreen();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,6 +273,12 @@ export class CoreIframeComponent implements OnChanges, OnDestroy {
|
||||||
this.orientationObs?.off();
|
this.orientationObs?.off();
|
||||||
this.navSubscription?.unsubscribe();
|
this.navSubscription?.unsubscribe();
|
||||||
window.removeEventListener('message', this.messageListenerFunction);
|
window.removeEventListener('message', this.messageListenerFunction);
|
||||||
|
|
||||||
|
if (this.fullscreen) {
|
||||||
|
// Make sure to leave fullscreen mode when the iframe is destroyed. This can happen if there's a race condition
|
||||||
|
// between clicking back button and some code toggling the fullscreen on.
|
||||||
|
this.toggleFullscreen(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue