How an action runs
What Vovó does with an action from the moment a scene shows it: what it promises, and what an action must not count on. For whoever writes actions to hand over or to sell; how to write one is under Actions.
A page of its own
Each action is a document of its own, in a frame the scene's page gives it: its HTML, with its style.css and its script.js written in, the script last, so it runs once the HTML is there. One broken action does not take the others down. A scene shows its actions one over the other in its order, the first at the bottom.
The frame takes no click and no key: an overlay is looked at, never pressed. What whoever runs the live does, they do on the pad, in the action's panel or in Customize, and it reaches the action as an event.
An action hears an event only while an open page shows it, and only in the active profile. With no page open, nothing counts the follows.
No network, nothing kept in the browser
An action reaches its own files and what Vovó serves it, and nothing else. Pictures, sounds, videos and fonts come from the action's files, by name, or from an address an event gave (an emote, a cover, a file chosen in Customize), never from the internet. Its code and styles are script.js and style.css alone: no script or stylesheet from a file or from elsewhere, no eval, no worker. fetch, XMLHttpRequest and WebSocket are refused, for the action's own files too, so data it needs goes in its script. The browser's storage (localStorage, cookies, IndexedDB) is not there: what an action keeps, it keeps in vovo.state.
What it needs from outside it asks Vovó for, within the permissions it declares: a line in chat, a webhook by its name, a sound of the pad, a clip in its official player.
Every open page runs it
The same action runs wherever its scene is open: in OBS, in the scene preview window, in a browser tab, in two sources at once. Each copy gets the same events and draws the same. So a page only draws and plays, and what must happen once is asked of Vovó, which does it once for the pages that ask while handling one event: a line in chat, a draw, a speech, a webhook, a sound of the pad, a source in OBS, an event of the action's own.
Vovó tells one page's call from another's by the event being handled. A call made after the listener returned (after an await, when an animation ends, in a timer) names the event it answers with { event: e }, the object the listener was given; without it, Vovó cannot tell a second page from a second time.
vovo.on("raid", async (e) =>
{
const gift = await vovo.draw(["a hug", "a song", "a dance"]);
vovo.show("raid", { user: e.user, gift: gift.value });
vovo.chat.send(e.user + " gets " + gift.value + "!", { event: e });
});
What a page starts by itself has no event behind it: a setInterval of the action's runs in every page, and so does what it does as it loads. Vovó can then go only by what is asked (the same line in chat, speech, webhook or event of its own within 5 seconds is one), so what counts, or must happen once, belongs in the timer trigger, which is the app's and one for every page.
State, never replay
vovo.state is kept by Vovó, not by the page. A page that loads (OBS starting, a source refreshed, panic ending, Vovó opened again) has all of it before its script runs, and a key set in one page reaches the others at once, without a word, since they handled the same event themselves. Events are never played again: a page that was not there when a raid came does not hear of it later. So an action keeps in its state whatever it must show again (a goal's count, a raffle's names, when a countdown ends) and draws from it as it loads.
function draw()
{
document.getElementById("count").textContent = vovo.state.count || 0;
}
vovo.on("follow", () =>
{
vovo.state.count = (vovo.state.count || 0) + 1;
draw();
});
draw();
- Set the key again when what it holds changes:
vovo.state.names = names.concat([user]). A list or an object changed where it is (push) is not saved. - Change state in the listener itself, before any
await: every page then starts from the same state and reaches the same one. After anawaitanother page may have written already, so write what does not depend on what is there (a winner's name, a time), never one more than it holds. - A key holds 64 KB at most, as JSON, and its name 64 characters.
What is current, and what streams
Four events are a value as much as a moment: obs.scene, media.track, twitch.viewers and screen.shared. An action that loads gets the latest of each it listens to at once, as if it had just happened, so it shows the track playing without waiting for the next. It comes again at every load, in every page, with no event behind it: draw from it, and do not answer it with what should happen once.
audio.level, screen.color, input.move and speech.text are readings, up to 60 a second: delivered and forgotten, read only while an open page shows an action listening. Keep their listeners light, and let CSS do the moving.
Loading, reloading and panic
A page may load at any moment, and more than once: OBS starts a source when its scene shows, may shut it down while hidden and refresh it as it shows again, and Vovó loads an action again when its code or files change. Panic clears every scene at once and loads its actions again, which stops whatever they were doing; when it ends they load once more and start from their state. Nothing reaches an action during panic, and nothing that came meanwhile is played afterwards; a control's value is kept, since it is state.
So an action comes up right from its state alone, at any moment, counting on nothing the page before it left half done.
The stage
An action draws on a stage in CSS pixels whose short side is 1080: 1920 × 1080 for a live in 16:9, whatever its resolution (a 4K source draws it sharp at twice the size), 1080 × 1920 for a vertical one, another shape for a screen of another shape. Its document is the size of the stage and see-through, so 100vw by 100vh is the whole picture, and what it does not draw shows the game. An action made for other people's lives does not assume the shape: place things from the edges and in percentages, and use @media (orientation: portrait) for a vertical live.
OBS's browser
OBS shows scenes in a browser of its own, older than the one in Vovó's editor and preview window (Chromium 127 in OBS 32), which may run without the graphics card's help. Check a new CSS or JavaScript feature against that version before counting on it, and see the action in OBS itself before handing it over. Sound plays at once there, with no click, and goes to the stream: nothing meant for the streamer alone belongs in an action.
A page has no console in OBS. Errors the action does not catch, and what Vovó has to say of its calls, reach Vovó's console under the action's name, with the line; console.log does not. For that, open the preview's address in a browser (vovo dev prints it) and use its developer tools.
Paces and limits
- Chat: one line of 500 characters; 20 lines in 30 seconds for all the actions, one a second for each.
- Webhooks: 16 KB of JSON; 30 a minute, one a second for each action.
- Clips and videos: 3 waiting for approval for each action.
- Cameras: 4 showing at once, and 4 camera settings for each action.
- A voice call: 300 seconds at most.
- A button shown at work: 15 minutes at most.
- State: 64 KB a key.
- A package: 200 files, 32 MB each and 128 MB in all;
index.html,style.cssandscript.js2 MB each.
A call past its pace is left out, never queued: its promise rejects, saying why.
Before handing it over
- Reload the scene in the middle of everything it does: it comes back right, from its state.
- Open the scene twice, in OBS and in the preview window, and set each trigger off: one line in chat, one sound, one count.
- Turn panic on and off while it shows something.
- Try it on a vertical profile, and in OBS itself.
- Ask only for the permissions it uses, and say what it is under Behaviour: its author, its site and a line.
- Give it an id, and a higher version each time you hand it over, so its update finds the action it replaces and says which version it brings.
- Say under which licence you hand it over, with its whole text when the terms are your own.