Ir al contenido

Modo presentador

El modo presentador provee un sistema de dos vistas: el presentador tiene una vista del presentador con notas, temporizadores y controles de navegación; la audiencia ve el slide actual sincronizado. Solo un presentador puede estar activo a la vez.

await presentation.presenter.start({
fromSlide: 0, // índice de slide (default: 0)
countdown: 1800, // cuenta regresiva de 30 minutos en segundos (opcional)
});

Opciones completas:

interface StartOptions {
fromSlide?: number;
countdown?: number;
autoAdvance?: boolean;
autoAdvanceInterval?: number; // segundos entre avances automáticos
}

Llamar a start() establece presentationState.isPresenting = true en el documento Y.js. Todos los clientes conectados reciben la actualización de inmediato.

presentation.presenter.next();
presentation.presenter.previous();
presentation.presenter.first();
presentation.presenter.last();
presentation.presenter.goToSlide(5); // índice base cero

Los slides con hidden: true se saltean automáticamente durante next() y previous().

Atajos de teclado sugeridos para una UI personalizada del presentador:

TeclaAcción
Espacio EnterSlide siguiente
BackspaceSlide anterior
HomePrimer slide
EndÚltimo slide
LActivar/desactivar láser
BActivar/desactivar blackout
EscFinalizar presentación

La vista del presentador típicamente muestra: preview del slide actual, miniatura del próximo slide, notas del presentador, temporizador y contador de slides. Construíla a partir del estado que devuelve subscribe():

presentation.presenter.subscribe(state => {
updateSlideCounter(state.currentSlideIndex, state.totalSlides);
updateTimer(state);
if (state.countdown) {
const { remaining } = state.countdown;
setTimerColor(remaining > 300 ? 'green' : remaining > 60 ? 'yellow' : 'red');
}
});

Las notas son Markdown. Renderizálas en el panel del presentador para el slide actual:

const currentSlideId = presentation.slides.list()[state.currentSlideIndex]?.id;
const notes = presentation.speakerNotes.get(currentSlideId);
notesPanel.innerHTML = renderMarkdown(notes.toString());
notes.observe(() => {
notesPanel.innerHTML = renderMarkdown(notes.toString());
});
timer.elapsed(): number // segundos desde que empezó la presentación
timer.slideElapsed(): number // segundos en el slide actual
timer.setCountdown(seconds: number): void
timer.remaining(): number | null // null si no se configuró cuenta regresiva
timer.pause(): void
timer.resume(): void
timer.reset(): void

Ejemplo — mostrar un reloj en vivo y cuenta regresiva:

setInterval(() => {
const elapsed = presentation.presenter.timer.elapsed();
const remaining = presentation.presenter.timer.remaining();
elapsedEl.textContent = formatSeconds(elapsed);
if (remaining !== null) countdownEl.textContent = formatSeconds(remaining);
}, 1000);

Convenciones de color del temporizador:

Tiempo restanteIndicador
> 5 minVerde
1–5 minAmarillo
< 1 minRojo (parpadeando)

La posición del láser se transmite a todos los clientes de la audiencia como coordenadas normalizadas (0–1).

// Habilitar láser
presentation.presenter.laser.enable();
// Seguir el mouse en el elemento del slide actual del presentador
currentSlideEl.onmousemove = (e) => {
if (!presentation.presenter.laser.isEnabled()) return;
const rect = currentSlideEl.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
presentation.presenter.laser.move(x, y);
};
// Deshabilitar
presentation.presenter.laser.disable();

Renderizado en el lado de la audiencia:

presentation.presenter.subscribe(state => {
if (state.laser.enabled && state.laser.position) {
const { x, y } = state.laser.position;
laserEl.style.left = `${x * 100}%`;
laserEl.style.top = `${y * 100}%`;
laserEl.style.display = 'block';
} else {
laserEl.style.display = 'none';
}
});

Ocultá temporalmente la vista de la audiencia (útil para pausas o preguntas):

presentation.presenter.blackout(true); // la audiencia ve pantalla negra
presentation.presenter.blackout(false); // reanudar

Los clientes de la audiencia se suscriben al estado del presentador y siguen el slide actual. No necesitan llamar a start():

presentation.presenter.subscribe(state => {
if (!presentation.presenter.isPresenter()) {
goToSlide(state.currentSlideIndex);
renderLaser(state.laser);
if (state.blackout) showBlackout();
}
});

Para verificar si alguien está presentando actualmente:

presentation.presenter.isActive(); // true si hay una presentación en curso
presentation.presenter.isPresenter(); // true si este cliente es el presentador

Si nadie está presentando, o si el presentador lo habilita, los viewers pueden navegar de forma independiente:

// Configurar en la presentación para permitir navegación de la audiencia
presentation.meta.set({ allowAudienceNavigation: true });
presentation.presenter.stop();
// Limpia presentationState.isPresenting
// La audiencia vuelve a la vista normal
// Los datos de timing por slide se guardan en el map timings

Solo un usuario puede presentar a la vez. Para transferir:

// Presentador actual
presentation.presenter.stop();
// Nuevo presentador (cliente separado)
await presentation.presenter.start();

Objeto de estado completo devuelto por presenter.state() y presenter.subscribe():

interface PresentationState {
isPresenting: boolean;
presenterId: string | null;
presenterName: string | null;
currentSlideIndex: number;
totalSlides: number;
startedAt: number | null; // timestamp unix ms
slideStartedAt: number | null;
countdown: {
total: number;
remaining: number;
paused: boolean;
} | null;
laser: {
enabled: boolean;
position: { x: number; y: number } | null;
};
blackout: boolean;
}

Creá una versión con nombre antes de presentar para que el rollback sea seguro:

await presentation.versions.create('Antes de la reunión de directorio', 'Backup pre-presentación');

Después de presentar, comparás qué cambió:

const diff = await presentation.versions.diff('ver_antes', 'ver_despues');
console.log(`${diff.slides.modified.length} slides modificados`);

API completa de versiones:

versions.list(): Promise<Version[]>
versions.create(name: string, description?: string): Promise<Version>
versions.restore(versionId: string): Promise<void> // auto-guarda el estado actual primero
versions.diff(fromId: string, toId: string): Promise<VersionDiff>
versions.delete(versionId: string): Promise<boolean>
versions.subscribe(handler: (versions: Version[]) => void): () => void

Los auto-saves se activan: cada 5 minutos si hay cambios, antes de iniciar una presentación, y cuando el último editor se desconecta. Se conservan los últimos 10 auto-saves; las versiones con nombre nunca se eliminan automáticamente.

RolPuede presentarPuede ver publicadaPuede editar
owner
editor
viewerNoNo
AnónimoNoSegún visibilidadNo

Gestioná colaboradores via sdk.collaborators:

await sdk.collaborators.add(['colega@ejemplo.com'], 'editor');
await sdk.collaborators.add(['stakeholder@ejemplo.com'], 'viewer');

La URL publicada (shareout.site/p/{slug}) está siempre disponible para la audiencia. Controlá la visibilidad y el embedding:

presentation.publish.setVisibility('public'); // 'private' | 'workspace' | 'public'
const url = presentation.publish.getUrl();

Embeber en una página externa:

<iframe src="https://shareout.site/embed/mi-deck/" width="100%" height="600" frameborder="0"></iframe>

El embedding está disponible para presentaciones public. Restringí a orígenes específicos via PATCH /v1/artifacts/{id} con embed_origins.

(unlisted es un alias legacy retirado, aún aceptado en la API y tratado como public.)