mega-stream-proxy 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +14 -49
  2. package/dist/megaFile.js +22 -9
  3. package/dist/{Player/playerAndroid/MegaVideoPlayerAndroid.d.ts → player/MegaVideoPlayer.d.ts} +4 -13
  4. package/dist/player/MegaVideoPlayer.js +307 -0
  5. package/dist/{Player/playerweb → player}/PlayerControls.d.ts +2 -14
  6. package/dist/player/PlayerControls.js +289 -0
  7. package/dist/{Player/playerweb → player}/SubtitleDisplay.d.ts +3 -3
  8. package/dist/{Player/playerweb → player}/SubtitleDisplay.js +1 -1
  9. package/dist/player/index.d.ts +3 -0
  10. package/dist/player/index.js +3 -0
  11. package/dist/{Player/playerweb/MegaVideoPlayer.d.ts → playerweb/MegaVideoPlayerWeb.d.ts} +6 -18
  12. package/dist/{Player/playerweb/MegaVideoPlayer.js → playerweb/MegaVideoPlayerWeb.js} +18 -18
  13. package/dist/{Player/playerAndroid/PlayerControlsAndroid.d.ts → playerweb/PlayerControlsWeb.d.ts} +2 -16
  14. package/dist/playerweb/PlayerControlsWeb.js +289 -0
  15. package/dist/playerweb/SubtitleDisplayWeb.d.ts +15 -0
  16. package/dist/playerweb/SubtitleDisplayWeb.js +32 -0
  17. package/dist/playerweb/index.d.ts +3 -0
  18. package/dist/playerweb/index.js +3 -0
  19. package/dist/proxy/native.js +11 -3
  20. package/expo-module.config.json +3 -0
  21. package/package.json +15 -18
  22. package/src/megaFile.ts +28 -15
  23. package/src/player/MegaVideoPlayer.tsx +474 -0
  24. package/src/{Player/playerweb → player}/PlayerControls.tsx +200 -369
  25. package/src/{Player/playerweb → player}/SubtitleDisplay.tsx +3 -3
  26. package/src/player/index.ts +7 -0
  27. package/src/{Player/playerweb/MegaVideoPlayer.tsx → playerweb/MegaVideoPlayerWeb.tsx} +41 -58
  28. package/src/playerweb/PlayerControlsWeb.tsx +629 -0
  29. package/src/playerweb/SubtitleDisplayWeb.tsx +48 -0
  30. package/src/playerweb/index.ts +3 -0
  31. package/src/proxy/native.ts +10 -3
  32. package/dist/Player/native.d.ts +0 -3
  33. package/dist/Player/native.js +0 -3
  34. package/dist/Player/playerAndroid/MegaVideoPlayerAndroid.js +0 -624
  35. package/dist/Player/playerAndroid/PlayerControlsAndroid.js +0 -370
  36. package/dist/Player/playerAndroid/SubtitleDisplayAndroid.d.ts +0 -21
  37. package/dist/Player/playerAndroid/SubtitleDisplayAndroid.js +0 -55
  38. package/dist/Player/playerAndroid/index.d.ts +0 -3
  39. package/dist/Player/playerAndroid/index.js +0 -3
  40. package/dist/Player/playerweb/PlayerControls.js +0 -346
  41. package/dist/Player/playerweb/index.d.ts +0 -3
  42. package/dist/Player/playerweb/index.js +0 -3
  43. package/dist/Player/web.d.ts +0 -3
  44. package/dist/Player/web.js +0 -3
  45. package/src/Player/playerAndroid/MegaVideoPlayerAndroid.tsx +0 -825
  46. package/src/Player/playerAndroid/PlayerControlsAndroid.tsx +0 -824
  47. package/src/Player/playerAndroid/SubtitleDisplayAndroid.tsx +0 -80
  48. package/src/Player/playerAndroid/index.ts +0 -7
  49. package/src/Player/playerweb/index.ts +0 -3
