as

Settings
Sign out
Notifications
Alexa
亚马逊应用商店
AWS
文档
Support
Contact Us
My Cases
新手入门
设计和开发
应用发布
参考
支持

网页应用开发者指南

网页应用开发者指南

在WebView中发送和接收消息

您可以使用适用于Vega的WebView发送和接收消息。要发送和接收消息,请使用以下选项:

  • 适用于Vega的React Native -> Web: injectJavaScript方法。
  • 网页 -> 适用于Vega的React Native: onMessage属性。

通过网页实现Vega设备的Fire TV功能

window.ReactNativeWebView.postMessage方法在适用于Vega的React Native中执行onMessage属性,该属性可用于触发Vega设备的Fire TV功能。window.ReactNativeWebView.postMessage只能接受一个参数,该参数必须是字符串。

将初始焦点设置到WebView

要将焦点设置到WebView,请执行以下操作。在src/App.tsx文件中,应用开发者需要通过向webview组件添加hasTVPreferredFocus={true}属性来设置初始焦点。

已复制到剪贴板。

  import { WebView } from "@amazon-devices/webview";
  import * as React from "react";
  import { useRef } from "react";
  import { View } from "react-native";

  export const App = () => {
  const webRef = useRef(null);
  return (
    <View >
        <WebView
        ref={webRef}
        hasTVPreferredFocus={true}
        source={{
          uri: "https://www.example.com",
        }}
        javaScriptEnabled={true}
        onLoadStart={(event) => {
          console.log("onLoadStart url: ", event.nativeEvent.url)
        }}
        onLoad={(event) => {
          console.log("onLoad url: ", event.nativeEvent.url)
        }}
        onError={(event) => {
          console.log("onError url: ", event.nativeEvent.url)
        }}
        />
    </View>
    );
  };

启用并使用DevTools进行调试

当使用调试版本 (process.env.NODE_ENV = 'development') 构建应用时,默认情况下,Chrome DevTools处于启用状态。要构建DevTools调试应用,请执行以下操作。

  • 在命令提示符处,运行以下命令。

    已复制到剪贴板。

    kepler build -b Debug
    

完成以下步骤以有效使用DevTools。要使用DevTools进行调试,请执行以下操作。

  1. 安装并启动调试应用,这样将打开WebView。
  2. 在命令提示符下,通过运行以下命令执行端口转发。

    已复制到剪贴板。

     vda forward tcp:9229 tcp:9229.
     DevTools在端口9229上运行。
    
  3. 打开Google Chrome并导航到chrome://inspect/#devices
  4. 在Remote Target(远程目标)部分已连接设备列表中,找到DevTools。
  5. 单击inspect(检查)打开DevTools。现在您可以检查WebView。

在网页JavaScript中启用后退按钮遥控键事件

allowSystemKeyEvents属性控制网页应用是否会主动侦听特定的系统按键事件,例如后退按钮(进行标识时使用的是keyCode: 27)。要启用后退按钮遥控键事件,请执行以下操作。

  • src/App.tsx文件中,将allowSystemKeyEvents属性设置为true

支持的遥控键事件

下表显示了支持的遥控键事件及其关联的键代码。

事件键 键码 需要allowSystemKeyEvents属性
GoBack 27
Enter 13
ArrowLeft 37
ArrowRight 39
ArrowDown 40
ArrowUp 38
MediaFastForward 228
MediaPlayPause 179
MediaRewind 227

在适用于Vega的WebView中检查HDR格式和编解码器支持

WebView在最新的Fire TV设备上支持HEVC Main10(HLG、HDR10、HDR10+)和VP9 Profile2(HLG、HDR10)。最新的Fire TV设备尚不支持AV1 HDR。建议不要将H264和VP8编解码器用于HDR。

使用canPlayType()isTypeSupported() 来检查HDR的可用性。

使用canPlayType() 检查HDR的示例

已复制到剪贴板。

>const video = document.querySelector('video');
>console.log(video.canPlayType('video/webm; codecs="vp09.02.10.10'));
probably

>console.log(video.canPlayType('video/mp4; codecs="hev1.2.4.L153.B0"'));
probably

使用isTypeSupported() 检查HDR的示例

已复制到剪贴板。

>MediaSource.isTypeSupported('video/webm; codecs="vp09.02.10.10"');
true

>MediaSource.isTypeSupported('video/mp4; codecs="hev1.2.4.L153.B0"');
true

由于已知问题,不建议使用MediaCapabilities.decodingInfo()。对于VP9 Profile 2的支持它会报告false。

使用HEVC的示例

已复制到剪贴板。

