Skip to content

Playback plugins ​

A playback plugin runs during the slideshow. It can follow the current slide, move the show, read shapes on the slide, and add a panel, a toolbar button, or an overlay.

Official playback plugins ship with the editor. Their ids start with official:. You can use them without an account. Whiteboard is on by default.

PluginidWhat it does
Whiteboardofficial:whiteboardPen, highlighter, and eraser on the slide
Laser pointerofficial:laser-pointerA red dot that follows the pointer
Outlineofficial:outlineJump by slide title
Speaker notesofficial:notes-panelNotes for the slide being shown
Thumbnailsofficial:thumbnail-barA strip for jumping between slides
Screenshotofficial:screenshotSave the current slide as a PNG

This speaker-notes plugin is only for the slideshow. The editor has a separate official plugin with the same job.

Imported plugins are files you add to your account. Sign in first. Each imported plugin is private to that account, and that cannot be changed. Importing does not turn the plugin on. Check it under Present → Plugins.

Import… asks for a .js file. Delete removes it from the account. The same id imported again replaces the previous file.

Playback plugins: official plugins, with Whiteboard checked.

Write one ​

The file calls definePlaybackPlugin once. defineEditorPlugin and definePlaybackPlugin are already in scope when the editor loads the file. Do not import them, and do not call PPTPlayer.definePlugin. Exported HTML wires definePlaybackPlugin to the player for you.

id starts with a letter and may contain letters, digits, ., _, and -, up to 64 characters. It cannot contain :. The file is at most 96 × 1024 characters. name is kept up to 80 characters. description is kept up to 240 characters.

js
definePlaybackPlugin({
  id: "acme.hello",
  name: "Hello",
  description: "Shows the deck title during the slideshow.",
  version: "1.0.0",
  activate(ctx) {
    const panel = ctx.ui.addPanel({
      title: "Hello",
      render(body) {
        body.textContent = ctx.presentation.title || "Untitled";
      },
    });
    const off = ctx.events.on("slidechange", (ev) => {
      ctx.log(ev.slide.name);
    });
    return () => {
      off();
      panel.remove();
    };
  },
});

activate can be async. Return a function to drop listeners, timers, and UI. deactivate(), if you set it, runs after that function and takes no arguments.

The plugin runs in the page, with the same access as an official plugin.

Checked playback plugins are packed into HTML / HTML zip when the account is allowed to export plugins. Editor plugins are not included. See Export.

Plugin object ​

FieldTypeRequiredMeaning
idstringyesStable id. Letter, then letters, digits, ., _, -. No :. At most 64 characters
namestringnoName in the Plugins list. Falls back to id. Trimmed to 80 characters
descriptionstringnoOne line under the name. Trimmed to 240 characters
versionstringnoYour own version string
activate(ctx) => cleanup | void | Promise<cleanup | void>yesCalled when the show starts with this plugin checked
deactivate() => voidnoCalled after cleanup, when the show ends

activate(ctx) ​

FieldUse
ctx.presentationSlide list, size, title, notes. A snapshot from when the show started
ctx.playerMove the show and its animation steps
ctx.eventsslidechange, stepchange, complete, destroy
ctx.uiPanels, toolbar buttons, overlays
ctx.sceneShapes on the current slide
ctx.thumbnailA vector thumbnail of any slide
ctx.contextMenuItems on the slideshow right-click menu
ctx.liveMessages to other people in a live present room
ctx.sharedOne bag shared by every playback plugin in this show
ctx.locale"en" or "zh-CN"
ctx.log(...args)console.log, prefixed with [plugin:<id>]

Two indexes show up everywhere:

  • Visible index is the slide the audience is on, starting at 0. Hidden slides are skipped. ctx.player.goto and slidechange.visibleIndex use this index.
  • Absolute index is the position in ctx.presentation.slides, which still includes hidden slides. ctx.presentation.visibleOrder[visibleIndex] is the absolute index.

activate often runs after the first slidechange. Read ctx.player.current(), or call ctx.events.emitCurrentSlide().

ctx.presentation ​

FieldTypeMeaning
idstringDeck id
size{ width: number, height: number }Slide size in pixels
titlestring optionalDocument title
slidesSlideInfo[]Every slide, in file order
visibleOrdernumber[]Absolute indexes in show order
metaobject optionalDocument properties such as author and keywords

SlideInfo:

FieldTypeMeaning
idstringSlide id
namestringSlide name
indexnumberAbsolute index
visibleIndexnumberPlace in the show. -1 when the slide is hidden
showbooleanfalse when the slide is hidden
hasAnimationsbooleanThe slide has steps you can advance
totalAnimationStepsnumberStep count. 0 when there is no timing
notesstring optionalSpeaker notes
transition{ kind: string, duration: number } optionalTransition name and duration in seconds

ctx.player ​

