Как работает действие
Что Vovó делает с действием с того момента, как его показывает сцена: что она гарантирует и на что действию нельзя рассчитывать. Для тех, кто пишет действия, чтобы передать их другим или продать; как написать действие, сказано в разделе «Действия».
Собственная страница
Каждое действие — отдельный документ в рамке, которую даёт ему страница сцены: его HTML, в который вписаны его style.css и script.js, скрипт последним, так что он запускается, когда HTML уже на месте. Одно сломанное действие не роняет остальные. Сцена показывает свои действия одно поверх другого, в своём порядке, первое внизу.
Рамка не принимает ни щелчков, ни клавиш: на оверлей смотрят, его никогда не нажимают. То, что делает ведущий стрим, он делает на паде, в панели действия или в «Настроить», и это приходит к действию как событие.
Действие слышит событие, только пока его показывает открытая страница, и только в активном профиле. Когда ни одна страница не открыта, отслеживания никто не считает.
Без сети, ничего не хранится в браузере
Действию доступны его собственные файлы и то, что отдаёт ему Vovó, и больше ничего. Картинки, звуки, видео и шрифты берутся из файлов действия, по имени, или с адреса, который дало событие (эмоут, обложка, файл, выбранный в «Настроить»), но никогда из интернета. Его код и стили — только script.js и style.css: никаких скриптов и таблиц стилей из файла или со стороны, никакого eval, никаких воркеров. fetch, XMLHttpRequest и WebSocket отклоняются, даже для собственных файлов действия, поэтому нужные ему данные кладут в его скрипт. Хранилища браузера (localStorage, cookie, IndexedDB) там нет: всё, что действие хранит, оно хранит в vovo.state.
То, что ему нужно извне, оно просит у Vovó в пределах разрешений, которые объявляет: строку в чате, вебхук по имени, звук с пада, клип в официальном плеере.
Его выполняет каждая открытая страница
Одно и то же действие работает везде, где открыта его сцена: в OBS, в окне предпросмотра сцены, во вкладке браузера, в двух источниках сразу. Каждая копия получает одни и те же события и рисует одно и то же. Поэтому страница только рисует и воспроизводит, а то, что должно случиться один раз, просят у Vovó, и она делает это один раз для страниц, которые просят, обрабатывая одно событие: строку в чате, розыгрыш, речь, вебхук, звук с пада, источник в OBS, собственное событие действия.
Vovó отличает вызов одной страницы от вызова другой по событию, которое сейчас обрабатывается. Вызов, сделанный после возврата обработчика (после await, когда закончилась анимация, в таймере), указывает событие, на которое отвечает, через { event: e } — объект, который получил обработчик; без этого Vovó не отличит вторую страницу от второго раза.
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 });
});
У того, что страница начинает сама, события за спиной нет: setInterval действия работает на каждой странице, как и то, что оно делает при загрузке. Тогда Vovó может судить только по тому, о чём просят (та же строка в чате, та же речь, тот же вебхук или собственное событие в течение 5 секунд считаются одним), поэтому тому, что считает или должно случиться один раз, место в триггере timer: он принадлежит приложению и один на все страницы.
Состояние, а не повтор
vovo.state хранит Vovó, а не страница. Страница, которая загружается (запуск OBS, обновление источника, конец паники, повторное открытие Vovó), получает его целиком до запуска своего скрипта, а ключ, заданный на одной странице, сразу доходит до остальных, молча, ведь они сами обработали то же событие. События никогда не проигрываются заново: страница, которой не было, когда пришёл рейд, позже о нём не узнает. Поэтому действие держит в состоянии всё, что должно показать снова (счётчик цели, имена в розыгрыше, конец обратного отсчёта), и при загрузке рисует из него.
function draw()
{
document.getElementById("count").textContent = vovo.state.count || 0;
}
vovo.on("follow", () =>
{
vovo.state.count = (vovo.state.count || 0) + 1;
draw();
});
draw();
- Задавайте ключ заново, когда меняется то, что в нём лежит:
vovo.state.names = names.concat([user]). Список или объект, изменённый на месте (push), не сохраняется. - Меняйте состояние в самом обработчике, до любого
await: тогда каждая страница исходит из одного состояния и приходит к одному. Послеawaitдругая страница могла уже записать, так что пишите то, что не зависит от уже записанного (имя победителя, время), и никогда не «на один больше, чем там есть». - В ключе помещается не больше 64 КБ в виде JSON, а его имя — до 64 символов.
Что является текущим и что измеряется непрерывно
Четыре события — это значение в той же мере, что и момент: obs.scene, media.track, twitch.viewers и screen.shared. Действие, которое загружается, сразу получает последнее из каждого, которое слушает, как будто оно только что произошло, и поэтому показывает играющий трек, не дожидаясь следующего. Оно приходит снова при каждой загрузке, на каждой странице, без события за спиной: рисуйте по нему и не отвечайте на него тем, что должно случиться один раз.
audio.level, screen.color, input.move и speech.text — это замеры, до 60 в секунду: доставлены и забыты, читаются, только пока открытая страница показывает действие, которое их слушает. Делайте их обработчики лёгкими, а движение оставьте CSS.
Загрузка, перезагрузка и паника
Страница может загрузиться в любой момент и не один раз: OBS запускает источник, когда показывается его сцена, может выключить его, пока он скрыт, и обновить, когда он появится снова, а Vovó загружает действие заново, когда меняются его код или файлы. Паника разом очищает все сцены и загружает их действия заново, что останавливает всё, чем они были заняты; когда она заканчивается, они загружаются ещё раз и начинают со своего состояния. Во время паники до действия ничего не доходит, и ничто из пришедшего за это время потом не проигрывается; значение элемента управления сохраняется, потому что это состояние.
Так что действие правильно поднимается из одного только состояния, в любой момент, не рассчитывая ни на что, что предыдущая страница оставила недоделанным.
Сцена профиля
Действие рисует на сцене в пикселях CSS, короткая сторона которой равна 1080: 1920 × 1080 для стрима 16:9 при любом разрешении (источник 4K рисует её чётко в двойном размере), 1080 × 1920 для вертикального, другая форма для экрана другой формы. Его документ размером с эту сцену и прозрачен, так что 100vw на 100vh — это вся картинка, а там, где оно ничего не рисует, видна игра. Действие, сделанное для чужих стримов, не предполагает форму: размещайте элементы от краёв и в процентах, а для вертикального стрима используйте @media (orientation: portrait).
Браузер OBS
OBS показывает сцены в собственном браузере, более старом, чем в редакторе и окне предпросмотра Vovó (Chromium 127 в OBS 32), и он может работать без помощи видеокарты. Проверьте новую возможность CSS или JavaScript по этой версии, прежде чем на неё полагаться, и посмотрите действие в самом OBS, прежде чем его передавать. Звук там играет сразу, без щелчка, и идёт в стрим: ничему, что предназначено только стримеру, в действии не место.
У страницы в OBS нет консоли. Ошибки, которые действие не перехватывает, и то, что Vovó хочет сказать о его вызовах, попадают в консоль Vovó под именем действия, с номером строки; console.log — нет. Для этого откройте адрес предпросмотра в браузере (vovo dev его печатает) и пользуйтесь его инструментами разработчика.
Темп и ограничения
- Чат: одна строка до 500 символов; 20 строк за 30 секунд на все действия, одна в секунду на каждое.
- Вебхуки: 16 КБ JSON; 30 в минуту, один в секунду на каждое действие.
- Клипы и видео: 3 ожидающих одобрения на каждое действие.
- Камеры: 4 одновременно на экране и 4 настройки камеры на действие.
- Голосовой звонок: не больше 300 секунд.
- Кнопка, показанная в работе: не больше 15 минут.
- Состояние: 64 КБ на ключ.
- Пакет: 200 файлов, каждый до 32 МБ и 128 МБ в сумме;
index.html,style.cssиscript.js— по 2 МБ.
Вызов сверх темпа отбрасывается и никогда не ставится в очередь: его промис отклоняется с указанием причины.
Перед тем как передать
- Перезагрузите сцену посреди всего, что оно делает: оно возвращается правильно, из своего состояния.
- Откройте сцену дважды, в OBS и в окне предпросмотра, и запустите каждый триггер: одна строка в чате, один звук, один отсчёт.
- Включите и выключите панику, пока оно что-то показывает.
- Проверьте его на вертикальном профиле и в самом OBS.
- Просите только те разрешения, которыми оно пользуется, и скажите в разделе «Поведение», что это: автор, сайт и одна строка.
- Дайте ему id и более высокую версию при каждой передаче, чтобы обновление находило действие, которое заменяет, и говорило, какую версию приносит.
- Скажите, по какой лицензии вы его передаёте, с полным текстом, если условия ваши собственные.