const hevcConfig = {
    type: 'media-source',
    video: {
        contentType: 'video/mp4; codecs="hvc1.2.4.L153.B0"',
        width: 3840,
        height: 2140,
        framerate: 60,
        bitrate: 20000000,
        transferFunction: 'pq',
        colorGamut: 'rec2020'
    }
};

navigator.mediaCapabilities.decodingInfo(hevcConfig)

覆盖默认媒体控制处理程序

有些应用需要对媒体播放进行精细控制,这超出了默认WebView媒体控制的范围。以下是一些可能超出范围的控制的示例。

  • 在广告期间禁用搜索操作。
  • 在实时播放期间禁用所有媒体控制。

要实现这种级别的精细控制,开发者有两种选择:

  • 使用navigator.mediaSession的基于网页的实现。为此,请启用allowsDefaultMediaControl。您可出于以下原因这样做。
    • 它支持对播放、暂停、向前跳过和向后跳过的传输控制。
    • 它可以直接在网页中实现,无需更改应用代码。
  • 在应用代码中直接与VegaMediaControl接口集成。为此,请禁用allowsDefaultMediaControl。您可出于以下原因这样做。
    • 它提供了对平台媒体控制功能的完全访问权限。
    • 建议对需要深度系统集成的应用如此操作。

使用navigator.mediaSession的基于网页的实现

navigator.mediaSession属性是一种网页标准,它在网页媒体内容和平台媒体控制系统之间提供强大的接口。它让网页应用能够为当前正在播放的媒体的媒体控制注册操作处理程序,并自定义媒体控制在不同内容上的行为。

当在WebView中将allowsDefaultMediaControl设置为true时,Alexa语音命令等平台级控制将传递给已注册的自定义处理程序(如果有)。如果没有已注册的处理程序,WebView将恢复至其默认实现。这让应用可以根据播放上下文(例如在广告或直播期间)实现自定义处理。

例如,当用户说“Alexa, fast forward”(Alexa,快进)并且allowsDefaultMediaControl设置为true时,WebView会将命令传递给该网页的向前搜索处理程序(如果该网页已注册)。如果未注册任何处理程序,WebView将回退到其默认搜索实现。这让网页可以根据播放的内容控制搜索行为,例如在广告期间屏蔽搜索。

注意:​ 无论allowsDefaultMediaControl的值为何,当应用从后台和前台移动时,将始终调用网页的暂停和播放处理程序。

使用navigator.mediaSession的示例

已复制到剪贴板。

// 网页JavaScript代码(将包含在网页中)
// 获取对视频元素的引用
const video = document.querySelector('video');
if ('mediaSession' in navigator) {
    // 覆盖播放操作
    navigator.mediaSession.setActionHandler('play', () => {
        // 此处为自定义播放逻辑
        video.play();
    });
    // 覆盖暂停操作
    navigator.mediaSession.setActionHandler('pause', () => {
        // 此处为自定义暂停逻辑
        video.pause();
    });
    // 覆盖搜索操作
    navigator.mediaSession.setActionHandler('seekbackward', (details) => {
        // 此处为自定义搜索逻辑
        const skipTime = details.seekOffset || 10;
        video.currentTime = Math.max(video.currentTime - skipTime, 0);
    });
    navigator.mediaSession.setActionHandler('seekforward', (details) => {
        // 此处为自定义搜索逻辑
        const skipTime = details.seekOffset || 10;
        video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
    });
}

// React Native应用代码(将在您的应用中实现)
return (
    <View style={styles.sectionContainer}>
      <WebView
        ref={webRef}
        hasTVPreferredFocus={true}
        allowsDefaultMediaControl={true}  // navigator.mediaSession正常工作所必需
        source={{
          uri: "https://example.com",  // 替换为您的URL
        }}
        javaScriptEnabled={true}
      />
    </View>
);

在应用中直接集成VegaMediaControl接口

VegaMediaControl为管理Vega应用中的媒体播放提供强大接口。这种直接集成可以实现对媒体功能(例如播放控制、搜索操作等)进行精细控制。

要将您的Vega网页应用与VegaMediaControl集成,请执行以下关键步骤:

  1. 配置WebView属性。

    • 默认情况下,WebView中的默认媒体控制处于禁用状态。要让控制保持禁用状态,要么让属性保持未设置状态,要么明确地将allowsDefaultMediaControl设置为false。这样可以确保您的VegaMediaControl集成优先于默认的WebView媒体控制。
  2. 实现VegaMediaControl接口。

    • VegaMediaControl集成添加到您的应用
  3. JavaScript桥通信

    • 使用injectJavaScript进行网页播放器控制,以响应VegaMediaControl处理程序。

在应用代码中集成VegaMediaControl的示例