FunctionParametersReturnsWhat it does
next()—voidAdvances one animation step. If the slide has no step left, goes to the next slide. On the last slide, fires complete
prev()—voidGoes to the previous visible slide. Does not step backward through animations
goto(visibleIndex)visibleIndex: numbervoidJumps to that visible slide, starting at 0
gotoById(slideId)slideId: stringbooleanJumps by slide id. false when the id is missing
current()—numberCurrent visible index
total()—numberNumber of visible slides
nextStep()—booleanAdvances one click step and does not change slides. false when this slide has no step left
triggerShape(shapeId)shapeId: stringbooleanPlays that shape’s click animation. shapeId is ShapeInfo.id. false when it has none
step()—{ totalSteps: number, currentStep: number }Animation progress on the current slide
freezeActive()—voidJumps every running step to its end state
setNavigationLocked(locked)locked: booleanvoidtrue turns off click, keyboard, shape-click, and hyperlink navigation. next() and goto() still work
isNavigationLocked()—booleanWhether that lock is on

Unlock navigation in your cleanup if you locked it.

ctx.events ​

FunctionParametersReturns
on(event, cb)event is a name below. cb receives that event’s payload() => void unsubscribe
off(event, cb)The same event and cb passed to onvoid
emitCurrentSlide()—void. Fires slidechange again for the slide on screen
EventPayloadWhen
slidechange{ visibleIndex, absoluteIndex, total, slide }The show lands on a slide, including the first load, goto, next across slides, and hyperlinks. total is the visible slide count. slide is that slide’s SlideInfo
stepchange{ visibleIndex, absoluteIndex, currentStep, totalSteps, frozen? }An animation step moves forward or back. frozen is true when the step was jumped to its end
completenoneThe show ends: advancing past the last slide, End Show, or an end-show hyperlink
destroynoneThe player is going away. Cleanup runs after this

ctx.scene ​

Shapes on the current slide, in slide pixels, not screen pixels. After a slide change these methods point at the new slide.

FunctionParametersReturnsWhat it does
shapes()—ShapeInfo[]Top-level shapes, bottom to top
byName(name)name: stringShapeInfo | nullFirst shape with that exact name
allByName(name)name: stringShapeInfo[]Every shape with that exact name
byId(id)id: stringShapeInfo | nullThe shape with that render id
hitTest(x, y)x: number, y: numberShapeInfo | nullTopmost visible shape at that slide point
toSlideCoords(clientX, clientY)clientX: number, clientY: number{ x, y } | nullScreen point to slide point. null before the stage has a size
setVisible(id, visible)id: string, visible: booleanbooleanShows or hides that shape. false when the id is missing
setOpacity(id, opacity)id: string, opacity: numberbooleanOpacity from 0 to 1. false when the id is missing
triggerShape(id)id: stringbooleanSame as ctx.player.triggerShape

ShapeInfo:

FieldTypeMeaning
idstringRender id, such as sp_12. Pass it to triggerShape
namestringThe name in the selection pane
kindstringtext, image, rect, ellipse, line, group, chart, and the other shape kinds
bbox{ x, y, w, h }Bounds in slide pixels
visiblebooleanWhether it is currently shown
textstring optionalPlain text, on text shapes and groups that contain text
hasClickAnimationbooleantriggerShape can play a click animation on it

Name the shapes you need in the selection pane, then find them with byName.

ctx.ui ​

Each call that adds something returns { remove(): void }.

FunctionParametersReturnsWhat it does
addPanel(opts)See below{ remove() }A collapsible panel in the dock
addToolbarButton(opts)See below{ remove() }A button on the playback toolbar
addOverlay(opts)See below{ remove() }HTML on top of the slide, in slide coordinates
mount(node)node: HTMLElement{ remove() }Appends node to the player root
setDockOpen(open)open: booleanvoidOpens or closes the dock
toggleDock()—voidToggles the dock
isDockOpen()—booleanWhether the dock is open
rootElement()—HTMLElementPlayer root
stageElement()—HTMLElementThe slide stage

addPanel:

FieldTypeRequiredMeaning
titlestringyesPanel heading
render(body: HTMLElement) => voidyesFills the body once, when the panel is created
idstringnoPanel id
initialOpenbooleannoStarts open unless this is false
ordernumbernoSmaller numbers sit higher. Default 100

addToolbarButton:

FieldTypeRequiredMeaning
labelstringyesButton text
onClick(e: MouseEvent) => voidyesClick handler
tooltipstringnoHover text
position"start" | "end"noLeft or right side of the toolbar

addOverlay:

FieldTypeRequiredMeaning
nodeHTMLElementyesThe element to show
bbox{ x, y, w, h }noSlide pixels. Omit it to cover the slide
zIndexnumbernoStacking order

ctx.contextMenu ​

