libaribhtml5 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,15 @@
1
1
  export { AribReceiverHost } from './receiver-host';
2
- export type { AribCaptionPacket, AribBroadcastClock, AribReceiverLifecycleEvent, AribReceiverHostOptions, } from './receiver-host';
2
+ export type { AribApplicationInformation, AribApplicationReplaceHandler, AribApplicationReplaceRequest, AribCaptionPacket, AribBroadcastClock, AribExitManagedStateHandler, AribReceiverLifecycleEvent, AribReceiverHostOptions, } from './receiver-host';
3
3
  export { BehindIframeMediaPlaneAdapter, DomObjectMediaPlaneAdapter, } from './media-plane';
4
4
  export type { AribMediaPlane, AribMediaPlaneAdapter, AribMediaPlaneLayer, AribMediaPlaneStackEntry, AribMediaPlaneUnmountReason, AribVideoPlane, BehindIframeMediaPlaneAdapterOptions, DomObjectMediaPlaneAdapterOptions, } from './media-plane';
5
5
  export { installRuntime } from './runtime/install';
6
- export { createReceiverSystemInformation, RECEIVER_SYSTEM_IDENTITY, } from './runtime/system-information';
6
+ export { createReceiverSystemInformation, readReceiverInformationArray, RECEIVER_SYSTEM_IDENTITY, synchronizeReceiverCompatibilityStorage, } from './runtime/system-information';
7
7
  export type { ReceiverSystemIdentity, ReceiverSystemInformation, ReceiverSystemInformationOverrides, } from './runtime/system-information';
8
8
  export { cloneProgramInfo } from './program-info';
9
9
  export type { ProgramInfo } from './program-info';
10
- export type { RuntimeEvent, RuntimeOptions, RuntimeWindow } from './runtime/install';
10
+ export { runtimeEventMatchesSelector } from './runtime/stream-event';
11
+ export type { RuntimeEvent, RuntimeEventSelector, RuntimeEventSource, } from './runtime/stream-event';
12
+ export type { RuntimeApplicationInformation, RuntimeOptions, RuntimeWindow, } from './runtime/install';
11
13
  export { DEFAULT_RECEIVER_DEVICE_IDENTIFIER, resolveReceiverDeviceIdentifier, } from './device-identifier';
12
14
  export type { ReceiverDeviceIdentifierProvider } from './device-identifier';
13
15
  export type { BroadcastResourceCacheEvent, BroadcastResourceCacheListener, BroadcastResourceChange, BroadcastResourceStore, } from './runtime/resources';
@@ -15,7 +17,7 @@ export { installBroadcastClock } from './runtime/clock';
15
17
  export type { BroadcastNowProvider } from './runtime/clock';
16
18
  export { installAribSymbolFont } from './runtime/fonts';
17
19
  export { DEFAULT_BROADCAST_BASE_PATH, deriveBroadcastRootUrl, normalizeBroadcastBaseUrl, resolveBroadcastUrl, } from './broadcast-url';
18
- export { deferRomSoundMarkup, installRomSoundProtocol, resolveRomSoundUrl, } from './runtime/romsound';
20
+ export { deferRomSoundMarkup, installRomSoundProtocol, resolveRomSoundUrl, } from './runtime/romsound.js';
19
21
  export { prefixBroadcastRootAttributes, prepareBroadcastHtml, prepareBroadcastStylesheet, rewriteBroadcastObjectMarkup, } from './broadcast-document';
20
22
  export type { BroadcastDocumentOptions } from './broadcast-document';
21
23
  export { BroadcastVfsSession, ServiceWorkerBroadcastVfs } from './service-worker-vfs';
