ウェブアプリ開発者ガイド
WebViewでメッセージを送受信する方法
Vega用WebViewを使用してメッセージを送受信できます。メッセージを送受信するには、次のオプションを使用します。
- Vega向けReact Native -> ウェブ:
injectJavaScriptメソッド。 - ウェブ -> Vega向けReact Native:
onMessageプロパティ。
VegaデバイスのFire TV機能をウェブページから実装する方法
window.ReactNativeWebView.postMessageメソッドは、Vega向けReact NativeのonMessageプロパティを実行します。これを使用して、VegaデバイスのFire TV機能をトリガーできます。window.ReactNativeWebView.postMessageは引数を1つだけ受け取り、これには文字列を渡す必要があります。
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をデバッグに使用するには、次の操作を行います。
- デバッグアプリをインストールして、起動します。これによってWebViewが開きます。
- コマンドプロンプトで、次のコマンドを実行してポートフォワーディングを行います。
vda forward tcp:9229 tcp:9229. DevTools runs on port 9229. - Google Chromeを開き、chrome://inspect/#devicesに移動します。
- [Remote Target] セクションで、接続されているデバイスの一覧からDevToolsを見つけます。
- [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)をサポートしています。AV1 HDRは、現在のFire TVデバイスではまだサポートされていません。H264とVP8は、HDRの推奨コーデックではありません。
HDRが利用可能かどうかを確認するには、canPlayType()またはisTypeSupported()を使用します。
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プロファイル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のデフォルトのメディアコントロールよりもきめ細かいメディア再生コントロールが必要になることがあります。次のような例が考えられます。
- 広告再生中のシーク操作を無効にする。
- ライブ再生中にすべてのメディアコントロールを無効にする。
このレベルのきめ細かい制御を実現する場合、開発者には次の2つの選択肢があります。
navigator.mediaSessionを使用してウェブベースで実装する。これを行うには、allowsDefaultMediaControlを有効にします。この方法は次のような理由で選ばれます。- 再生、一時停止、早送り、早戻しのトランスポートコントロールが可能になる。
- アプリのコードを変更せずにウェブページに直接実装できる。
- アプリのコード内で
VegaMediaControlインターフェイスを直接統合する。これを行うには、allowsDefaultMediaControlを無効にします。この方法は次のような理由で選ばれます。- プラットフォームのメディアコントロール機能へのフルアクセスが提供される。
- システムとの密接な統合を必要とするアプリに適している。
navigator.mediaSessionを使用したウェブベースの実装
navigator.mediaSessionプロパティは、ウェブページのメディアコントロールとプラットフォームのメディアコントロールシステムの間に強力なインターフェイスを提供するウェブ標準です。これにより、ウェブアプリでは、現在再生中のメディアに対するアクションハンドラーをメディアコントロールに登録したり、さまざまなコンテンツでのメディアコントロールの動作をカスタマイズしたりできます。
WebViewでallowsDefaultMediaControlがtrueに設定されていると、Alexa音声コマンドなどのプラットフォームレベルのコントロールは、ハンドラーがあればハンドラーに渡されます。登録されたハンドラーがない場合、WebViewはデフォルトの実装に戻ります。これにより、アプリでは、広告やライブストリームなどの再生コンテキストに基づいてカスタム処理を実装できます。
たとえば、allowsDefaultMediaControlがtrueに設定されている場合にユーザーが「アレクサ、早送りして」と言うと、ウェブページにseekforwardハンドラーが登録されていれば、WebViewはそのハンドラーにコマンドを渡します。ハンドラーが登録されていない場合、WebViewはデフォルトのシークの実装にフォールバックします。これによってウェブページでは、広告の再生中にはシークをブロックするなど、再生中のコンテンツに基づいてシークの動作を制御できます。
注: allowsDefaultMediaControlの値にかかわらず、アプリがバックグラウンドまたはフォアグランドに移行したときは、常にウェブページのpauseハンドラーまたはplayハンドラーが呼び出されます。
navigator.mediaSessionを使用する例
// ウェブページのJavaScriptコード(ウェブページに含めます)
// video要素への参照を取得します。
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と統合する主な手順は次のとおりです。
- WebViewのプロパティを構成します。
- WebViewのデフォルトのメディアコントロールは、デフォルトで無効になっています。コントロールを無効にしておくために、プロパティは未設定のままにするか、
allowsDefaultMediaControlを明示的にfalseに設定します。これで、統合されたVegaMediaControlがWebViewのデフォルトのメディアコントロールよりも優先されます。
VegaMediaControlインターフェイスを実装します。
VegaMediaControlの統合をアプリに追加します。
- ブリッジを介してJavaScriptと通信します。
VegaMediaControlハンドラーの応答で、ウェブプレーヤーコントロールのinjectJavaScriptを利用します。
アプリのコードでVegaMediaControlを統合する例
以下の例では、VegaウェブアプリでVegaMediaControlを使用して、一時停止、再生、早送り、早戻しの各コマンドを処理する方法を詳しく示します。
- manifest.tomlで、
VegaMediaControlのサポートを追加します。KMCとその他のエントリでマニフェストを更新します。[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", ] - package.jsonに次の依存関係を追加します。
"@amazon-devices/kepler-media-controls": "^1.0.0", "@amazon-devices/kepler-media-types": "^1.0.0", - 以下のアプリコードの例では、KMCサーバーと、関連付けられるハンドラーを作成します。これらのハンドラーは、カスタム動作の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", sessionId, id, rating]); // ウェブページの実装に基づいてJavaScriptスクリプトを挿入します。 return Promise.resolve(); }, handleGetMetadataInfo: function (id: MediaId): Promise<IMediaMetadata> { console.log("メディアコントロール", ["handleGetMetadataInfo", id]); return Promise.resolve({ mediaId: id.contentId, 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を使用してassetsディレクトリから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でのD-Padナビゲーション
WebViewでは、基本的なD-Padナビゲーションを提供するために、デフォルトで空間ナビゲーションが有効になっています。
WebViewで空間ナビゲーションを防ぐ方法
デフォルトの空間ナビゲーションフォーカスではなくカスタムのフォーカス管理を実装するアプリでは、keydownイベントをキャッチするときに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日