FunctionParametersReturnsWhat it does
addItem(item)item fields below{ remove() }Adds one menu row
open(x, y)x: number, y: number screen pixelsvoidOpens the menu at that point
close()—voidCloses it

addItem fields:

FieldTypeMeaning
idstring optionalId used if you remove the row later
kind"item" | "checkbox" | "separator"Default "item"
labelstring optionalRow text. A separator can omit it
shortcutstring optionalShown on the right, and bound while the show is running. No modifier keys. A single character, or ← → ↑ ↓ Esc Space Home End Enter F1 PageUp PageDown
ordernumber optionalSmaller numbers sit higher. Default 100
groupstring optionalRows in different groups get a divider between them
checkedboolean or () => booleanCheckbox state. A function is read each time the menu opens
disabledboolean or () => booleanA function is read each time the menu opens
onClick(e?: MouseEvent) => voide is the right-click, with clientX and clientY

ctx.thumbnail ​

target is a visible index (number), { visibleIndex }, { absoluteIndex }, or { id }.

FunctionParametersReturnsWhat it does
mount(host, target, opts?)host: HTMLElement. opts.transparent?: boolean{ element: SVGSVGElement, destroy() }Draws the thumbnail into host. Give host a fixed size. Call destroy() to remove it
toSvgString(target, opts?)same optsstringSVG markup
toSvgBlob(target, opts?)same optsBlobimage/svg+xml
toDataUrl(target, opts?)same optsstringSVG data URL
clearCache()—voidDrops cached thumbnails

byVisibleIndex, byAbsoluteIndex, byId, and dataUrlByVisibleIndex still exist and return a Promise. New plugins should use mount or toSvgBlob.

ctx.live ​

Empty when this show is not a live present session. send then does nothing, and attached() is false.

FunctionParametersReturnsWhat it does
attached()—booleantrue when this browser is the host, or an admitted viewer
send(data, opts?)data: unknown. opts.snap?: booleanvoidSends data to the rest of the room. snap: true keeps the latest payload for someone who joins late
on(fn)fn: (data, meta: { from: string }) => void() => void unsubscribeAnother participant sent a message
onAttach(fn)fn: (attached: boolean) => void() => void unsubscribeThe live channel connected or dropped. On connect, send your current state with snap: true

In a live present session, the host page syncs slide changes, animation steps, and embedded media. ctx.live carries this plugin's own messages. The host and every admitted viewer can send and receive.

data is dropped when its JSON text is longer than 65536 characters. snap: true keeps only this plugin's latest payload. Someone who joins late receives that payload when the channel connects. On connect, send the current state again from onAttach.

setVisible and setOpacity apply on the browser that called them. To show the same result to the room, call them again inside on.

Whiteboard sends one snapshot of the strokes on every slide, with snap: true. Laser pointer sends slide coordinates and draws one dot per meta.from. Releasing the pointer sends a snapshot that turns the dot off.

IdeaWhat to sendsnap
Tally votes{ t: "vote", q, choice }. Count one vote per fromSend the current question number when it changes
Reveal{ t: "show", id, visible }. On receipt, setVisible or triggerShapeYes. Late joiners match shapes already revealed
QuestionsOne line from a panel. The host lists them by fromYes, while the queue is still on screen
Reactions{ t: "emoji", emoji }. Show it briefly on an overlayNo. Late joiners do not need a reaction that already faded
CountdownAn end time. Each browser subtracts it from the local clockYes
SpotlightSlide coordinates, drawn as a circleSend an off snapshot when the pointer is released

The host page and the audience page run the playback plugins packed into this session's snapshot. After you change a checkmark or the source, push the snapshot again before the room runs the new plugin. When the plan allows plugins in export, checked playback plugins are written into that HTML. See Export.

During a local show, and in exported HTML opened outside a live room, the channel is empty and the plugin still runs.

js
definePlaybackPlugin({
  id: "acme.vote",
  name: "Tally",
  activate(ctx) {
    const tally = new Map();
    const off = ctx.live.on((data, meta) => {
      if (!data || data.t !== "vote") return;
      tally.set(meta.from, data.choice);
      ctx.log(tally.size);
    });
    const offAttach = ctx.live.onAttach((on) => {
      if (on) ctx.live.send({ t: "q", n: 1 }, { snap: true });
    });
    const button = ctx.ui.addToolbarButton({
      label: "A",
      onClick() {
        ctx.live.send({ t: "vote", q: 1, choice: "A" });
      },
    });
    return () => {
      off();
      offAttach();
      button.remove();
    };
  },
});

ctx.shared, ctx.locale, ctx.log ​

ctx.shared is one object for every plugin in this show. ctx.shared["ppt:fileName"] is the file name when the editor started the show. ctx.locale is "en" or "zh-CN". ctx.log(...) prints with a [plugin:<id>] prefix.