unified-video-framework 1.4.365 → 1.4.366
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/package.json +1 -1
- package/packages/core/dist/interfaces/IVideoPlayer.d.ts +0 -1
- package/packages/core/dist/interfaces/IVideoPlayer.d.ts.map +1 -1
- package/packages/core/dist/interfaces.d.ts +1 -1
- package/packages/core/dist/interfaces.d.ts.map +1 -1
- package/packages/core/src/interfaces/IVideoPlayer.ts +1 -2
- package/packages/core/src/interfaces.ts +4 -1
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +1 -33
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/dist/chapters/ChapterManager.d.ts +4 -3
- package/packages/web/dist/chapters/ChapterManager.d.ts.map +1 -1
- package/packages/web/dist/chapters/ChapterManager.js +52 -34
- package/packages/web/dist/chapters/ChapterManager.js.map +1 -1
- package/packages/web/dist/chapters/CreditsButtonController.d.ts +44 -0
- package/packages/web/dist/chapters/CreditsButtonController.d.ts.map +1 -0
- package/packages/web/dist/chapters/CreditsButtonController.js +243 -0
- package/packages/web/dist/chapters/CreditsButtonController.js.map +1 -0
- package/packages/web/dist/chapters/SkipButtonController.d.ts +1 -4
- package/packages/web/dist/chapters/SkipButtonController.d.ts.map +1 -1
- package/packages/web/dist/chapters/SkipButtonController.js +22 -118
- package/packages/web/dist/chapters/SkipButtonController.js.map +1 -1
- package/packages/web/dist/chapters/types/ChapterTypes.d.ts +21 -12
- package/packages/web/dist/chapters/types/ChapterTypes.d.ts.map +1 -1
- package/packages/web/dist/chapters/types/ChapterTypes.js.map +1 -1
- package/packages/web/dist/react/WebPlayerView.d.ts +0 -10
- package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
- package/packages/web/dist/react/WebPlayerView.js +1 -5
- package/packages/web/dist/react/WebPlayerView.js.map +1 -1
- package/packages/web/dist/react/components/SkipButton.d.ts +0 -2
- package/packages/web/dist/react/components/SkipButton.d.ts.map +1 -1
- package/packages/web/dist/react/components/SkipButton.js +13 -30
- package/packages/web/dist/react/components/SkipButton.js.map +1 -1
- package/packages/web/src/WebPlayer.ts +7475 -7507
- package/packages/web/src/chapters/ChapterManager.ts +76 -57
- package/packages/web/src/chapters/CreditsButtonController.ts +392 -0
- package/packages/web/src/chapters/SkipButtonController.ts +33 -148
- package/packages/web/src/chapters/types/ChapterTypes.ts +34 -19
- package/packages/web/src/react/WebPlayerView.tsx +1 -14
- package/packages/web/src/react/components/SkipButton.tsx +24 -82
|
@@ -12,21 +12,18 @@ import {
|
|
|
12
12
|
|
|
13
13
|
export class SkipButtonController {
|
|
14
14
|
private skipButton: HTMLElement | null = null;
|
|
15
|
-
private secondaryButton: HTMLElement | null = null;
|
|
16
|
-
private container: HTMLElement | null = null;
|
|
17
15
|
private currentSegment: VideoSegment | null = null;
|
|
18
16
|
private autoSkipTimeout: NodeJS.Timeout | null = null;
|
|
19
17
|
private hideTimeout: NodeJS.Timeout | null = null;
|
|
20
18
|
private countdownInterval: NodeJS.Timeout | null = null;
|
|
21
19
|
private state: SkipButtonState;
|
|
22
|
-
|
|
20
|
+
|
|
23
21
|
constructor(
|
|
24
22
|
private playerContainer: HTMLElement,
|
|
25
23
|
private config: ChapterConfig,
|
|
26
24
|
private onSkip: (segment: VideoSegment) => void,
|
|
27
25
|
private onButtonShown: (segment: VideoSegment) => void,
|
|
28
|
-
private onButtonHidden: (segment: VideoSegment, reason: string) => void
|
|
29
|
-
private onSecondaryAction?: (segment: VideoSegment) => void
|
|
26
|
+
private onButtonHidden: (segment: VideoSegment, reason: string) => void
|
|
30
27
|
) {
|
|
31
28
|
this.state = {
|
|
32
29
|
visible: false,
|
|
@@ -53,13 +50,9 @@ export class SkipButtonController {
|
|
|
53
50
|
this.state.segment = segment;
|
|
54
51
|
|
|
55
52
|
// Create button if it doesn't exist
|
|
56
|
-
if (!this.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
this.playerContainer.appendChild(containerElement);
|
|
60
|
-
console.log('[SkipButtonController] Container appended to player. Container:', this.container);
|
|
61
|
-
} else {
|
|
62
|
-
console.log('[SkipButtonController] Container already exists, reusing it');
|
|
53
|
+
if (!this.skipButton) {
|
|
54
|
+
this.skipButton = this.createSkipButton();
|
|
55
|
+
this.playerContainer.appendChild(this.skipButton);
|
|
63
56
|
}
|
|
64
57
|
|
|
65
58
|
// Update button content and show it
|
|
@@ -126,13 +119,7 @@ export class SkipButtonController {
|
|
|
126
119
|
*/
|
|
127
120
|
public destroy(): void {
|
|
128
121
|
this.clearTimeouts();
|
|
129
|
-
if (this.
|
|
130
|
-
this.container.remove();
|
|
131
|
-
this.container = null;
|
|
132
|
-
this.skipButton = null;
|
|
133
|
-
this.secondaryButton = null;
|
|
134
|
-
} else if (this.skipButton) {
|
|
135
|
-
// Fallback if no container (shouldn't happen with new logic but safe to keep)
|
|
122
|
+
if (this.skipButton) {
|
|
136
123
|
this.skipButton.remove();
|
|
137
124
|
this.skipButton = null;
|
|
138
125
|
}
|
|
@@ -142,70 +129,31 @@ export class SkipButtonController {
|
|
|
142
129
|
}
|
|
143
130
|
|
|
144
131
|
/**
|
|
145
|
-
* Create the skip button DOM element
|
|
132
|
+
* Create the skip button DOM element
|
|
146
133
|
*/
|
|
147
134
|
private createSkipButton(): HTMLElement {
|
|
148
|
-
// Create container
|
|
149
|
-
const container = document.createElement('div');
|
|
150
|
-
container.className = 'uvf-skip-container';
|
|
151
|
-
this.container = container;
|
|
152
|
-
|
|
153
|
-
// Create secondary button (Watch Credits) - initially hidden
|
|
154
|
-
const secondaryBtn = document.createElement('button');
|
|
155
|
-
secondaryBtn.className = 'uvf-skip-button uvf-skip-secondary';
|
|
156
|
-
secondaryBtn.setAttribute('type', 'button');
|
|
157
|
-
secondaryBtn.textContent = 'Watch Credits';
|
|
158
|
-
secondaryBtn.style.display = 'none'; // Hidden by default
|
|
159
|
-
secondaryBtn.style.marginRight = '10px';
|
|
160
|
-
secondaryBtn.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
|
|
161
|
-
secondaryBtn.style.border = '1px solid rgba(255, 255, 255, 0.5)';
|
|
162
|
-
|
|
163
|
-
// Explicit click listener for secondary button
|
|
164
|
-
secondaryBtn.addEventListener('click', (e) => {
|
|
165
|
-
e.stopPropagation(); // prevent triggering other things
|
|
166
|
-
if (this.currentSegment && this.onSecondaryAction) {
|
|
167
|
-
this.clearTimeouts(); // Stop auto-skip
|
|
168
|
-
this.onSecondaryAction(this.currentSegment);
|
|
169
|
-
// Hide the container after action
|
|
170
|
-
this.hideSkipButton('user-action');
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
this.secondaryButton = secondaryBtn;
|
|
174
|
-
container.appendChild(secondaryBtn);
|
|
175
|
-
|
|
176
|
-
// Create primary button
|
|
177
135
|
const button = document.createElement('button');
|
|
178
136
|
button.className = 'uvf-skip-button';
|
|
179
137
|
button.setAttribute('type', 'button');
|
|
180
138
|
button.setAttribute('aria-label', 'Skip segment');
|
|
181
139
|
|
|
140
|
+
// Apply position styles
|
|
141
|
+
this.applyPositionStyles(button, this.state.position);
|
|
142
|
+
|
|
182
143
|
// Add click handler
|
|
183
144
|
button.addEventListener('click', () => {
|
|
184
|
-
console.log('[SkipButtonController] Primary button (Next Episode) clicked! Current segment:', this.currentSegment?.type);
|
|
185
145
|
if (this.currentSegment) {
|
|
186
146
|
this.onSkip(this.currentSegment);
|
|
187
147
|
this.hideSkipButton('user-action');
|
|
188
148
|
}
|
|
189
149
|
});
|
|
190
150
|
|
|
191
|
-
|
|
192
|
-
container.appendChild(button);
|
|
193
|
-
|
|
194
|
-
// Apply custom styles if provided (to the primary button mostly)
|
|
151
|
+
// Apply custom styles if provided
|
|
195
152
|
if (this.config.customStyles?.skipButton) {
|
|
196
153
|
Object.assign(button.style, this.config.customStyles.skipButton);
|
|
197
154
|
}
|
|
198
155
|
|
|
199
|
-
|
|
200
|
-
this.applyPositionStyles(container, this.state.position);
|
|
201
|
-
|
|
202
|
-
// Add flex layout for side-by-side buttons
|
|
203
|
-
container.style.display = 'flex';
|
|
204
|
-
container.style.flexDirection = 'row';
|
|
205
|
-
container.style.alignItems = 'center';
|
|
206
|
-
container.style.gap = '10px';
|
|
207
|
-
|
|
208
|
-
return container;
|
|
156
|
+
return button;
|
|
209
157
|
}
|
|
210
158
|
|
|
211
159
|
/**
|
|
@@ -215,75 +163,33 @@ export class SkipButtonController {
|
|
|
215
163
|
if (!this.skipButton) return;
|
|
216
164
|
|
|
217
165
|
// Set button text
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
// Check for Next Episode override
|
|
221
|
-
let showSecondary = false;
|
|
222
|
-
// We infer it's next episode scenario if it's credits and we have a config for it
|
|
223
|
-
// AND secondary action is available.
|
|
224
|
-
// Ideally we checked for `nextEpisode` config in ChapterManager, but here we only know if onSecondaryAction exists.
|
|
225
|
-
// Let's assume onSecondaryAction implies we support it.
|
|
226
|
-
if (segment.type === 'credits' && this.onSecondaryAction) {
|
|
227
|
-
console.log('[SkipButtonController] Credits segment detected, nextEpisode config:', this.config.nextEpisode);
|
|
228
|
-
if (this.config.nextEpisode?.title) {
|
|
229
|
-
skipLabel = `Next Episode: ${this.config.nextEpisode.title}`;
|
|
230
|
-
} else {
|
|
231
|
-
skipLabel = "Next Episode";
|
|
232
|
-
}
|
|
233
|
-
showSecondary = true;
|
|
234
|
-
} else {
|
|
235
|
-
console.log('[SkipButtonController] Not showing secondary button. segment.type:', segment.type, 'onSecondaryAction:', !!this.onSecondaryAction);
|
|
236
|
-
}
|
|
237
|
-
|
|
166
|
+
const skipLabel = segment.skipLabel || DEFAULT_SKIP_LABELS[segment.type];
|
|
238
167
|
this.skipButton.textContent = skipLabel;
|
|
239
168
|
|
|
240
|
-
// Handle Secondary Button Visibility
|
|
241
|
-
if (this.secondaryButton) {
|
|
242
|
-
console.log('[SkipButtonController] Secondary button exists. showSecondary:', showSecondary, 'onSecondaryAction:', !!this.onSecondaryAction);
|
|
243
|
-
if (showSecondary && this.onSecondaryAction) {
|
|
244
|
-
console.log('[SkipButtonController] Showing Watch Credits button');
|
|
245
|
-
this.secondaryButton.style.display = 'inline-block';
|
|
246
|
-
if (this.config.customStyles?.skipButton) {
|
|
247
|
-
// Inherit some basic styles like font size if available
|
|
248
|
-
if (this.config.customStyles.skipButton.fontSize) {
|
|
249
|
-
this.secondaryButton.style.fontSize = this.config.customStyles.skipButton.fontSize;
|
|
250
|
-
}
|
|
251
|
-
if (this.config.customStyles.skipButton.padding) {
|
|
252
|
-
this.secondaryButton.style.padding = this.config.customStyles.skipButton.padding;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
} else {
|
|
256
|
-
console.log('[SkipButtonController] Hiding Watch Credits button');
|
|
257
|
-
this.secondaryButton.style.display = 'none';
|
|
258
|
-
}
|
|
259
|
-
} else {
|
|
260
|
-
console.log('[SkipButtonController] Secondary button element does not exist!');
|
|
261
|
-
}
|
|
262
|
-
|
|
263
169
|
// Update aria-label for accessibility
|
|
264
170
|
this.skipButton.setAttribute('aria-label', `${skipLabel} - ${segment.title || segment.type}`);
|
|
265
171
|
|
|
266
172
|
// Add segment type class for styling
|
|
267
173
|
this.skipButton.className = `uvf-skip-button uvf-skip-${segment.type}`;
|
|
268
|
-
|
|
269
|
-
//
|
|
270
|
-
this.
|
|
174
|
+
|
|
175
|
+
// Apply position class
|
|
176
|
+
this.skipButton.classList.add(`uvf-skip-button-${this.state.position}`);
|
|
271
177
|
}
|
|
272
178
|
|
|
273
179
|
/**
|
|
274
|
-
* Apply position styles to
|
|
180
|
+
* Apply position styles to skip button
|
|
275
181
|
*/
|
|
276
|
-
private applyPositionStyles(
|
|
182
|
+
private applyPositionStyles(button: HTMLElement, position: SkipButtonPosition): void {
|
|
277
183
|
// Reset position classes
|
|
278
|
-
|
|
184
|
+
button.classList.remove(
|
|
279
185
|
'uvf-skip-button-bottom-right',
|
|
280
|
-
'uvf-skip-button-bottom-left',
|
|
186
|
+
'uvf-skip-button-bottom-left',
|
|
281
187
|
'uvf-skip-button-top-right',
|
|
282
188
|
'uvf-skip-button-top-left'
|
|
283
189
|
);
|
|
284
190
|
|
|
285
191
|
// Add new position class
|
|
286
|
-
|
|
192
|
+
button.classList.add(`uvf-skip-button-${position}`);
|
|
287
193
|
|
|
288
194
|
// Apply CSS styles based on position
|
|
289
195
|
const styles: Partial<CSSStyleDeclaration> = {
|
|
@@ -295,58 +201,39 @@ export class SkipButtonController {
|
|
|
295
201
|
case 'bottom-right':
|
|
296
202
|
Object.assign(styles, {
|
|
297
203
|
bottom: '100px',
|
|
298
|
-
right: '30px'
|
|
299
|
-
top: 'auto',
|
|
300
|
-
left: 'auto'
|
|
204
|
+
right: '30px'
|
|
301
205
|
});
|
|
302
206
|
break;
|
|
303
207
|
case 'bottom-left':
|
|
304
208
|
Object.assign(styles, {
|
|
305
209
|
bottom: '100px',
|
|
306
|
-
left: '30px'
|
|
307
|
-
top: 'auto',
|
|
308
|
-
right: 'auto'
|
|
210
|
+
left: '30px'
|
|
309
211
|
});
|
|
310
212
|
break;
|
|
311
213
|
case 'top-right':
|
|
312
214
|
Object.assign(styles, {
|
|
313
215
|
top: '30px',
|
|
314
|
-
right: '30px'
|
|
315
|
-
bottom: 'auto',
|
|
316
|
-
left: 'auto'
|
|
216
|
+
right: '30px'
|
|
317
217
|
});
|
|
318
218
|
break;
|
|
319
219
|
case 'top-left':
|
|
320
220
|
Object.assign(styles, {
|
|
321
221
|
top: '30px',
|
|
322
|
-
left: '30px'
|
|
323
|
-
bottom: 'auto',
|
|
324
|
-
right: 'auto'
|
|
222
|
+
left: '30px'
|
|
325
223
|
});
|
|
326
224
|
break;
|
|
327
225
|
}
|
|
328
226
|
|
|
329
|
-
Object.assign(
|
|
227
|
+
Object.assign(button.style, styles);
|
|
330
228
|
}
|
|
331
229
|
|
|
332
230
|
/**
|
|
333
231
|
* Show the skip button with animation
|
|
334
232
|
*/
|
|
335
233
|
private showButton(): void {
|
|
336
|
-
|
|
337
|
-
if (!this.container) {
|
|
338
|
-
console.error('[SkipButtonController] Cannot show button - container is null!');
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
234
|
+
if (!this.skipButton) return;
|
|
341
235
|
|
|
342
|
-
this.
|
|
343
|
-
// Ensure visibility with inline styles as fallback
|
|
344
|
-
this.container.style.display = 'flex';
|
|
345
|
-
this.container.style.opacity = '1';
|
|
346
|
-
this.container.style.visibility = 'visible';
|
|
347
|
-
this.container.style.pointerEvents = 'auto';
|
|
348
|
-
console.log('[SkipButtonController] Container visibility set. Display:', this.container.style.display);
|
|
349
|
-
// Also ensure buttons are visible (opacity handled by CSS on container usually, but let's be safe)
|
|
236
|
+
this.skipButton.classList.add('visible');
|
|
350
237
|
this.state.visible = true;
|
|
351
238
|
}
|
|
352
239
|
|
|
@@ -354,12 +241,10 @@ export class SkipButtonController {
|
|
|
354
241
|
* Hide the skip button with animation
|
|
355
242
|
*/
|
|
356
243
|
private hideButton(): void {
|
|
357
|
-
if (!this.
|
|
244
|
+
if (!this.skipButton) return;
|
|
358
245
|
|
|
359
|
-
this.
|
|
360
|
-
|
|
361
|
-
this.skipButton.classList.remove('auto-skip', 'countdown');
|
|
362
|
-
}
|
|
246
|
+
this.skipButton.classList.remove('visible');
|
|
247
|
+
this.skipButton.classList.remove('auto-skip', 'countdown');
|
|
363
248
|
}
|
|
364
249
|
|
|
365
250
|
/**
|
|
@@ -400,10 +285,10 @@ export class SkipButtonController {
|
|
|
400
285
|
|
|
401
286
|
// Update button text with countdown
|
|
402
287
|
const originalText = this.skipButton.textContent || '';
|
|
403
|
-
|
|
288
|
+
|
|
404
289
|
// Start countdown animation
|
|
405
290
|
this.skipButton.classList.add('countdown');
|
|
406
|
-
|
|
291
|
+
|
|
407
292
|
// Update countdown every second
|
|
408
293
|
this.countdownInterval = setInterval(() => {
|
|
409
294
|
remainingTime -= 1;
|
|
@@ -39,6 +39,15 @@ export interface VideoSegment {
|
|
|
39
39
|
|
|
40
40
|
/** Whether to show skip button for this segment */
|
|
41
41
|
showSkipButton?: boolean;
|
|
42
|
+
|
|
43
|
+
/** URL to navigate to for next episode (used in credits segments) */
|
|
44
|
+
nextEpisodeUrl?: string;
|
|
45
|
+
|
|
46
|
+
/** Custom label for "Watch Credits" button */
|
|
47
|
+
watchCreditsLabel?: string;
|
|
48
|
+
|
|
49
|
+
/** Custom label for "Next Episode/Play Next" button */
|
|
50
|
+
nextEpisodeLabel?: string;
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
/**
|
|
@@ -103,21 +112,6 @@ export interface ChapterConfig {
|
|
|
103
112
|
|
|
104
113
|
/** User preferences */
|
|
105
114
|
userPreferences?: ChapterPreferences;
|
|
106
|
-
|
|
107
|
-
/** Next Episode Configuration */
|
|
108
|
-
nextEpisode?: NextEpisodeConfig;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Configuration for the next episode
|
|
113
|
-
*/
|
|
114
|
-
export interface NextEpisodeConfig {
|
|
115
|
-
enabled: boolean;
|
|
116
|
-
url: string;
|
|
117
|
-
title?: string;
|
|
118
|
-
thumbnail?: string;
|
|
119
|
-
autoPlayDelay?: number; // seconds, overrides segment autoSkipDelay if present
|
|
120
|
-
description?: string;
|
|
121
115
|
}
|
|
122
116
|
|
|
123
117
|
/**
|
|
@@ -197,10 +191,31 @@ export interface ChapterEvents {
|
|
|
197
191
|
url?: string;
|
|
198
192
|
};
|
|
199
193
|
|
|
200
|
-
/** When
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
194
|
+
/** When user clicks "Watch Credits" button */
|
|
195
|
+
creditsWatched: {
|
|
196
|
+
segment: VideoSegment;
|
|
197
|
+
currentTime: number;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/** When user manually clicks "Next Episode" button */
|
|
201
|
+
nextEpisodeClicked: {
|
|
202
|
+
segment: VideoSegment;
|
|
203
|
+
nextEpisodeUrl: string;
|
|
204
|
+
currentTime: number;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/** When auto-redirect timer triggers */
|
|
208
|
+
creditsAutoRedirect: {
|
|
209
|
+
segment: VideoSegment;
|
|
210
|
+
nextEpisodeUrl: string;
|
|
211
|
+
currentTime: number;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/** When credits segment ends after user watched them */
|
|
215
|
+
creditsFullyWatched: {
|
|
216
|
+
segment: VideoSegment;
|
|
217
|
+
nextEpisodeUrl?: string;
|
|
218
|
+
currentTime: number;
|
|
204
219
|
};
|
|
205
220
|
}
|
|
206
221
|
|
|
@@ -370,16 +370,9 @@ export type WebPlayerViewProps = {
|
|
|
370
370
|
autoSkipCredits?: boolean; // Auto-skip credits segments (default: false)
|
|
371
371
|
showSkipButtons?: boolean; // Show skip buttons (default: true)
|
|
372
372
|
skipButtonTimeout?: number; // Button timeout in milliseconds (default: 5000)
|
|
373
|
-
|
|
374
373
|
rememberChoices?: boolean; // Remember user preferences (default: true)
|
|
375
374
|
resumePlaybackAfterSkip?: boolean; // Resume playback after skipping (default: true)
|
|
376
375
|
};
|
|
377
|
-
nextEpisode?: { // Next episode configuration
|
|
378
|
-
title: string;
|
|
379
|
-
url: string;
|
|
380
|
-
thumbnail?: string;
|
|
381
|
-
autoPlayDelay?: number;
|
|
382
|
-
};
|
|
383
376
|
};
|
|
384
377
|
|
|
385
378
|
// Navigation Configuration
|
|
@@ -436,7 +429,6 @@ export type WebPlayerViewProps = {
|
|
|
436
429
|
onChapterSkipButtonHidden?: (data: { segment: any; reason: string }) => void; // Skip button hidden
|
|
437
430
|
onChaptersLoaded?: (data: { segmentCount: number; chapters: any[] }) => void; // Chapters loaded
|
|
438
431
|
onChaptersLoadError?: (data: { error: Error; url?: string }) => void; // Chapters load error
|
|
439
|
-
onNextEpisode?: (data: { config: any; autoPlay: boolean }) => void; // Next episode triggered
|
|
440
432
|
|
|
441
433
|
// Flash News Ticker
|
|
442
434
|
flashNewsTicker?: FlashNewsTickerConfig; // Flash news ticker configuration
|
|
@@ -993,9 +985,7 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
993
985
|
skipButtonTimeout: chaptersConfig.userPreferences?.skipButtonTimeout ?? 5000,
|
|
994
986
|
rememberChoices: chaptersConfig.userPreferences?.rememberChoices ?? true,
|
|
995
987
|
resumePlaybackAfterSkip: chaptersConfig.userPreferences?.resumePlaybackAfterSkip ?? true,
|
|
996
|
-
|
|
997
|
-
},
|
|
998
|
-
nextEpisode: chaptersConfig.nextEpisode
|
|
988
|
+
}
|
|
999
989
|
} : { enabled: false }
|
|
1000
990
|
};
|
|
1001
991
|
|
|
@@ -1131,9 +1121,6 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
1131
1121
|
if (props.onChaptersLoadError && typeof (player as any).on === 'function') {
|
|
1132
1122
|
(player as any).on('chaptersLoadError', props.onChaptersLoadError);
|
|
1133
1123
|
}
|
|
1134
|
-
if (props.onNextEpisode && typeof (player as any).on === 'function') {
|
|
1135
|
-
(player as any).on('nextEpisode', props.onNextEpisode);
|
|
1136
|
-
}
|
|
1137
1124
|
|
|
1138
1125
|
// Navigation event listeners
|
|
1139
1126
|
if (props.onNavigationBackClicked && typeof (player as any).on === 'function') {
|
|
@@ -45,12 +45,6 @@ export interface SkipButtonProps {
|
|
|
45
45
|
|
|
46
46
|
/** Auto-skip delay in seconds */
|
|
47
47
|
autoSkipDelay?: number;
|
|
48
|
-
|
|
49
|
-
/** Secondary action label (e.g., "Watch Credits") */
|
|
50
|
-
secondaryLabel?: string;
|
|
51
|
-
|
|
52
|
-
/** Callback for secondary action */
|
|
53
|
-
onSecondaryAction?: (segment: VideoSegment) => void;
|
|
54
48
|
}
|
|
55
49
|
|
|
56
50
|
export const SkipButton: React.FC<SkipButtonProps> = ({
|
|
@@ -65,9 +59,7 @@ export const SkipButton: React.FC<SkipButtonProps> = ({
|
|
|
65
59
|
className = '',
|
|
66
60
|
style = {},
|
|
67
61
|
enableAutoSkip = false,
|
|
68
|
-
autoSkipDelay = 10
|
|
69
|
-
secondaryLabel,
|
|
70
|
-
onSecondaryAction
|
|
62
|
+
autoSkipDelay = 10
|
|
71
63
|
}) => {
|
|
72
64
|
// State
|
|
73
65
|
const [isVisible, setIsVisible] = useState(visible);
|
|
@@ -138,33 +130,6 @@ export const SkipButton: React.FC<SkipButtonProps> = ({
|
|
|
138
130
|
handleHide('user-action');
|
|
139
131
|
};
|
|
140
132
|
|
|
141
|
-
/**
|
|
142
|
-
* Handle secondary button click
|
|
143
|
-
*/
|
|
144
|
-
const handleSecondary = (e: React.MouseEvent) => {
|
|
145
|
-
e.stopPropagation(); // Prevent bubbling
|
|
146
|
-
if (!segment) return;
|
|
147
|
-
|
|
148
|
-
// Stop auto-skip and countdown
|
|
149
|
-
clearTimeouts();
|
|
150
|
-
setIsAutoSkip(false);
|
|
151
|
-
setCountdown(null);
|
|
152
|
-
|
|
153
|
-
// Call callback
|
|
154
|
-
onSecondaryAction?.(segment);
|
|
155
|
-
|
|
156
|
-
// We DON'T hide the button immediately for "Watch Credits",
|
|
157
|
-
// we just stop the auto-skip countdown. User can still click "Next Episode" later.
|
|
158
|
-
// But typically "Watch Credits" implies dismissing the "Next Episode" urgency.
|
|
159
|
-
// If we want to hide it:
|
|
160
|
-
// handleHide('user-secondary-action');
|
|
161
|
-
|
|
162
|
-
// For now, let's keep it visible but stop the countdown so they can click 'Next' later if they want,
|
|
163
|
-
// OR we can hide it if that's the desired UX.
|
|
164
|
-
// Based on Netflix UX: "Watch Credits" usually minimizes the overlay.
|
|
165
|
-
// Here we'll just stop the auto-skip.
|
|
166
|
-
};
|
|
167
|
-
|
|
168
133
|
/**
|
|
169
134
|
* Handle button show
|
|
170
135
|
*/
|
|
@@ -283,54 +248,31 @@ export const SkipButton: React.FC<SkipButtonProps> = ({
|
|
|
283
248
|
}
|
|
284
249
|
|
|
285
250
|
return (
|
|
286
|
-
<
|
|
287
|
-
|
|
288
|
-
{
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
251
|
+
<button
|
|
252
|
+
type="button"
|
|
253
|
+
className={buttonClasses}
|
|
254
|
+
style={defaultStyles}
|
|
255
|
+
onClick={handleSkip}
|
|
256
|
+
aria-label={`${buttonText} - ${segment.title || segment.type}`}
|
|
257
|
+
>
|
|
258
|
+
{displayText}
|
|
259
|
+
|
|
260
|
+
{/* Progress bar for auto-skip countdown */}
|
|
261
|
+
{isAutoSkip && countdown !== null && (
|
|
262
|
+
<div
|
|
263
|
+
className="uvf-skip-countdown-progress"
|
|
293
264
|
style={{
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
265
|
+
position: 'absolute',
|
|
266
|
+
bottom: 0,
|
|
267
|
+
left: 0,
|
|
268
|
+
height: '3px',
|
|
269
|
+
backgroundColor: 'currentColor',
|
|
270
|
+
width: `${((autoSkipDelay - countdown) / autoSkipDelay) * 100}%`,
|
|
271
|
+
transition: 'width 1s linear',
|
|
272
|
+
borderRadius: '0 0 6px 6px'
|
|
299
273
|
}}
|
|
300
|
-
|
|
301
|
-
{secondaryLabel}
|
|
302
|
-
</button>
|
|
274
|
+
/>
|
|
303
275
|
)}
|
|
304
|
-
|
|
305
|
-
{/* Primary Button (Next Episode / Skip) */}
|
|
306
|
-
<button
|
|
307
|
-
type="button"
|
|
308
|
-
className={buttonClasses}
|
|
309
|
-
// style is applied to container now, but we keep button specific classes
|
|
310
|
-
// We need to adjust styles since we are wrapping in a div now
|
|
311
|
-
style={{ pointerEvents: 'auto' }}
|
|
312
|
-
onClick={handleSkip}
|
|
313
|
-
aria-label={`${buttonText} - ${segment.title || segment.type}`}
|
|
314
|
-
>
|
|
315
|
-
{displayText}
|
|
316
|
-
|
|
317
|
-
{/* Progress bar for auto-skip countdown */}
|
|
318
|
-
{isAutoSkip && countdown !== null && (
|
|
319
|
-
<div
|
|
320
|
-
className="uvf-skip-countdown-progress"
|
|
321
|
-
style={{
|
|
322
|
-
position: 'absolute',
|
|
323
|
-
bottom: 0,
|
|
324
|
-
left: 0,
|
|
325
|
-
height: '3px',
|
|
326
|
-
backgroundColor: 'currentColor',
|
|
327
|
-
width: `${((autoSkipDelay - countdown) / autoSkipDelay) * 100}%`,
|
|
328
|
-
transition: 'width 1s linear',
|
|
329
|
-
borderRadius: '0 0 6px 6px'
|
|
330
|
-
}}
|
|
331
|
-
/>
|
|
332
|
-
)}
|
|
333
|
-
</button>
|
|
334
|
-
</div>
|
|
276
|
+
</button>
|
|
335
277
|
);
|
|
336
278
|
};
|