· deepdives  · 7 min read

Harnessing the Power of Presentation API: A Comprehensive Guide to Building Seamless Web Experiences

Learn how to use the Web Presentation API to project content to external screens, build remote-controlled slideshows and synchronized media, handle connection lifecycle, and fall back gracefully where support is limited. Includes practical code examples, implementation tips, and references.

Learn how to use the Web Presentation API to project content to external screens, build remote-controlled slideshows and synchronized media, handle connection lifecycle, and fall back gracefully where support is limited. Includes practical code examples, implementation tips, and references.

Why the Presentation API matters

The Web Presentation API enables web pages to request and manage a presentation on a secondary screen (a TV, projector, or another device) and to communicate with that presentation once it is launched. For developers, this opens the door to immersive, multi-screen experiences without native apps: slide shows controlled by a phone, second-screen dashboards, synchronized video playback, and collaborative displays.

This guide covers the core API surfaces, practical examples (a remote-controlled slideshow and a synchronized video), best practices, fallback strategies, and resources to test and debug your implementation.

Key concepts and terminology

  • Controller (controlling page): The page that starts or reconnects to a presentation and sends control messages. Example: the speaker’s laptop or a mobile phone.
  • Receiver (presentation page): The page opened on the secondary screen that receives messages and renders the presentation.
  • PresentationRequest: Used by the controller to request a presentation URL on an external display.
  • PresentationConnection: A bidirectional messaging channel between controller and receiver. Supports send(), onmessage, onclose, terminate(), etc.
  • Availability: An object that notifies when presentation displays become available.

For the formal specification and detailed behavior, see the W3C Presentation API spec and the MDN reference.

Browser support and important notes

  • The Presentation API is implemented in Chromium-based browsers but support varies by platform and version. Always perform feature detection.
  • The API requires a secure context (HTTPS) to function.
  • The receiver URL must be hosted and reachable by the device chosen for presentation.

Feature detection example:

if ('presentation' in navigator) {
  // Presentation API available - continue
} else {
  // Fallback: WebSocket / WebRTC / Cast SDK or guide user
}

Basic controller workflow (start a presentation)

  1. Create a PresentationRequest with one or more URLs for the receiver page.
  2. Call start() to open the presentation on an available display.
  3. Use the returned PresentationConnection to communicate.

Controller example (controller.js):

// controller.js
const request = new PresentationRequest(['/receiver.html']);
// Optional: make this the default request for the page
navigator.presentation.defaultRequest = request;

async function startPresentation() {
  try {
    const connection = await request.start();
    console.log('Presentation started:', connection);

    connection.onmessage = event => {
      console.log('Message from receiver:', event.data);
    };

    // Send commands to the receiver
    connection.send(JSON.stringify({ type: 'slide', index: 0 }));

    connection.onclose = () => console.log('Presentation connection closed');
    connection.onterminate = () => console.log('Presentation terminated');
  } catch (err) {
    console.error('Failed to start presentation', err);
  }
}

Basic receiver workflow (the presentation page)

The receiver responds to connection events from the controller. The controlling browser will open the receiver page on the external display; the receiver then listens for an incoming PresentationConnection.

Receiver example (receiver.html):

<!-- receiver.html -->
<!doctype html>
<html>
  <body>
    <div id="slide">Waiting for controller...</div>
    <script>
      navigator.presentation.onconnectionavailable = event => {
        const connection = event.connection;
        connection.onmessage = msg => {
          const data = JSON.parse(msg.data);
          if (data.type === 'slide') {
            document.getElementById('slide').textContent =
              'Slide ' + data.index;
          }
        };

        connection.onclose = () => console.log('Controller disconnected');
        connection.send(JSON.stringify({ type: 'status', ready: true }));
      };
    </script>
  </body>
</html>

Note: Not all implementations expose navigator.presentation.onconnectionavailable; some use other ways to expose receiver connections. Always test on your target platforms.

Availability detection and UI improvements

You can query whether displays are available and react to changes so the UI only shows a “Present” button when appropriate:

const request = new PresentationRequest(['/receiver.html']);
const availability = await request.getAvailability();

availability.onchange = () => {
  document.querySelector('#presentBtn').disabled = !availability.value;
};

// initial state
document.querySelector('#presentBtn').disabled = !availability.value;

This avoids presenting users with a control that will always fail.

Example: Remote-controlled slideshow (controller + receiver)

Controller (app controlling slides):

// controller-slides.js
const request = new PresentationRequest(['/receiver.html']);
let conn;

async function start() {
  try {
    conn = await request.start();
    conn.onmessage = handleReceiverMessage;
    conn.onclose = () => console.warn('Connection closed');
  } catch (e) {
    console.error(e);
  }
}

function nextSlide(index) {
  if (!conn) return;
  conn.send(JSON.stringify({ type: 'slide', index }));
}

