@swiftbrowser/native 0.17.6 → 0.17.7
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/README.md +22 -0
- package/dist/bridge.d.ts +9 -1
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mediaSession.d.ts +59 -0
- package/dist/mediaSession.d.ts.map +1 -0
- package/dist/mediaSession.js +119 -0
- package/dist/mediaSession.js.map +1 -0
- package/dist/runtime.d.ts +2 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +14 -3
- package/dist/runtime.js.map +1 -1
- package/package.json +3 -3
- package/src/bridge.ts +9 -1
- package/src/index.ts +1 -0
- package/src/mediaSession.ts +163 -0
- package/src/runtime.ts +13 -3
- package/swift/Sources/SwiftBrowserNative/Host/HostBridge.swift +14 -0
- package/swift/Sources/SwiftBrowserNative/Host/NowPlayingBridge.swift +190 -0
- package/swift/Sources/SwiftBrowserNative/Host/ShimAliases.swift +2 -0
- package/swift/Sources/SwiftBrowserNative/Views/ElementView.swift +2 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Controls.swift +6 -4
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Navigation.swift +8 -5
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Video.swift +304 -0
- package/swift/Sources/SwiftBrowserProtocol/Props.swift +43 -0
- package/swift/Sources/SwiftBrowserProtocol/RendererEvent.swift +12 -2
- package/wasm/SwiftBrowserNative.wasm +2 -2
package/README.md
CHANGED
|
@@ -152,6 +152,28 @@ project `native init` writes does); `onOpenURL` here delivers it to the
|
|
|
152
152
|
app's listeners. A URL that launched the app waits for the bundle and then
|
|
153
153
|
for the first listener, so nothing is lost on a cold start.
|
|
154
154
|
|
|
155
|
+
## Now playing
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
navigator.mediaSession.metadata = new MediaMetadata({ title, artist, album, artwork: [{ src }] });
|
|
159
|
+
navigator.mediaSession.playbackState = 'playing';
|
|
160
|
+
navigator.mediaSession.setPositionState({ duration, position, playbackRate: 1 });
|
|
161
|
+
navigator.mediaSession.setActionHandler('nexttrack', () => player.advanceToNextItem());
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The browser's Media Session API, as an audio app writes it for the page,
|
|
165
|
+
works in the native app too: the runtime's `navigator.mediaSession` and
|
|
166
|
+
`MediaMetadata` hand what is set to the host, which puts it on the lock
|
|
167
|
+
screen and the system's player controls through MediaPlayer, and the
|
|
168
|
+
buttons there (and the media keys) run the handlers `setActionHandler`
|
|
169
|
+
registered: `play`, `pause`, `stop`, `nexttrack`, `previoustrack`,
|
|
170
|
+
`seekto` (with `seekTime`) and `seekforward` / `seekbackward` (with
|
|
171
|
+
`seekOffset`, 15 seconds when the system names none). Setting
|
|
172
|
+
`playbackState` to `playing` activates a playback audio session, which the
|
|
173
|
+
lock screen needs; for the audio to go on with the screen locked, the app's
|
|
174
|
+
Xcode target also needs the `audio` background mode (Signing &
|
|
175
|
+
Capabilities → Background Modes → Audio, AirPlay, and Picture in Picture).
|
|
176
|
+
|
|
155
177
|
## The dev loop and Fast Refresh
|
|
156
178
|
|
|
157
179
|
`swiftbrowser-native dev` serves the bundle in two halves and a manifest:
|
package/dist/bridge.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* (Native/Sources/SwiftBrowserNative/Host/HostBridge.swift). Every call is
|
|
4
4
|
* synchronous; the ones that answer later do so by calling a global this
|
|
5
5
|
* package defines (`__sbTimerFired`, `__sbFetchResult`, `__sbImageProbed`,
|
|
6
|
-
* `__sbWorkerMessage`, `__sbOpenURL`).
|
|
6
|
+
* `__sbWorkerMessage`, `__sbOpenURL`, `__sbMediaSessionCommand`).
|
|
7
7
|
*/
|
|
8
8
|
export interface NativeBridge {
|
|
9
9
|
/** Applies a render pass's ops (a JSON array). Returns an error message, or nothing. */
|
|
@@ -35,6 +35,14 @@ export interface NativeBridge {
|
|
|
35
35
|
locale(): string;
|
|
36
36
|
/** The process's launch arguments (`-screen list` from an Xcode scheme or `simctl launch`). */
|
|
37
37
|
launchArguments?(): string[];
|
|
38
|
+
/**
|
|
39
|
+
* `navigator.mediaSession` (mediaSession.ts): the whole session as JSON
|
|
40
|
+
* (`metadata`, `playbackState`, `position`, `commands`, as the web
|
|
41
|
+
* renderer's `mediaSession` request carries them), for the lock screen
|
|
42
|
+
* and the media keys; a command the viewer gives arrives at
|
|
43
|
+
* `__sbMediaSessionCommand(json)`. Hosts before 0.19 lack it.
|
|
44
|
+
*/
|
|
45
|
+
mediaSession?(update: string): void;
|
|
38
46
|
/**
|
|
39
47
|
* Starts worker `id` from `<name>.worker.js` in `engine` (`'context'`
|
|
40
48
|
* when absent; hosts before 0.16 take no engine); returns an error
|
package/dist/bridge.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,wFAAwF;IACxF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,+BAA+B;IAC/B,GAAG,IAAI,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9D,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAAC;IAC9D;;;;;;OAMG;IACH,UAAU,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACvC,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,IAAI,MAAM,EAAE,CAAC;IACxB,YAAY,IAAI,IAAI,CAAC;IACrB,sCAAsC;IACtC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,gDAAgD;IAChD,MAAM,IAAI,MAAM,CAAC;IACjB,+FAA+F;IAC/F,eAAe,CAAC,IAAI,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
|
package/dist/bridge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAwEA,kEAAkE;AAClE,MAAM,UAAU,eAAe;IAC7B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,OAAO,MAAM,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,CAAC;AAC3C,CAAC;AA0BD,yFAAyF;AACzF,MAAM,UAAU,YAAY,CAAC,IAAuB,UAAU;IAC5D,OAAQ,CAA8C,CAAC,UAAU,CAAC;AACpE,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,4 +19,5 @@ export { SimpleURL, SimpleURLSearchParams } from './url';
|
|
|
19
19
|
export { SimpleTextEncoder, SimpleTextDecoder, base64Encode, base64Decode, utf8Encode, utf8Decode } from './encoding';
|
|
20
20
|
export { createWorker, NativeWorker, encodeMessage, decodeMessage, workerName, type NativeWorkerOptions, type WorkerLike, type WorkerMessageEvent, type WorkerErrorEvent } from './worker';
|
|
21
21
|
export { onOpenURL } from './openURL';
|
|
22
|
+
export { createMediaSession, NativeMediaMetadata, type NativeMediaSession, type MediaMetadataInit, type MediaActionDetails, type MediaActionHandler, type MediaPositionState } from './mediaSession';
|
|
22
23
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,GAAG,EAAE,KAAK,UAAU,EAAE,KAAK,OAAO,EAAE,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAChJ,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC9H,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACtH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,KAAK,mBAAmB,EAAE,KAAK,UAAU,EAAE,KAAK,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC3L,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,GAAG,EAAE,KAAK,UAAU,EAAE,KAAK,OAAO,EAAE,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAChJ,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC9H,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACtH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,KAAK,mBAAmB,EAAE,KAAK,UAAU,EAAE,KAAK,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC3L,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -19,4 +19,5 @@ export { SimpleURL, SimpleURLSearchParams } from './url';
|
|
|
19
19
|
export { SimpleTextEncoder, SimpleTextDecoder, base64Encode, base64Decode, utf8Encode, utf8Decode } from './encoding';
|
|
20
20
|
export { createWorker, NativeWorker, encodeMessage, decodeMessage, workerName } from './worker';
|
|
21
21
|
export { onOpenURL } from './openURL';
|
|
22
|
+
export { createMediaSession, NativeMediaMetadata } from './mediaSession';
|
|
22
23
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,GAAG,EAAiC,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAoB,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,cAAc,EAAuB,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAyF,MAAM,UAAU,CAAC;AAChJ,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC9H,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACtH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAA6F,MAAM,UAAU,CAAC;AAC3L,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,GAAG,EAAiC,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAoB,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,cAAc,EAAuB,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAyF,MAAM,UAAU,CAAC;AAChJ,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC9H,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACtH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAA6F,MAAM,UAAU,CAAC;AAC3L,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAA8H,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `navigator.mediaSession` and `MediaMetadata` for the native app: what an
|
|
3
|
+
* audio app tells the browser about what plays (the lock screen, the media
|
|
4
|
+
* keys) reaches the host's `mediaSession(update)` instead, which sets
|
|
5
|
+
* `MPNowPlayingInfoCenter` and `MPRemoteCommandCenter` (HostBridge.swift,
|
|
6
|
+
* NowPlayingBridge.swift); the commands the viewer gives come back to
|
|
7
|
+
* `__sbMediaSessionCommand(json)` and run the handlers `setActionHandler`
|
|
8
|
+
* registered, with the details the browser would give (`seekTime`,
|
|
9
|
+
* `seekOffset`). The update is the one the web renderer's `mediaSession`
|
|
10
|
+
* request carries (docs/ops-protocol.md, "Now playing"), so an app writes
|
|
11
|
+
* the same code for the page and the native app. In the page (the round
|
|
12
|
+
* trip) the page's own `navigator.mediaSession` is there already and
|
|
13
|
+
* nothing is installed over it.
|
|
14
|
+
*/
|
|
15
|
+
import type { NativeBridge } from './bridge';
|
|
16
|
+
type Action = 'play' | 'pause' | 'stop' | 'nexttrack' | 'previoustrack' | 'seekto' | 'seekforward' | 'seekbackward' | 'skipad' | 'togglemicrophone' | 'togglecamera' | 'hangup';
|
|
17
|
+
export interface MediaImage {
|
|
18
|
+
src: string;
|
|
19
|
+
sizes?: string;
|
|
20
|
+
type?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface MediaMetadataInit {
|
|
23
|
+
title?: string;
|
|
24
|
+
artist?: string;
|
|
25
|
+
album?: string;
|
|
26
|
+
artwork?: MediaImage[];
|
|
27
|
+
}
|
|
28
|
+
/** The browser's `MediaMetadata`. */
|
|
29
|
+
export declare class NativeMediaMetadata {
|
|
30
|
+
title: string;
|
|
31
|
+
artist: string;
|
|
32
|
+
album: string;
|
|
33
|
+
artwork: MediaImage[];
|
|
34
|
+
constructor(init?: MediaMetadataInit);
|
|
35
|
+
}
|
|
36
|
+
export interface MediaPositionState {
|
|
37
|
+
duration?: number;
|
|
38
|
+
playbackRate?: number;
|
|
39
|
+
position?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface MediaActionDetails {
|
|
42
|
+
action: Action;
|
|
43
|
+
seekTime?: number;
|
|
44
|
+
seekOffset?: number;
|
|
45
|
+
}
|
|
46
|
+
export type MediaActionHandler = (details: MediaActionDetails) => void;
|
|
47
|
+
/** The browser's `navigator.mediaSession`, as far as audio apps use it. */
|
|
48
|
+
export interface NativeMediaSession {
|
|
49
|
+
metadata: NativeMediaMetadata | null;
|
|
50
|
+
playbackState: 'none' | 'playing' | 'paused';
|
|
51
|
+
setPositionState(state?: MediaPositionState): void;
|
|
52
|
+
setActionHandler(action: Action, handler: MediaActionHandler | null): void;
|
|
53
|
+
}
|
|
54
|
+
export declare function createMediaSession(native: Pick<NativeBridge, 'mediaSession'>): {
|
|
55
|
+
session: NativeMediaSession;
|
|
56
|
+
receive: (json: string) => void;
|
|
57
|
+
};
|
|
58
|
+
export {};
|
|
59
|
+
//# sourceMappingURL=mediaSession.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mediaSession.d.ts","sourceRoot":"","sources":["../src/mediaSession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,KAAK,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,GAAG,eAAe,GAAG,QAAQ,GAAG,aAAa,GAAG,cAAc,GAAG,QAAQ,GAAG,kBAAkB,GAAG,cAAc,GAAG,QAAQ,CAAC;AAchL,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;CACxB;AAED,qCAAqC;AACrC,qBAAa,mBAAmB;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,UAAU,EAAE,CAAC;gBAEV,IAAI,GAAE,iBAAsB;CAMzC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAEvE,2EAA2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC7C,gBAAgB,CAAC,KAAK,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACnD,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,IAAI,GAAG,IAAI,CAAC;CAC5E;AASD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC,GAAG;IAAE,OAAO,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,CA0E/I"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `navigator.mediaSession` and `MediaMetadata` for the native app: what an
|
|
3
|
+
* audio app tells the browser about what plays (the lock screen, the media
|
|
4
|
+
* keys) reaches the host's `mediaSession(update)` instead, which sets
|
|
5
|
+
* `MPNowPlayingInfoCenter` and `MPRemoteCommandCenter` (HostBridge.swift,
|
|
6
|
+
* NowPlayingBridge.swift); the commands the viewer gives come back to
|
|
7
|
+
* `__sbMediaSessionCommand(json)` and run the handlers `setActionHandler`
|
|
8
|
+
* registered, with the details the browser would give (`seekTime`,
|
|
9
|
+
* `seekOffset`). The update is the one the web renderer's `mediaSession`
|
|
10
|
+
* request carries (docs/ops-protocol.md, "Now playing"), so an app writes
|
|
11
|
+
* the same code for the page and the native app. In the page (the round
|
|
12
|
+
* trip) the page's own `navigator.mediaSession` is there already and
|
|
13
|
+
* nothing is installed over it.
|
|
14
|
+
*/
|
|
15
|
+
/** The protocol's command each Media Session action is; the rest get no button. */
|
|
16
|
+
const COMMANDS = {
|
|
17
|
+
play: 'play',
|
|
18
|
+
pause: 'pause',
|
|
19
|
+
stop: 'stop',
|
|
20
|
+
nexttrack: 'nextTrack',
|
|
21
|
+
previoustrack: 'previousTrack',
|
|
22
|
+
seekto: 'seekTo',
|
|
23
|
+
seekforward: 'skipForward',
|
|
24
|
+
seekbackward: 'skipBackward',
|
|
25
|
+
};
|
|
26
|
+
/** The browser's `MediaMetadata`. */
|
|
27
|
+
export class NativeMediaMetadata {
|
|
28
|
+
title;
|
|
29
|
+
artist;
|
|
30
|
+
album;
|
|
31
|
+
artwork;
|
|
32
|
+
constructor(init = {}) {
|
|
33
|
+
this.title = init.title ?? '';
|
|
34
|
+
this.artist = init.artist ?? '';
|
|
35
|
+
this.album = init.album ?? '';
|
|
36
|
+
this.artwork = (init.artwork ?? []).map((image) => ({ ...image }));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function createMediaSession(native) {
|
|
40
|
+
let metadata = null;
|
|
41
|
+
let playbackState = 'none';
|
|
42
|
+
let position = null;
|
|
43
|
+
const handlers = new Map();
|
|
44
|
+
// The whole state each time: the host keeps what it has and changes what differs.
|
|
45
|
+
const sync = () => {
|
|
46
|
+
const commands = [...handlers.keys()].map((action) => COMMANDS[action]).filter((command) => command !== undefined);
|
|
47
|
+
native.mediaSession?.(JSON.stringify({
|
|
48
|
+
metadata: metadata === null ? null : { title: metadata.title, artist: metadata.artist, album: metadata.album, artwork: metadata.artwork[0]?.src },
|
|
49
|
+
playbackState,
|
|
50
|
+
position: position === null
|
|
51
|
+
? null
|
|
52
|
+
: { duration: Number.isFinite(position.duration) ? position.duration : 0, position: position.position ?? 0, rate: position.playbackRate ?? 1 },
|
|
53
|
+
commands,
|
|
54
|
+
}));
|
|
55
|
+
};
|
|
56
|
+
const session = {
|
|
57
|
+
get metadata() {
|
|
58
|
+
return metadata;
|
|
59
|
+
},
|
|
60
|
+
set metadata(value) {
|
|
61
|
+
metadata = value === null || value === undefined ? null : value instanceof NativeMediaMetadata ? value : new NativeMediaMetadata(value);
|
|
62
|
+
sync();
|
|
63
|
+
},
|
|
64
|
+
get playbackState() {
|
|
65
|
+
return playbackState;
|
|
66
|
+
},
|
|
67
|
+
set playbackState(value) {
|
|
68
|
+
if (value !== 'none' && value !== 'playing' && value !== 'paused')
|
|
69
|
+
throw new TypeError(`${String(value)} is not a valid MediaSessionPlaybackState`);
|
|
70
|
+
playbackState = value;
|
|
71
|
+
sync();
|
|
72
|
+
},
|
|
73
|
+
setPositionState(state) {
|
|
74
|
+
if (state === undefined || state.duration === undefined) {
|
|
75
|
+
position = null;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
if (!(state.duration >= 0))
|
|
79
|
+
throw new TypeError('the duration must be a non-negative number');
|
|
80
|
+
if (state.position !== undefined && (state.position < 0 || state.position > state.duration))
|
|
81
|
+
throw new TypeError('the position must be within the duration');
|
|
82
|
+
position = { ...state };
|
|
83
|
+
}
|
|
84
|
+
sync();
|
|
85
|
+
},
|
|
86
|
+
setActionHandler(action, handler) {
|
|
87
|
+
if (!(action in COMMANDS) && !['skipad', 'togglemicrophone', 'togglecamera', 'hangup'].includes(action))
|
|
88
|
+
throw new TypeError(`${String(action)} is not a valid MediaSessionAction`);
|
|
89
|
+
if (handler)
|
|
90
|
+
handlers.set(action, handler);
|
|
91
|
+
else
|
|
92
|
+
handlers.delete(action);
|
|
93
|
+
sync();
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
const receive = (json) => {
|
|
97
|
+
let received;
|
|
98
|
+
try {
|
|
99
|
+
received = JSON.parse(json);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const action = Object.keys(COMMANDS).find((name) => COMMANDS[name] === received.command);
|
|
105
|
+
if (!action)
|
|
106
|
+
return;
|
|
107
|
+
const handler = handlers.get(action);
|
|
108
|
+
if (!handler)
|
|
109
|
+
return;
|
|
110
|
+
const details = { action };
|
|
111
|
+
if (action === 'seekto' && typeof received.position === 'number')
|
|
112
|
+
details.seekTime = received.position;
|
|
113
|
+
if ((action === 'seekforward' || action === 'seekbackward') && typeof received.interval === 'number')
|
|
114
|
+
details.seekOffset = received.interval;
|
|
115
|
+
handler(details);
|
|
116
|
+
};
|
|
117
|
+
return { session, receive };
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=mediaSession.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mediaSession.js","sourceRoot":"","sources":["../src/mediaSession.ts"],"names":[],"mappings":"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"}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* `atob`, `btoa`, `fetch` with `Headers`, `Request`, `Response`,
|
|
12
12
|
* `AbortController` and `AbortSignal`, `URL`, `URLSearchParams`, `crypto`
|
|
13
13
|
* (`getRandomValues`, `randomUUID`), `localStorage`, `structuredClone`,
|
|
14
|
-
* `navigator
|
|
14
|
+
* `navigator` (with `mediaSession` and `MediaMetadata` on a bridge with
|
|
15
|
+
* `mediaSession`, mediaSession.ts), `Image` (image.ts, on a bridge with `probeImage`) with the
|
|
15
16
|
* bridge's `__sbImageProbed`, `Worker` (worker.ts) with the bridge's
|
|
16
17
|
* `__sbWorkerMessage` and `__sbWorkerError`, `__sbOpenURL` (openURL.ts),
|
|
17
18
|
* and the bridge's callbacks `__sbTimerFired` and `__sbFetchResult`. In a
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAS3D,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAQzC,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,GAAE,SAA8C,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,EAAE,CA8LnJ"}
|
package/dist/runtime.js
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* `atob`, `btoa`, `fetch` with `Headers`, `Request`, `Response`,
|
|
12
12
|
* `AbortController` and `AbortSignal`, `URL`, `URLSearchParams`, `crypto`
|
|
13
13
|
* (`getRandomValues`, `randomUUID`), `localStorage`, `structuredClone`,
|
|
14
|
-
* `navigator
|
|
14
|
+
* `navigator` (with `mediaSession` and `MediaMetadata` on a bridge with
|
|
15
|
+
* `mediaSession`, mediaSession.ts), `Image` (image.ts, on a bridge with `probeImage`) with the
|
|
15
16
|
* bridge's `__sbImageProbed`, `Worker` (worker.ts) with the bridge's
|
|
16
17
|
* `__sbWorkerMessage` and `__sbWorkerError`, `__sbOpenURL` (openURL.ts),
|
|
17
18
|
* and the bridge's callbacks `__sbTimerFired` and `__sbFetchResult`. In a
|
|
@@ -27,6 +28,7 @@ import { createFetch } from './fetch';
|
|
|
27
28
|
import { createImageGlobals } from './image';
|
|
28
29
|
import { SimpleURL, SimpleURLSearchParams } from './url';
|
|
29
30
|
import { installWorkerScope, NativeWorker } from './worker';
|
|
31
|
+
import { createMediaSession, NativeMediaMetadata } from './mediaSession';
|
|
30
32
|
import { receiveOpenURL } from './openURL';
|
|
31
33
|
/**
|
|
32
34
|
* Installs the runtime on `target` (the global object) over `native`.
|
|
@@ -191,14 +193,23 @@ export function installRuntime(native, target = globalThis, options = {}) {
|
|
|
191
193
|
};
|
|
192
194
|
define('localStorage', storage);
|
|
193
195
|
define('structuredClone', (value) => (value === undefined ? value : JSON.parse(JSON.stringify(value))));
|
|
194
|
-
|
|
196
|
+
const navigator = {
|
|
195
197
|
userAgent: 'SwiftBrowserNative',
|
|
196
198
|
language: native.locale().replace('_', '-'),
|
|
197
199
|
languages: [native.locale().replace('_', '-')],
|
|
198
200
|
onLine: true,
|
|
199
201
|
platform: 'iOS',
|
|
200
202
|
hardwareConcurrency: 1,
|
|
201
|
-
}
|
|
203
|
+
};
|
|
204
|
+
// What plays, for the lock screen and the media keys (mediaSession.ts); not in a worker.
|
|
205
|
+
const mediaSession = native.mediaSession && !native.workerPostMessage ? createMediaSession(native) : null;
|
|
206
|
+
if (mediaSession)
|
|
207
|
+
navigator['mediaSession'] = mediaSession.session;
|
|
208
|
+
define('navigator', navigator);
|
|
209
|
+
if (mediaSession) {
|
|
210
|
+
define('MediaMetadata', NativeMediaMetadata);
|
|
211
|
+
define('__sbMediaSessionCommand', mediaSession.receive);
|
|
212
|
+
}
|
|
202
213
|
if (native.workerPostMessage) {
|
|
203
214
|
installed.push(...installWorkerScope(native, target));
|
|
204
215
|
}
|
package/dist/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swiftbrowser/native",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Run a @swiftbrowser/react app natively: the runtime a JavaScriptCore-hosted app needs (timers, console, fetch and the other web globals over the SwiftBrowser Native bridge), the `run` entry that renders into the native SwiftUI renderer, and the bundler that builds app.js for the Xcode target.",
|
|
6
6
|
"repository": {
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@babel/core": "^7.29.7",
|
|
62
|
-
"@swiftbrowser/react": "0.17.
|
|
63
|
-
"@swiftbrowser/web": "0.17.
|
|
62
|
+
"@swiftbrowser/react": "0.17.7",
|
|
63
|
+
"@swiftbrowser/web": "0.17.7",
|
|
64
64
|
"esbuild": "^0.28.2",
|
|
65
65
|
"react-refresh": "^0.17.0"
|
|
66
66
|
},
|
package/src/bridge.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* (Native/Sources/SwiftBrowserNative/Host/HostBridge.swift). Every call is
|
|
4
4
|
* synchronous; the ones that answer later do so by calling a global this
|
|
5
5
|
* package defines (`__sbTimerFired`, `__sbFetchResult`, `__sbImageProbed`,
|
|
6
|
-
* `__sbWorkerMessage`, `__sbOpenURL`).
|
|
6
|
+
* `__sbWorkerMessage`, `__sbOpenURL`, `__sbMediaSessionCommand`).
|
|
7
7
|
*/
|
|
8
8
|
export interface NativeBridge {
|
|
9
9
|
/** Applies a render pass's ops (a JSON array). Returns an error message, or nothing. */
|
|
@@ -35,6 +35,14 @@ export interface NativeBridge {
|
|
|
35
35
|
locale(): string;
|
|
36
36
|
/** The process's launch arguments (`-screen list` from an Xcode scheme or `simctl launch`). */
|
|
37
37
|
launchArguments?(): string[];
|
|
38
|
+
/**
|
|
39
|
+
* `navigator.mediaSession` (mediaSession.ts): the whole session as JSON
|
|
40
|
+
* (`metadata`, `playbackState`, `position`, `commands`, as the web
|
|
41
|
+
* renderer's `mediaSession` request carries them), for the lock screen
|
|
42
|
+
* and the media keys; a command the viewer gives arrives at
|
|
43
|
+
* `__sbMediaSessionCommand(json)`. Hosts before 0.19 lack it.
|
|
44
|
+
*/
|
|
45
|
+
mediaSession?(update: string): void;
|
|
38
46
|
|
|
39
47
|
// Workers (worker.ts): a second context on its own thread, from a bundle
|
|
40
48
|
// `swiftbrowser-native build --worker <name>=<entry>` wrote next to app.js
|
package/src/index.ts
CHANGED
|
@@ -20,3 +20,4 @@ export { SimpleURL, SimpleURLSearchParams } from './url';
|
|
|
20
20
|
export { SimpleTextEncoder, SimpleTextDecoder, base64Encode, base64Decode, utf8Encode, utf8Decode } from './encoding';
|
|
21
21
|
export { createWorker, NativeWorker, encodeMessage, decodeMessage, workerName, type NativeWorkerOptions, type WorkerLike, type WorkerMessageEvent, type WorkerErrorEvent } from './worker';
|
|
22
22
|
export { onOpenURL } from './openURL';
|
|
23
|
+
export { createMediaSession, NativeMediaMetadata, type NativeMediaSession, type MediaMetadataInit, type MediaActionDetails, type MediaActionHandler, type MediaPositionState } from './mediaSession';
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `navigator.mediaSession` and `MediaMetadata` for the native app: what an
|
|
3
|
+
* audio app tells the browser about what plays (the lock screen, the media
|
|
4
|
+
* keys) reaches the host's `mediaSession(update)` instead, which sets
|
|
5
|
+
* `MPNowPlayingInfoCenter` and `MPRemoteCommandCenter` (HostBridge.swift,
|
|
6
|
+
* NowPlayingBridge.swift); the commands the viewer gives come back to
|
|
7
|
+
* `__sbMediaSessionCommand(json)` and run the handlers `setActionHandler`
|
|
8
|
+
* registered, with the details the browser would give (`seekTime`,
|
|
9
|
+
* `seekOffset`). The update is the one the web renderer's `mediaSession`
|
|
10
|
+
* request carries (docs/ops-protocol.md, "Now playing"), so an app writes
|
|
11
|
+
* the same code for the page and the native app. In the page (the round
|
|
12
|
+
* trip) the page's own `navigator.mediaSession` is there already and
|
|
13
|
+
* nothing is installed over it.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { NativeBridge } from './bridge';
|
|
17
|
+
|
|
18
|
+
type Action = 'play' | 'pause' | 'stop' | 'nexttrack' | 'previoustrack' | 'seekto' | 'seekforward' | 'seekbackward' | 'skipad' | 'togglemicrophone' | 'togglecamera' | 'hangup';
|
|
19
|
+
|
|
20
|
+
/** The protocol's command each Media Session action is; the rest get no button. */
|
|
21
|
+
const COMMANDS: Partial<Record<Action, string>> = {
|
|
22
|
+
play: 'play',
|
|
23
|
+
pause: 'pause',
|
|
24
|
+
stop: 'stop',
|
|
25
|
+
nexttrack: 'nextTrack',
|
|
26
|
+
previoustrack: 'previousTrack',
|
|
27
|
+
seekto: 'seekTo',
|
|
28
|
+
seekforward: 'skipForward',
|
|
29
|
+
seekbackward: 'skipBackward',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export interface MediaImage {
|
|
33
|
+
src: string;
|
|
34
|
+
sizes?: string;
|
|
35
|
+
type?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface MediaMetadataInit {
|
|
39
|
+
title?: string;
|
|
40
|
+
artist?: string;
|
|
41
|
+
album?: string;
|
|
42
|
+
artwork?: MediaImage[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The browser's `MediaMetadata`. */
|
|
46
|
+
export class NativeMediaMetadata {
|
|
47
|
+
title: string;
|
|
48
|
+
artist: string;
|
|
49
|
+
album: string;
|
|
50
|
+
artwork: MediaImage[];
|
|
51
|
+
|
|
52
|
+
constructor(init: MediaMetadataInit = {}) {
|
|
53
|
+
this.title = init.title ?? '';
|
|
54
|
+
this.artist = init.artist ?? '';
|
|
55
|
+
this.album = init.album ?? '';
|
|
56
|
+
this.artwork = (init.artwork ?? []).map((image) => ({ ...image }));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface MediaPositionState {
|
|
61
|
+
duration?: number;
|
|
62
|
+
playbackRate?: number;
|
|
63
|
+
position?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface MediaActionDetails {
|
|
67
|
+
action: Action;
|
|
68
|
+
seekTime?: number;
|
|
69
|
+
seekOffset?: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type MediaActionHandler = (details: MediaActionDetails) => void;
|
|
73
|
+
|
|
74
|
+
/** The browser's `navigator.mediaSession`, as far as audio apps use it. */
|
|
75
|
+
export interface NativeMediaSession {
|
|
76
|
+
metadata: NativeMediaMetadata | null;
|
|
77
|
+
playbackState: 'none' | 'playing' | 'paused';
|
|
78
|
+
setPositionState(state?: MediaPositionState): void;
|
|
79
|
+
setActionHandler(action: Action, handler: MediaActionHandler | null): void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A command the host received: the protocol's name, with the position of a seek or the interval of a skip. */
|
|
83
|
+
interface ReceivedCommand {
|
|
84
|
+
command: string;
|
|
85
|
+
position?: number;
|
|
86
|
+
interval?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createMediaSession(native: Pick<NativeBridge, 'mediaSession'>): { session: NativeMediaSession; receive: (json: string) => void } {
|
|
90
|
+
let metadata: NativeMediaMetadata | null = null;
|
|
91
|
+
let playbackState: NativeMediaSession['playbackState'] = 'none';
|
|
92
|
+
let position: MediaPositionState | null = null;
|
|
93
|
+
const handlers = new Map<Action, MediaActionHandler>();
|
|
94
|
+
|
|
95
|
+
// The whole state each time: the host keeps what it has and changes what differs.
|
|
96
|
+
const sync = (): void => {
|
|
97
|
+
const commands = [...handlers.keys()].map((action) => COMMANDS[action]).filter((command): command is string => command !== undefined);
|
|
98
|
+
native.mediaSession?.(
|
|
99
|
+
JSON.stringify({
|
|
100
|
+
metadata: metadata === null ? null : { title: metadata.title, artist: metadata.artist, album: metadata.album, artwork: metadata.artwork[0]?.src },
|
|
101
|
+
playbackState,
|
|
102
|
+
position:
|
|
103
|
+
position === null
|
|
104
|
+
? null
|
|
105
|
+
: { duration: Number.isFinite(position.duration) ? position.duration : 0, position: position.position ?? 0, rate: position.playbackRate ?? 1 },
|
|
106
|
+
commands,
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const session: NativeMediaSession = {
|
|
112
|
+
get metadata() {
|
|
113
|
+
return metadata;
|
|
114
|
+
},
|
|
115
|
+
set metadata(value) {
|
|
116
|
+
metadata = value === null || value === undefined ? null : value instanceof NativeMediaMetadata ? value : new NativeMediaMetadata(value as MediaMetadataInit);
|
|
117
|
+
sync();
|
|
118
|
+
},
|
|
119
|
+
get playbackState() {
|
|
120
|
+
return playbackState;
|
|
121
|
+
},
|
|
122
|
+
set playbackState(value) {
|
|
123
|
+
if (value !== 'none' && value !== 'playing' && value !== 'paused') throw new TypeError(`${String(value)} is not a valid MediaSessionPlaybackState`);
|
|
124
|
+
playbackState = value;
|
|
125
|
+
sync();
|
|
126
|
+
},
|
|
127
|
+
setPositionState(state) {
|
|
128
|
+
if (state === undefined || state.duration === undefined) {
|
|
129
|
+
position = null;
|
|
130
|
+
} else {
|
|
131
|
+
if (!(state.duration >= 0)) throw new TypeError('the duration must be a non-negative number');
|
|
132
|
+
if (state.position !== undefined && (state.position < 0 || state.position > state.duration)) throw new TypeError('the position must be within the duration');
|
|
133
|
+
position = { ...state };
|
|
134
|
+
}
|
|
135
|
+
sync();
|
|
136
|
+
},
|
|
137
|
+
setActionHandler(action, handler) {
|
|
138
|
+
if (!(action in COMMANDS) && !['skipad', 'togglemicrophone', 'togglecamera', 'hangup'].includes(action)) throw new TypeError(`${String(action)} is not a valid MediaSessionAction`);
|
|
139
|
+
if (handler) handlers.set(action, handler);
|
|
140
|
+
else handlers.delete(action);
|
|
141
|
+
sync();
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const receive = (json: string): void => {
|
|
146
|
+
let received: ReceivedCommand;
|
|
147
|
+
try {
|
|
148
|
+
received = JSON.parse(json) as ReceivedCommand;
|
|
149
|
+
} catch {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const action = (Object.keys(COMMANDS) as Action[]).find((name) => COMMANDS[name] === received.command);
|
|
153
|
+
if (!action) return;
|
|
154
|
+
const handler = handlers.get(action);
|
|
155
|
+
if (!handler) return;
|
|
156
|
+
const details: MediaActionDetails = { action };
|
|
157
|
+
if (action === 'seekto' && typeof received.position === 'number') details.seekTime = received.position;
|
|
158
|
+
if ((action === 'seekforward' || action === 'seekbackward') && typeof received.interval === 'number') details.seekOffset = received.interval;
|
|
159
|
+
handler(details);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return { session, receive };
|
|
163
|
+
}
|