/** * Один слот превью на страницу: одновременно играет только одна тумба. * * Тумб в блоке двенадцать, и без арбитра на мобиле стартовали все, что попали в * кадр, — это несколько живых HLS-потоков одновременно, лишний трафик и нагрузка * на сеть. Поэтому слот один: наведение мышью забирает его силой (пользователь * явно показал, что смотрит), а автозапуск по появлению в кадре — только если * слот свободен. Когда владелец уходит, слот достаётся следующему из ожидающих. * * Состояние модульное, а не в `useState`: превью — чисто клиентская история, на * сервере слот никому не нужен. */ const active = ref(null); const waiting = new Set(); export const useWebcamPreviewSlot = () => { /** Забрать слот силой — для наведения мышью. */ function claim(id: string | number) { waiting.add(id); active.value = id; } /** Занять слот, только если он свободен — для автозапуска в кадре. */ function request(id: string | number) { waiting.add(id); if (active.value === null) active.value = id; } /** Отпустить слот: если владели мы — передаём следующему ожидающему. */ function release(id: string | number) { waiting.delete(id); if (active.value !== id) return; const next = waiting.values().next(); active.value = next.done ? null : next.value; } return { claim, request, release, isActive: (id: string | number) => computed(() => active.value === id), }; };