function handleReceiverMessage(evt) {
  const msg = JSON.parse(evt.data);
  if (msg.type === 'status') {
    console.log('Receiver status:', msg);
  }
}

Receiver (displaying slides):

// receiver-slides.js
const slides = ['Welcome', 'Agenda', 'Demo', 'Q&A'];
const slideEl = document.getElementById('slide');

navigator.presentation.onconnectionavailable = e => {
  const c = e.connection;
  c.onmessage = m => {
    const data = JSON.parse(m.data);
    if (data.type === 'slide') {
      const i = data.index;
      slideEl.textContent = slides[i] || '(blank)';
    }
  };
  c.send(JSON.stringify({ type: 'status', ready: true }));
};

This pattern keeps the messaging simple (JSON messages with a type field) and the receiver stateless with regard to the controlling interface.

Example: Synchronized video playback

To synchronize playback across controller and receiver, send control messages (play, pause, seek) and optionally periodic time updates. Use a small tolerance window to correct drift.

Controller:

function play() {
  conn.send(JSON.stringify({ type: 'play' }));
}
function pause() {
  conn.send(JSON.stringify({ type: 'pause' }));
}
function seek(time) {
  conn.send(JSON.stringify({ type: 'seek', time }));
}

// Optionally ask receiver to broadcast its time or implement heartbeat

Receiver:

const video = document.querySelector('video');

navigator.presentation.onconnectionavailable = e => {
  const c = e.connection;
  c.onmessage = m => {
    const data = JSON.parse(m.data);
    if (data.type === 'play') video.play();
    if (data.type === 'pause') video.pause();
    if (data.type === 'seek') video.currentTime = data.time;
  };
};

Tips for synchronization:

  • Send timestamps and perform small corrections rather than abrupt jumps.
  • Use an agreed-upon message format and drift threshold (e.g., 200ms) before seeking.
  • Implement a ping/latency measurement to compensate for network delay.

Reconnect and lifecycle handling

Presentations may be interrupted (network issues, device sleep). Use these API features:

  • PresentationConnection.id: a unique id you can store to allow reconnecting later.
  • PresentationRequest.reconnect(connectionId): attempt to reconnect an existing presentation when supported.

Store connection.id on the controller and share (persist or display) so the user can reconnect if the controller reloads.

Example:

// After start
const id = connection.id;
// Save id somewhere; later
try {
  const conn = await request.reconnect(id);
  // resume messaging
} catch (err) {
  // fallback to start()
}

Not all user agents will implement reconnect; always feature-detect and provide fallback.

Security and privacy considerations

  • The API requires secure contexts (HTTPS).
  • The user will typically be shown a system pairing prompt; UI flows differ across platforms and user agents.
  • Treat messages as untrusted: validate and parse JSON carefully.
  • Avoid exposing sensitive user data to a receiver if it might be on an external, shared display.

Fallback strategies when Presentation API isn’t available

If the Presentation API is not present or the platform doesn’t support it, consider:

  • WebRTC DataChannel for direct peer-to-peer messaging and media streaming.
  • WebSockets to a server that both the controller and a receiver page connect to.
  • Google Cast / Chromecast SDK for casting to Cast-enabled devices.
  • Simple UX fallback: download/print or local device-only presentation controls.

Which fallback to pick depends on constraints: if low-latency P2P is required, WebRTC is better; for broad compatibility with TVs, Cast may be preferable.

Best practices and patterns

  • Message format: use JSON with a type field and a version number for forward-compatibility.
  • Heartbeat: send keepalive pings every few seconds to detect dead connections quickly.
  • Error handling: show clear user messages if presentation fails (e.g., no displays found).
  • Respect user expectations: don’t automatically start presentations without a clear user action.
  • UX: show availability indicators, and explain what the “Present” flow will do (which screen will be used).
  • Logging: provide verbose logs during development, and strip or restrict logs in production.

Testing and debugging

  • Use Chromium-based browsers with remote debugging tools.
  • Test on real devices and real displays (projectors, TVs) in addition to emulators.
  • Use the browser console for both controller and receiver windows; the receiver will often open in a separate window or device.
  • Check the WICG repo issues for known implementation differences.

Real-world examples and use-cases

  • Conference presentation control: speaker controls slides from a laptop or phone.
  • Museum exhibits: curated content on a display controlled by staff or a kiosk.
  • Learning environments: instructor broadcasts a quiz or video to classroom displays.
  • Retail kiosks: synchronized promotional content across multiple screens.

Resources and further reading

Summary

The Presentation API provides a standardized way to open a receiver page on a second screen and exchange messages between controller and receiver. While browser support is still evolving, the API simplifies building multi-screen experiences compared with fully custom WebRTC or server-based approaches. Focus on robust message design, graceful degradation, and testing across devices to deliver smooth, reliable presentations and second-screen interactions.

Back to Blog

Related Posts

View All Posts »