Presentation API - 允许用户代理通过大型演示设备有效地显示 Web 内容

安全上下文
该功能仅在部分或所有支持的浏览器中的安全上下文(HTTPS)中可用。

这是一个实验中的功能
此功能某些浏览器尚在开发中,请参考浏览器兼容性表格以得到在不同浏览器中适合使用的前缀。由于该功能对应的标准文档可能被重新修订,所以在未来版本的浏览器中该功能的语法和行为可能随之改变。

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

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

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

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

接口

Presentation

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

PresentationRequest

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

PresentationAvailability

PresentationAvailability 对象表示可用的演示显示,并表示演示请求的显示可用性

PresentationConnectionAvailableEvent

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

PresentationConnection

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

PresentationConnectionCloseEvent

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

PresentationReceiver

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

PresentationConnectionList

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

实例

下面的实例代码突出显示了 Presentation API 主要功能的使用:controller.html 实现了控制器,而 presentation.html 实现了演示。这两个页面都来自域 http://example.orghttp://example.org/controller.htmlhttp://example.org/presentation.html)。这些示例假定控制页面一次管理一个演示文稿。有关更多详细信息,请参阅代码示例中的注释。

监控演示显示的可用性

<!-- controller.html -->
<button id="presentBtn" style="display: none;">Present</button>
<script>
  // 如果至少有一个演示显示可用,则演示按钮可见
  var presentBtn = document.getElementById("presentBtn");
  // 也可以使用相对路径表示 URL,例如 “presentation.html”
  var presUrls = ["http://example.com/presentation.html",
                  "http://example.net/alternate.html"];
  // 根据显示可用性显示或隐藏当前按钮
  var handleAvailabilityChange = function(available) {
    presentBtn.style.display = available ? "inline" : "none";
  };
  // 一旦知道演示文稿显示的可用性,Promise 就会得到解析。
  var request = new PresentationRequest(presUrls);
  request.getAvailability().then(function(availability) {
    // 只要可用性对象处于活动状态,控制 UA 就可以获得 `availability.value` 的最新值。
    handleAvailabilityChange(availability.value);
    availability.onchange = function() { handleAvailabilityChange(this.value); };
  }).catch(function() {
    // 平台不支持可用性监控,因此只有在调用 `request.start()` 后才会发现演示显示。为了简单起见,假装这些设备可用;或者,可以为按钮实现第三种状态。
    handleAvailabilityChange(true);
  });
</script>

开始新的演示

<!-- controller.html -->
<script>
  presentBtn.onclick = function () {
    // 开始新的演示。
    request.start()
      // 成功时,与演示的连接将被传递给 setConnection。
      .then(setConnection);
      // 否则,则是用户取消了选择对话框或没有找到任何屏幕。
  };
</script>

重新连接到演示

<!-- controller.html -->
<button id="reconnectBtn" style="display: none;">重新连接</button>
<script>
  var reconnect = function () {
    // 如果存在,则从 localStorage 读取 presId
    var presId = localStorage["presId"];
    // 重新连接到演示时,pressId 是必需的。
    if (!!presId) {
      request.reconnect(presId)
        // 成功时,演示文稿的新连接将传递给 setConnection。
        .then(setConnection);
        // 否则是未找到 presUrl 和 presId 的连接,或发生错误。
    }
  };
  // 在控制器发生导航时,自动重新连接。
  document.addEventListener("DOMContentLoaded", reconnect);
  // 或者允许手动重新连接。
  reconnectBtn.onclick = reconnect;
</script>

由控制 UA 发起的演示

<!-- controller.html -->
<!-- 设置 presentation.defaultRequest 允许页面指定在控制 UA 启动演示时要使用的 PresentationRequest。 -->
<script>
  navigator.presentation.defaultRequest = new PresentationRequest(presUrls);
  navigator.presentation.defaultRequest.onconnectionavailable = function(evt) {
    setConnection(evt.connection);
  };
</script>

监控连接状态并交换数据

<!-- controller.html -->
<button id="disconnectBtn" style="display: none;">断开</button>
<button id="stopBtn" style="display: none;">停止</button>
<button id="reconnectBtn" style="display: none;">重新连接</button>
<script>
  let connection;

  // 如果有连接的演示,则断开连接和停止按钮可见
  const stopBtn = document.querySelector("#stopBtn");
  const reconnectBtn = document.querySelector("#reconnectBtn");
  const disconnectBtn = document.querySelector("#disconnectBtn");

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

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

  function setConnection(newConnection) {
    // 如果不尝试重新连接,则断开与现有演示的连接
    if (connection && connection != newConnection && connection.state != 'closed') {
      connection.onclosed = undefined;
      connection.close();
    }

    // 设置新连接并保存演示 ID
    connection = newConnection;
    localStorage["presId"] = connection.id;

    function showConnectedUI() {
      // 允许用户断开或终止演示
      stopBtn.style.display = "inline";
      disconnectBtn.style.display = "inline";
      reconnectBtn.style.display = "none";
    }

    function showDisconnectedUI() {
      disconnectBtn.style.display = "none";
      stopBtn.style.display = "none";
      reconnectBtn.style.display = localStorage["presId"] ? "inline" : "none";
    }

    // 监控连接状态
    connection.onconnect = _ => {
      showConnectedUI();

      // 注册消息处理程序
      connection.onmessage = message => {
        console.log(`收到消息:${message.data}`);
      };

      // 将初始消息发送到演示页面
      connection.send("问好");
    };

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

    connection.onterminate = _ => {
      // localStorage 如果存在 presId,则将其删除
      delete localStorage["presId"];
      connection = null;
      showDisconnectedUI();
    };
  };
</script>

监控可用连接并打个招呼

<!-- presentation.html -->
<script>
  var addConnection = function(connection) {
    this.onmessage = function (message) {
      if (message.data === "问好")
        this.send("你好");
    };
  };

  navigator.presentation.receiver.connectionList.then(function (list) {
    list.connections.map(function (connection) {
      addConnection(connection);
    });
    list.onconnectionavailable = function (evt) {
      addConnection(evt.connection);
    };
  });
</script>

使用消息传递本地化信息

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

<!-- presentation.html -->
<script>
  connection.onmessage = function (message) {
    var messageObj = JSON.parse(message.data);
    var spanElt = document.createElement("SPAN");
    spanElt.lang = messageObj.lang;
    spanElt.textContent = messageObj.string;
    document.appendChild(spanElt);
  };
</script>

规范

规范
Presentation API

桌面浏览器兼容性

特性ChromeEdgeFirefoxInternet ExplorerOperaSafari
基础支持48≤7951 不支持 支持 未知
defaultRequest48≤7951 不支持 支持 未知
receiver48≤7951 不支持 支持 未知

移动浏览器兼容性

特性AndroidChrome for AndroidEdge mobileFirefox for AndroidIE mobileOpera AndroidiOS Safari
基础支持 不支持48 未知51 未知 支持 未知
defaultRequest 不支持48 未知51 未知 支持 未知
receiver 不支持48 未知51 未知 支持 未知

相关链接

Presentation API polyfill 包含一个符合 W3C Second Screen 工作组标准化的 Presentation API 规范的 JavaScript polyfill。polyfill 主要用于探索如何在不同的表示机制之上实现 Presentation API。