@@ -11,6 +11,11 @@ export type AribMediaPlaneStackEntry = {
11
11
  export type AribMediaPlaneLayer = {
12
12
  documentOrder: number;
13
13
  stackingPath: AribMediaPlaneStackEntry[];
14
+ /**
15
+ * External compositor fallback for a media-only foreground wrapper.
16
+ * Omitted/behind-application keeps the host surface below the iframe.
17
+ */
18
+ externalPlacement?: 'behind-application' | 'above-application';
14
19
  };
15
20
  export type AribMediaPlane = {
16
21
  slotId: string;
@@ -62,15 +67,22 @@ export declare class DomObjectMediaPlaneAdapter implements AribMediaPlaneAdapter
62
67
  export type BehindIframeMediaPlaneAdapterOptions = {
63
68
  surface: HTMLElement;
64
69
  keepVisible?: boolean;
70
+ /** Stacking level used for the ordinary media plane behind the iframe. */
71
+ behindZIndex?: string | number;
72
+ /** Stacking level used for a foreground media-only wrapper. */
73
+ aboveZIndex?: string | number;
65
74
  };
66
75
  /**
67
- * Simplified compatibility fallback. It cannot interleave media with elements
68
- * inside the application iframe; the complete iframe is always above it.
76
+ * Simplified iframe compositor. It normally keeps media behind the complete
77
+ * application iframe, but can raise a foreground media-only slot as a whole.
78
+ * It still cannot interleave that surface with individual elements in iframe.
69
79
  */
70
80
  export declare class BehindIframeMediaPlaneAdapter implements AribMediaPlaneAdapter {
71
81
  readonly renderMode: "external";
72
82
  private readonly surface;
73
83
  private readonly keepVisible;
84
+ private readonly behindZIndex;
85
+ private readonly aboveZIndex;
74
86
  constructor(options: BehindIframeMediaPlaneAdapterOptions);
75
87
  mountMediaPlane(_object: HTMLElement, plane: AribMediaPlane): void;
76
88
  updateMediaPlane(_object: HTMLElement, plane: AribMediaPlane): void;
@@ -1,4 +1,4 @@
1
- import { type RuntimeEvent, type RuntimeWindow } from './runtime/install';
1
+ import { type RuntimeApplicationInformation, type RuntimeEvent, type RuntimeWindow } from './runtime/install';
2
2
  import { type ProgramInfo } from './program-info';
3
3
  import type { ReceiverSystemInformationOverrides } from './runtime/system-information';
4
4
  import type { ReceiverDeviceIdentifierProvider } from './device-identifier';
@@ -13,6 +13,17 @@ export type AribCaptionPacket = {
13
13
  tmd?: string;
14
14
  data?: string;
15
15
  };
16
+ export type AribCaptionSubscription = {
17
+ componentTags: number[];
18
+ };
19
+ export type AribApplicationInformation = RuntimeApplicationInformation;
20
+ export type AribApplicationReplaceRequest = {
21
+ organizationId: number;
22
+ applicationId: number;
23
+ aitUrl: string | null;
24
+ };
25
+ export type AribApplicationReplaceHandler = (request: AribApplicationReplaceRequest) => void | Promise<void>;
26
+ export type AribExitManagedStateHandler = (url: string) => void | Promise<void>;
16
27
  export type AribBroadcastClock = {
17
28
  /** Absolute broadcast time at the anchor, as Unix epoch milliseconds. */
18
29
  epochMilliseconds: number;
@@ -52,6 +63,8 @@ export type AribReceiverHostOptions = {
52
63
  onStatus?: (status: string) => void;
53
64
  /** Machine-readable application/runtime lifecycle; never branch on onStatus text. */
54
65
  onLifecycle?: (event: AribReceiverLifecycleEvent) => void;
66
+ /** Time to wait for a loaded document to install the receiver runtime. 0 disables the fallback. */
67
+ applicationLoadTimeoutMs?: number;
55
68
  onUrlChange?: (url: string) => void;
56
69
  onMediaPlane?: (plane: AribMediaPlane) => void;
57
70
  /** @deprecated Use onMediaPlane. */
@@ -65,6 +78,12 @@ export type AribReceiverHostOptions = {
65
78
  resourceStore?: BroadcastResourceStore;
66
79
  /** Receiver/WebView bridge which opens the native EPG or a program detail page. */
67
80
  onOpenProgramGuide?: AribProgramGuideHandler;
81
+ /** Active caption components requested by the current broadcast document. */
82
+ onCaptionSubscription?: (subscription: AribCaptionSubscription) => void;
83
+ /** Resolve Application.replaceApplication() through the receiver's current MH-AIT. */
84
+ onReplaceApplication?: AribApplicationReplaceHandler;
85
+ /** Transfer an application to an unmanaged general-application URL. */
86
+ onExitManagedState?: AribExitManagedStateHandler;
68
87
  /** Override the default browser alert when the receiver cannot open the EPG. */
69
88
  onProgramGuideUnavailable?: AribProgramGuideUnavailableHandler;
70
89
  /** Receiver identity/capabilities exposed to the broadcast application. */
@@ -80,6 +99,7 @@ export declare class AribReceiverHost {
80
99
  private readonly origin;
81
100
  private readonly onStatus?;
82
101
  private readonly onLifecycle?;
102
+ private readonly applicationLoadTimeoutMs;
83
103
  private readonly onUrlChange?;
84
104
  private readonly onMediaPlane?;
85
105
  private readonly onVideoPlane?;
@@ -89,6 +109,9 @@ export declare class AribReceiverHost {
89
109
  private readonly broadcastBaseUrl;
90
110
  private readonly resourceStore?;
91
111
  private readonly onOpenProgramGuide?;
112
+ private readonly onCaptionSubscription?;
113
+ private readonly onReplaceApplication?;
114
+ private readonly onExitManagedState?;
92
115
  private readonly onProgramGuideUnavailable?;
93
116
  private readonly systemInformation?;
94
117
  private readonly getDeviceIdentifier?;
@@ -97,14 +120,19 @@ export declare class AribReceiverHost {
97
120
  private logicalWidth;
98
121
  private logicalHeight;
99
122
  private captionComponentTags;
123
+ private applicationInformation;
100
124
  private programInfo;
125
+ private readonly pendingStreamEvents;
101
126
  private broadcastClock;
102
127
  private video;
103
128
  private mediaPlaneEnabled;
104
129
  private applicationExited;
130
+ private applicationLoadTimer;
105
131
  private destroyed;
106
132
  constructor(options: AribReceiverHostOptions);
107
133
  installRuntime(target: RuntimeWindow): void;
134
+ /** Set the MH-AIT identity exposed by applicationManager.getOwnerApplication(). */
135
+ setApplicationInformation(application: AribApplicationInformation | null): void;
108
136
  /** @deprecated Pass an AribMediaPlaneAdapter to the constructor. */
109
137
  attachVideo(video: HTMLVideoElement): void;
110
138
  loadApplication(url: string, status?: string): void;
@@ -138,6 +166,9 @@ export declare class AribReceiverHost {
138
166
  private applyMediaPlane;
139
167
  private readMediaLayer;
140
168
  private postToRuntime;
169
+ private armApplicationLoadTimer;
170
+ private failApplicationLoad;
171
+ private clearApplicationLoadTimer;
141
172
  private invalidateRuntime;
142
173
  private fitBroadcastCanvas;
143
174
  private assertAlive;
@@ -4,14 +4,8 @@ import { type BroadcastResourceStore } from './resources';
4
4
  import { type ReceiverSystemInformationOverrides } from './system-information';
5
5
  import { type ReceiverDeviceIdentifierProvider } from '../device-identifier';
6
6
  export type { ProgramInfo } from '../program-info';
7
+ export type { RuntimeEvent, RuntimeEventSelector, RuntimeEventSource, } from './stream-event';
7
8
  export type RuntimeWindow = Window & typeof globalThis & Record<string, unknown>;
8
- export type RuntimeEvent = {
9
- source: {
10
- event_message_tag: number;
11
- };
12
- message_id: number;
13
- private_data_byte: string;
14
- };
15
9
  export type RuntimeOptions = {
16
10
  /** Permit HTTP requests outside the receiver-managed application origin. */
17
11
  allowExternalNetwork?: boolean;
@@ -27,5 +21,14 @@ export type RuntimeOptions = {
27
21
  systemInformation?: ReceiverSystemInformationOverrides;
28
22
  /** Resolve receiver/CAS identifiers by receiverDevice identifier kind. */
29
23
  getDeviceIdentifier?: ReceiverDeviceIdentifierProvider;
24
+ /** MH-AIT metadata for the application which owns this document. */
25
+ application?: RuntimeApplicationInformation;
26
+ };
27
+ export type RuntimeApplicationInformation = {
28
+ type?: string;
29
+ organizationId?: number;
30
+ applicationId?: number;
31
+ controlCode?: string;
32
+ autostartPriority?: number;
30
33
  };
31
34
  export declare function installRuntime(target: RuntimeWindow, options?: RuntimeOptions): void;
@@ -0,0 +1,13 @@
1
+ export type BroadcastMediaSlot = {
2
+ element: HTMLElement;
3
+ rect: DOMRect;
4
+ };
5
+ /**
6
+ * Resolve the receiver video slot represented by a broadcast object.
7
+ *
8
+ * Chromium gives an unsized `<object>` its replaced-element fallback size
9
+ * (normally 300 x 150). Broadcast applications also use an unsized object as
10
+ * the sole child of a positioned video-slot wrapper. In that case the wrapper,
11
+ * rather than the browser fallback box, is the receiver media plane.
12
+ */
13
+ export declare const resolveBroadcastMediaSlot: (object: HTMLElement) => BroadcastMediaSlot;
@@ -0,0 +1,21 @@
1
+ export type RuntimeEventSource = {
2
+ original_network_id?: number;
3
+ tlv_stream_id?: number;
4
+ service_id?: number;
5
+ event_message_tag: number;
6
+ };
7
+ export type RuntimeEvent = {
8
+ source: RuntimeEventSource;
9
+ message_group_id: number;
10
+ message_id: number;
11
+ message_version: number;
12
+ private_data_byte: string;
13
+ };
14
+ export type RuntimeEventSelector = {
15
+ source?: Partial<RuntimeEventSource>;
16
+ message_group_id?: number;
17
+ message_id?: number;
18
+ message_version?: number;
19
+ };
20
+ export declare function runtimeEventMatchesSelector(event: RuntimeEvent, selector: RuntimeEventSelector): boolean;
21
+ export declare function sameRuntimeEventSelector(first: RuntimeEventSelector, second: RuntimeEventSelector): boolean;
@@ -12,3 +12,15 @@ export type ReceiverSystemInformation = ReceiverSystemIdentity & {
12
12
  export type ReceiverSystemInformationOverrides = Partial<ReceiverSystemIdentity> & Record<string, unknown>;
13
13
  export declare const RECEIVER_SYSTEM_IDENTITY: Readonly<ReceiverSystemIdentity>;
14
14
  export declare function createReceiverSystemInformation(baseurl: string, overrides?: ReceiverSystemInformationOverrides): ReceiverSystemInformation;
15
+ /**
16
+ * Mirror receiver-owned regional data into compatibility keys used directly
17
+ * by broadcaster libraries. NHK's nhksh.getZipCode(), for example, reads the
18
+ * historical `_zipcode` localStorage key instead of receiverinfo NVRAM.
19
+ */
20
+ export declare function synchronizeReceiverCompatibilityStorage(storage: Pick<Storage, 'setItem' | 'removeItem'>, systemInformation: ReceiverSystemInformation): void;
21
+ /**
22
+ * Project receiver-owned regional settings into the legacy receiverinfo Ureg.
23
+ * The host remains the sole owner of persistence; broadcast applications only
24
+ * receive the snapshot supplied in systemInformation.
25
+ */
26
+ export declare function readReceiverInformationArray(namespace: string, structure: string, systemInformation: ReceiverSystemInformation): unknown[] | null;
@@ -38,12 +38,19 @@ export declare class BroadcastVfsSession {
38
38
  private drainScheduled;
39
39
  private ioTail;
40
40
  private sessionReady;
41
+ private disposed;
41
42
  constructor(backend: BroadcastVfsBackend, options?: BroadcastVfsSessionOptions);
42
43
  beginSession(): Promise<void>;
43
44
  enqueue(resource: BroadcastVfsResource): number;
44
45
  waitFor(revision: number): Promise<void>;
45
46
  /** Ensure a staged path is readable, replaying the page-owned mirror if needed. */
46
47
  ensure(path: string, revision?: number): Promise<void>;
48
+ /**
49
+ * Stop accepting resources, discard queued writes, and wait for the one
50
+ * backend operation which may already be running. The backend itself remains
51
+ * owned by the caller and can be reset or disposed after this resolves.
52
+ */
53
+ dispose(): Promise<void>;
47
54
  private appendIo;
48
55
  private scheduleDrain;
49
56
  private drainOne;
@@ -55,12 +62,23 @@ export declare class ServiceWorkerBroadcastVfs {
55
62
  private readonly uniqueBasenameFallback;
56
63
  private registration;
57
64
  private ready;
65
+ private readonly inFlight;
66
+ private disposing;
67
+ private disposed;
58
68
  constructor(options: ServiceWorkerBroadcastVfsOptions);
59
69
  initialize(): Promise<ServiceWorkerRegistration>;
60
70
  begin(): Promise<void>;
61
71
  put(resource: BroadcastVfsResource): Promise<void>;
62
72
  canRead(path: string): Promise<boolean>;
63
73
  reset(): Promise<void>;
74
+ /**
75
+ * Clear the receiver-owned VFS session after outstanding requests finish.
76
+ * The shared Service Worker registration is intentionally retained so other
77
+ * tabs and future receiver instances keep a stable scoped fetch handler.
78
+ */
79
+ dispose(): Promise<void>;
64
80
  private request;
81
+ private performRequest;
82
+ private performRequestWithRegistration;
65
83
  private waitForActivation;
66
84
  }
@@ -1,80 +1,17 @@
1
1
  # Android の「放送予定」と番組表連携
2
2
 
3
- ## Android ファームウェアで確認できた処理
3
+ ## 受信機所有の EPG へ渡す
4
4
 
5
- 「放送予定」は放送 HTML 内のページではなく、受信機が所有する EPG 機能です。
6
- SMB400 rev24.24 では次の経路になっています。
5
+ 「放送予定」は放送 HTML 内へ任意の番組表 URL を開く操作ではなく、受信機が所有する
6
+ 番組詳細・予約 UI へ番組識別子を渡す host operation として扱います。
7
7
 
8
- 1. `ProgramInfoProvider` がチューナーの `program_info.sqlite3` を検索用 DB へ同期する。
9
- 2. `ProgramInfoOpenHelper` が現在時刻と番組の開始・終了時刻を比較する。
10
- 放送中は `live`、開始前は `future` とし、future の検索タイトルへ
11
- `[放送予定]` を付ける。
12
- 3. Android TV の検索結果を選ぶと `SearchableProgramInfo` が
13
- `TvTunerService` を起動し、番組情報と `future` を渡す。
14
- 4. `TvTunerService` は future の場合に `StartMode.Reserve` を選び、別パッケージの
15
- `jp.pixela.atv_app.MainActivity` を起動する。
16
- 5. その Activity の WebView は次の内蔵ページを開く。
8
+ 現在時刻と番組の開始・終了時刻から `live` / `future` を判定し、放送種別、network ID、
9
+ service ID、event ID を receiver bridge へ渡します。受信機側は識別子を現在の EIT/EPG と
10
+ 照合してから、自アプリ内の画面を開いてください。放送 application から渡された URL を
11
+ そのまま WebView や外部 Activity へ転送してはいけません。
17
12
 
18
- ```text
19
- file:///android_asset/programList/index.html?ts=...
20
- #/?program_id=...&broadcast_type=...&service_id=...&event_id=...
21
- ```
22
-
23
- したがって、これは任意の外部 URL を開く機能ではありません。検索結果から受信機所有の
24
- 番組詳細・予約 UI へ移る、明示的なホスト機能です。
25
-
26
- ### future 判定と検索データ
27
-
28
- `ProgramInfoOpenHelper` の判定は単純な派生状態です。番組時刻は DB 内では秒単位で、
29
- 比較時にミリ秒へ変換されます。
30
-
31
- ```text
32
- now >= start + duration -> 検索結果に入れない
33
- start <= now < end -> live
34
- now < start -> future(最大 50 件)
35
- ```
36
-
37
- future の `suggest_text_1` は `[放送予定] 番組名` になります。同じ放送種別、
38
- service ID、event ID の有効な予約があれば `[予約済]` も追加されます。
39
- `suggest_intent_extra_data` には番組 JSON と `#future`、live の場合は `#live` が入り、
40
- クリック後の `TvTunerService` はこの suffix で遷移を分けます。
41
-
42
- Provider は次の標準 suggestion 列を公開します。
43
-
44
- - `suggest_text_1` / `suggest_text_2`
45
- - `suggest_result_card_image`(ジャンル別 320 x 180 画像)
46
- - `suggest_is_live`
47
- - `suggest_intent_extra_data`
48
- - `suggest_intent_data_id`
49
-
50
- Katniss 側のローカル suggestion 変換では、タイトル、説明、カード画像、intent extra data
51
- をそのまま読み、`suggest_is_live` 自体では分岐していません。したがって、この機種で
52
- future を決定している本体は `ProgramInfoOpenHelper` と `#future` の受け側です。
53
-
54
- また、APK の日本語リソースに存在する原文は `[放送予定]` で、括弧なしの
55
- `放送予定` という独立した文字列リソースは見つかりません。スクリーンショットの括弧なし
56
- 表示は検索 UI 側のプレゼンテーションであり、放送 HTML やフォントが生成する状態では
57
- ありません。
58
-
59
- ### EPG の保存と更新
60
-
61
- 検索時に放送波を直接スキャンしているわけではありません。ネイティブチューナーが持つ
62
- `program_info.sqlite3` の更新時刻を監視し、検索用の `program_info.db` へコピーします。
63
- 検索要求ごとに一時 `suggest_content.db` を作り直し、番組名・説明とチャンネル名を検索します。
64
-
65
- バックグラウンド更新は起動後 5 分、その後 30 分ごとに判定されます。EPG スキャンを
66
- 実行する JST の時間帯はコード上で次の二つです。
67
-
68
- - 04:38–07:37
69
- - 16:38–19:37
70
-
71
- 更新サービスは `StartMode.UpdateEPG` でチューナーを初期化し、EPG cache target `0` を
72
- 更新して最大 120 秒待ちます。録画対応機では録画コンテンツ target `5` と予約 target `6`
73
- も更新します。テレビアプリが前面へ出た場合、バックグラウンド更新は中止されます。
74
-
75
- なお、ARIB STD-B62 Volume 2 には `receiverDevice.scheduleToTune()` などの
76
- 視聴・録画予約 API もありますが、上記の Android 検索結果から番組詳細を開く処理とは
77
- 別のインターフェースです。
13
+ ARIB STD-B62 Volume 2 の `receiverDevice.scheduleToTune()` 等の視聴・録画予約 API と、
14
+ この host 固有の番組表画面を開く操作は別のインターフェースです。
78
15
 
79
16
  ## libaribhtml5 のホストインターフェース
80
17
 
@@ -0,0 +1,95 @@
1
+ # MH-AIT アプリケーションライフサイクル
2
+
3
+ この文書は、ARIB HTML5 データ放送の自動起動、表示用アプリケーションへの切替、終了、
4
+ および EMT の責務境界を receiver host 向けにまとめます。放送局固有の URL や EMT ID を
5
+ プレーヤーへハードコードしないための実装条件です。
6
+
7
+ ## 制御の所有者
8
+
9
+ アプリケーションの起動と終了は MH-AIT の application control code が制御します。
10
+
11
+ | control code | 値 | receiver の動作 |
12
+ | --- | ---: | --- |
13
+ | `AUTOSTART` | `0x01` | entry resource が利用可能になった時点で自動起動する |
14
+ | `PRESENT` | `0x02` | 実行可能として保持する。新しいページを自動起動しない |
15
+ | `KILL` | `0x04` | 対象の managed application を直ちに終了する |
16
+ | `PREFETCH` | `0x05` | resource を取得・保持する。表示や起動は行わない |
17
+
18
+ 根拠は ARIB STD-B60 1.5版 10.3.2.1 表10-8の application control code と、同規格
19
+ 10.3.3.1 の MH-application descriptor です。
20
+
21
+ `AUTOSTART` は起動を指示する control code であり、CSS の表示状態を指示しません。
22
+ ユーザーへの可視性は別フィールドである MH-application descriptor の `visibility` で決まります。
23
+
24
+ | visibility | receiver の動作 |
25
+ | ---: | --- |
26
+ | `00` | エラー報告等を除き、ユーザーと他 application の双方へ不可視 |
27
+ | `01` | ユーザーへ不可視。他 application からは API 等を介して可視 |
28
+ | `10` | reserved |
29
+ | `11` | ユーザーと他 application の双方へ可視 |
30
+
31
+ `00` / `01` でも document、timer、通信、EMT listener は実行を続けます。iframe を `display:none`
32
+ にして実行を止めず、receiver compositor の application plane だけをユーザーへ合成しません。
33
+ `11` の AUTOSTART は通常どおり画面を描画できます。
34
+
35
+ 別 application へ切り替える必要がある場合、実行中のページが
36
+ `navigator.applicationManager.getOwnerApplication().replaceApplication(...)` を呼びます。
37
+
38
+ ## replaceApplication
39
+
40
+ ARIB STD-B62 第二編 3.3.7–3.3.8 は `ApplicationManager` と `Application` を定義します。
41
+ `replaceApplication(organization_id, application_id, ait_url)` は現在の application を終了し、
42
+ 指定した application を起動する受信機要求です。EMT を受信した host が URL を推測する機能ではありません。
43
+
44
+ `libaribhtml5` はこの要求を `AribReceiverHostOptions.onReplaceApplication` へ渡します。
45
+ host は現在の MH-AIT から `(organizationId, applicationId)` を検索し、entry resource の VFS commit を
46
+ 待ってから、同じ `AribReceiverHost` / iframe で `loadApplication()` を実行してください。
47
+
48
+ ```ts
49
+ const host = new AribReceiverHost({
50
+ iframe,
51
+ viewport,
52
+ onReplaceApplication: async request => {
53
+ const target = currentApplications.get(
54
+ `${request.organizationId}:${request.applicationId}`,
55
+ )
56
+ if (!target) throw new Error('Application is not present in the current MH-AIT')
57
+ await vfs.waitFor(target.entryRevision)
58
+ host.setApplicationInformation(target.information)
59
+ host.loadApplication(target.entryUrl, 'アプリケーション切替中')
60
+ },
61
+ })
62
+ ```
63
+
64
+ `exitFromManagedState(url)` は放送管理外の general application へ移る別の操作です。
65
+ 許可境界と外部 network policy を receiver 側で検証し、対応する場合だけ
66
+ `onExitManagedState` で処理します。
67
+
68
+ ## AUTOSTART と PRESENT の同居
69
+
70
+ 一つの tuning/session generation につき managed application slot は一つにします。
71
+
72
+ 1. active application がない時だけ、ready になった `AUTOSTART` を読み込む。
73
+ 2. `PRESENT` は data button 用の候補として保持するが、それだけで iframe を reload しない。
74
+ 3. 同じ application が AUTOSTART から PRESENT へ更新され、継続条件を満たす場合は既存 runtime を再利用する。
75
+ 4. `KILL`、AIT からの消失、AIT location descriptor の消失、tune/session reset で runtime と listener を破棄する。
76
+ 5. 404 または runtime bootstrap 不成立が確定した時点で直ちに破棄する。固定の再試行待ち時間中、
77
+ 壊れたページを表示し続けない。
78
+
79
+ application state、resource revision、EMT listener、version deduplication はすべて tuning generation に
80
+ 所属させます。前チャンネルの非同期完了や event を新しい iframe へ渡してはいけません。
81
+
82
+ ## EMT は起動コマンドではない
83
+
84
+ ARIB STD-B60 1.5版 11章の Event Message Table は、実行中 application の登録済み selector へ
85
+ event message を指定時刻に配送する仕組みです。規格には即時、UTC、NPT、相対時刻等の time mode が
86
+ あります。現在の runtime が保証するのは即時配送 (`time_mode=0`) だけであり、その他の ignition time は
87
+ receiver clock scheduler を実装するまで未対応です。
88
+
89
+ - EMT `40/5` 等を見て host が表示ページ URL を決めない。
90
+ - application が listener を登録する前に到着した event を、起動後に独自再生しない。
91
+ - 同じ message ID/version は重複配送しない。
92
+ - listener 登録後に受信した、または放送側が再送した event だけを application へ渡す。
93
+
94
+ したがって紅白等の連動ページは、MH-AIT による AUTOSTART、ページ内 listener 登録、EMT 配送、
95
+ ページによる `replaceApplication()` の順に進みます。
@@ -0,0 +1,106 @@
1
+ # ブラウザ iframe 合成の注意事項
2
+
3
+ この文書は、通常ブラウザでデータ放送 iframe と既存プレーヤーの `<video>` を合成する際の
4
+ ブラウザ固有の制約をまとめます。ARIB のプレーン順序そのものは
5
+ [メディアプレーン統合ガイド](media-plane-integration.md)を参照してください。
6
+
7
+ ## 必須のレイヤー構成
8
+
9
+ 外部映像面を使う場合は、同じ 16:9 container の sibling として次の順に置きます。
10
+
11
+ ```text
12
+ z=0 receiver-owned background plane
13
+ z=1 プレーヤーが所有する既存 video
14
+ z=2 データ放送 iframe
15
+ ```
16
+
17
+ video の複製や canvas への 1:1 コピーは行いません。MSE に接続済みのプレーヤー所有 video を
18
+ media object の矩形へ移動・リサイズして使います。video を iframe より前へ上げると、字幕説明、
19
+ ダイアログ、字幕ボタンなど本来映像より前にある application UI まで隠れるため禁止です。
20
+
21
+ iframe 内の `object[type="video/x-arib2-broadcast"]` は映像そのものではなく、外部 video plane の
22
+ 位置・寸法・可視状態を宣言する geometry anchor として残します。external adapter は object の描画を
23
+ `opacity: 0 !important` で隠します。さらに `#vstream > object#av` のように、object と同じ矩形で
24
+ object だけを子に持つ media-only wrapper は wrapper 自身も隠します。wrapper が高い `z-index` の
25
+ 独立した compositing layer を作る場合、背景だけを透明化しても Chromium が外部 video の上へその layer を
26
+ 残すためです。字幕、ダイアログ、操作 UI を含む祖先は media-only wrapper として扱いません。
27
+
28
+ 放送ページが object 自身に `width` / `height` を指定せず、positioned wrapper だけに映像枠の寸法を
29
+ 指定する場合があります。通常ブラウザはその object を 300 x 150 の既定 replaced-element として計測しますが、
30
+ receiver runtime は object が wrapper の唯一の子である場合に限り wrapper を media slot として採用します。
31
+ この wrapper は外部合成中だけ object と一緒に透明化されるため、放送ページが指定した高い `z-index` を
32
+ 削除したり、host の video を iframe より前へ出したりする必要はありません。
33
+
34
+ ただし、異なる寸法の media-only wrapper が正の `z-index` を持つ場合、その video slot はページ背景の
35
+ 前に置くことを意図した foreground media です。runtime は `layer.externalPlacement` を
36
+ `above-application` として報告します。external adapter はこの場合だけ media surface を application
37
+ iframe より前の layer に置きます。標準の `BehindIframeMediaPlaneAdapter` は通常 `z=0`、この場合だけ
38
+ `z=2` を使います(必要なら `behindZIndex` / `aboveZIndex` で host の stacking context に合わせます)。
39
+ 通常の全面映像や application background として使われる slot は `behind-application` のままです。
40
+
41
+ `display: none`、`visibility: hidden`、`content-visibility: hidden` は矩形計測または可視判定を壊すため
42
+ 使用しません。runtime は隠す前の computed opacity を media-plane report に使い、host 側の video は
43
+ object の CSS を複製せず、報告された `getBoundingClientRect()` を logical viewport に対する割合へ
44
+ 変換して配置します。
45
+
46
+ ## Chromium の白い iframe backdrop
47
+
48
+ Chromium は iframe owner と iframe 内 root の used `color-scheme` が異なる場合、透明 iframe の
49
+ 代わりに scheme に対応する不透明 backdrop を描画します。dark theme の host に、標準の light
50
+ canvas を使う放送ページを埋め込むと白になります。
51
+
52
+ この白は DOM の背景ではないため、次をすべて確認しても残ります。
53
+
54
+ - iframe、`html`、`body` の computed `background-color` が透明
55
+ - 背景画像と疑似要素がない
56
+ - iframe の `opacity` や `z-index` を変更した
57
+
58
+ この判定を無効化する公開 feature flag はありません。receiver が管理する iframe owner と
59
+ 放送ページ root の双方に同じ scheme を明示します。
60
+
61
+ ```ts
62
+ iframe.style.colorScheme = 'only light'
63
+ iframe.contentDocument.documentElement.style.setProperty('color-scheme', 'only light', 'important')
64
+ ```
65
+
66
+ これは clickjacking/XSS 対策による透明 iframe の禁止ではありません。Blink は owner と embedded
67
+ document の scheme が異なる場合に canvas backdrop を意図的に描画しています。
68
+
69
+ ## application background と media hole
70
+
71
+ external adapter は video を iframe の背後に置くため、application canvas の `html` / `body` を
72
+ 透明化します。ただし元の stage 背景色まで捨てると、映像領域外に host アプリの背景が露出します。
73
+ runtime は透明化前の stage 色を host へ報告し、host はその色を z=0 の receiver background plane
74
+ へ置きます。`body` が白でも `#backscreen` のような不透明要素が application canvas 全体を覆う場合は、
75
+ その全画布要素を実際の stage 色として優先します。色を取得できない間の実装 fallback は黒です。
76
+
77
+ iframe 内では、media object 自身を透明にしても、その祖先や object より前にある装飾用背景要素が
78
+ 同じ矩形を塗ると外部 video を隠します。external runtime は次の背景だけを一時的に透明化します。
79
+
80
+ - media object の不透明な祖先背景
81
+ - media object の矩形全体を覆う application element の不透明な背景
82
+
83
+ 要素自体は削除せず、`background-color` と `background-image` だけを退避して透明化します。
84
+ media object が消える、ページが遷移する、または application が終了すると元の inline style を
85
+ 復元します。要素内の文字、画像、字幕、ダイアログ、操作 UI は削除せず、透明な debug overlay
86
+ も変更しません。media slot 全体を覆わないボタンやパネルの背景も対象外です。
87
+
88
+ runtime が一度透明化した背景要素は、media slot を覆っている間は管理対象として保持します。
89
+ 透明化後の computed style だけを見て対象外にすると、MutationObserver の各走査で
90
+ 「透明化 → 元へ復元」を繰り返し、映像面と application 背景が点滅します。
91
+
92
+ ## DevTools での切り分け
93
+
94
+ 白い全面シートでは、まず owner/root の scheme を確認します。
95
+
96
+ ```js
97
+ const frame = document.querySelector('iframe.dplayer-tlv-data-broadcast')
98
+ console.table({
99
+ owner: getComputedStyle(frame).colorScheme,
100
+ root: getComputedStyle(frame.contentDocument.documentElement).colorScheme,
101
+ })
102
+ ```
103
+
104
+ 黒い media slot では、video の `readyState` と矩形を確認した後、iframe 内で media object より前に
105
+ ある covering element の背景を確認します。DevTools の青い半透明表示は選択要素の highlight で、
106
+ 実際の video frame や application background ではありません。