以下是详细示例,演示如何在Vega网页应用中使用VegaMediaControl处理暂停、播放、向前搜索和向后搜索命令。

  1. manifest.toml中添加对VegaMediaControl的支持。更新VegaMediaControl的清单以及其他条目。

    已复制到剪贴板。

     [components]
     [[components.interactive]]
     categories = ["com.amazon.category.kepler.media"]
    
     [[extras]]
     key = "interface.provider"
     component-id = "com.amazondeveloper.keplerwebapp.main"
    
     [extras.value.application]
     [[extras.value.application.interface]]
     interface_name = "com.amazon.kepler.media.IMediaPlaybackServer"
     command_options = [
         "Play",
         "Pause",
         "SkipForward",
         "SkipBackward",
     ]
    
  2. 在package.json中,添加以下依赖项。

    已复制到剪贴板。

     "@amazon-devices/kepler-media-controls": "^1.0.0",
     "@amazon-devices/kepler-media-types": "^1.0.0",
    
  3. 以下应用代码示例创建VegaMediaControl服务器和关联的处理程序。这些处理程序用于通过自定义行为将JavaScript注入网页。

    已复制到剪贴板。

     import { WebView } from "@amazon-devices/webview";
     import { useEffect, useRef, useMemo } from "react";
     import { StyleSheet, View } from "react-native";
     import {
       Action,
       IMediaControlHandlerAsync,
       IMediaMetadata,
       IMediaSessionId,
       ITimeValue,
       ITrack,
       MediaControlServerComponentAsync,
       MediaSessionState,
       RepeatMode,
     } from "@amazon-devices/kepler-media-controls";
     import { IComponentInstance, useComponentInstance } from "@amazon-devices/react-native-kepler";
    
     export const App = () => {
       const webRef = useRef(null);
       const componentInstance: IComponentInstance = useComponentInstance();
    
       const mediaControlsHandler: IMediaControlHandlerAsync = useMemo(
         (): IMediaControlHandlerAsync => ({
           handlePlay: function (sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handlePlay"]);
             // 根据网页实现更新JS脚本。
             webRef.current?.injectJavaScript("document.querySelector('video')?.play();");
             return Promise.resolve();
           },
           handlePause: function (sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handlePause"]);
             // 根据网页实现更新JS脚本。
             webRef.current?.injectJavaScript("document.querySelector('video')?.pause();");
             return Promise.resolve();
           },
           handleTogglePlayPause: function (sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleTogglePlayPause"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleStop: function (sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleStop"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleStartOver: function (sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleStartOver"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleFastForward: function (sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleFastForward"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleRewind: function (sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleRewind"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleSetPlaybackSpeed: function (speed: number, sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleSetPlaybackSpeed"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleSkipForward: function (delta: ITimeValue, sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleSkipForward"]);
             // 根据网页实现更新JS脚本。
             webRef.current?.injectJavaScript("document.querySelector('video').currentTime += 30;");
             return Promise.resolve();
           },
           handleSkipBackward: function (delta: ITimeValue, sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleSkipBackward"]);
             // 根据网页实现更新JS脚本。
             webRef.current?.injectJavaScript("document.querySelector('video').currentTime -= 30;");
             return Promise.resolve();
           },
           handleSeek: function (position: ITimeValue, sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleSeek"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleSetAudioVolume: function (volume: number, sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleSetAudioVolume"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleSetAudioTrack: function (audioTrack: ITrack, sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleSetAudioTrack"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleEnableTextTrack: function (textTrack: ITrack, sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleEnableTextTrack"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleDisableTextTrack: function (sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleDisableTextTrack"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleNext: function (sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleNext"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handlePrevious: function (sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handlePrevious"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleEnableShuffle: function (enable: boolean, sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleEnableShuffle"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleSetRepeatMode: function (mode: RepeatMode, sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleSetRepeatMode"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleSetRating: function (id: MediaId, rating: number, sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleSetRating"sessionIdidrating]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleGetMetadataInfo: function (id: MediaId): Promise<IMediaMetadata> {
             console.log("媒体控制"["handleGetMetadataInfo"id]);
             return Promise.resolve({
               mediaId: id.contentId,
               title: 'Sample Title',
               date: '2024-01-01',
               artwork: [
                 {
                   url: 'https://example.com/artwork1.jpg',
                   sizeTag: 'medium',
                   id: 'sample artwork id1',
                   tag: 'fantastic',
                 },
                 {
                   url: 'https://example.com/artwork2.jpg',
                   sizeTag: 'large',
                   id: 'sample artwork id2',
                   tag: 'wonderful',
                 },
               ],
             });
           },
           handleCustomAction: function (action: Action, sessionId?: IMediaSessionId): Promise<void> {
             console.log("媒体控制"["handleCustomAction"]);
             // 根据网页实现注入javascript
             return Promise.resolve();
           },
           handleGetSessionState: function (sessionId?: IMediaSessionId): Promise<MediaSessionState[]> {
             console.log("媒体控制"["handleGetSessionState"]);
             // 根据网页实现注入javascript
             return Promise.resolve([]);
           },
         }),
         []
       );
    
       useEffect(() => {
         const mediaControlServer = MediaControlServerComponentAsync.getOrMakeServer();
         mediaControlServer.setHandlerForComponent(mediaControlsHandler, componentInstance);
       }, [componentInstance, mediaControlsHandler]);
    
       const styles = StyleSheet.create({
         sectionContainer: {
           flex: 1,
         },
       });
    
       return (
         <View style={styles.sectionContainer}>
           <WebView
             ref={webRef}
             hasTVPreferredFocus={true}
             source={{
               // 替换为您的URL。
               uri: "https://example.com",
             }}
             javaScriptEnabled={true}
           />
         </View>
       );
     };
    

将存储在设备上的本地文件URL加载到WebView中

默认情况下,WebView允许从/pkg/assets目录加载应用文件。但是,如果要从其他目录加载应用文件,则必须将属性设置为true。要加载存储在设备上的本地应用文件URL,请执行以下操作。

  • src/App.tsx中,将allowFileAccess属性设置为true

如果将属性设置为true,会让WebView能够从指定目录访问文件并能够无缝显示应用文件。如果未将allowFileAccess设置为true,尝试从其他目录加载应用文件时将导致“拒绝访问”错误。文件应用在沙盒环境中运行,限制了其对设备文件系统的访问。下表详细介绍了文件应用可以访问的目录。

路径 可写入 描述
/pkg 应用程序包的根目录
/pkg/assets 由程序包安装的资产
/pkg/lib 由程序包安装的应用入口点组件和任何其他库
/data 应用数据的可写入位置。在设备重启和程序包升级期间保持不变。不在应用或服务之间共享。
/tmp 不能与任何其他应用或服务共享的临时存储区。它是非永久性的,在设备重启期间会被删除。
/proc 应用只可使用/proc/self

示例: 使用WebView从资产目录加载HTML文件

已复制到剪贴板。

<View>
    <WebView
        source={{
            uri: "file:///pkg/assets/sample.html",
        }}
    />
</View>

示例: 使用WebView从应用数据目录加载HTML文件

已复制到剪贴板。

<View>
    <WebView
        source={{
            uri: "file:///data/sample.html",
        }}
        allowFileAccess={true}
    />
</View>

WebView中的方向键导航

WebView默认启用了空间导航,以提供基本的方向键导航。

阻止WebView中的空间导航

对于实现自定义焦点管理而非默认空间导航焦点的应用,应用在捕获按键按下事件时可以使用Event::preventDefault。使用Event::preventDefault可以避免自定义焦点逻辑和内置的WebView空间导航之间发生冲突。

要管理序列化的Cookie或清除应用的Cookie,请参阅Vega网页应用Cookie管理器。要查看其他WebView API,请参阅Vega网页应用组件参考

如何防止页面加载前出现白屏闪烁

加载网页之前,视图使用组件的背景颜色。如果未设置背景颜色,则选择白色背景。可以通过更新WebView组件的backgroundColor样式来更改此项设置。

已复制到剪贴板。


import { WebView } from "@amazon-devices/webview";
import * as React from "react";
import { useRef } from "react";
import { View, StyleSheet } from "react-native";

export const App = () => {
  const webRef = useRef(null);
  return (
    <View style={styles.container}>
      <WebView
        style={styles.webview}
        ref={webRef}
        hasTVPreferredFocus={true}
        source={{
          uri: "https://www.example.com",
        }}
        javaScriptEnabled={true}
        onLoadStart={(event) => {
          console.log("onLoadStart", event.nativeEvent.description)
        }}
        onLoad={(event) => {
          console.log("onLoad", event.nativeEvent.description)
        }}
        onError={(event) => {
          console.log("onError", event.nativeEvent.description)
        }}
      />
    </View>
  );
};

// 布局和背景颜色的样式
const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  webview: {
    backgroundColor: "#000000"
  }
});

可以修改WebView的背景颜色样式,使其适合您的应用。

如何删除屏幕顶部显示的图标

如果播放时屏幕顶部出现图标,则可能是由disableRemotePlayback属性造成。可以通过将该属性设置为true来删除该图标。true表示它已被禁用,而false表示它保持启用状态。

示例如下:

已复制到剪贴板。

const obj = document.createElement("audio");
obj.disableRemotePlayback = true;

Last updated: 2025年10月7日