Embed the lesson player and exchange runtime commands and events.
The lesson player is available at
/move/lesson/play/{lessonPlayId}. Embed that route in an iframe after creating
a lesson play:
<iframe
id="gofa-lesson-player"
src="https://your-client.gofa.app/move/lesson/play/lesson-play-123?noAutoNav=true&locale=en"
allow="camera; microphone; autoplay; fullscreen"
></iframe>The host name is client-derived, so use the client host assigned to your
integration. allow permissions are required when the lesson needs camera or
microphone access.
| Flag | Effect |
|---|---|
plain=true | Uses the plain lesson player without AI pose tracking. |
noAutoNav=true | Prevents the default result-page navigation after completion and prevents the default back navigation on exit. |
locale=en | Selects English; values beginning with en use English and other values use Traditional Chinese. |
performanceTier=high|medium|low | Selects the requested playback/performance tier. |
If a lesson play has a returnUrl, that URL is used before the default
navigation choice. noAutoNav is useful when the parent application handles
the result event itself.
The parent can send these command messages to the iframe:
skipSession, skipCalibration, pauseVideo, playVideo, startCountdown,
pausePlay, and unpausePlay.
Send to the exact frame origin. Derive the origin from the configured frame URL or from an allowlist owned by the integrating application:
const frame = document.querySelector('#gofa-lesson-player');
const expectedOrigin = new URL(frame.src, window.location.href).origin;
frame.contentWindow?.postMessage(
{ type: 'pauseVideo' },
expectedOrigin,
);The player sends completion and exit events to its parent. The current wire shape is flat; the lesson play ID is a top-level field:
{
"type": "FINISH_LESSON_PLAY",
"lessonPlayId": "lesson-play-123"
}{
"type": "EXIT_LESSON_PLAY",
"lessonPlayId": "lesson-play-123"
}In plain=true mode, it can also send:
{
"type": "SESSION_COMPLETE",
"completionPct": 100,
"durationMinutes": 3,
"scoringMode": "no-tracking"
}The parent must validate both the sender origin and the sender window before acting on a message. The checks below are part of the integration boundary:
const frame = document.querySelector('#gofa-lesson-player');
const expectedOrigin = new URL(frame.src, window.location.href).origin;
window.addEventListener('message', (event) => {
if (event.origin !== expectedOrigin || event.source !== frame.contentWindow) {
return;
}
const message = event.data;
if (!message || typeof message !== 'object' || typeof message.type !== 'string') {
return;
}
if (
(message.type === 'FINISH_LESSON_PLAY' || message.type === 'EXIT_LESSON_PLAY') &&
typeof message.lessonPlayId === 'string'
) {
// Update the parent UI or navigate according to your application policy.
}
});The current player listener accepts the listed command names without checking
event.origin or event.source. The parent-side checks above therefore remain
required, and hardening the player listener itself requires an application
change. This page records the released runtime contract; it is not a deep
security review.