libaribhtml5 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +124 -0
- package/THIRD_PARTY_NOTICES.md +24 -0
- package/dist/sdk/arib-vfs-sw.js +336 -0
- package/dist/sdk/libaribhtml5.js +11 -0
- package/dist/sdk/libaribhtml5.mjs +1353 -0
- package/dist/types/broadcast-document.d.ts +12 -0
- package/dist/types/broadcast-url.d.ts +9 -0
- package/dist/types/device-identifier.d.ts +4 -0
- package/dist/types/index.d.ts +23 -0
- package/dist/types/media-plane.d.ts +79 -0
- package/dist/types/program-guide.d.ts +25 -0
- package/dist/types/program-info.d.ts +29 -0
- package/dist/types/receiver-host.d.ts +144 -0
- package/dist/types/runtime/clock.d.ts +5 -0
- package/dist/types/runtime/fonts.d.ts +4 -0
- package/dist/types/runtime/install.d.ts +31 -0
- package/dist/types/runtime/resources.d.ts +22 -0
- package/dist/types/runtime/romsound-markup.d.ts +5 -0
- package/dist/types/runtime/romsound.d.ts +4 -0
- package/dist/types/runtime/system-information.d.ts +14 -0
- package/dist/types/service-worker-vfs.d.ts +66 -0
- package/docs/android-program-guide.md +127 -0
- package/docs/broadcast-clock.md +57 -0
- package/docs/broadcast-resources-and-fonts.md +143 -0
- package/docs/media-plane-integration.md +227 -0
- package/docs/receiver-host-contracts.md +92 -0
- package/package.json +69 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# 録画再生時の放送時計
|
|
2
|
+
|
|
3
|
+
データ放送アプリケーションは現在時刻を `new Date()` / `Date.now()` で取得します。
|
|
4
|
+
録画再生でブラウザのシステム時計をそのまま返すと、録画当時ではなく再生日の日時が表示されます。
|
|
5
|
+
|
|
6
|
+
`AribReceiverHost.setBroadcastClock()` へ、放送時刻とメディア時刻の対応点を渡してください。
|
|
7
|
+
runtime は子 iframe に限って、引数なしの `new Date()`、`Date()`、`Date.now()` をこの時計へ
|
|
8
|
+
置き換えます。`new Date(value)`、`Date.parse()`、`Date.UTC()` は通常の JavaScript と同じです。
|
|
9
|
+
|
|
10
|
+
## tlvdemux WASM との接続
|
|
11
|
+
|
|
12
|
+
tlvdemux の `onBroadcastClock` は NTP epoch の絶対時刻と、それに対応するメディア時刻を返します。
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
const NTP_UNIX_EPOCH_SECONDS = 2208988800;
|
|
16
|
+
|
|
17
|
+
onBroadcastClock(clock) {
|
|
18
|
+
const epochMilliseconds =
|
|
19
|
+
Number(clock.broadcastTimeValue) * 1000 /
|
|
20
|
+
Number(clock.broadcastTimeTimescale) -
|
|
21
|
+
NTP_UNIX_EPOCH_SECONDS * 1000;
|
|
22
|
+
|
|
23
|
+
const mediaTimeSeconds =
|
|
24
|
+
Number(clock.mediaTimeValue) / Number(clock.mediaTimeTimescale);
|
|
25
|
+
|
|
26
|
+
host.setBroadcastClock({
|
|
27
|
+
epochMilliseconds,
|
|
28
|
+
mediaTimeSeconds,
|
|
29
|
+
currentMediaTimeSeconds: () => video.currentTime,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
runtime が返す時刻は次の式になります。
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
録画内の現在時刻
|
|
38
|
+
= epochMilliseconds
|
|
39
|
+
+ (video.currentTime - mediaTimeSeconds) * 1000
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
このため、pause 中は時計も停止し、倍速再生では同じ倍率で進み、seek 後は移動先の放送時刻へ
|
|
43
|
+
追従します。新しい入力またはチャンネルへ切り替える時は `host.clearBroadcastClock()` を呼びます。
|
|
44
|
+
|
|
45
|
+
NTP の値がまだ得られていない場合、`getBroadcastTime()` と子 iframe の Date はシステム時刻へ
|
|
46
|
+
フォールバックします。録画ファイルの更新日やファイル名から日時を推測しないでください。
|
|
47
|
+
|
|
48
|
+
## 絶対時刻だけを持つ場合
|
|
49
|
+
|
|
50
|
+
メディア timeline がない入力では、受信した絶対時刻から通常速度で進む時計も設定できます。
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
host.setBroadcastTime(new Date('2026-07-01T12:00:00+09:00'))
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
この形式は pause、再生倍率、seek には追従しません。録画プレーヤーでは
|
|
57
|
+
`setBroadcastClock()` と `currentMediaTimeSeconds` を使用してください。
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Broadcast resources, Worker routing, and fonts
|
|
2
|
+
|
|
3
|
+
## URL namespace
|
|
4
|
+
|
|
5
|
+
`AribReceiverHost` mounts receiver paths below `/data-broadcast/` by default:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
const host = new AribReceiverHost({
|
|
9
|
+
iframe,
|
|
10
|
+
viewport,
|
|
11
|
+
broadcastBaseUrl: '/data-broadcast/',
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
host.loadApplication('/sh4/40/001/startup/html/index.html')
|
|
15
|
+
// iframe: /data-broadcast/sh4/40/001/startup/html/index.html
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The base must be same-origin and end-user code may change it. The runtime
|
|
19
|
+
reports this stable scope as `receiverDevice.getSystemInformation().baseurl`.
|
|
20
|
+
Broadcaster startup libraries may append a selected mount such as `sh4`
|
|
21
|
+
themselves. Separately, HTML/CSS/runtime URL mapping derives the active mount
|
|
22
|
+
from the current document when resolving root-relative paths such as `/60/...`.
|
|
23
|
+
|
|
24
|
+
A production server or Service Worker should route only this prefix to its
|
|
25
|
+
carousel/VFS and remove the prefix before looking up the broadcast path:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
/data-broadcast/sh4/60/001/image/logo.png
|
|
29
|
+
-> /sh4/60/001/image/logo.png in the carousel
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Keeping the prefix makes Worker scope and receiver-owned HTTP resources
|
|
33
|
+
explicit. `subt://` caption resources and `romsound://` receiver sounds are not
|
|
34
|
+
HTTP URLs and must not be rewritten into this namespace.
|
|
35
|
+
|
|
36
|
+
## Optional browser VFS
|
|
37
|
+
|
|
38
|
+
The SDK build emits `arib-vfs-sw.js` and exports a matching client. The demuxer
|
|
39
|
+
or carousel owner feeds complete resources; application/context selection stays
|
|
40
|
+
outside the receiver runtime:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
const backend = new ServiceWorkerBroadcastVfs({
|
|
44
|
+
workerUrl: '/receiver/arib-vfs-sw.js',
|
|
45
|
+
baseUrl: '/data-broadcast/',
|
|
46
|
+
})
|
|
47
|
+
const vfs = new BroadcastVfsSession(backend)
|
|
48
|
+
|
|
49
|
+
await vfs.beginSession()
|
|
50
|
+
const revision = vfs.enqueue({ path, contentType, data })
|
|
51
|
+
await vfs.waitFor(revision)
|
|
52
|
+
await vfs.ensure(entry)
|
|
53
|
+
host.loadApplication(entry)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`enqueue()` copies a callback-lifetime `Uint8Array` synchronously. Writes are
|
|
57
|
+
ordered in the background and its returned revision is an application launch
|
|
58
|
+
barrier. `ensure()` probes the Worker and, if its memory was reclaimed, begins
|
|
59
|
+
a fresh Worker session and replays the page-owned resource mirror. Demuxer
|
|
60
|
+
context selection and the `contextId -> revision` mapping stay in the player.
|
|
61
|
+
|
|
62
|
+
`ServiceWorkerBroadcastVfs.begin()`, `put()`, and `canRead()` remain available
|
|
63
|
+
as low-level backend operations for hosts which already own these guarantees.
|
|
64
|
+
|
|
65
|
+
The server which hosts the Worker script must permit the chosen scope. When the
|
|
66
|
+
script itself is outside `/data-broadcast/`, return this response header:
|
|
67
|
+
|
|
68
|
+
```http
|
|
69
|
+
Service-Worker-Allowed: /data-broadcast/
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The main player page may intentionally live outside the Worker scope. VFS
|
|
73
|
+
mutations and probes therefore use direct messages to the scoped active Worker;
|
|
74
|
+
only the data-broadcast iframe's HTTP requests are intercepted. Cache Storage
|
|
75
|
+
preserves the active resource session if the browser reclaims or replaces the
|
|
76
|
+
Worker.
|
|
77
|
+
|
|
78
|
+
HTML and CSS preparation is shared by the development middleware and Worker.
|
|
79
|
+
`prepareBroadcastHtml()` handles the receiver object marker, ROM-sound deferral,
|
|
80
|
+
root-relative URL mapping, and runtime bootstrap. `prepareBroadcastStylesheet()`
|
|
81
|
+
maps root-relative CSS resources into the same namespace.
|
|
82
|
+
|
|
83
|
+
## Stored-resource lifecycle
|
|
84
|
+
|
|
85
|
+
`navigator.receiverDevice.cacheEvent.storeDataResource()` now waits for the
|
|
86
|
+
complete response before dispatching `store_finished`. A failed response emits
|
|
87
|
+
`store_failed`; `releaseDataResource()` aborts an in-flight request. Page exit
|
|
88
|
+
releases every retained resource.
|
|
89
|
+
|
|
90
|
+
The default implementation fetches from the broadcast document, which lets a
|
|
91
|
+
Service Worker controlling `/data-broadcast/` handle and cache the request. A
|
|
92
|
+
native carousel or another VFS may instead provide `resourceStore`:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const host = new AribReceiverHost({
|
|
96
|
+
iframe,
|
|
97
|
+
viewport,
|
|
98
|
+
resourceStore: {
|
|
99
|
+
async store(url, { signal }) {
|
|
100
|
+
await carousel.retain(url, signal)
|
|
101
|
+
},
|
|
102
|
+
release(url) {
|
|
103
|
+
return carousel.release(url)
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
When a carousel module changes, notify the active application. An update is
|
|
110
|
+
downloaded again before its listener receives `updated`; deletion aborts and
|
|
111
|
+
releases it before the listener receives `deleted`.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
host.notifyDataResource('/sh4/60/001/font/service.woff', 'updated')
|
|
115
|
+
host.notifyDataResource('/sh4/60/001/font/service.woff', 'deleted')
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
CSS fonts referenced by a d-data page are ordinary broadcast HTTP resources
|
|
119
|
+
and therefore follow the Worker route. A font explicitly retained through
|
|
120
|
+
`storeDataResource()` additionally follows the lifecycle above. TTML
|
|
121
|
+
`subt://n` fonts remain part of the caption data pipeline.
|
|
122
|
+
|
|
123
|
+
## Receiver symbol font
|
|
124
|
+
|
|
125
|
+
`src/runtime/fonts/arib-symbols.svg` is the auditable SVG Font source for the
|
|
126
|
+
television-symbol block `U+1F19B..U+1F1AC`, including `U+1F19E SQUARED FOUR K`
|
|
127
|
+
(`🆞`). They are a minimal subset of the OFL-licensed Noto Sans Symbols font
|
|
128
|
+
used by the reference receiver. See `src/runtime/fonts/NOTICE.md` and
|
|
129
|
+
`src/runtime/fonts/OFL.txt`; no commercial font outline is included.
|
|
130
|
+
|
|
131
|
+
Modern browsers no longer reliably render SVG Fonts, so the same script emits
|
|
132
|
+
`arib-symbols.woff` for runtime use. The runtime registers the sparse font only
|
|
133
|
+
for its 18 code points under `ARIB Symbols` and the ARIB generic family names
|
|
134
|
+
`丸ゴシック`, `太丸ゴシック`, and `角ゴシック`. Ordinary Japanese glyphs continue
|
|
135
|
+
through the page's next fallback font. Dynamic DRCS and broadcaster-delivered
|
|
136
|
+
SVG/WOFF fonts stay separate from this static receiver font.
|
|
137
|
+
|
|
138
|
+
Regenerate both assets with:
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
nix-shell -p python313Packages.fonttools --run \
|
|
142
|
+
'python scripts/build-arib-symbol-font.py /path/to/NotoSansSymbols.ttf'
|
|
143
|
+
```
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# メディアスロットの使い方
|
|
2
|
+
|
|
3
|
+
## まず結論
|
|
4
|
+
|
|
5
|
+
通常は次のどれか一つを選びます。
|
|
6
|
+
|
|
7
|
+
| 用途 | 選ぶもの | できる重なり順 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| 通常ブラウザの demo / Web プレーヤー | `DomObjectMediaPlaneAdapter` | アプリ背景 `<` 映像 `<` アプリ UI |
|
|
10
|
+
| ネイティブ Surface / plugin / compositor | 独自 `AribMediaPlaneAdapter` | compositor の能力に依存 |
|
|
11
|
+
| 既存コードを当面維持 | `BehindIframeMediaPlaneAdapter` | 映像 `<` iframe 全体のみ |
|
|
12
|
+
|
|
13
|
+
caption ページを正しく表示するなら、1 番目または 2 番目が必要です。
|
|
14
|
+
`BehindIframeMediaPlaneAdapter` では実現できません。
|
|
15
|
+
|
|
16
|
+
## ブラウザへ組み込む最小コード
|
|
17
|
+
|
|
18
|
+
普通の動画表示領域とデータ放送 iframe を同じ viewport に置きます。
|
|
19
|
+
|
|
20
|
+
```html
|
|
21
|
+
<div id="viewport">
|
|
22
|
+
<div id="normal-player">
|
|
23
|
+
<video id="video" autoplay muted playsinline></video>
|
|
24
|
+
</div>
|
|
25
|
+
<iframe id="broadcast" sandbox="allow-scripts allow-same-origin"></iframe>
|
|
26
|
+
</div>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`DomObjectMediaPlaneAdapter` に既存の video と、データ放送を終了した時に video を戻す
|
|
30
|
+
通常プレーヤー container を渡します。
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import {
|
|
34
|
+
AribReceiverHost,
|
|
35
|
+
DomObjectMediaPlaneAdapter,
|
|
36
|
+
type RuntimeWindow,
|
|
37
|
+
} from 'libaribhtml5'
|
|
38
|
+
|
|
39
|
+
const iframe = document.querySelector<HTMLIFrameElement>('#broadcast')!
|
|
40
|
+
const viewport = document.querySelector<HTMLElement>('#viewport')!
|
|
41
|
+
const normalPlayer = document.querySelector<HTMLElement>('#normal-player')!
|
|
42
|
+
const video = document.querySelector<HTMLVideoElement>('#video')!
|
|
43
|
+
|
|
44
|
+
const host = new AribReceiverHost({
|
|
45
|
+
iframe,
|
|
46
|
+
viewport,
|
|
47
|
+
mediaPlaneAdapter: new DomObjectMediaPlaneAdapter({
|
|
48
|
+
media: video,
|
|
49
|
+
normalPlayerContainer: normalPlayer,
|
|
50
|
+
}),
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
window.__ARIB_HTML5_INSTALL__ = (child: RuntimeWindow) => {
|
|
54
|
+
host.installRuntime(child)
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
データ放送への出入りは次の二つだけです。
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// データ放送へ入る。video は放送 object の中へ移動する。
|
|
62
|
+
// The receiver path is mounted at /data-broadcast/sh4/... by default.
|
|
63
|
+
host.loadApplication('/sh4/40/001/startup/html/index.html')
|
|
64
|
+
|
|
65
|
+
// データ放送を終了する。iframe を閉じ、video を通常プレーヤーへ戻して表示する。
|
|
66
|
+
host.exitApplication()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
ページ内の「戻る」はアプリケーション内遷移です。`exitApplication()` ではありません。
|
|
70
|
+
テレビ UI の「データ放送終了」操作、チャンネル変更、または
|
|
71
|
+
`applicationManager.destroyApplication()` を受けた時だけ `exitApplication()` を呼びます。
|
|
72
|
+
|
|
73
|
+
## 実行時に何が起きるか
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
通常プレーヤー
|
|
77
|
+
host.loadApplication()
|
|
78
|
+
↓
|
|
79
|
+
runtime が broadcast object を検出
|
|
80
|
+
mountMediaPlane(object, plane)
|
|
81
|
+
↓
|
|
82
|
+
データ放送ページ内で object が移動・非表示
|
|
83
|
+
updateMediaPlane(object, plane)
|
|
84
|
+
↓
|
|
85
|
+
別のデータ放送 document へ遷移
|
|
86
|
+
unmountMediaPlane('document-unload') → 次ページで mount
|
|
87
|
+
↓
|
|
88
|
+
host.exitApplication()
|
|
89
|
+
unmountMediaPlane('application-exit')
|
|
90
|
+
↓
|
|
91
|
+
通常プレーヤーへ video / Surface を戻す
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`DomObjectMediaPlaneAdapter` は `document-unload` では video を通常 container へ一時退避して
|
|
95
|
+
非表示にします。`application-exit` の時だけ通常 container を全画面表示します。この区別に
|
|
96
|
+
より、データ放送のページ遷移中に普通のプレーヤー UI が点滅しません。
|
|
97
|
+
|
|
98
|
+
## 独自 adapter を書く
|
|
99
|
+
|
|
100
|
+
ネイティブ Surface や compositor を使う場合の最小骨格です。
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import type {
|
|
104
|
+
AribMediaPlane,
|
|
105
|
+
AribMediaPlaneAdapter,
|
|
106
|
+
AribMediaPlaneUnmountReason,
|
|
107
|
+
} from 'libaribhtml5'
|
|
108
|
+
|
|
109
|
+
class NativeSurfaceAdapter implements AribMediaPlaneAdapter {
|
|
110
|
+
readonly renderMode = 'external' as const
|
|
111
|
+
|
|
112
|
+
mountMediaPlane(object: HTMLElement, plane: AribMediaPlane): void {
|
|
113
|
+
nativeSurface.bindBroadcastSource(plane.videoSource, plane.audioSource)
|
|
114
|
+
this.updateMediaPlane(object, plane)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
updateMediaPlane(_object: HTMLElement, plane: AribMediaPlane): void {
|
|
118
|
+
nativeSurface.setVisible(plane.visible)
|
|
119
|
+
nativeSurface.setLogicalCanvas(plane.screenWidth, plane.screenHeight)
|
|
120
|
+
nativeSurface.setRect(plane.x, plane.y, plane.width, plane.height)
|
|
121
|
+
nativeSurface.setApplicationLayer(plane.layer)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
unmountMediaPlane(reason: AribMediaPlaneUnmountReason): void {
|
|
125
|
+
if (reason === 'application-exit') {
|
|
126
|
+
nativeSurface.returnToNormalPlayer()
|
|
127
|
+
} else if (reason === 'host-destroy') {
|
|
128
|
+
nativeSurface.release()
|
|
129
|
+
} else {
|
|
130
|
+
nativeSurface.hide()
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`unmountMediaPlane` の reason は次の意味です。
|
|
137
|
+
|
|
138
|
+
| reason | adapter の動作 |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| `slot-removed` | decoder は維持し、Surface を隠す |
|
|
141
|
+
| `document-unload` | 次のデータ放送ページに備えて一時退避する |
|
|
142
|
+
| `application-exit` | 通常プレーヤーへ映像を戻して表示する |
|
|
143
|
+
| `host-destroy` | listener、Surface、decoder など所有資源を解放する |
|
|
144
|
+
|
|
145
|
+
各メソッドは同じ状態で複数回呼ばれても壊れないように実装してください。
|
|
146
|
+
|
|
147
|
+
## `AribMediaPlane` の読み方
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
type AribMediaPlane = {
|
|
151
|
+
slotId: string
|
|
152
|
+
visible: boolean
|
|
153
|
+
x: number
|
|
154
|
+
y: number
|
|
155
|
+
width: number
|
|
156
|
+
height: number
|
|
157
|
+
screenWidth: number
|
|
158
|
+
screenHeight: number
|
|
159
|
+
videoSource?: string
|
|
160
|
+
audioSource?: string
|
|
161
|
+
layer: {
|
|
162
|
+
documentOrder: number
|
|
163
|
+
stackingPath: Array<{
|
|
164
|
+
tagName: string
|
|
165
|
+
id?: string
|
|
166
|
+
position: string
|
|
167
|
+
zIndex: string
|
|
168
|
+
display: string
|
|
169
|
+
visibility: string
|
|
170
|
+
opacity: number
|
|
171
|
+
transform: string
|
|
172
|
+
}>
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`x`、`y`、`width`、`height` は iframe の表示 pixel ではなく、アプリケーションの
|
|
178
|
+
論理画布上の CSS pixel です。たとえば `screenWidth=3840`、`x=480` なら左端は 12.5% です。
|
|
179
|
+
|
|
180
|
+
`stackingPath` は html から object までの祖先を順番に格納します。ネイティブ compositor は
|
|
181
|
+
単一の `zIndex` だけで判断せず、この path と `documentOrder` を使ってアプリケーションとの
|
|
182
|
+
合成方針を決めます。ブラウザ用 `DomObjectMediaPlaneAdapter` では DOM 自身が合成するため、
|
|
183
|
+
この変換は不要です。
|
|
184
|
+
|
|
185
|
+
## なぜ iframe 背面方式では不足するか
|
|
186
|
+
|
|
187
|
+
iframe と親 document の間で選べるのは次の順序だけです。
|
|
188
|
+
|
|
189
|
+
```text
|
|
190
|
+
映像 < iframe 全体
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
一方、caption ページの要求は次です。
|
|
194
|
+
|
|
195
|
+
```text
|
|
196
|
+
iframe 内の背景 < 映像 < iframe 内の字幕説明ボタン
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
親側の z-index を何度変更しても、iframe の内部を二つに分割できません。
|
|
200
|
+
`DomObjectMediaPlaneAdapter` は video を object の fallback content にすることで解決します。
|
|
201
|
+
ネイティブ統合では compositor が同じ役割を担当します。
|
|
202
|
+
|
|
203
|
+
旧 API の `videoSurface` だけを `AribReceiverHost` に渡すと、互換のため自動的に
|
|
204
|
+
`BehindIframeMediaPlaneAdapter` が選択されます。`attachVideo()` と `keepVideoVisible` も
|
|
205
|
+
旧統合用です。新規コードでは使用しないでください。
|
|
206
|
+
|
|
207
|
+
## 字幕
|
|
208
|
+
|
|
209
|
+
通常の放送字幕は video / native Surface と同じプレーヤー側 renderer に置きます。
|
|
210
|
+
したがって media plane の移動、非表示、通常プレーヤーへの復帰へ一緒に追従します。
|
|
211
|
+
|
|
212
|
+
D Data の字幕コンテンツはアプリケーション DOM です。`setCaptionTracks()` と
|
|
213
|
+
`pushCaption()` で iframe へ渡し、通常の放送字幕として親側へ二重描画しないでください。
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## English summary
|
|
218
|
+
|
|
219
|
+
Use `DomObjectMediaPlaneAdapter` for browser integration, implement
|
|
220
|
+
`AribMediaPlaneAdapter` for a native Surface/compositor, and use
|
|
221
|
+
`BehindIframeMediaPlaneAdapter` only as a limited compatibility fallback.
|
|
222
|
+
|
|
223
|
+
Call `host.loadApplication()` to enter data-broadcast mode. Call
|
|
224
|
+
`host.exitApplication()` to leave it; this produces
|
|
225
|
+
`unmountMediaPlane('application-exit')`, hides the iframe, and lets the adapter
|
|
226
|
+
return video to the ordinary player. A data-broadcast document navigation uses
|
|
227
|
+
`document-unload` instead and must not flash the ordinary player UI.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Receiver host contracts
|
|
2
|
+
|
|
3
|
+
`libaribhtml5` owns the browser-facing receiver API. Stream and product data
|
|
4
|
+
remain owned by the integrating player and are supplied through
|
|
5
|
+
`AribReceiverHost`.
|
|
6
|
+
|
|
7
|
+
## Program information
|
|
8
|
+
|
|
9
|
+
Pass decoded present/following event information with `host.setProgramInfo()`.
|
|
10
|
+
`start_time` and `f_start_time` must be JavaScript `Date` objects; `duration`
|
|
11
|
+
and `f_duration` are milliseconds.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
host.setProgramInfo({
|
|
15
|
+
original_network_id: event.originalNetworkId,
|
|
16
|
+
transport_stream_id: event.transportStreamId,
|
|
17
|
+
service_id: event.serviceId,
|
|
18
|
+
event_id: event.eventId,
|
|
19
|
+
event_name: event.title,
|
|
20
|
+
start_time: new Date(event.startTimeUnixMilliseconds),
|
|
21
|
+
duration: event.durationSeconds * 1000,
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Call `host.clearProgramInfo()` before changing input, service, or demux session.
|
|
26
|
+
Until the host supplies an event, `receiverDevice.getCurrentEventInformation()`
|
|
27
|
+
returns `null`. The SDK does not infer a service or video format from carousel
|
|
28
|
+
directory names such as `sh4` or `sh8`.
|
|
29
|
+
|
|
30
|
+
EIT parsing, present/following selection, AIT context selection, and synthetic
|
|
31
|
+
demo metadata belong to the integrating player rather than this SDK.
|
|
32
|
+
|
|
33
|
+
## System information
|
|
34
|
+
|
|
35
|
+
Receiver identity and capability fields can be injected when the host is
|
|
36
|
+
created. Defaults come from the SDK package metadata.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const host = new AribReceiverHost({
|
|
40
|
+
iframe,
|
|
41
|
+
viewport,
|
|
42
|
+
systemInformation: {
|
|
43
|
+
makerid: 'receiver-vendor',
|
|
44
|
+
modelname: 'Living Room Receiver',
|
|
45
|
+
decoder: 'native',
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`baseurl` is the stable receiver VFS scope, normally `/data-broadcast/`.
|
|
51
|
+
Broadcaster startup libraries may append their selected carousel mount (for
|
|
52
|
+
example `sh4`) themselves. The current document mount is used only when mapping
|
|
53
|
+
root-relative resources such as `/60/...`; it is not included in `baseurl`.
|
|
54
|
+
An injected `baseurl` cannot override the actual VFS scope.
|
|
55
|
+
|
|
56
|
+
## Device identifiers
|
|
57
|
+
|
|
58
|
+
The default package combination remains `huggy`, `Huggy ARIB HTML5 Receiver`,
|
|
59
|
+
and the 48-bit identifier `4194c4ae4730`. A product host can resolve identifiers
|
|
60
|
+
by the requested receiver API kind without changing the runtime:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const host = new AribReceiverHost({
|
|
64
|
+
iframe,
|
|
65
|
+
viewport,
|
|
66
|
+
getDeviceIdentifier: async kind => nativeReceiver.identifierFor(kind),
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Returning `null` means that identifier kind is unavailable. Provider failures
|
|
71
|
+
are isolated and reported to the application as an empty identifier.
|
|
72
|
+
|
|
73
|
+
## Runtime lifecycle
|
|
74
|
+
|
|
75
|
+
`onStatus` is presentation text and may be localized. State machines must use
|
|
76
|
+
the typed `onLifecycle` callback instead:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const host = new AribReceiverHost({
|
|
80
|
+
iframe,
|
|
81
|
+
viewport,
|
|
82
|
+
onStatus: text => statusLabel.textContent = text,
|
|
83
|
+
onLifecycle(event) {
|
|
84
|
+
if (event.type === 'installed') clearApplicationTimeout()
|
|
85
|
+
if (event.type === 'navigating') armApplicationTimeout()
|
|
86
|
+
if (event.type === 'error') reportRuntimeError(event.message)
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Lifecycle event types are `loading`, `installed`, `navigating`, `exited`,
|
|
92
|
+
`navigation-blocked`, `frame-blocked`, and `error`.
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "libaribhtml5",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Browser runtime and receiver host for ARIB HTML5 data broadcasting",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"arib",
|
|
7
|
+
"html5",
|
|
8
|
+
"data-broadcasting",
|
|
9
|
+
"bml",
|
|
10
|
+
"isdb",
|
|
11
|
+
"broadcast"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": {
|
|
15
|
+
"name": "huggy",
|
|
16
|
+
"url": "https://huggy.moe",
|
|
17
|
+
"email": "libaribhtml5@huggy.moe"
|
|
18
|
+
},
|
|
19
|
+
"aribReceiver": {
|
|
20
|
+
"modelName": "Huggy ARIB HTML5 Receiver"
|
|
21
|
+
},
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "./dist/sdk/libaribhtml5.mjs",
|
|
24
|
+
"module": "./dist/sdk/libaribhtml5.mjs",
|
|
25
|
+
"types": "./dist/types/index.d.ts",
|
|
26
|
+
"unpkg": "./dist/sdk/libaribhtml5.js",
|
|
27
|
+
"jsdelivr": "./dist/sdk/libaribhtml5.js",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/types/index.d.ts",
|
|
31
|
+
"import": "./dist/sdk/libaribhtml5.mjs",
|
|
32
|
+
"default": "./dist/sdk/libaribhtml5.mjs"
|
|
33
|
+
},
|
|
34
|
+
"./iife": "./dist/sdk/libaribhtml5.js",
|
|
35
|
+
"./arib-vfs-sw.js": "./dist/sdk/arib-vfs-sw.js",
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist/sdk",
|
|
40
|
+
"dist/types",
|
|
41
|
+
"docs",
|
|
42
|
+
"LICENSE",
|
|
43
|
+
"README.md",
|
|
44
|
+
"THIRD_PARTY_NOTICES.md"
|
|
45
|
+
],
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/makeding/libaribhtml5.git"
|
|
49
|
+
},
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/makeding/libaribhtml5/issues"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/makeding/libaribhtml5#readme",
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"typescript": "^5.8.0",
|
|
59
|
+
"vite": "^7.0.0"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"dev": "vite --host 127.0.0.1",
|
|
63
|
+
"test": "node --experimental-strip-types scripts/test-runtime-contracts.mjs && node --experimental-strip-types scripts/test-vfs-worker.mjs",
|
|
64
|
+
"build": "tsc --noEmit && vite build && vite build --config vite.sdk.config.mjs && vite build --config vite.worker.config.mjs",
|
|
65
|
+
"build:sdk": "tsc --noEmit && vite build --config vite.sdk.config.mjs && vite build --config vite.worker.config.mjs && npm run build:types",
|
|
66
|
+
"build:types": "tsc -p tsconfig.build.json && node scripts/rewrite-declaration-imports.mjs",
|
|
67
|
+
"preview": "vite preview --host 127.0.0.1"
|
|
68
|
+
}
|
|
69
|
+
}
|