as

Settings
Sign out
Notifications
Alexa
Amazonアプリストア
AWS
ドキュメント
Support
Contact Us
My Cases
開発
設計と開発
公開
リファレンス
サポート

ウェブアプリ開発者ガイド

ウェブアプリ開発者ガイド

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をデバッグに使用するには、次の操作を行います。

  1. デバッグアプリをインストールして、起動します。これによってWebViewが開きます。
  2. コマンドプロンプトで、次のコマンドを実行してポートフォワーディングを行います。

    クリップボードにコピーしました。

     vda forward tcp:9229 tcp:9229.
     DevTools runs on port 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)をサポートしています。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と統合する主な手順は次のとおりです。

  1. WebViewのプロパティを構成します。
  • WebViewのデフォルトのメディアコントロールは、デフォルトで無効になっています。コントロールを無効にしておくために、プロパティは未設定のままにするか、allowsDefaultMediaControlを明示的にfalseに設定します。これで、統合されたVegaMediaControlがWebViewのデフォルトのメディアコントロールよりも優先されます。
  1. VegaMediaControlインターフェイスを実装します。
  • VegaMediaControlの統合をアプリに追加します。
  1. ブリッジを介してJavaScriptと通信します。
  • VegaMediaControlハンドラーの応答で、ウェブプレーヤーコントロールのinjectJavaScriptを利用します。

アプリのコードでVegaMediaControlを統合する例

以下の例では、VegaウェブアプリでVegaMediaControlを使用して、一時停止、再生、早送り、早戻しの各コマンドを処理する方法を詳しく示します。

  1. 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",
     ]
    
  2. package.jsonに次の依存関係を追加します。

    クリップボードにコピーしました。

     "@amazon-devices/kepler-media-controls": "^1.0.0",
     "@amazon-devices/kepler-media-types": "^1.0.0",
    
  3. 以下のアプリコードの例では、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でアプリファイルへのアクセスが可能になり、指定したディレクトリからシームレスにアプリファイルを表示できます。allowFileAccesstrueに設定しないと、別のディレクトリからアプリファイルを読み込もうとしたときに、「アクセスが拒否されました」というエラーが発生します。ファイルアプリはサンドボックス環境内で機能し、デバイスのファイルシステムへのアクセスが制限されます。ファイルアプリからアクセスできるディレクトリの詳細を以下の表に示します。

パス 書き込み可能 説明
/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日