@@ -1,824 +0,0 @@
1
- import { useMemo, useState } from "react";
2
- import {
3
- ActivityIndicator,
4
- PanResponder,
5
- Pressable,
6
- StyleSheet,
7
- Text,
8
- TouchableOpacity,
9
- View,
10
- } from "react-native";
11
- import { Ionicons } from "@expo/vector-icons";
12
-
13
- export type PlayerControlsAndroidProps = {
14
- playing: boolean;
15
- buffering: boolean;
16
- currentTime: number;
17
- duration: number;
18
- muted: boolean;
19
- title?: string;
20
- visible: boolean;
21
- subtitlesOn: boolean;
22
- volume: number;
23
- /** subtitle language labels (when >1 the CC switcher is shown) */
24
- subtitleTracks?: string[];
25
- /** currently active subtitle language */
26
- subtitleTrack?: string;
27
- /** select a subtitle language from the list directly */
28
- onSelectSubtitleTrack?: (lang: string) => void;
29
- /** current subtitle font size (px) */
30
- subtitleFontSize?: number;
31
- /** adjust the subtitle font size (px) */
32
- onChangeSubtitleFontSize?: (px: number) => void;
33
- /** 0..1 — how much of the whole merged episode has been downloaded. Renders
34
- * a second, thinner line under the playhead track. */
35
- loadProgress?: number;
36
- /** resolution labels in display order — the FIRST is the default source
37
- * (usually "HD: <url>"), e.g. ["Default", "1080p", "720p"]. Shown in the
38
- * settings popup. */
39
- resolutions?: string[];
40
- /** currently active resolution label */
41
- resolution?: string;
42
- /** user picked a resolution label — resolve it to a URL and update `uri`. */
43
- onSelectResolution?: (label: string) => void;
44
- /** [start, end] seconds (merged timeline) of the intro — highlighted on the
45
- * track + shows a "Skip Intro" button while the playhead is inside it. */
46
- introRegion?: [number, number];
47
- /** [start, end] seconds (merged timeline) of the outro */
48
- outroRegion?: [number, number];
49
- onSkipIntro?: () => void;
50
- onSkipOutro?: () => void;
51
- onToggleVisible: () => void;
52
- onPlayPause: () => void;
53
- onSkip: (deltaSeconds: number) => void;
54
- onSeek: (seconds: number) => void;
55
- onToggleMute: () => void;
56
- onToggleSubtitles: () => void;
57
- onVolume: (v: number) => void;
58
- onFullscreen: () => void;
59
- /** current fullscreen state — switches the expand/contract icon */
60
- fullscreen?: boolean;
61
- /** any user interaction with the bar/menus — reset the auto-hide timer */
62
- onInteract?: () => void;
63
- width?: number;
64
- height?: number;
65
- };
66
-
67
- const fmt = (s: number) => {
68
- if (!Number.isFinite(s) || s < 0) s = 0;
69
- const h = Math.floor(s / 3600);
70
- const m = Math.floor((s % 3600) / 60);
71
- const sec = Math.floor(s % 60);
72
- return `${h.toString().padStart(2, "0")}:${m
73
- .toString()
74
- .padStart(2, "0")}:${sec.toString().padStart(2, "0")}`;
75
- };
76
-
77
- const clamp01 = (v: number) => Math.min(1, Math.max(0, v));
78
-
79
- const SIZE_MIN = 10;
80
- const SIZE_MAX = 34;
81
-
82
- export function PlayerControlsAndroid(props: PlayerControlsAndroidProps) {
83
- const {
84
- playing,
85
- buffering,
86
- currentTime,
87
- duration,
88
- muted,
89
- title,
90
- visible,
91
- subtitlesOn,
92
- volume,
93
- subtitleTracks,
94
- subtitleTrack,
95
- onSelectSubtitleTrack,
96
- subtitleFontSize,
97
- onChangeSubtitleFontSize,
98
- loadProgress,
99
- resolutions,
100
- resolution,
101
- onSelectResolution,
102
- introRegion,
103
- outroRegion,
104
- onSkipIntro,
105
- onSkipOutro,
106
- onToggleVisible,
107
- onPlayPause,
108
- onSkip,
109
- onSeek,
110
- onToggleMute,
111
- onToggleSubtitles,
112
- onVolume,
113
- onFullscreen,
114
- fullscreen = false,
115
- onInteract,
116
- width,
117
- height,
118
- } = props;
119
-
120
- const hasSubs = (subtitleTracks?.length ?? 0) > 0;
121
- const showSubsSection = hasSubs || !!onChangeSubtitleFontSize;
122
- // empty resolution = the implicit default (first row, usually "HD: <url>")
123
- const activeRes = (resolution && resolution.trim()) || resolutions?.[0] || "";
124
-
125
- const [trackW, setTrackW] = useState(0);
126
- const [scrubPct, setScrubPct] = useState<number | null>(null);
127
- const [volW, setVolW] = useState(0);
128
- const [sizeW, setSizeW] = useState(0);
129
- const [settingsOpen, setSettingsOpen] = useState(false);
130
-
131
- const pct = duration > 0 ? clamp01(currentTime / duration) : 0;
132
- const volPct = clamp01(volume);
133
- const inIntro =
134
- !!introRegion &&
135
- introRegion[1] > introRegion[0] &&
136
- currentTime >= introRegion[0] &&
137
- currentTime <= introRegion[1] &&
138
- !!onSkipIntro;
139
- const inOutro =
140
- !!outroRegion &&
141
- outroRegion[1] > outroRegion[0] &&
142
- currentTime >= outroRegion[0] &&
143
- currentTime <= outroRegion[1] &&
144
- !!onSkipOutro;
145
- const currentSize = subtitleFontSize ?? 14;
146
- const sizePct = clamp01((currentSize - SIZE_MIN) / (SIZE_MAX - SIZE_MIN));
147
- const shownPct = scrubPct ?? pct;
148
- const volIcon = (
149
- muted || volume === 0
150
- ? "volume-mute"
151
- : volume < 0.5
152
- ? "volume-low"
153
- : "volume-high"
154
- ) as "volume-mute" | "volume-low" | "volume-high";
155
-
156
- const pan = useMemo(
157
- () =>
158
- PanResponder.create({
159
- onStartShouldSetPanResponder: () => true,
160
- onMoveShouldSetPanResponder: () => true,
161
- onPanResponderGrant: (e) => {
162
- onInteract?.();
163
- setScrubPct(
164
- trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0,
165
- );
166
- },
167
- onPanResponderMove: (e) =>
168
- setScrubPct(
169
- trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0,
170
- ),
171
- onPanResponderRelease: (e) => {
172
- const p = trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0;
173
- setScrubPct(null);
174
- onInteract?.();
175
- onSeek(p * duration);
176
- },
177
- }),
178
- [trackW, duration, onSeek, onInteract],
179
- );
180
-
181
- const volPan = useMemo(
182
- () =>
183
- PanResponder.create({
184
- onStartShouldSetPanResponder: () => true,
185
- onMoveShouldSetPanResponder: () => true,
186
- onPanResponderGrant: (e) => {
187
- onInteract?.();
188
- onVolume(volW > 0 ? clamp01(e.nativeEvent.locationX / volW) : 1);
189
- },
190
- onPanResponderMove: (e) =>
191
- onVolume(volW > 0 ? clamp01(e.nativeEvent.locationX / volW) : 1),
192
- }),
193
- [volW, onVolume, onInteract],
194
- );
195
-
196
- const sizePan = useMemo(
197
- () =>
198
- PanResponder.create({
199
- onStartShouldSetPanResponder: () => true,
200
- onMoveShouldSetPanResponder: () => true,
201
- onPanResponderGrant: (e) => {
202
- if (!onChangeSubtitleFontSize) return;
203
- const p = sizeW > 0 ? clamp01(e.nativeEvent.locationX / sizeW) : 0;
204
- onChangeSubtitleFontSize(
205
- Math.round(SIZE_MIN + p * (SIZE_MAX - SIZE_MIN)),
206
- );
207
- },
208
- onPanResponderMove: (e) => {
209
- if (!onChangeSubtitleFontSize) return;
210
- const p = sizeW > 0 ? clamp01(e.nativeEvent.locationX / sizeW) : 0;
211
- onChangeSubtitleFontSize(
212
- Math.round(SIZE_MIN + p * (SIZE_MAX - SIZE_MIN)),
213
- );
214
- },
215
- }),
216
- [sizeW, onChangeSubtitleFontSize],
217
- );
218
-
219
- const interact = () => onInteract?.();
220
-
221
- return (
222
- <View
223
- style={[
224
- styles.root,
225
- StyleSheet.absoluteFill,
226
- { width: width ? width : "100%", height: height ? height : "100%" },
227
- ]}
228
- >
229
- {/* tap surface toggles the chrome */}
230
- <TouchableOpacity
231
- style={StyleSheet.absoluteFill}
232
- activeOpacity={1}
233
- onPress={onToggleVisible}
234
- />
235
-
236
- {buffering && (
237
- <View style={styles.centerBadge} pointerEvents="none">
238
- <ActivityIndicator color="#e50914" size="large" />
239
- </View>
240
- )}
241
-
242
- {settingsOpen && (
243
- <Pressable
244
- style={StyleSheet.absoluteFill}
245
- onPress={() => setSettingsOpen(false)}
246
- />
247
- )}
248
-
249
- {visible && (
250
- <>
251
- {/* top gradient-ish bar */}
252
- <View style={[styles.bar, styles.top]} pointerEvents="box-none">
253
- {!!title && (
254
- <Text numberOfLines={1} style={styles.title}>
255
- {title}
256
- </Text>
257
- )}
258
- </View>
259
-
260
- {/* center buttons */}
261
- <View
262
- style={[
263
- styles.centerRow,
264
- StyleSheet.absoluteFill,
265
- { width: width },
266
- ]}
267
- pointerEvents="box-none"
268
- >
269
- <TouchableOpacity
270
- style={styles.skipBtn}
271
- onPress={() => onSkip(-10)}
272
- >
273
- <Ionicons name="play-back" size={30} color="#fff" />
274
- </TouchableOpacity>
275
- <TouchableOpacity style={styles.ppBtn} onPress={onPlayPause}>
276
- <Ionicons
277
- name={
278
- buffering ? "ellipsis-horizontal" : playing ? "pause" : "play"
279
- }
280
- size={38}
281
- color="#fff"
282
- />
283
- </TouchableOpacity>
284
- <TouchableOpacity style={styles.skipBtn} onPress={() => onSkip(10)}>
285
- <Ionicons name="play-forward" size={30} color="#fff" />
286
- </TouchableOpacity>
287
- </View>
288
-
289
- {/* bottom bar */}
290
- <View style={[styles.bar, styles.bottom]} pointerEvents="box-none">
291
- <Text style={styles.time}>
292
- {fmt(scrubPct !== null ? scrubPct * duration : currentTime)}
293
- </Text>
294
-
295
- <View
296
- style={styles.trackWrap}
297
- onLayout={(e) => setTrackW(e.nativeEvent.layout.width)}
298
- {...pan.panHandlers}
299
- >
300
- <View style={styles.track}>
301
- {loadProgress != null && loadProgress > 0 && (
302
- <View
303
- style={[
304
- styles.loadLine,
305
- { width: `${clamp01(loadProgress) * 100}%` },
306
- ]}
307
- />
308
- )}
309
- {introRegion &&
310
- duration > 0 &&
311
- introRegion[1] > introRegion[0] && (
312
- <View
313
- style={[
314
- styles.introHighlight,
315
- {
316
- left: `${clamp01(introRegion[0] / duration) * 100}%`,
317
- width: `${clamp01((introRegion[1] - introRegion[0]) / duration) * 100}%`,
318
- },
319
- ]}
320
- />
321
- )}
322
- {outroRegion &&
323
- duration > 0 &&
324
- outroRegion[1] > outroRegion[0] && (
325
- <View
326
- style={[
327
- styles.outroHighlight,
328
- {
329
- left: `${clamp01(outroRegion[0] / duration) * 100}%`,
330
- width: `${clamp01((outroRegion[1] - outroRegion[0]) / duration) * 100}%`,
331
- },
332
- ]}
333
- />
334
- )}
335
- <View style={[styles.fill, { width: `${shownPct * 100}%` }]} />
336
- </View>
337
- <View
338
- style={[
339
- styles.thumb,
340
- { left: `${shownPct * 100}%` },
341
- scrubPct === null && styles.thumbIdle,
342
- ]}
343
- />
344
- </View>
345
-
346
- <Text style={styles.time}>{fmt(duration)}</Text>
347
-
348
- {/* settings popup: subtitles + volume + resolution */}
349
- <View style={styles.settingsWrap}>
350
- <TouchableOpacity
351
- style={[styles.iconBtn, settingsOpen && styles.iconBtnOn]}
352
- onPress={() => {
353
- setSettingsOpen((v) => !v);
354
- interact();
355
- }}
356
- hitSlop={6}
357
- >
358
- <Ionicons
359
- name="settings-outline"
360
- size={17}
361
- color={settingsOpen ? "#7dd3fc" : "#fff"}
362
- />
363
- </TouchableOpacity>
364
-
365
- {settingsOpen && (
366
- <View style={styles.settingsMenu}>
367
- <Text style={styles.settingsTitle}>Settings</Text>
368
-
369
- {/* ---- Subtitles ---- */}
370
- {showSubsSection && (
371
- <View style={styles.section}>
372
- <View style={styles.sectionHead}>
373
- <Ionicons
374
- name="chatbox-ellipses-outline"
375
- size={15}
376
- color="#9aa0aa"
377
- />
378
- <Text style={styles.sectionLabel}>Subtitles</Text>
379
- <TouchableOpacity
380
- onPress={() => {
381
- onToggleSubtitles();
382
- interact();
383
- }}
384
- hitSlop={6}
385
- style={{ marginLeft: "auto" }}
386
- >
387
- <View
388
- style={[
389
- styles.switchTrack,
390
- subtitlesOn && styles.switchTrackOn,
391
- ]}
392
- >
393
- <View
394
- style={[
395
- styles.switchThumb,
396
- subtitlesOn && styles.switchThumbOn,
397
- ]}
398
- />
399
- </View>
400
- </TouchableOpacity>
401
- </View>
402
-
403
- {hasSubs && (
404
- <View style={styles.menuList}>
405
- {subtitleTracks?.map((lang) => {
406
- const active = lang === subtitleTrack;
407
- return (
408
- <TouchableOpacity
409
- key={lang}
410
- style={[
411
- styles.menuItem,
412
- active && styles.menuItemOn,
413
- ]}
414
- onPress={() => {
415
- setSettingsOpen(false);
416
- interact();
417
- onSelectSubtitleTrack?.(lang);
418
- }}
419
- >
420
- <Text
421
- numberOfLines={1}
422
- style={[
423
- styles.menuItemText,
424
- active && styles.menuItemTextOn,
425
- ]}
426
- >
427
- {lang}
428
- </Text>
429
- {active ? (
430
- <Ionicons
431
- name="checkmark"
432
- size={14}
433
- color="#fff"
434
- />
435
- ) : null}
436
- </TouchableOpacity>
437
- );
438
- })}
439
- </View>
440
- )}
441
-
442
- {!!onChangeSubtitleFontSize && (
443
- <View style={styles.sliderRow}>
444
- <TouchableOpacity
445
- style={styles.minusBtn}
446
- onPress={() =>
447
- onChangeSubtitleFontSize(
448
- Math.max(SIZE_MIN, Math.round(currentSize - 2)),
449
- )
450
- }
451
- >
452
- <Ionicons name="remove" size={16} color="#fff" />
453
- </TouchableOpacity>
454
-
455
- <View
456
- style={styles.sliderTrack}
457
- onLayout={(e) =>
458
- setSizeW(e.nativeEvent.layout.width)
459
- }
460
- {...sizePan.panHandlers}
461
- >
462
- <View
463
- style={[
464
- styles.sliderFill,
465
- { width: `${sizePct * 100}%` },
466
- ]}
467
- />
468
- </View>
469
-
470
- <TouchableOpacity
471
- style={styles.minusBtn}
472
- onPress={() =>
473
- onChangeSubtitleFontSize(
474
- Math.min(SIZE_MAX, Math.round(currentSize + 2)),
475
- )
476
- }
477
- >
478
- <Ionicons name="add" size={16} color="#fff" />
479
- </TouchableOpacity>
480
- <Text style={styles.sliderValue}>
481
- {currentSize}px
482
- </Text>
483
- </View>
484
- )}
485
- </View>
486
- )}
487
-
488
- {/* ---- Volume ---- */}
489
- <View style={styles.section}>
490
- <View style={styles.sectionHead}>
491
- <Ionicons
492
- name="volume-medium-outline"
493
- size={15}
494
- color="#9aa0aa"
495
- />
496
- <Text style={styles.sectionLabel}>Volume</Text>
497
- <TouchableOpacity
498
- onPress={() => {
499
- onToggleMute();
500
- interact();
501
- }}
502
- hitSlop={6}
503
- style={{ marginLeft: "auto" }}
504
- >
505
- <Ionicons
506
- name={volIcon}
507
- size={17}
508
- color={muted ? "#e50914" : "#fff"}
509
- />
510
- </TouchableOpacity>
511
- <Text style={styles.sliderValue}>
512
- {Math.round(volPct * 100)}
513
- </Text>
514
- </View>
515
- <View style={styles.sliderRow}>
516
- <View
517
- style={styles.sliderTrack}
518
- onLayout={(e) => setVolW(e.nativeEvent.layout.width)}
519
- {...volPan.panHandlers}
520
- >
521
- <View
522
- style={[
523
- styles.sliderFill,
524
- { width: `${volPct * 100}%` },
525
- ]}
526
- />
527
- </View>
528
- </View>
529
- </View>
530
-
531
- {/* ---- Resolution ---- */}
532
- {(resolutions?.length ?? 0) > 0 && (
533
- <View style={styles.section}>
534
- <View style={styles.sectionHead}>
535
- <Ionicons
536
- name="resize-outline"
537
- size={15}
538
- color="#9aa0aa"
539
- />
540
- <Text style={styles.sectionLabel}>Resolution</Text>
541
- </View>
542
- <View style={styles.menuList}>
543
- {resolutions?.map((label) => {
544
- const active = label === activeRes;
545
- return (
546
- <TouchableOpacity
547
- key={label}
548
- style={[
549
- styles.menuItem,
550
- active && styles.menuItemOn,
551
- ]}
552
- onPress={() => {
553
- setSettingsOpen(false);
554
- interact();
555
- onSelectResolution?.(label);
556
- }}
557
- >
558
- <Text
559
- numberOfLines={1}
560
- style={[
561
- styles.menuItemText,
562
- active && styles.menuItemTextOn,
563
- ]}
564
- >
565
- {label}
566
- </Text>
567
- {active ? (
568
- <Ionicons
569
- name="checkmark"
570
- size={14}
571
- color="#fff"
572
- />
573
- ) : null}
574
- </TouchableOpacity>
575
- );
576
- })}
577
- </View>
578
- </View>
579
- )}
580
- </View>
581
- )}
582
- </View>
583
-
584
- {/* fullscreen toggle */}
585
- <TouchableOpacity
586
- onPress={() => {
587
- onFullscreen();
588
- interact();
589
- }}
590
- hitSlop={8}
591
- >
592
- <Ionicons
593
- name={fullscreen ? "contract" : "expand"}
594
- size={18}
595
- color="#fff"
596
- />
597
- </TouchableOpacity>
598
- </View>
599
- </>
600
- )}
601
-
602
- {visible && inIntro && (
603
- <TouchableOpacity style={styles.skipRegionBtn} onPress={onSkipIntro}>
604
- <Text style={styles.skipRegionText}>Skip Intro ›</Text>
605
- </TouchableOpacity>
606
- )}
607
- {visible && inOutro && (
608
- <TouchableOpacity style={styles.skipRegionBtn} onPress={onSkipOutro}>
609
- <Text style={styles.skipRegionText}>Skip Outro ›</Text>
610
- </TouchableOpacity>
611
- )}
612
- </View>
613
- );
614
- }
615
-
616
- const styles = StyleSheet.create({
617
- root: { zIndex: 2 },
618
-
619
- centerBadge: {
620
- justifyContent: "center",
621
- alignItems: "center",
622
- width: "100%",
623
- height: "100%",
624
- },
625
-
626
- bar: {
627
- position: "absolute",
628
- left: 0,
629
- right: 0,
630
- flexDirection: "row",
631
- alignItems: "center",
632
- paddingHorizontal: 12,
633
- backgroundColor: "rgba(0,0,0,0.45)",
634
- gap: 8,
635
- zIndex: 5,
636
- },
637
- top: { top: 0, height: 42 },
638
- bottom: { bottom: 0, height: 44 },
639
-
640
- title: { color: "#fff", fontWeight: "700", fontSize: 13, flex: 1 },
641
-
642
- centerRow: {
643
- justifyContent: "center",
644
- alignItems: "center",
645
- flexDirection: "row",
646
- gap: 34,
647
- },
648
- ppBtn: {
649
- width: 64,
650
- height: 64,
651
- borderRadius: 32,
652
- backgroundColor: "rgba(229,9,20,0.92)",
653
- justifyContent: "center",
654
- alignItems: "center",
655
- },
656
- skipBtn: { opacity: 0.9 },
657
-
658
- time: {
659
- color: "#ddd",
660
- fontSize: 11,
661
- fontVariant: ["tabular-nums"],
662
- minWidth: 58,
663
- },
664
- trackWrap: { flex: 1, paddingVertical: 12, justifyContent: "center" },
665
- track: {
666
- height: 4,
667
- borderRadius: 2,
668
- backgroundColor: "#3a3f47",
669
- overflow: "hidden",
670
- },
671
- fill: { height: 4, borderRadius: 2, backgroundColor: "#e50914" },
672
- loadLine: {
673
- position: "absolute",
674
- left: 0,
675
- top: 0,
676
- height: 4,
677
- borderRadius: 2,
678
- backgroundColor: "rgba(255,255,255,0.35)",
679
- },
680
- introHighlight: {
681
- position: "absolute",
682
- top: 0,
683
- height: 4,
684
- backgroundColor: "rgba(0,229,255,0.55)",
685
- },
686
- outroHighlight: {
687
- position: "absolute",
688
- top: 0,
689
- height: 4,
690
- backgroundColor: "rgba(255,0,200,0.55)",
691
- },
692
- skipRegionBtn: {
693
- position: "absolute",
694
- right: 16,
695
- bottom: 60,
696
- flexDirection: "row",
697
- alignItems: "center",
698
- backgroundColor: "rgba(0,0,0,0.6)",
699
- borderWidth: 1,
700
- borderColor: "rgba(255,255,255,0.25)",
701
- borderRadius: 20,
702
- paddingHorizontal: 14,
703
- paddingVertical: 8,
704
- },
705
- skipRegionText: { color: "#fff", fontWeight: "700", fontSize: 13 },
706
- thumb: {
707
- position: "absolute",
708
- top: 12 - 7 + 2,
709
- marginLeft: -6,
710
- width: 12,
711
- height: 12,
712
- borderRadius: 6,
713
- backgroundColor: "#e50914",
714
- },
715
- thumbIdle: { opacity: 0.85 },
716
-
717
- iconBtn: {
718
- width: 26,
719
- height: 26,
720
- borderRadius: 4,
721
- alignItems: "center",
722
- justifyContent: "center",
723
- },
724
- iconBtnOn: { backgroundColor: "rgba(125,211,252,0.18)" },
725
-
726
- settingsWrap: { position: "relative", zIndex: 20 },
727
- settingsMenu: {
728
- position: "absolute",
729
- bottom: 30,
730
- right: 0,
731
- zIndex: 30,
732
- width: 250,
733
- maxHeight: 420,
734
- backgroundColor: "#14161c",
735
- borderWidth: 1,
736
- borderColor: "#2a2f3a",
737
- borderRadius: 8,
738
- overflow: "hidden",
739
- paddingBottom: 6,
740
- elevation: 12,
741
- shadowColor: "#000",
742
- shadowOpacity: 0.5,
743
- shadowRadius: 10,
744
- shadowOffset: { width: 0, height: 3 },
745
- },
746
- settingsTitle: {
747
- color: "#9aa0aa",
748
- fontSize: 11,
749
- fontWeight: "800",
750
- textTransform: "uppercase",
751
- letterSpacing: 1,
752
- paddingHorizontal: 12,
753
- paddingTop: 10,
754
- paddingBottom: 6,
755
- borderBottomWidth: 1,
756
- borderBottomColor: "#262a34",
757
- },
758
-
759
- section: { paddingHorizontal: 6, paddingTop: 8 },
760
- sectionHead: {
761
- flexDirection: "row",
762
- alignItems: "center",
763
- gap: 6,
764
- paddingHorizontal: 6,
765
- paddingBottom: 2,
766
- },
767
- sectionLabel: { color: "#cbd5e1", fontSize: 12, fontWeight: "700" },
768
-
769
- menuList: { paddingVertical: 4 },
770
- menuItem: {
771
- flexDirection: "row",
772
- alignItems: "center",
773
- justifyContent: "space-between",
774
- gap: 10,
775
- paddingHorizontal: 14,
776
- paddingVertical: 6,
777
- borderRadius: 6,
778
- },
779
- menuItemOn: { backgroundColor: "rgba(125,211,252,0.15)" },
780
- menuItemText: { color: "#cbd5e1", fontSize: 13, fontWeight: "600" },
781
- menuItemTextOn: { color: "#7dd3fc", fontWeight: "800" },
782
-
783
- sliderRow: {
784
- flexDirection: "row",
785
- alignItems: "center",
786
- gap: 10,
787
- paddingHorizontal: 10,
788
- paddingVertical: 8,
789
- },
790
- minusBtn: {
791
- width: 24,
792
- height: 24,
793
- borderRadius: 4,
794
- alignItems: "center",
795
- justifyContent: "center",
796
- backgroundColor: "#2a2f3a",
797
- },
798
- sliderTrack: { flex: 1, height: 22, justifyContent: "center" },
799
- sliderFill: { height: 4, borderRadius: 2, backgroundColor: "#7dd3fc" },
800
- sliderValue: {
801
- color: "#9aa0aa",
802
- fontSize: 11,
803
- fontWeight: "700",
804
- minWidth: 32,
805
- textAlign: "right",
806
- },
807
-
808
- switchTrack: {
809
- width: 34,
810
- height: 20,
811
- borderRadius: 10,
812
- backgroundColor: "#3a3f47",
813
- justifyContent: "center",
814
- paddingHorizontal: 2,
815
- },
816
- switchTrackOn: { backgroundColor: "rgba(229,9,20,0.85)" },
817
- switchThumb: {
818
- width: 16,
819
- height: 16,
820
- borderRadius: 8,
821
- backgroundColor: "#fff",
822
- },
823
- switchThumbOn: { alignSelf: "flex-end" },
824
- });