Presentation API

可用性有限

此特性不是基线特性,因为它在一些最广泛使用的浏览器中不起作用。

安全上下文: 此功能仅在安全上下文(HTTPS)中可用,且支持此功能的浏览器数量有限。

实验性: 这是一项实验性技术
在生产中使用此技术之前,请仔细检查浏览器兼容性表格

Presentation API 允许 用户代理(例如 Web 浏览器)有效地通过大型显示设备(如投影仪和联网电视)显示 Web 内容。支持的多媒体设备类型包括通过 HDMI、DVI 或类似接口有线连接的显示器,以及通过 DLNAChromecastAirPlayMiracast 无线连接的显示器。

1-UA mode loaded the Controlling and Presenting pages together before outputting to displays. 2-UA mode loaded them separately using the Presentation Control Protocol.

通常,网页使用 Presentation Controller API 来指定要在演示设备上呈现的 Web 内容并启动演示会话。通过 Presentation Receiver API,正在演示的 Web 内容可以获取会话状态。通过为控制器页面和接收器页面提供基于消息的通道,Web 开发人员可以实现这两个页面之间的交互。

根据演示设备提供的连接机制,任何控制器页面和接收器页面都可以由同一个用户代理呈现,也可以由不同的用户代理呈现。

  • 对于 1-UA 模式设备,两个页面都由同一个用户代理加载。但是,接收器页面的渲染结果将通过支持的远程渲染协议发送到演示设备。
  • 对于 2-UAs 模式设备,接收器页面直接在演示设备上加载。控制用户代理通过支持的演示控制协议与演示设备通信,以控制演示会话并在两个页面之间传输消息。

接口

Presentation

在控制浏览上下文时,Presentation 接口提供了一种机制来覆盖浏览器将演示内容启动到外部屏幕的默认行为。在接收浏览上下文时,Presentation 接口提供对可用演示连接的访问。

PresentationRequest

启动或重新连接到由控制浏览上下文发起的演示。

PresentationAvailability

一个 PresentationAvailability 对象与可用的演示显示相关联,并代表演示请求的演示显示可用性

PresentationConnectionAvailableEvent

当与 PresentationRequest 对象关联的连接创建时,会在该对象上触发 PresentationConnectionAvailableEvent

PresentationConnection

每个演示连接都由一个 PresentationConnection 对象表示。

PresentationConnectionCloseEvent

当演示连接进入 closed 状态时,会触发 PresentationConnectionCloseEvent

PresentationReceiver

PresentationReceiver 允许接收浏览上下文访问控制浏览上下文并与它们进行通信。

PresentationConnectionList

PresentationConnectionList 表示未终止的演示连接的集合。它也是新可用演示连接事件的监视器。

示例

下面的示例代码重点介绍了 Presentation API 的主要功能:controller.html 实现控制器,presentation.html 实现演示。两个页面都从域名 https://example.org 提供服务(https://example.org/controller.htmlhttps://example.org/presentation.html)。这些示例假定控制页面一次只管理一个演示。有关更多详细信息,请参阅代码示例中的注释。

监视演示显示的可用性

controller.html

html
<button id="presentBtn" class="hidden">Present</button>
css
.hidden {
  display: none;
}
js
// The Present button is visible if at least one presentation display is available
const presentBtn = document.getElementById("presentBtn");

// It is also possible to use relative presentation URL e.g. "presentation.html"
const presUrls = [
  "https://example.com/presentation.html",
  "https://example.net/alternate.html",
];

// Show or hide present button depending on display availability
const handleAvailabilityChange = (available) => {
  if (available) {
    presentBtn.classList.remove("hidden");
  } else {
    presentBtn.classList.add("hidden");
  }
};

// Promise is resolved as soon as the presentation display availability is known.
const request = new PresentationRequest(presUrls);
request
  .getAvailability()
  .then((availability) => {
    // availability.value may be kept up-to-date by the controlling UA as long
    // as the availability object is alive. It is advised for the web developers
    // to discard the object as soon as it's not needed.
    handleAvailabilityChange(availability.value);
    availability.onchange = () => {
      handleAvailabilityChange(availability.value);
    };
  })
  .catch(() => {
    // Availability monitoring is not supported by the platform, so discovery of
    // presentation displays will happen only after request.start() is called.
    // Pretend the devices are available for simplicity; or, one could implement
    // a third state for the button.
    handleAvailabilityChange(true);
  });

开始新的演示

controller.html

js
presentBtn.onclick = () => {
  // Start new presentation.
  request
    .start()
    // The connection to the presentation will be passed to setConnection on success.
    .then(setConnection);
  // Otherwise, the user canceled the selection dialog or no screens were found.
};

