@thewhateverapp/tile-sdk 0.12.11 → 0.12.13
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/dist/bridge/TileBridge.d.ts +33 -23
- package/dist/bridge/TileBridge.d.ts.map +1 -1
- package/dist/bridge/TileBridge.js +155 -44
- package/package.json +1 -1
|
@@ -59,8 +59,9 @@ export declare class TileBridge {
|
|
|
59
59
|
private tokenExpiresAt;
|
|
60
60
|
private keyboardState;
|
|
61
61
|
private visibilityState;
|
|
62
|
-
private autoMuteEnabled;
|
|
63
62
|
private mediaOriginalMutedState;
|
|
63
|
+
private trackedAudioContexts;
|
|
64
|
+
private audioContextPatched;
|
|
64
65
|
private baselineViewportHeight;
|
|
65
66
|
private keyboardDetectionEnabled;
|
|
66
67
|
private lastReportedKeyboardState;
|
|
@@ -112,12 +113,25 @@ export declare class TileBridge {
|
|
|
112
113
|
*/
|
|
113
114
|
private handleVisibility;
|
|
114
115
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
116
|
+
* ROBUST: Monkey-patch AudioContext to auto-intercept ALL new AudioContext() calls
|
|
117
|
+
* This catches WASM/Emscripten audio without tiles needing to call registerAudioContext()
|
|
118
|
+
*/
|
|
119
|
+
private patchAudioContext;
|
|
120
|
+
/**
|
|
121
|
+
* ROBUST: MutationObserver to watch for dynamically added media elements
|
|
122
|
+
* Auto-mutes any <video> or <audio> elements added while tile is offscreen
|
|
123
|
+
*/
|
|
124
|
+
private setupMediaObserver;
|
|
125
|
+
/**
|
|
126
|
+
* MANDATORY: Mute all audio sources when tile is offscreen
|
|
127
|
+
* - Mutes all <video> and <audio> elements
|
|
128
|
+
* - Suspends all registered AudioContexts (for WASM/Emscripten audio)
|
|
117
129
|
*/
|
|
118
130
|
private muteAllMedia;
|
|
119
131
|
/**
|
|
120
|
-
* Unmute all
|
|
132
|
+
* Unmute all audio sources when tile becomes visible
|
|
133
|
+
* - Unmutes <video> and <audio> elements (restoring original state)
|
|
134
|
+
* - Resumes all registered AudioContexts
|
|
121
135
|
*/
|
|
122
136
|
private unmuteAllMedia;
|
|
123
137
|
private sendToParent;
|
|
@@ -333,30 +347,26 @@ export declare class TileBridge {
|
|
|
333
347
|
*/
|
|
334
348
|
onVisibilityChange(handler: (state: VisibilityState) => void): () => void;
|
|
335
349
|
/**
|
|
336
|
-
*
|
|
337
|
-
* By default, auto-mute is ENABLED - all <video> and <audio> elements are
|
|
338
|
-
* automatically muted when the tile becomes hidden/offscreen.
|
|
350
|
+
* Register an AudioContext for mandatory muting when tile goes offscreen.
|
|
339
351
|
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
352
|
+
* PROTOCOL FOR TILES WITH CUSTOM AUDIO:
|
|
353
|
+
* Tiles using AudioContext (e.g., Emscripten/WASM games like DOOM) MUST call
|
|
354
|
+
* this method to register their AudioContext. The TileBridge will automatically
|
|
355
|
+
* suspend/resume the context when the tile goes offscreen/onscreen.
|
|
342
356
|
*
|
|
343
|
-
*
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
*
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Manually mute all audio/video elements
|
|
352
|
-
* Useful for tiles that need to mute on demand
|
|
357
|
+
* Example usage in DoomManager:
|
|
358
|
+
* ```
|
|
359
|
+
* const bridge = getTileBridge();
|
|
360
|
+
* bridge.registerAudioContext(myAudioContext);
|
|
361
|
+
* ```
|
|
362
|
+
*
|
|
363
|
+
* @param ctx - The AudioContext to track for automatic suspension
|
|
353
364
|
*/
|
|
354
|
-
|
|
365
|
+
registerAudioContext(ctx: AudioContext): void;
|
|
355
366
|
/**
|
|
356
|
-
*
|
|
357
|
-
* Restores original muted state (elements that were muted before stay muted)
|
|
367
|
+
* Unregister an AudioContext (e.g., when closing it)
|
|
358
368
|
*/
|
|
359
|
-
|
|
369
|
+
unregisterAudioContext(ctx: AudioContext): void;
|
|
360
370
|
/**
|
|
361
371
|
* Wait for ready state
|
|
362
372
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TileBridge.d.ts","sourceRoot":"","sources":["../../src/bridge/TileBridge.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,iGAAiG;IACjG,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAGD,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAEF,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,gBAAgB,CAAmD;IAC3E,OAAO,CAAC,aAAa,CAAoD;IACzE,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAA2B;IAGzC,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,cAAc,CAAqB;IAG3C,OAAO,CAAC,aAAa,CAAgD;IAIrE,OAAO,CAAC,eAAe,CAAoD;IAG3E,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"TileBridge.d.ts","sourceRoot":"","sources":["../../src/bridge/TileBridge.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,iGAAiG;IACjG,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAGD,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAEF,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,gBAAgB,CAAmD;IAC3E,OAAO,CAAC,aAAa,CAAoD;IACzE,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAA2B;IAGzC,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,cAAc,CAAqB;IAG3C,OAAO,CAAC,aAAa,CAAgD;IAIrE,OAAO,CAAC,eAAe,CAAoD;IAG3E,OAAO,CAAC,uBAAuB,CAAqD;IAEpF,OAAO,CAAC,oBAAoB,CAAgC;IAC5D,OAAO,CAAC,mBAAmB,CAAkB;IAG7C,OAAO,CAAC,sBAAsB,CAAa;IAC3C,OAAO,CAAC,wBAAwB,CAAkB;IAClD,OAAO,CAAC,yBAAyB,CAAuE;IACxG,OAAO,CAAC,qBAAqB,CAA8C;IAC3E,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAO;IAC1C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAO;IAGnC,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,cAAc,CAAwB;gBAElC,cAAc,GAAE,MAAkC,EAAE,MAAM,CAAC,EAAE,UAAU;IA6BnF,OAAO,CAAC,UAAU;IAwClB;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IAwDnC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA0C7B;;OAEG;IACH,OAAO,CAAC,aAAa;IA+BrB;;OAEG;IACH,OAAO,CAAC,cAAc;IAgCtB;;OAEG;IACH,OAAO,CAAC,cAAc;IAuBtB,OAAO,CAAC,aAAa;IA6DrB,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,oBAAoB;IAoB5B;;OAEG;IACH,OAAO,CAAC,WAAW;IAuBnB;;OAEG;IACH,OAAO,CAAC,cAAc;IAmBtB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAgCxB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IA4CzB;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IA8C1B;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAqCpB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAyCtB,OAAO,CAAC,YAAY;IAmCpB;;;;;OAKG;IACI,cAAc,IAAI,IAAI;IAuD7B;;;;;OAKG;IACI,cAAc,IAAI,IAAI;IAmD7B;;;OAGG;IACI,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAI1C;;OAEG;IACI,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,QAAQ,GAAG,OAAkB,GAAG,IAAI;IAOxE;;OAEG;IACI,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAOtD;;;OAGG;IACI,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAOpD;;OAEG;IACU,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B1D;;OAEG;IACU,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B/D;;OAEG;IACU,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAoBlD;;OAEG;IACI,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAOzD;;OAEG;IACU,OAAO,CAAC,OAAO,CAAC,EAAE;QAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,GAAG,CAAC;IAiChB;;OAEG;IACU,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IA8B9D;;OAEG;IACU,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAyB3C;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAoCzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAgCxB;;;;;OAKG;IACU,YAAY,CAAC,OAAO,CAAC,EAAE;QAClC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;KACvC,GAAG,OAAO,CAAC;QACV,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IA+CF;;;;;OAKG;IACU,UAAU,CAAC,OAAO,CAAC,EAAE;QAChC,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC;QACV,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IAwCF;;;;;OAKG;IACU,WAAW,CAAC,OAAO,CAAC,EAAE;QACjC,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,KAAK,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IA6CH;;OAEG;IACI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,MAAM,IAAI;IAgBlE;;OAEG;IACI,SAAS,IAAI,UAAU,GAAG,IAAI;IAIrC;;OAEG;IACI,OAAO,IAAI,OAAO;IAMzB;;;OAGG;IACI,QAAQ,IAAI,MAAM,GAAG,IAAI;IAWhC;;;OAGG;IACI,YAAY,IAAI,aAAa,GAAG,IAAI;IAU3C;;OAEG;IACI,aAAa,IAAI,OAAO;IAO/B;;;;OAIG;IACU,YAAY,CAAC,SAAS,GAAE,MAAc,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAyC5E;;;OAGG;IACI,aAAa,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,IAAI,CAAA;KAAE,KAAK,IAAI,GAAG,MAAM,IAAI;IAM7F;;;OAGG;IACI,gBAAgB,IAAI,aAAa;IAIxC;;OAEG;IACI,iBAAiB,IAAI,OAAO;IAInC;;OAEG;IACI,iBAAiB,IAAI,MAAM;IAIlC;;;;;OAKG;IACI,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI;IAM5E;;;;OAIG;IACI,kBAAkB,IAAI,eAAe;IAI5C;;;OAGG;IACI,SAAS,IAAI,OAAO;IAI3B;;;;OAIG;IACI,OAAO,IAAI,OAAO;IAIzB;;;;;OAKG;IACI,kBAAkB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GAAG,MAAM,IAAI;IAQhF;;;;;;;;;;;;;;;OAeG;IACI,oBAAoB,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI;IAWpD;;OAEG;IACI,sBAAsB,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI;IAKtD;;;;;OAKG;IACU,YAAY,IAAI,OAAO,CAAC,UAAU,CAAC;IAuBhD,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,aAAa;CAgCtB;AAKD,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,UAAU,CAQ7D"}
|
|
@@ -18,10 +18,11 @@ export class TileBridge {
|
|
|
18
18
|
// Visibility state from parent (for TikTok-style feeds)
|
|
19
19
|
// Includes muted state for atomic updates to prevent race conditions
|
|
20
20
|
this.visibilityState = { visible: true, muted: false };
|
|
21
|
-
// Automatic audio enforcement - enabled by default
|
|
22
|
-
this.autoMuteEnabled = true;
|
|
23
21
|
// Store original muted state of media elements to restore when unmuting
|
|
24
22
|
this.mediaOriginalMutedState = new WeakMap();
|
|
23
|
+
// Track all AudioContext instances for suspension (catches Emscripten/WASM audio)
|
|
24
|
+
this.trackedAudioContexts = new Set();
|
|
25
|
+
this.audioContextPatched = false;
|
|
25
26
|
// VisualViewport tracking for keyboard detection (Plan A)
|
|
26
27
|
this.baselineViewportHeight = 0;
|
|
27
28
|
this.keyboardDetectionEnabled = false;
|
|
@@ -85,6 +86,9 @@ export class TileBridge {
|
|
|
85
86
|
this.sendToParent({ type: 'tile:ready' });
|
|
86
87
|
// Initialize keyboard detection (Plan A: VisualViewport + Plan B: Input Focus)
|
|
87
88
|
this.initializeKeyboardDetection();
|
|
89
|
+
// ROBUST AUDIO MUTING: Auto-intercept all audio sources
|
|
90
|
+
this.patchAudioContext();
|
|
91
|
+
this.setupMediaObserver();
|
|
88
92
|
}
|
|
89
93
|
/**
|
|
90
94
|
* Initialize keyboard detection using VisualViewport API and input focus tracking
|
|
@@ -411,14 +415,12 @@ export class TileBridge {
|
|
|
411
415
|
if (this.isDevelopment() || this.isPreview()) {
|
|
412
416
|
console.log('[TileBridge] 👁️ Received visibility state', this.visibilityState);
|
|
413
417
|
}
|
|
414
|
-
//
|
|
415
|
-
if (this.
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
this.unmuteAllMedia();
|
|
421
|
-
}
|
|
418
|
+
// MANDATORY: Enforce audio muting when offscreen - no exceptions
|
|
419
|
+
if (this.visibilityState.muted) {
|
|
420
|
+
this.muteAllMedia();
|
|
421
|
+
}
|
|
422
|
+
else if (wasMuted && !this.visibilityState.muted) {
|
|
423
|
+
this.unmuteAllMedia();
|
|
422
424
|
}
|
|
423
425
|
// Emit if visibility OR muted state changed
|
|
424
426
|
if (wasVisible !== this.visibilityState.visible || wasMuted !== this.visibilityState.muted) {
|
|
@@ -426,12 +428,98 @@ export class TileBridge {
|
|
|
426
428
|
}
|
|
427
429
|
}
|
|
428
430
|
/**
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
+
* ROBUST: Monkey-patch AudioContext to auto-intercept ALL new AudioContext() calls
|
|
432
|
+
* This catches WASM/Emscripten audio without tiles needing to call registerAudioContext()
|
|
433
|
+
*/
|
|
434
|
+
patchAudioContext() {
|
|
435
|
+
if (typeof window === 'undefined' || this.audioContextPatched)
|
|
436
|
+
return;
|
|
437
|
+
this.audioContextPatched = true;
|
|
438
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
439
|
+
const bridge = this;
|
|
440
|
+
const OriginalAudioContext = window.AudioContext || window.webkitAudioContext;
|
|
441
|
+
if (!OriginalAudioContext) {
|
|
442
|
+
console.log('[TileBridge] AudioContext not available in this environment');
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
// Create a patched constructor that auto-registers all instances
|
|
446
|
+
function PatchedAudioContext(options) {
|
|
447
|
+
// Call original constructor
|
|
448
|
+
const ctx = new OriginalAudioContext(options);
|
|
449
|
+
// Auto-register this context
|
|
450
|
+
bridge.trackedAudioContexts.add(ctx);
|
|
451
|
+
console.log(`[TileBridge] 🎵 AudioContext auto-intercepted (${bridge.trackedAudioContexts.size} total)`);
|
|
452
|
+
// If tile is currently muted, suspend immediately
|
|
453
|
+
if (bridge.visibilityState.muted && ctx.state === 'running') {
|
|
454
|
+
console.log('[TileBridge] 🔇 New AudioContext auto-suspended (tile is muted)');
|
|
455
|
+
ctx.suspend().catch(() => { });
|
|
456
|
+
}
|
|
457
|
+
return ctx;
|
|
458
|
+
}
|
|
459
|
+
// Copy prototype and static properties
|
|
460
|
+
PatchedAudioContext.prototype = OriginalAudioContext.prototype;
|
|
461
|
+
Object.setPrototypeOf(PatchedAudioContext, OriginalAudioContext);
|
|
462
|
+
// Replace global AudioContext
|
|
463
|
+
window.AudioContext = PatchedAudioContext;
|
|
464
|
+
if (window.webkitAudioContext) {
|
|
465
|
+
window.webkitAudioContext = PatchedAudioContext;
|
|
466
|
+
}
|
|
467
|
+
console.log('[TileBridge] 🔧 AudioContext patched for auto-interception');
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* ROBUST: MutationObserver to watch for dynamically added media elements
|
|
471
|
+
* Auto-mutes any <video> or <audio> elements added while tile is offscreen
|
|
472
|
+
*/
|
|
473
|
+
setupMediaObserver() {
|
|
474
|
+
if (typeof document === 'undefined' || typeof MutationObserver === 'undefined')
|
|
475
|
+
return;
|
|
476
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
477
|
+
const bridge = this;
|
|
478
|
+
const observer = new MutationObserver((mutations) => {
|
|
479
|
+
// Only process if we're currently muted
|
|
480
|
+
if (!bridge.visibilityState.muted)
|
|
481
|
+
return;
|
|
482
|
+
mutations.forEach((mutation) => {
|
|
483
|
+
mutation.addedNodes.forEach((node) => {
|
|
484
|
+
// Check if the added node is a media element
|
|
485
|
+
if (node instanceof HTMLMediaElement) {
|
|
486
|
+
if (!node.muted) {
|
|
487
|
+
bridge.mediaOriginalMutedState.set(node, node.muted);
|
|
488
|
+
node.muted = true;
|
|
489
|
+
console.log('[TileBridge] 🔇 Auto-muted dynamically added media element');
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
// Also check children of added nodes
|
|
493
|
+
if (node instanceof Element) {
|
|
494
|
+
const mediaElements = node.querySelectorAll('video, audio');
|
|
495
|
+
mediaElements.forEach((element) => {
|
|
496
|
+
const media = element;
|
|
497
|
+
if (!media.muted) {
|
|
498
|
+
bridge.mediaOriginalMutedState.set(media, media.muted);
|
|
499
|
+
media.muted = true;
|
|
500
|
+
console.log('[TileBridge] 🔇 Auto-muted dynamically added media element (child)');
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
// Start observing the entire document for added nodes
|
|
508
|
+
observer.observe(document.documentElement, {
|
|
509
|
+
childList: true,
|
|
510
|
+
subtree: true,
|
|
511
|
+
});
|
|
512
|
+
console.log('[TileBridge] 👁️ MutationObserver active for dynamic media elements');
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* MANDATORY: Mute all audio sources when tile is offscreen
|
|
516
|
+
* - Mutes all <video> and <audio> elements
|
|
517
|
+
* - Suspends all registered AudioContexts (for WASM/Emscripten audio)
|
|
431
518
|
*/
|
|
432
519
|
muteAllMedia() {
|
|
433
520
|
if (typeof document === 'undefined')
|
|
434
521
|
return;
|
|
522
|
+
// 1. Mute all HTML media elements
|
|
435
523
|
const mediaElements = document.querySelectorAll('video, audio');
|
|
436
524
|
let mutedCount = 0;
|
|
437
525
|
mediaElements.forEach((element) => {
|
|
@@ -445,16 +533,29 @@ export class TileBridge {
|
|
|
445
533
|
mutedCount++;
|
|
446
534
|
}
|
|
447
535
|
});
|
|
448
|
-
|
|
449
|
-
|
|
536
|
+
// 2. Suspend all registered AudioContexts (for tiles like DOOM with WASM audio)
|
|
537
|
+
let suspendedCount = 0;
|
|
538
|
+
this.trackedAudioContexts.forEach((ctx) => {
|
|
539
|
+
if (ctx.state === 'running') {
|
|
540
|
+
ctx.suspend().catch((err) => {
|
|
541
|
+
console.warn('[TileBridge] Failed to suspend AudioContext:', err);
|
|
542
|
+
});
|
|
543
|
+
suspendedCount++;
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
if (mutedCount > 0 || suspendedCount > 0) {
|
|
547
|
+
console.log(`[TileBridge] 🔇 MUTED: ${mutedCount} media element(s), ${suspendedCount} AudioContext(s)`);
|
|
450
548
|
}
|
|
451
549
|
}
|
|
452
550
|
/**
|
|
453
|
-
* Unmute all
|
|
551
|
+
* Unmute all audio sources when tile becomes visible
|
|
552
|
+
* - Unmutes <video> and <audio> elements (restoring original state)
|
|
553
|
+
* - Resumes all registered AudioContexts
|
|
454
554
|
*/
|
|
455
555
|
unmuteAllMedia() {
|
|
456
556
|
if (typeof document === 'undefined')
|
|
457
557
|
return;
|
|
558
|
+
// 1. Unmute HTML media elements
|
|
458
559
|
const mediaElements = document.querySelectorAll('video, audio');
|
|
459
560
|
let unmutedCount = 0;
|
|
460
561
|
mediaElements.forEach((element) => {
|
|
@@ -471,8 +572,18 @@ export class TileBridge {
|
|
|
471
572
|
// Clear stored state
|
|
472
573
|
this.mediaOriginalMutedState.delete(media);
|
|
473
574
|
});
|
|
474
|
-
|
|
475
|
-
|
|
575
|
+
// 2. Resume all registered AudioContexts
|
|
576
|
+
let resumedCount = 0;
|
|
577
|
+
this.trackedAudioContexts.forEach((ctx) => {
|
|
578
|
+
if (ctx.state === 'suspended') {
|
|
579
|
+
ctx.resume().catch((err) => {
|
|
580
|
+
console.warn('[TileBridge] Failed to resume AudioContext:', err);
|
|
581
|
+
});
|
|
582
|
+
resumedCount++;
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
if (unmutedCount > 0 || resumedCount > 0) {
|
|
586
|
+
console.log(`[TileBridge] 🔊 UNMUTED: ${unmutedCount} media element(s), ${resumedCount} AudioContext(s)`);
|
|
476
587
|
}
|
|
477
588
|
}
|
|
478
589
|
sendToParent(message) {
|
|
@@ -1184,40 +1295,40 @@ export class TileBridge {
|
|
|
1184
1295
|
onVisibilityChange(handler) {
|
|
1185
1296
|
return this.on('visibility:update', handler);
|
|
1186
1297
|
}
|
|
1187
|
-
// =============
|
|
1298
|
+
// ============= AUDIOCONTEXT REGISTRATION API =============
|
|
1299
|
+
// Tiles with custom audio (WASM/Emscripten) MUST register their AudioContexts
|
|
1300
|
+
// to be automatically suspended/resumed when the tile goes offscreen.
|
|
1188
1301
|
/**
|
|
1189
|
-
*
|
|
1190
|
-
* By default, auto-mute is ENABLED - all <video> and <audio> elements are
|
|
1191
|
-
* automatically muted when the tile becomes hidden/offscreen.
|
|
1302
|
+
* Register an AudioContext for mandatory muting when tile goes offscreen.
|
|
1192
1303
|
*
|
|
1193
|
-
*
|
|
1194
|
-
*
|
|
1304
|
+
* PROTOCOL FOR TILES WITH CUSTOM AUDIO:
|
|
1305
|
+
* Tiles using AudioContext (e.g., Emscripten/WASM games like DOOM) MUST call
|
|
1306
|
+
* this method to register their AudioContext. The TileBridge will automatically
|
|
1307
|
+
* suspend/resume the context when the tile goes offscreen/onscreen.
|
|
1195
1308
|
*
|
|
1196
|
-
*
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
* Check if automatic audio muting is enabled
|
|
1204
|
-
*/
|
|
1205
|
-
isAutoMuteEnabled() {
|
|
1206
|
-
return this.autoMuteEnabled;
|
|
1207
|
-
}
|
|
1208
|
-
/**
|
|
1209
|
-
* Manually mute all audio/video elements
|
|
1210
|
-
* Useful for tiles that need to mute on demand
|
|
1309
|
+
* Example usage in DoomManager:
|
|
1310
|
+
* ```
|
|
1311
|
+
* const bridge = getTileBridge();
|
|
1312
|
+
* bridge.registerAudioContext(myAudioContext);
|
|
1313
|
+
* ```
|
|
1314
|
+
*
|
|
1315
|
+
* @param ctx - The AudioContext to track for automatic suspension
|
|
1211
1316
|
*/
|
|
1212
|
-
|
|
1213
|
-
this.
|
|
1317
|
+
registerAudioContext(ctx) {
|
|
1318
|
+
this.trackedAudioContexts.add(ctx);
|
|
1319
|
+
console.log(`[TileBridge] 🎵 AudioContext registered (${this.trackedAudioContexts.size} total)`);
|
|
1320
|
+
// If tile is currently muted, suspend immediately
|
|
1321
|
+
if (this.visibilityState.muted && ctx.state === 'running') {
|
|
1322
|
+
console.log('[TileBridge] 🔇 New AudioContext auto-suspended (tile is muted)');
|
|
1323
|
+
ctx.suspend().catch(() => { });
|
|
1324
|
+
}
|
|
1214
1325
|
}
|
|
1215
1326
|
/**
|
|
1216
|
-
*
|
|
1217
|
-
* Restores original muted state (elements that were muted before stay muted)
|
|
1327
|
+
* Unregister an AudioContext (e.g., when closing it)
|
|
1218
1328
|
*/
|
|
1219
|
-
|
|
1220
|
-
this.
|
|
1329
|
+
unregisterAudioContext(ctx) {
|
|
1330
|
+
this.trackedAudioContexts.delete(ctx);
|
|
1331
|
+
console.log(`[TileBridge] 🎵 AudioContext unregistered (${this.trackedAudioContexts.size} remaining)`);
|
|
1221
1332
|
}
|
|
1222
1333
|
/**
|
|
1223
1334
|
* Wait for ready state
|