Integrate PIE: Video with questions

Integrate PIE

Video with questions

Show questions during a video and pause for answers.

From the pie-players documentation · View source(opens in new tab)

Try it

Try a passage and questions with layouts and learner tools.

Timed media

Set sectionType: "timed-media" and a timedMedia block, and the section's cue timeline decides when its items are delivered:

const section: AssessmentSection = {
  identifier: "water-cycle",
  sectionType: "timed-media",
  // A correctness gate needs unlimited Tries; see below.
  formative: { enabled: true, maxTries: "unlimited", feedback: "correctness" },
  rubricBlocks: [
    {
      identifier: "video-stimulus-1",
      class: "stimulus",
      view: ["candidate"],
      // An ordinary passage. Its config mounts the media element — a PIE element,
      // or authored `<video>` markup, whose `<track>` carries the captions — and
      // its accessibility catalogs carry the transcript and signed alternates.
      passage: videoPassage,
    },
  ],
  assessmentItemRefs: [{ identifier: "q1", item }, { identifier: "q2", item }],
  timedMedia: {
    stimulusRef: "video-stimulus-1",
    cues: [
      { identifier: "c1", range: { startSeconds: 4 }, itemRefs: ["q1"], policy: { activation: "reveal" } },
      {
        identifier: "c2",
        range: { startSeconds: 10 },
        itemRefs: ["q2"],
        policy: { activation: "gate", releaseOn: "correct", onUnknownCorrectness: "release" },
      },
    ],
    playbackPolicy: { allowSeekAhead: false, pauseOnRequiredCue: true, requireMediaCompletion: false },
  },
};

Every item a gate names must satisfy its releaseOn. To split must-answer items from optional ones, author two cues at the same timestamp: a gate over the first set and a reveal over the second. Both activate in the same pass, the reveal completes at once, and only the gate holds playback.

Without sectionType, delivery is unchanged: no projection, no session slice, no cue behavior. An item no reveal or gate cue names is delivered normally, including one a metadata cue names, since metadata records state and reveals nothing. A cued item is mounted and hidden until its cue fires, so its session and shell registration survive a seek backwards.

The section reaches media only through a Media Time Source. The stimulus card finds the media element its passage mounted and registers a native adapter; a host with its own player registers its own port, which outranks the card's adapter for as long as it is attached:

const controller = await host.waitForSectionController(5000);
// No `renderableId`: a host asserts its own port. A renderable's adapter names
// itself, and is ignored unless it is the resolved stimulus.
controller?.attachMediaTimeSource?.(myThirdPartyPort);
controller?.detachMediaTimeSource?.();
controller?.getTimedMediaProjection?.(); // cues, gate, enforcement, revealed items
// One half of the read-aloud handoff. `false` means the port cannot pause, and
// the overlap stands; the accommodation is not withheld.
controller?.pauseMediaForCompetingAudio?.();

Read-aloud and media audio never run at once, and the action the learner just took wins: starting read-aloud pauses media, starting media pauses read-aloud. The section supplies both halves, the method above and timed-media-audio-started, and the toolkit arbitrates between them, because only the toolkit holds both the text-to-speech service and the section. Neither direction resumes what it silenced.

Where the port reports canPause: false or canRestrictSeeking: false, the matching policy degrades to advisory: cues still fire, state is still recorded, the projection says enforcement: "advisory", and a recoverable timed-media framework warning names the policy that can no longer be enforced.

Three authoring mistakes fail loudly: a stimulusRef that resolves to no renderable in the section; a gate on correctness over an item without unlimited Tries, where a learner who spent a finite budget could never release playback again; and a stimulusRef that resolves to a renderable which mounts no media, reported once the section's content has loaded and no time source has attached. Each reports a timed-media framework error, after which the section delivers as an ordinary section with every item visible.

Cue state persists inside SectionControllerSessionState.timedMedia and hydrates with the rest of the snapshot, including the furthest position reached, which allowSeekAhead: false clamps against across a reload. The timed-media section contract holds the contract and its decision record.