Using Presentation API to Cast to Secondary Screens
The Presentation API enables web applications to extend their user interface to secondary displays, such as connected monitors, smart TVs, or wireless casting devices like Chromecasts. By establishing a bridge between a controlling web page and a separate receiving web page, the API allows developers to deliver dual-screen experiences—such as slide presentations, video players with remote controls, or gaming dashboards. This article explains the core architecture of the Presentation API, how it discovers external displays, initiates connections, and facilitates real-time data exchange across screens.
Core Architecture: Controller and Receiver
The Presentation API operates on a dual-role architecture:
- The Controller: The primary web application running on the user's main device (such as a laptop or smartphone). It requests the presentation, selects the target screen, and sends control messages or state updates.
- The Receiver: A distinct HTML document loaded directly onto the secondary display. This page receives instructions from the controller and renders the presentation view, free from controller-specific UI like buttons or speaker notes.
Detecting Device Availability
Before offering cast functionality to the user, the controller page checks whether a compatible display is accessible on the network or via hardware cables.
Developers instantiate a PresentationRequest object,
providing the URL of the receiver page:
const presentationRequest = new PresentationRequest(['https://example.com/receiver.html']);Using the getAvailability() method, the application
observes device availability:
presentationRequest.getAvailability()
.then(availability => {
// Show or hide the cast button based on availability
castButton.style.display = availability.value ? 'inline-block' : 'none';
availability.onchange = () => {
castButton.style.display = availability.value ? 'inline-block' : 'none';
};
})
.catch(() => {
// Availability monitoring is not supported; show button by default
castButton.style.display = 'inline-block';
});Initiating the Cast Session
When a user clicks the cast trigger, the application invokes
presentationRequest.start(). Because casting involves
hardware access and external networks, browsers require this call to be
triggered directly by a user gesture, such as a button click.
Calling start() prompts the browser's native
device-picker dialog, allowing the user to select the target display
(e.g., an HDMI-connected monitor, AirPlay receiver, or Google Cast
target). Once selected, the browser launches the receiver URL on that
screen and returns a PresentationConnection object to the
controller:
castButton.addEventListener('click', async () => {
try {
const connection = await presentationRequest.start();
setupConnection(connection);
} catch (error) {
console.error('Connection failed or was cancelled by the user:', error);
}
});Two-Way Communication Between Screens
Once established, the PresentationConnection functions
similarly to a WebSocket. Both the controlling page and the receiving
page can send text strings, JSON payloads, or binary data (such as
Blob or ArrayBuffer) back and forth.
On the Controller:
function setupConnection(connection) {
// Send state or commands
connection.send(JSON.stringify({ command: 'PLAY_MEDIA', timestamp: 120 }));
// Listen for messages from the receiver
connection.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Status from secondary display:', data);
};
// Monitor connection states (connected, closed, terminated)
connection.onterminate = () => {
console.log('Presentation session ended.');
};
}On the Receiver:
The receiver page accesses the incoming connection through the
navigator.presentation.receiver property:
if (navigator.presentation.receiver) {
navigator.presentation.receiver.connectionList.then(list => {
list.connections.forEach(connection => {
listenToController(connection);
});
list.onconnectionavailable = (event) => {
listenToController(event.connection);
};
});
}
function listenToController(connection) {
connection.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.command === 'PLAY_MEDIA') {
playVideoAt(message.timestamp);
}
};
// Notify the controller that the receiver is ready
connection.send(JSON.stringify({ status: 'READY' }));
}Session Reconnection and Termination
The Presentation API supports session persistence. If a controller
page is refreshed or accidentally closed, it can reconnect to an ongoing
session using
presentationRequest.reconnect(presentationId).
When the user chooses to stop casting, either party can close the
message channel via connection.close() or completely shut
down the receiver instance on the remote screen using
connection.terminate().