Why does SCORM break on mobile?
SCORM mobile compatibility is a property of each package, not of the standard. SCORM runs anywhere a browser can reach the LMS's JavaScript API, phones included. What fails on mobile is how the content was built: saving progress only on the unload event, a fixed desktop layout, and media that expects to autoplay with sound.
The SCORM specifications say nothing about screen size, touch input, or what happens when an operating system suspends a browser. They assume a desktop session: a window that stays open until the learner clicks exit, a mouse, a large screen, and speakers that play when asked. A phone breaks each assumption in a specific way.
| Desktop assumption | What a phone does | What the LMS records |
|---|---|---|
| The window closes through the course's exit, or at least fires unload | The learner switches app; the OS later closes the browser without firing unload | Whatever was last committed, which may be nothing |
| The course has a 1024-pixel-wide stage | Scales it down to phone width, or crops it | An attempt the learner could not physically complete |
| Narration starts when the slide loads | Blocks audio that no tap started | A slide that waits forever for audio to end |
| The LMS page that holds the API stays alive | May discard background tabs to reclaim memory | Calls into an API whose page no longer exists |
That last row matters when the LMS launches courses in a new window. On a phone the new window is a new tab, and the LMS page that owns the API object becomes a background tab the browser may discard.
The unload-event completion trap
SCORM persists only what the content asks it to persist with Commit (LMSCommit in 1.2). Many courses save once, at the end, from a beforeunload or unload handler. On a desktop that mostly works. On a phone it routinely does not.
Mobile browsers do not reliably fire unload. The ordinary sequence — learner finishes the last slide, switches to email, and the operating system later closes the browser in the background — never fires it at all. The course set the completion in memory, the save never ran, and the LMS shows incomplete.
SetValue("cmi.completion_status", "completed") → "true"
... learner switches app; the OS closes the browser later ...
(no Commit, no Terminate — nothing reaches the LMS)
pagehide does not fix it. pagehide is also unreliable on mobile. And when a page really is being closed, Chrome refuses synchronous requests inside beforeunload, unload, pagehide and visibilitychange handlers. SCORM's Commit must return true or false, so an LMS adapter that saves to the server on Commit naturally does it with a synchronous request — exactly the kind Chrome blocks at that moment. The course cannot choose how the LMS built its adapter, so it cannot rely on an exit-time save at all.The general version of this failure, on every device, is covered in why SCORM completions go missing. Mobile turns it from an occasional loss into a routine one.
Responsive vs fixed layouts
Many courses are built on a fixed stage — 1024×768, 960×540, or whatever the authoring tool defaulted to — and scaled to fit the window. On a phone held upright, a scaled stage makes body text a few pixels tall and buttons smaller than a fingertip. Learners who cannot read or hit the controls abandon the attempt, and the LMS records exactly that.
A responsive course reflows instead of shrinking: text stays readable, controls stay tappable, and the layout adapts when the phone rotates. The work is in the content, but several mobile problems come from how it is hosted:
- The viewport belongs to the LMS page. A viewport meta tag inside the course's own HTML is ignored when that page runs in the LMS's iframe. If the LMS player page renders at desktop width, a responsive course inside it still looks like a desktop page zoomed out.
- There is no real hover. Rollover reveals, tooltips and hover-only menus behave inconsistently or not at all on touch. Anything a learner must see to progress needs a tap target.
- Mouse-only interactions do nothing. Drag-and-drop built on mouse events alone ignores a finger. Pointer events handle mouse, pen and touch with one code path.
- Rotation resizes the viewport. A layout calculated once at launch is wrong after the phone turns.
A course that cannot be made responsive can still be honest about it: detect a narrow screen and ask the learner to rotate, rather than presenting controls no one can use.
Autoplay and media restrictions
Mobile browsers block media that plays sound without a user gesture. On iPhone, video with an audio track will not autoplay unless it is muted, and video without the playsinline attribute plays fullscreen, outside the course's layout and controls. Chrome always allows muted autoplay, but generally allows autoplay with sound only after the learner has interacted with the site.
For a course, the failure is quiet. Slide one tries to start narration on load, the browser rejects the play() call, and a course that advances when the audio ends now never advances. If completion depends on reaching the last slide, it never arrives.
- Start with a tap. A "Begin" screen gives the browser the user gesture it needs before any audio plays.
- Handle the rejected
play()promise. When playback is blocked, show a play button instead of waiting silently. - Add
playsinlineto every video element. - Never make progress depend on an autoplay event. Provide a visible way forward.
- Web Audio starts suspended until a gesture; call
resume()from the learner's first tap.
Hosting matters here too. Chrome allows autoplay in same-origin iframes by default, but an iframe from another origin needs the LMS to delegate it with allow="autoplay". If your LMS serves content from a different domain than its player page, test media on that exact setup.
Commit early, commit often
The only save you can rely on is one that already happened. Commit after every meaningful change — each page, each answer, and the final score and status writes — so that when the phone closes the browser without warning, the LMS already holds the result.
// After each page or answer, not only at the end
api.SetValue("cmi.location", "page-07");
api.SetValue("cmi.suspend_data", state);
api.Commit("");
// Best effort when the page is hidden: app switch, lock screen
document.addEventListener("visibilitychange", function () {
if (document.visibilityState === "hidden") {
api.Commit("");
}
});
Becoming hidden is often the last event a mobile page reliably sees, so a Commit there is worth adding. When the learner merely switches apps, the page is still alive and that save usually lands. Treat it as a backstop, not the plan.
Keep the order right at the end: score, then success status, then completion status, then Commit. And commit sensibly — some LMSs throttle frequent commits, so one per page beats one per click. The call semantics are in the SCORM API explained.
Testing SCORM mobile compatibility on real devices
Desktop emulation reproduces screen size and touch input. It does not reproduce the operating system closing a background browser, the real autoplay policy, or iPhone's browser engine. On iPhone, most browsers run on Apple's WebKit, so Chrome there behaves far more like Safari than like Chrome on Android. Test on at least one real iPhone and one real Android phone, through the same route learners use: the phone's browser or the LMS's own mobile app, whose embedded web view may behave differently again.
- Launch the course and check that every page is readable and every control tappable, upright and rotated.
- Confirm the first narration or video plays, with sound, after the first tap.
- Go halfway, switch to another app for several minutes, return, and check that the bookmark held.
- Finish the course, then close the browser from the app switcher without using the course's exit.
- Open the LMS on another device and check completion and score in the report, not in the course.
Step 4 is the test that matters most, because it is how phones end sessions. If the status survived, your commits are in the right place. Where it did not, the runtime log shows which calls reached the LMS and which never left the phone. The full pre-delivery checklist is in how to test a SCORM package before the LMS does.
Where SCORM Central fits
SCORM Central converts files you already have — PDF, PowerPoint, Word, or video — into SCORM 1.2, SCORM 2004, cmi5, or xAPI, and shows you the manifest, the full runtime log, and a conformance report on every build, so you can see exactly what a package will report before an LMS does. See how it works, or start with one file.