重新连接到演示

controller.html 文件中

html
<button id="reconnectBtn" class="hidden">Reconnect</button>
js
const reconnect = () => {
  const presId = localStorage.getItem("presId");
  // presId is mandatory when reconnecting to a presentation.
  if (presId) {
    request
      .reconnect(presId)
      // The new connection to the presentation will be passed to
      // setConnection on success.
      .then(setConnection);
    // No connection found for presUrl and presId, or an error occurred.
  }
};
// On navigation of the controller, reconnect automatically.
reconnect();
// Or allow manual reconnection.
reconnectBtn.onclick = reconnect;

由控制用户代理发起演示

controller.html 文件中

js
navigator.presentation.defaultRequest = new PresentationRequest(presUrls);
navigator.presentation.defaultRequest.onconnectionavailable = (evt) => {
  setConnection(evt.connection);
};

设置 presentation.defaultRequest 允许页面在控制用户代理启动演示时指定要使用的 PresentationRequest

监视连接状态并交换数据

controller.html

html
<button id="disconnectBtn" class="hidden">Disconnect</button>
<button id="stopBtn" class="hidden">Stop</button>
<button id="reconnectBtn" class="hidden">Reconnect</button>
js
let connection;

// The Disconnect and Stop buttons are visible if there is a connected presentation
const stopBtn = document.querySelector("#stopBtn");
const reconnectBtn = document.querySelector("#reconnectBtn");
const disconnectBtn = document.querySelector("#disconnectBtn");

stopBtn.onclick = () => {
  connection?.terminate();
};

disconnectBtn.onclick = () => {
  connection?.close();
};

function setConnection(newConnection) {
  // Disconnect from existing presentation, if not attempting to reconnect
  if (
    connection &&
    connection !== newConnection &&
    connection.state !== "closed"
  ) {
    connection.onclose = undefined;
    connection.close();
  }

  // Set the new connection and save the presentation ID
  connection = newConnection;
  localStorage.setItem("presId", connection.id);

  function showConnectedUI() {
    // Allow the user to disconnect from or terminate the presentation
    stopBtn.classList.remove("hidden");
    disconnectBtn.classList.remove("hidden");
    reconnectBtn.classList.add("hidden");
  }

  function showDisconnectedUI() {
    disconnectBtn.classList.add("hidden");
    stopBtn.classList.add("hidden");
    if (localStorage.getItem("presId")) {
      // If there is a presId in localStorage, allow the user to reconnect
      reconnectBtn.classList.remove("hidden");
    } else {
      reconnectBtn.classList.add("hidden");
    }
  }

  // Monitor the connection state
  connection.onconnect = () => {
    showConnectedUI();

    // Register message handler
    connection.onmessage = (message) => {
      console.log(`Received message: ${message.data}`);
    };

    // Send initial message to presentation page
    connection.send("Say hello");
  };

  connection.onclose = () => {
    connection = null;
    showDisconnectedUI();
  };

  connection.onterminate = () => {
    localStorage.removeItem("presId");
    connection = null;
    showDisconnectedUI();
  };
}

监视可用的连接并发送问候

presentation.html

js
const addConnection = (connection) => {
  connection.onmessage = (message) => {
    if (message.data === "Say hello") connection.send("hello");
  };
};

navigator.presentation.receiver.connectionList.then((list) => {
  list.connections.forEach((connection) => {
    addConnection(connection);
  });
  list.onconnectionavailable = (evt) => {
    addConnection(evt.connection);
  };
});

通过消息传递区域信息

controller.html 文件中

html
connection.send('{"string": "你好,世界!", "lang": "zh-CN"}');
connection.send('{"string": "こんにちは、世界!", "lang": "ja"}');
connection.send('{"string": "안녕하세요, 세계!", "lang": "ko"}');
connection.send('{"string": "Hello, world!", "lang": "en-US"}');

presentation.html 文件中

js
connection.onmessage = (message) => {
  const messageObj = JSON.parse(message.data);
  const spanElt = document.createElement("SPAN");
  spanElt.lang = messageObj.lang;
  spanElt.textContent = messageObj.string;
  document.body.appendChild(spanElt);
};

规范

规范
Presentation API
# interface-presentation

浏览器兼容性

另见

Presentation API polyfill 包含一个 JavaScript polyfill,实现了 W3C Second Screen Working Group 正在标准化的 Presentation API 规范。该 polyfill 主要用于探索 Presentation API 如何基于不同的演示机制进行实现。