Use the player returned by the quickstart. Keep one owner for its lifecycle so event listeners and the player are disposed together.
import type { Player } from '@realsee/vreo'
import type { VreoUnit } from '@realsee/vreo/typings'
export function bindNarrativeControls(player: Player, report: (status: string) => void) {
const stops = [
player.on('loaded', () => report('loaded')),
player.on('playing', () => report('playing')),
player.on('paused', (ended) => report(ended ? 'ended' : 'paused'))
]
return {
pause() { player.pause() },
resume() { return player.play(player.getCurrentTime(), true) },
// In 2.6.4 play(0) does not seek; load resets the timeline and keyframes, then starts playback.
async replay(unit: VreoUnit) { return player.load(unit, 0, false, true, true) },
hide() { player.hide() },
show() { player.show() },
dispose() { stops.forEach((stop) => stop()); player.dispose() }
}
}Connect the returned methods to user controls. Times are milliseconds. Resume passes player.getCurrentTime() explicitly. Retain the current VreoUnit and pass it to await controls.replay(unit) to restart the same narrative from zero, whether it is playing or paused. Disable repeated Replay clicks until the promise settles, handle rejection, and check the returned boolean before announcing playback.
In Vreo 2.6.4, play(0) does not seek: an already playing player returns immediately, and a paused player skips the zero-time assignment. The example instead calls the public load(unit, 0, false, true, true) method. This cancels the previous playback, resets the timeline and keyframe state, copies the script, and starts playback as a user action. Replay is asynchronous; Resume remains a synchronous play() call. A false result means the requested operation did not start.
Keep UI state honest
loaded, playing, and paused are published events. The paused event may receive true when the narrative has ended. This example uses that signal because the published Player forwards completion through paused.
hide() changes interface visibility. It is not a substitute for pause(). If closing your panel should stop playback, call pause as well. On route or scene replacement, call the returned dispose() before disposing the owning Five instance. Do not use a disposed player for another script.
Verify
Pause midway, resume near the saved time, then replay from zero. Hide and show the UI without claiming it pauses automatically. Leave and remount the route, and confirm the old narrative and callbacks no longer run.
