@thewhateverapp/tile-sdk 0.11.0 → 0.11.2
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 +59 -0
- package/dist/bridge/TileBridge.d.ts.map +1 -1
- package/dist/bridge/TileBridge.js +237 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/react/overlay/VideoPlayer.d.ts.map +1 -1
- package/dist/react/overlay/VideoPlayer.js +34 -0
- package/package.json +1 -1
|
@@ -33,6 +33,12 @@ export interface KeyboardState {
|
|
|
33
33
|
visible: boolean;
|
|
34
34
|
height: number;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Visibility state from parent (for TikTok-style feeds where tiles are preloaded)
|
|
38
|
+
*/
|
|
39
|
+
export interface VisibilityState {
|
|
40
|
+
visible: boolean;
|
|
41
|
+
}
|
|
36
42
|
type NextRouter = {
|
|
37
43
|
push: (href: string) => void;
|
|
38
44
|
replace: (href: string) => void;
|
|
@@ -49,8 +55,38 @@ export declare class TileBridge {
|
|
|
49
55
|
private currentToken;
|
|
50
56
|
private tokenExpiresAt;
|
|
51
57
|
private keyboardState;
|
|
58
|
+
private visibilityState;
|
|
59
|
+
private baselineViewportHeight;
|
|
60
|
+
private keyboardDetectionEnabled;
|
|
61
|
+
private lastReportedKeyboardState;
|
|
62
|
+
private viewportDebounceTimer;
|
|
63
|
+
private readonly KEYBOARD_THRESHOLD;
|
|
64
|
+
private readonly DEBOUNCE_MS;
|
|
65
|
+
private inputFocused;
|
|
66
|
+
private focusedElement;
|
|
52
67
|
constructor(expectedOrigin?: string, router?: NextRouter);
|
|
53
68
|
private initialize;
|
|
69
|
+
/**
|
|
70
|
+
* Initialize keyboard detection using VisualViewport API and input focus tracking
|
|
71
|
+
* This runs inside the WebView to detect when the soft keyboard appears
|
|
72
|
+
*/
|
|
73
|
+
private initializeKeyboardDetection;
|
|
74
|
+
/**
|
|
75
|
+
* Process viewport change and determine keyboard state
|
|
76
|
+
*/
|
|
77
|
+
private processViewportChange;
|
|
78
|
+
/**
|
|
79
|
+
* Handle focusin events - input element gained focus
|
|
80
|
+
*/
|
|
81
|
+
private handleFocusIn;
|
|
82
|
+
/**
|
|
83
|
+
* Handle focusout events - input element lost focus
|
|
84
|
+
*/
|
|
85
|
+
private handleFocusOut;
|
|
86
|
+
/**
|
|
87
|
+
* Check if an element is an input-like element that would trigger keyboard
|
|
88
|
+
*/
|
|
89
|
+
private isInputElement;
|
|
54
90
|
private handleMessage;
|
|
55
91
|
private handleConfig;
|
|
56
92
|
private handleResponse;
|
|
@@ -64,6 +100,11 @@ export declare class TileBridge {
|
|
|
64
100
|
* Handle keyboard state message from parent (mobile app)
|
|
65
101
|
*/
|
|
66
102
|
private handleKeyboard;
|
|
103
|
+
/**
|
|
104
|
+
* Handle visibility state message from parent (mobile app)
|
|
105
|
+
* Used for TikTok-style feeds where tiles are preloaded but not visible
|
|
106
|
+
*/
|
|
107
|
+
private handleVisibility;
|
|
67
108
|
private sendToParent;
|
|
68
109
|
/**
|
|
69
110
|
* Request to navigate to full page view
|
|
@@ -252,6 +293,24 @@ export declare class TileBridge {
|
|
|
252
293
|
* @returns Unsubscribe function
|
|
253
294
|
*/
|
|
254
295
|
onKeyboardChange(handler: (state: KeyboardState) => void): () => void;
|
|
296
|
+
/**
|
|
297
|
+
* Get the current visibility state from parent
|
|
298
|
+
* For TikTok-style feeds where tiles may be preloaded but not visible
|
|
299
|
+
* Returns { visible: true } by default (for web/non-mobile platforms)
|
|
300
|
+
*/
|
|
301
|
+
getVisibilityState(): VisibilityState;
|
|
302
|
+
/**
|
|
303
|
+
* Check if the tile is currently visible
|
|
304
|
+
* Returns true by default (for standalone tiles not in a feed)
|
|
305
|
+
*/
|
|
306
|
+
isVisible(): boolean;
|
|
307
|
+
/**
|
|
308
|
+
* Subscribe to visibility state changes
|
|
309
|
+
* Called when parent sends visibility show/hide events (e.g., TikTok-style feed)
|
|
310
|
+
* @param handler Callback receiving { visible: boolean }
|
|
311
|
+
* @returns Unsubscribe function
|
|
312
|
+
*/
|
|
313
|
+
onVisibilityChange(handler: (state: VisibilityState) => void): () => void;
|
|
255
314
|
/**
|
|
256
315
|
* Wait for ready state
|
|
257
316
|
*
|
|
@@ -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;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;
|
|
1
|
+
{"version":3,"file":"TileBridge.d.ts","sourceRoot":"","sources":["../../src/bridge/TileBridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,18 @@ export class TileBridge {
|
|
|
15
15
|
this.tokenExpiresAt = null;
|
|
16
16
|
// Keyboard state from parent (mobile app)
|
|
17
17
|
this.keyboardState = { visible: false, height: 0 };
|
|
18
|
+
// Visibility state from parent (for TikTok-style feeds)
|
|
19
|
+
this.visibilityState = { visible: true };
|
|
20
|
+
// VisualViewport tracking for keyboard detection (Plan A)
|
|
21
|
+
this.baselineViewportHeight = 0;
|
|
22
|
+
this.keyboardDetectionEnabled = false;
|
|
23
|
+
this.lastReportedKeyboardState = { visible: false, height: 0 };
|
|
24
|
+
this.viewportDebounceTimer = null;
|
|
25
|
+
this.KEYBOARD_THRESHOLD = 150; // Minimum height change to consider keyboard visible
|
|
26
|
+
this.DEBOUNCE_MS = 100; // Debounce viewport events
|
|
27
|
+
// Input focus tracking (Plan B)
|
|
28
|
+
this.inputFocused = false;
|
|
29
|
+
this.focusedElement = null;
|
|
18
30
|
this.parentOrigin = expectedOrigin;
|
|
19
31
|
this.router = router || null;
|
|
20
32
|
// Auto-detect parent origin based on environment
|
|
@@ -66,6 +78,182 @@ export class TileBridge {
|
|
|
66
78
|
this.ready = true;
|
|
67
79
|
// Send ready signal to parent
|
|
68
80
|
this.sendToParent({ type: 'tile:ready' });
|
|
81
|
+
// Initialize keyboard detection (Plan A: VisualViewport + Plan B: Input Focus)
|
|
82
|
+
this.initializeKeyboardDetection();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Initialize keyboard detection using VisualViewport API and input focus tracking
|
|
86
|
+
* This runs inside the WebView to detect when the soft keyboard appears
|
|
87
|
+
*/
|
|
88
|
+
initializeKeyboardDetection() {
|
|
89
|
+
if (typeof window === 'undefined')
|
|
90
|
+
return;
|
|
91
|
+
// Only enable in iframe context (when running in mobile WebView)
|
|
92
|
+
if (!this.isInIframe()) {
|
|
93
|
+
if (this.isDevelopment()) {
|
|
94
|
+
console.log('[TileBridge] Keyboard detection skipped - not in iframe');
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.keyboardDetectionEnabled = true;
|
|
99
|
+
// ==================== PLAN A: VisualViewport Detection ====================
|
|
100
|
+
// Use VisualViewport API to detect keyboard by monitoring viewport height changes
|
|
101
|
+
const vv = window.visualViewport;
|
|
102
|
+
if (vv) {
|
|
103
|
+
// Set baseline height (full viewport without keyboard)
|
|
104
|
+
this.baselineViewportHeight = vv.height;
|
|
105
|
+
if (this.isDevelopment() || this.isPreview()) {
|
|
106
|
+
console.log('[TileBridge] ⌨️ Keyboard detection initialized', {
|
|
107
|
+
baselineHeight: this.baselineViewportHeight,
|
|
108
|
+
hasVisualViewport: true,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// Handler for viewport resize events
|
|
112
|
+
const handleViewportChange = () => {
|
|
113
|
+
if (!this.keyboardDetectionEnabled)
|
|
114
|
+
return;
|
|
115
|
+
// Debounce rapid events
|
|
116
|
+
if (this.viewportDebounceTimer) {
|
|
117
|
+
clearTimeout(this.viewportDebounceTimer);
|
|
118
|
+
}
|
|
119
|
+
this.viewportDebounceTimer = setTimeout(() => {
|
|
120
|
+
this.processViewportChange();
|
|
121
|
+
}, this.DEBOUNCE_MS);
|
|
122
|
+
};
|
|
123
|
+
// Subscribe to resize and scroll events on visualViewport
|
|
124
|
+
vv.addEventListener('resize', handleViewportChange);
|
|
125
|
+
vv.addEventListener('scroll', handleViewportChange);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
if (this.isDevelopment() || this.isPreview()) {
|
|
129
|
+
console.log('[TileBridge] ⌨️ VisualViewport not available, using focus-only detection');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// ==================== PLAN B: Input Focus Detection ====================
|
|
133
|
+
// Track when input elements are focused/blurred
|
|
134
|
+
document.addEventListener('focusin', this.handleFocusIn.bind(this));
|
|
135
|
+
document.addEventListener('focusout', this.handleFocusOut.bind(this));
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Process viewport change and determine keyboard state
|
|
139
|
+
*/
|
|
140
|
+
processViewportChange() {
|
|
141
|
+
const vv = window.visualViewport;
|
|
142
|
+
if (!vv)
|
|
143
|
+
return;
|
|
144
|
+
const currentHeight = vv.height;
|
|
145
|
+
const heightDiff = this.baselineViewportHeight - currentHeight;
|
|
146
|
+
const keyboardVisible = heightDiff > this.KEYBOARD_THRESHOLD;
|
|
147
|
+
const keyboardHeight = keyboardVisible ? Math.round(heightDiff) : 0;
|
|
148
|
+
// Only report if state changed
|
|
149
|
+
if (this.lastReportedKeyboardState.visible !== keyboardVisible ||
|
|
150
|
+
Math.abs(this.lastReportedKeyboardState.height - keyboardHeight) > 10) {
|
|
151
|
+
this.lastReportedKeyboardState = { visible: keyboardVisible, height: keyboardHeight };
|
|
152
|
+
if (this.isDevelopment() || this.isPreview()) {
|
|
153
|
+
console.log('[TileBridge] ⌨️ Keyboard state changed (viewport)', {
|
|
154
|
+
visible: keyboardVisible,
|
|
155
|
+
height: keyboardHeight,
|
|
156
|
+
baselineHeight: this.baselineViewportHeight,
|
|
157
|
+
currentHeight,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
// Send keyboard state to parent (React Native)
|
|
161
|
+
this.sendToParent({
|
|
162
|
+
type: 'tile:keyboard',
|
|
163
|
+
payload: {
|
|
164
|
+
visible: keyboardVisible,
|
|
165
|
+
height: keyboardHeight,
|
|
166
|
+
source: 'viewport',
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
// Update baseline when keyboard is hidden (to handle orientation changes)
|
|
171
|
+
if (!keyboardVisible && currentHeight > this.baselineViewportHeight) {
|
|
172
|
+
this.baselineViewportHeight = currentHeight;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Handle focusin events - input element gained focus
|
|
177
|
+
*/
|
|
178
|
+
handleFocusIn(event) {
|
|
179
|
+
const target = event.target;
|
|
180
|
+
if (!target)
|
|
181
|
+
return;
|
|
182
|
+
// Check if the focused element is an input-like element
|
|
183
|
+
const isInputLike = this.isInputElement(target);
|
|
184
|
+
if (isInputLike && !this.inputFocused) {
|
|
185
|
+
this.inputFocused = true;
|
|
186
|
+
this.focusedElement = target;
|
|
187
|
+
if (this.isDevelopment() || this.isPreview()) {
|
|
188
|
+
console.log('[TileBridge] ⌨️ Input focused', {
|
|
189
|
+
tagName: target.tagName,
|
|
190
|
+
type: target.type,
|
|
191
|
+
id: target.id,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
// Send focus event to parent
|
|
195
|
+
this.sendToParent({
|
|
196
|
+
type: 'tile:input-focused',
|
|
197
|
+
payload: {
|
|
198
|
+
tagName: target.tagName.toLowerCase(),
|
|
199
|
+
type: target.type || null,
|
|
200
|
+
id: target.id || null,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Handle focusout events - input element lost focus
|
|
207
|
+
*/
|
|
208
|
+
handleFocusOut(event) {
|
|
209
|
+
const target = event.target;
|
|
210
|
+
if (!target)
|
|
211
|
+
return;
|
|
212
|
+
// Check if the blurred element is the one we're tracking
|
|
213
|
+
const isInputLike = this.isInputElement(target);
|
|
214
|
+
if (isInputLike && this.inputFocused) {
|
|
215
|
+
// Use setTimeout to check if focus moved to another input
|
|
216
|
+
// If relatedTarget is another input, we stay in "focused" state
|
|
217
|
+
setTimeout(() => {
|
|
218
|
+
const activeElement = document.activeElement;
|
|
219
|
+
const stillFocused = activeElement && this.isInputElement(activeElement);
|
|
220
|
+
if (!stillFocused) {
|
|
221
|
+
this.inputFocused = false;
|
|
222
|
+
this.focusedElement = null;
|
|
223
|
+
if (this.isDevelopment() || this.isPreview()) {
|
|
224
|
+
console.log('[TileBridge] ⌨️ Input blurred');
|
|
225
|
+
}
|
|
226
|
+
// Send blur event to parent
|
|
227
|
+
this.sendToParent({
|
|
228
|
+
type: 'tile:input-blurred',
|
|
229
|
+
payload: {},
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}, 10);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Check if an element is an input-like element that would trigger keyboard
|
|
237
|
+
*/
|
|
238
|
+
isInputElement(element) {
|
|
239
|
+
const tagName = element.tagName.toLowerCase();
|
|
240
|
+
// Standard input elements
|
|
241
|
+
if (tagName === 'textarea')
|
|
242
|
+
return true;
|
|
243
|
+
if (tagName === 'select')
|
|
244
|
+
return false; // Select doesn't trigger keyboard
|
|
245
|
+
if (tagName === 'input') {
|
|
246
|
+
const inputType = element.type?.toLowerCase();
|
|
247
|
+
// These input types trigger the keyboard
|
|
248
|
+
const keyboardTypes = ['text', 'email', 'password', 'search', 'tel', 'url', 'number'];
|
|
249
|
+
return !inputType || keyboardTypes.includes(inputType);
|
|
250
|
+
}
|
|
251
|
+
// Contenteditable elements
|
|
252
|
+
if (element.hasAttribute('contenteditable')) {
|
|
253
|
+
const value = element.getAttribute('contenteditable');
|
|
254
|
+
return value === 'true' || value === '';
|
|
255
|
+
}
|
|
256
|
+
return false;
|
|
69
257
|
}
|
|
70
258
|
handleMessage(event) {
|
|
71
259
|
// Validate origin (skip validation in dev and preview)
|
|
@@ -101,6 +289,9 @@ export class TileBridge {
|
|
|
101
289
|
case 'parent:keyboard':
|
|
102
290
|
this.handleKeyboard(message.payload);
|
|
103
291
|
break;
|
|
292
|
+
case 'parent:visibility':
|
|
293
|
+
this.handleVisibility(message.payload);
|
|
294
|
+
break;
|
|
104
295
|
case 'parent:navigateToPage':
|
|
105
296
|
this.handleParentNavigate({ target: 'page' });
|
|
106
297
|
break;
|
|
@@ -195,6 +386,27 @@ export class TileBridge {
|
|
|
195
386
|
// Emit keyboard update event for listeners
|
|
196
387
|
this.emitEvent('keyboard:update', this.keyboardState);
|
|
197
388
|
}
|
|
389
|
+
/**
|
|
390
|
+
* Handle visibility state message from parent (mobile app)
|
|
391
|
+
* Used for TikTok-style feeds where tiles are preloaded but not visible
|
|
392
|
+
*/
|
|
393
|
+
handleVisibility(payload) {
|
|
394
|
+
if (payload?.visible === undefined) {
|
|
395
|
+
console.warn('[TileBridge] Invalid visibility payload received');
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const wasVisible = this.visibilityState.visible;
|
|
399
|
+
this.visibilityState = {
|
|
400
|
+
visible: !!payload.visible,
|
|
401
|
+
};
|
|
402
|
+
if (this.isDevelopment() || this.isPreview()) {
|
|
403
|
+
console.log('[TileBridge] 👁️ Received visibility state', this.visibilityState);
|
|
404
|
+
}
|
|
405
|
+
// Only emit if visibility actually changed
|
|
406
|
+
if (wasVisible !== this.visibilityState.visible) {
|
|
407
|
+
this.emitEvent('visibility:update', this.visibilityState);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
198
410
|
sendToParent(message) {
|
|
199
411
|
if (typeof window === 'undefined' || !window.parent)
|
|
200
412
|
return;
|
|
@@ -874,6 +1086,31 @@ export class TileBridge {
|
|
|
874
1086
|
onKeyboardChange(handler) {
|
|
875
1087
|
return this.on('keyboard:update', handler);
|
|
876
1088
|
}
|
|
1089
|
+
// ============= VISIBILITY API =============
|
|
1090
|
+
/**
|
|
1091
|
+
* Get the current visibility state from parent
|
|
1092
|
+
* For TikTok-style feeds where tiles may be preloaded but not visible
|
|
1093
|
+
* Returns { visible: true } by default (for web/non-mobile platforms)
|
|
1094
|
+
*/
|
|
1095
|
+
getVisibilityState() {
|
|
1096
|
+
return { ...this.visibilityState };
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Check if the tile is currently visible
|
|
1100
|
+
* Returns true by default (for standalone tiles not in a feed)
|
|
1101
|
+
*/
|
|
1102
|
+
isVisible() {
|
|
1103
|
+
return this.visibilityState.visible;
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Subscribe to visibility state changes
|
|
1107
|
+
* Called when parent sends visibility show/hide events (e.g., TikTok-style feed)
|
|
1108
|
+
* @param handler Callback receiving { visible: boolean }
|
|
1109
|
+
* @returns Unsubscribe function
|
|
1110
|
+
*/
|
|
1111
|
+
onVisibilityChange(handler) {
|
|
1112
|
+
return this.on('visibility:update', handler);
|
|
1113
|
+
}
|
|
877
1114
|
/**
|
|
878
1115
|
* Wait for ready state
|
|
879
1116
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { withTile } from './react/withTile';
|
|
|
7
7
|
export { VideoPlayer, useVideoState, Slideshow, useSlideshowState, OverlaySlot, FullOverlay, GradientOverlay, } from './react/overlay';
|
|
8
8
|
export type { VideoState, VideoControls, VideoContextValue, VideoPlayerProps, SlideImage, SlideshowState, SlideshowControls, SlideshowContextValue, SlideshowProps, SlotPosition, OverlaySlotProps, FullOverlayProps, GradientOverlayProps, } from './react/overlay';
|
|
9
9
|
export { getTileBridge, TileBridge } from './bridge/TileBridge';
|
|
10
|
-
export type { TileMessage, TileConfig, TileTokenData, KeyboardState } from './bridge/TileBridge';
|
|
10
|
+
export type { TileMessage, TileConfig, TileTokenData, KeyboardState, VisibilityState } from './bridge/TileBridge';
|
|
11
11
|
export { StateClient } from './state/StateClient';
|
|
12
12
|
export type { TileStats, ViewResponse } from './state/StateClient';
|
|
13
13
|
export * from './tools';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C,OAAO,EAEL,WAAW,EACX,aAAa,EAEb,SAAS,EACT,iBAAiB,EAEjB,WAAW,EACX,WAAW,EACX,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAEV,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAEhB,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EAEd,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAChE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C,OAAO,EAEL,WAAW,EACX,aAAa,EAEb,SAAS,EACT,iBAAiB,EAEjB,WAAW,EACX,WAAW,EACX,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAEV,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAEhB,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EAEd,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAChE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGlH,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnE,cAAc,SAAS,CAAC;AAGxB,cAAc,SAAS,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoPlayer.d.ts","sourceRoot":"","sources":["../../../src/react/overlay/VideoPlayer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAOZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"VideoPlayer.d.ts","sourceRoot":"","sources":["../../../src/react/overlay/VideoPlayer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import React, { createContext, useContext, useEffect, useRef, useState, useCallback, } from 'react';
|
|
3
|
+
import { getTileBridge } from '../../bridge/TileBridge';
|
|
3
4
|
const VideoContext = createContext(null);
|
|
4
5
|
/**
|
|
5
6
|
* VideoPlayer component with HLS streaming support.
|
|
@@ -155,6 +156,39 @@ export function VideoPlayer({ src, autoplay = true, loop = false, muted = true,
|
|
|
155
156
|
video.removeEventListener('error', handleError);
|
|
156
157
|
};
|
|
157
158
|
}, []);
|
|
159
|
+
// Visibility handling - play/pause based on tile visibility
|
|
160
|
+
// Enables TikTok-style preloaded video tiles that only play when visible
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
const video = videoRef.current;
|
|
163
|
+
if (!video || !autoplay)
|
|
164
|
+
return;
|
|
165
|
+
try {
|
|
166
|
+
const bridge = getTileBridge();
|
|
167
|
+
// Handle visibility changes from parent (mobile app)
|
|
168
|
+
const unsubscribe = bridge.onVisibilityChange((visibilityState) => {
|
|
169
|
+
if (visibilityState.visible) {
|
|
170
|
+
// Tile became visible - play the video
|
|
171
|
+
video.play().catch(() => { });
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// Tile became hidden - pause the video
|
|
175
|
+
video.pause();
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
// Check initial visibility state
|
|
179
|
+
if (!bridge.isVisible()) {
|
|
180
|
+
// If not visible on mount, pause any autoplay
|
|
181
|
+
video.pause();
|
|
182
|
+
}
|
|
183
|
+
return () => {
|
|
184
|
+
unsubscribe();
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Bridge not available (e.g., in SSR or outside TileProvider)
|
|
189
|
+
// Video will just use autoplay behavior
|
|
190
|
+
}
|
|
191
|
+
}, [autoplay]);
|
|
158
192
|
const contextValue = {
|
|
159
193
|
state,
|
|
160
194
|
controls: { play, pause, toggle, seek, setVolume, setMuted },
|