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
@@ -8,9 +8,8 @@ import {
8
8
  View,
9
9
  } from "react-native";
10
10
  import { Ionicons } from "@expo/vector-icons";
11
- import { SubtitleDisplayWeb } from "./SubtitleDisplay.js";
12
11
 
13
- export type PlayerControlsWebProps = {
12
+ export type PlayerControlsProps = {
14
13
  playing: boolean;
15
14
  buffering: boolean;
16
15
  currentTime: number;
@@ -40,18 +39,6 @@ export type PlayerControlsWebProps = {
40
39
  outroRegion?: [number, number];
41
40
  onSkipIntro?: () => void;
42
41
  onSkipOutro?: () => void;
43
- /** resolution labels in display order — the FIRST is the default source
44
- * (the episode URL), e.g. ["Default", "1080p", "720p", "480p"]. */
45
- resolutions?: string[];
46
- /** currently active resolution label */
47
- resolution?: string;
48
- width?: number;
49
- height?: number;
50
- cue?: any;
51
- enabled?: any;
52
- fontSize?: any;
53
- /** select a resolution — the parent resolves the label to its URL */
54
- onSelectResolution?: (label: string) => void;
55
42
  onToggleVisible: () => void;
56
43
  onPlayPause: () => void;
57
44
  onSkip: (deltaSeconds: number) => void;
@@ -74,7 +61,7 @@ const fmt = (s: number) => {
74
61
 
75
62
  const clamp01 = (v: number) => Math.min(1, Math.max(0, v));
76
63
 
77
- export function PlayerControlsWeb(props: PlayerControlsWebProps) {
64
+ export function PlayerControls(props: PlayerControlsProps) {
78
65
  const {
79
66
  playing,
80
67
  buffering,
@@ -95,9 +82,6 @@ export function PlayerControlsWeb(props: PlayerControlsWebProps) {
95
82
  outroRegion,
96
83
  onSkipIntro,
97
84
  onSkipOutro,
98
- resolutions,
99
- resolution,
100
- onSelectResolution,
101
85
  onToggleVisible,
102
86
  onPlayPause,
103
87
  onSkip,
@@ -106,21 +90,19 @@ export function PlayerControlsWeb(props: PlayerControlsWebProps) {
106
90
  onToggleSubtitles,
107
91
  onVolume,
108
92
  onFullscreen,
109
- width,
110
- height,
111
- cue,
112
- enabled,
113
- fontSize,
114
93
  } = props;
115
94
 
116
95
  const hasSubs = (subtitleTracks?.length ?? 0) > 0;
117
- const activeRes = resolution ?? resolutions?.[0] ?? "";
96
+ const subLang = (subtitleTrack ?? subtitleTracks?.[0] ?? "CC")
97
+ .split("-")[0]
98
+ .slice(0, 5)
99
+ .toUpperCase();
118
100
 
119
101
  const [trackW, setTrackW] = useState(0);
120
102
  const [scrubPct, setScrubPct] = useState<number | null>(null);
121
103
  const [volW, setVolW] = useState(0);
122
- const [sizeW, setSizeW] = useState(0);
123
- const [settingsOpen, setSettingsOpen] = useState(false);
104
+ const [subOpen, setSubOpen] = useState(false);
105
+ const [subTab, setSubTab] = useState<"lang" | "size">("lang");
124
106
 
125
107
  const pct = duration > 0 ? clamp01(currentTime / duration) : 0;
126
108
  const volPct = clamp01(volume);
@@ -143,13 +125,9 @@ export function PlayerControlsWeb(props: PlayerControlsWebProps) {
143
125
  onStartShouldSetPanResponder: () => true,
144
126
  onMoveShouldSetPanResponder: () => true,
145
127
  onPanResponderGrant: (e) =>
146
- setScrubPct(
147
- trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0,
148
- ),
128
+ setScrubPct(trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0),
149
129
  onPanResponderMove: (e) =>
150
- setScrubPct(
151
- trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0,
152
- ),
130
+ setScrubPct(trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0),
153
131
  onPanResponderRelease: (e) => {
154
132
  const p = trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0;
155
133
  setScrubPct(null);
@@ -174,6 +152,7 @@ export function PlayerControlsWeb(props: PlayerControlsWebProps) {
174
152
 
175
153
  const SIZE_MIN = 10;
176
154
  const SIZE_MAX = 34;
155
+ const [sizeW, setSizeW] = useState(0);
177
156
  const currentSize = subtitleFontSize ?? 14;
178
157
  const sizePct = clamp01((currentSize - SIZE_MIN) / (SIZE_MAX - SIZE_MIN));
179
158
  const sizePan = useMemo(
@@ -184,39 +163,22 @@ export function PlayerControlsWeb(props: PlayerControlsWebProps) {
184
163
  onPanResponderGrant: (e) => {
185
164
  if (!onChangeSubtitleFontSize) return;
186
165
  const p = sizeW > 0 ? clamp01(e.nativeEvent.locationX / sizeW) : 0;
187
- onChangeSubtitleFontSize?.(
188
- Math.round(SIZE_MIN + p * (SIZE_MAX - SIZE_MIN)),
189
- );
166
+ onChangeSubtitleFontSize?.(Math.round(SIZE_MIN + p * (SIZE_MAX - SIZE_MIN)));
190
167
  },
191
168
  onPanResponderMove: (e) => {
192
169
  if (!onChangeSubtitleFontSize) return;
193
170
  const p = sizeW > 0 ? clamp01(e.nativeEvent.locationX / sizeW) : 0;
194
- onChangeSubtitleFontSize?.(
195
- Math.round(SIZE_MIN + p * (SIZE_MAX - SIZE_MIN)),
196
- );
171
+ onChangeSubtitleFontSize?.(Math.round(SIZE_MIN + p * (SIZE_MAX - SIZE_MIN)));
197
172
  },
198
173
  }),
199
174
  [sizeW, onChangeSubtitleFontSize],
200
175
  );
201
176
 
202
177
  const shownPct = scrubPct ?? pct;
203
- const volIcon =
204
- muted || volume === 0
205
- ? "volume-mute"
206
- : volume < 0.5
207
- ? "volume-low"
208
- : "volume-high";
209
-
210
- const showSubsSection = hasSubs || !!onChangeSubtitleFontSize;
178
+ const volIcon = muted || volume === 0 ? "volume-mute" : volume < 0.5 ? "volume-low" : "volume-high";
211
179
 
212
180
  return (
213
- <View
214
- style={[
215
- styles.root,
216
- StyleSheet.absoluteFill,
217
- { width: width, height: height },
218
- ]}
219
- >
181
+ <View style={styles.root}>
220
182
  {/* tap surface toggles the chrome */}
221
183
  <TouchableOpacity
222
184
  style={StyleSheet.absoluteFill}
@@ -225,22 +187,13 @@ export function PlayerControlsWeb(props: PlayerControlsWebProps) {
225
187
  />
226
188
 
227
189
  {buffering && (
228
- <View
229
- style={[StyleSheet.absoluteFill, styles.centerBadge]}
230
- pointerEvents="none"
231
- >
190
+ <View style={styles.centerBadge} pointerEvents="none">
232
191
  <ActivityIndicator color="#e50914" size="large" />
233
192
  </View>
234
193
  )}
235
194
 
236
195
  {visible && (
237
- <View
238
- style={[
239
- StyleSheet.absoluteFill,
240
- { width: "100%", height: "100%", position: "absolute" },
241
- ]}
242
- pointerEvents="box-none"
243
- >
196
+ <>
244
197
  {/* top gradient-ish bar */}
245
198
  <View style={[styles.bar, styles.top]} pointerEvents="box-none">
246
199
  {!!title && (
@@ -248,24 +201,19 @@ export function PlayerControlsWeb(props: PlayerControlsWebProps) {
248
201
  {title}
249
202
  </Text>
250
203
  )}
204
+ <TouchableOpacity onPress={onToggleMute} hitSlop={8}>
205
+ <Ionicons name={muted ? "volume-mute" : "volume-high"} size={20} color="#fff" />
206
+ </TouchableOpacity>
251
207
  </View>
252
208
 
253
209
  {/* center buttons */}
254
- <View
255
- style={[styles.centerRow, { flex: 1 }]}
256
- pointerEvents="box-none"
257
- >
258
- <TouchableOpacity
259
- style={styles.skipBtn}
260
- onPress={() => onSkip(-10)}
261
- >
210
+ <View style={styles.centerRow} pointerEvents="box-none">
211
+ <TouchableOpacity style={styles.skipBtn} onPress={() => onSkip(-10)}>
262
212
  <Ionicons name="play-back" size={30} color="#fff" />
263
213
  </TouchableOpacity>
264
214
  <TouchableOpacity style={styles.ppBtn} onPress={onPlayPause}>
265
215
  <Ionicons
266
- name={
267
- buffering ? "ellipsis-horizontal" : playing ? "pause" : "play"
268
- }
216
+ name={buffering ? "ellipsis-horizontal" : playing ? "pause" : "play"}
269
217
  size={38}
270
218
  color="#fff"
271
219
  />
@@ -275,7 +223,7 @@ export function PlayerControlsWeb(props: PlayerControlsWebProps) {
275
223
  </TouchableOpacity>
276
224
  </View>
277
225
 
278
- {/* bottom bar: time — timeline — duration — settings — fullscreen */}
226
+ {/* bottom bar */}
279
227
  <View style={[styles.bar, styles.bottom]} pointerEvents="box-none">
280
228
  <Text style={styles.time}>
281
229
  {fmt(scrubPct !== null ? scrubPct * duration : currentTime)}
@@ -295,32 +243,28 @@ export function PlayerControlsWeb(props: PlayerControlsWebProps) {
295
243
  ]}
296
244
  />
297
245
  )}
298
- {introRegion &&
299
- duration > 0 &&
300
- introRegion[1] > introRegion[0] && (
301
- <View
302
- style={[
303
- styles.introHighlight,
304
- {
305
- left: `${clamp01(introRegion[0] / duration) * 100}%`,
306
- width: `${clamp01((introRegion[1] - introRegion[0]) / duration) * 100}%`,
307
- },
308
- ]}
309
- />
310
- )}
311
- {outroRegion &&
312
- duration > 0 &&
313
- outroRegion[1] > outroRegion[0] && (
314
- <View
315
- style={[
316
- styles.outroHighlight,
317
- {
318
- left: `${clamp01(outroRegion[0] / duration) * 100}%`,
319
- width: `${clamp01((outroRegion[1] - outroRegion[0]) / duration) * 100}%`,
320
- },
321
- ]}
322
- />
323
- )}
246
+ {introRegion && duration > 0 && introRegion[1] > introRegion[0] && (
247
+ <View
248
+ style={[
249
+ styles.introHighlight,
250
+ {
251
+ left: `${clamp01(introRegion[0] / duration) * 100}%`,
252
+ width: `${clamp01((introRegion[1] - introRegion[0]) / duration) * 100}%`,
253
+ },
254
+ ]}
255
+ />
256
+ )}
257
+ {outroRegion && duration > 0 && outroRegion[1] > outroRegion[0] && (
258
+ <View
259
+ style={[
260
+ styles.outroHighlight,
261
+ {
262
+ left: `${clamp01(outroRegion[0] / duration) * 100}%`,
263
+ width: `${clamp01((outroRegion[1] - outroRegion[0]) / duration) * 100}%`,
264
+ },
265
+ ]}
266
+ />
267
+ )}
324
268
  <View style={[styles.fill, { width: `${shownPct * 100}%` }]} />
325
269
  </View>
326
270
  <View
@@ -334,96 +278,87 @@ export function PlayerControlsWeb(props: PlayerControlsWebProps) {
334
278
 
335
279
  <Text style={styles.time}>{fmt(duration)}</Text>
336
280
 
337
- {/* settings dropdown: subtitles + volume + resolutions */}
338
- <View style={styles.settingsWrap}>
339
- <TouchableOpacity
340
- style={[styles.iconBtn, settingsOpen && styles.iconBtnOn]}
341
- onPress={() => setSettingsOpen((v) => !v)}
342
- hitSlop={6}
343
- >
344
- <Ionicons
345
- name="settings-outline"
346
- size={17}
347
- color={settingsOpen ? "#7dd3fc" : "#fff"}
348
- />
349
- </TouchableOpacity>
350
-
351
- {settingsOpen ? (
352
- <View style={styles.settingsMenu}>
353
- <Text style={styles.settingsTitle}>Settings</Text>
354
-
355
- {/* ---- Subtitles ---- */}
356
- {showSubsSection ? (
357
- <View style={styles.section}>
358
- <View style={styles.sectionHead}>
359
- <Ionicons
360
- name="chatbox-ellipses-outline"
361
- size={15}
362
- color="#9aa0aa"
363
- />
364
- <Text style={styles.sectionLabel}>Subtitles</Text>
365
- <TouchableOpacity
366
- onPress={onToggleSubtitles}
367
- hitSlop={6}
368
- style={{ marginLeft: "auto" }}
369
- >
370
- <View
371
- style={[
372
- styles.switchTrack,
373
- subtitlesOn && styles.switchTrackOn,
374
- ]}
375
- >
376
- <View
377
- style={[
378
- styles.switchThumb,
379
- subtitlesOn && styles.switchThumbOn,
380
- ]}
381
- />
382
- </View>
383
- </TouchableOpacity>
384
- </View>
281
+ {/* CC toggle */}
282
+ <TouchableOpacity
283
+ style={[styles.ccBtn, subtitlesOn && styles.ccBtnOn]}
284
+ onPress={onToggleSubtitles}
285
+ hitSlop={6}
286
+ >
287
+ <Text style={[styles.ccText, subtitlesOn && styles.ccTextOn]}>CC</Text>
288
+ </TouchableOpacity>
385
289
 
386
- {hasSubs && (
387
- <View style={styles.menuList}>
388
- {subtitleTracks?.map((lang) => {
389
- const active = lang === subtitleTrack;
390
- return (
391
- <TouchableOpacity
392
- key={lang}
393
- style={[
394
- styles.menuItem,
395
- active && styles.menuItemOn,
396
- ]}
397
- onPress={() => {
398
- setSettingsOpen(false);
399
- onSelectSubtitleTrack?.(lang);
400
- }}
401
- >
402
- <Text
403
- style={[
404
- styles.menuItemText,
405
- active && styles.menuItemTextOn,
406
- ]}
407
- >
408
- {lang}
409
- </Text>
410
- {active ? (
411
- <Ionicons
412
- name="checkmark"
413
- size={14}
414
- color="#fff"
415
- />
416
- ) : null}
417
- </TouchableOpacity>
418
- );
419
- })}
420
- </View>
421
- )}
290
+ {/* subtitle selector (shown with at least ONE track so the Text Size
291
+ slider is reachable). Tap to open a dropdown with two tabs:
292
+ language list | text size slider. */}
293
+ {hasSubs ? (
294
+ <View style={styles.subWrap}>
295
+ <TouchableOpacity
296
+ style={[styles.langBtn, subtitlesOn && styles.langBtnOn]}
297
+ onPress={() => setSubOpen((v) => !v)}
298
+ hitSlop={6}
299
+ >
300
+ <Text style={[styles.ccText, subtitlesOn && styles.ccTextOn]}>
301
+ {subLang}
302
+ </Text>
303
+ <Ionicons
304
+ name={subOpen ? "chevron-up" : "chevron-down"}
305
+ size={12}
306
+ color={subtitlesOn ? "#fff" : "#9aa0aa"}
307
+ />
308
+ </TouchableOpacity>
309
+ {subOpen ? (
310
+ <View style={styles.subMenu}>
311
+ <View style={styles.subTabs}>
312
+ <TouchableOpacity
313
+ style={[styles.subTab, subTab === "lang" && styles.subTabOn]}
314
+ onPress={() => setSubTab("lang")}
315
+ >
316
+ <Text
317
+ style={[styles.subTabText, subTab === "lang" && styles.subTabTextOn]}
318
+ >
319
+ Language
320
+ </Text>
321
+ </TouchableOpacity>
322
+ <TouchableOpacity
323
+ style={[styles.subTab, subTab === "size" && styles.subTabOn]}
324
+ onPress={() => setSubTab("size")}
325
+ >
326
+ <Text
327
+ style={[styles.subTabText, subTab === "size" && styles.subTabTextOn]}
328
+ >
329
+ Text Size
330
+ </Text>
331
+ </TouchableOpacity>
332
+ </View>
422
333
 
423
- {!!onChangeSubtitleFontSize && (
424
- <View style={styles.sliderRow}>
334
+ {subTab === "lang" ? (
335
+ <View style={styles.subBody}>
336
+ {subtitleTracks?.map((lang) => {
337
+ const active = lang === subtitleTrack;
338
+ return (
339
+ <TouchableOpacity
340
+ key={lang}
341
+ style={[styles.subItem, active && styles.subItemOn]}
342
+ onPress={() => {
343
+ setSubOpen(false);
344
+ onSelectSubtitleTrack?.(lang);
345
+ }}
346
+ >
347
+ <Text style={[styles.subItemText, active && styles.subItemTextOn]}>
348
+ {lang}
349
+ </Text>
350
+ {active ? (
351
+ <Ionicons name="checkmark" size={14} color="#fff" />
352
+ ) : null}
353
+ </TouchableOpacity>
354
+ );
355
+ })}
356
+ </View>
357
+ ) : (
358
+ <View style={styles.subBody}>
359
+ <View style={styles.sizeRow}>
425
360
  <TouchableOpacity
426
- style={styles.minusBtn}
361
+ style={styles.sizeBtn}
427
362
  onPress={() =>
428
363
  onChangeSubtitleFontSize?.(
429
364
  Math.max(
@@ -437,22 +372,18 @@ export function PlayerControlsWeb(props: PlayerControlsWebProps) {
437
372
  </TouchableOpacity>
438
373
 
439
374
  <View
440
- style={styles.sliderTrack}
441
- onLayout={(e) =>
442
- setSizeW(e.nativeEvent.layout.width)
443
- }
375
+ style={[
376
+ styles.sizeTrack,
377
+ { opacity: onChangeSubtitleFontSize ? 1 : 0.5 },
378
+ ]}
379
+ onLayout={(e) => setSizeW(e.nativeEvent.layout.width)}
444
380
  {...sizePan.panHandlers}
445
381
  >
446
- <View
447
- style={[
448
- styles.sliderFill,
449
- { width: `${sizePct * 100}%` },
450
- ]}
451
- />
382
+ <View style={[styles.sizeFill, { width: `${sizePct * 100}%` }]} />
452
383
  </View>
453
384
 
454
385
  <TouchableOpacity
455
- style={styles.minusBtn}
386
+ style={styles.sizeBtn}
456
387
  onPress={() =>
457
388
  onChangeSubtitleFontSize?.(
458
389
  Math.min(
@@ -464,116 +395,36 @@ export function PlayerControlsWeb(props: PlayerControlsWebProps) {
464
395
  >
465
396
  <Ionicons name="add" size={16} color="#fff" />
466
397
  </TouchableOpacity>
467
- <Text style={styles.sliderValue}>
468
- {currentSize}px
469
- </Text>
470
398
  </View>
471
- )}
472
- </View>
473
- ) : null}
474
-
475
- {/* ---- Volume ---- */}
476
- <View style={styles.section}>
477
- <View style={styles.sectionHead}>
478
- <Ionicons
479
- name="volume-medium-outline"
480
- size={15}
481
- color="#9aa0aa"
482
- />
483
- <Text style={styles.sectionLabel}>Volume</Text>
484
- <TouchableOpacity
485
- onPress={onToggleMute}
486
- hitSlop={6}
487
- style={{ marginLeft: "auto" }}
488
- >
489
- <Ionicons
490
- name={volIcon as any}
491
- size={17}
492
- color={muted ? "#e50914" : "#fff"}
493
- />
494
- </TouchableOpacity>
495
- <Text style={styles.sliderValue}>
496
- {Math.round(volPct * 100)}
497
- </Text>
498
- </View>
499
- <View style={styles.sliderRow}>
500
- <View
501
- style={styles.sliderTrack}
502
- onLayout={(e) => setVolW(e.nativeEvent.layout.width)}
503
- {...volPan.panHandlers}
504
- >
505
- <View
506
- style={[
507
- styles.sliderFill,
508
- { width: `${volPct * 100}%` },
509
- ]}
510
- />
399
+ <Text style={styles.sizeLabel}>{currentSize}px</Text>
511
400
  </View>
512
- </View>
401
+ )}
513
402
  </View>
403
+ ) : null}
404
+ </View>
405
+ ) : null}
514
406
 
515
- {/* ---- Resolution ---- */}
516
- {(resolutions?.length ?? 0) > 0 ? (
517
- <View style={styles.section}>
518
- <View style={styles.sectionHead}>
519
- <Ionicons
520
- name="resize-outline"
521
- size={15}
522
- color="#9aa0aa"
523
- />
524
- <Text style={styles.sectionLabel}>Resolution</Text>
525
- </View>
526
- <View style={styles.menuList}>
527
- {resolutions?.map((label) => {
528
- const active = label === activeRes;
529
- return (
530
- <TouchableOpacity
531
- key={label}
532
- style={[
533
- styles.menuItem,
534
- active && styles.menuItemOn,
535
- ]}
536
- onPress={() => {
537
- setSettingsOpen(false);
538
- onSelectResolution?.(label);
539
- }}
540
- >
541
- <Text
542
- style={[
543
- styles.menuItemText,
544
- active && styles.menuItemTextOn,
545
- ]}
546
- >
547
- {label}
548
- </Text>
549
- {active ? (
550
- <Ionicons
551
- name="checkmark"
552
- size={14}
553
- color="#fff"
554
- />
555
- ) : null}
556
- </TouchableOpacity>
557
- );
558
- })}
559
- </View>
560
- </View>
561
- ) : null}
562
- </View>
563
- ) : null}
407
+ {/* volume: icon tap = mute, slider drag = level */}
408
+ <TouchableOpacity onPress={onToggleMute} hitSlop={6}>
409
+ <Ionicons name={volIcon as any} size={17} color="#fff" />
410
+ </TouchableOpacity>
411
+ <View
412
+ style={styles.volTrackWrap}
413
+ onLayout={(e) => setVolW(e.nativeEvent.layout.width)}
414
+ {...volPan.panHandlers}
415
+ >
416
+ <View style={styles.volTrack}>
417
+ <View style={[styles.volFill, { width: `${volPct * 100}%` }]} />
418
+ </View>
564
419
  </View>
565
420
 
566
421
  <TouchableOpacity onPress={onFullscreen} hitSlop={8}>
567
422
  <Ionicons name="expand" size={18} color="#fff" />
568
423
  </TouchableOpacity>
569
424
  </View>
570
- </View>
425
+ </>
571
426
  )}
572
- <SubtitleDisplayWeb
573
- cue={cue}
574
- enabled={enabled}
575
- fontSize={fontSize}
576
- />
427
+
577
428
  {visible && inIntro && (
578
429
  <TouchableOpacity style={styles.skipRegionBtn} onPress={onSkipIntro}>
579
430
  <Text style={styles.skipRegionText}>Skip Intro ›</Text>
@@ -589,9 +440,10 @@ export function PlayerControlsWeb(props: PlayerControlsWebProps) {
589
440
  }
590
441
 
591
442
  const styles = StyleSheet.create({
592
- root: { zIndex: 2 },
443
+ root: { ...StyleSheet.absoluteFillObject, zIndex: 2 },
593
444
 
594
445
  centerBadge: {
446
+ ...StyleSheet.absoluteFillObject,
595
447
  justifyContent: "center",
596
448
  alignItems: "center",
597
449
  width: "100%",
@@ -614,6 +466,7 @@ const styles = StyleSheet.create({
614
466
  title: { color: "#fff", fontWeight: "700", fontSize: 13, flex: 1 },
615
467
 
616
468
  centerRow: {
469
+ ...StyleSheet.absoluteFillObject,
617
470
  justifyContent: "center",
618
471
  alignItems: "center",
619
472
  flexDirection: "row",
@@ -688,80 +541,71 @@ const styles = StyleSheet.create({
688
541
  },
689
542
  thumbIdle: { opacity: 0.85 },
690
543
 
691
- iconBtn: {
692
- width: 26,
693
- height: 26,
544
+ ccBtn: {
545
+ borderWidth: 1.5,
546
+ borderColor: "#666",
694
547
  borderRadius: 4,
695
- alignItems: "center",
696
- justifyContent: "center",
548
+ paddingHorizontal: 5,
549
+ paddingVertical: 1,
550
+ },
551
+ ccBtnOn: { borderColor: "#e50914", backgroundColor: "rgba(229,9,20,0.25)" },
552
+ ccText: { color: "#888", fontSize: 10, fontWeight: "900" },
553
+ ccTextOn: { color: "#fff" },
554
+ langBtn: {
555
+ borderWidth: 1.5,
556
+ borderColor: "#555",
557
+ borderRadius: 4,
558
+ paddingHorizontal: 6,
559
+ paddingVertical: 1,
560
+ },
561
+ langBtnOn: {
562
+ borderColor: "#7dd3fc",
563
+ backgroundColor: "rgba(125,211,252,0.18)",
697
564
  },
698
- iconBtnOn: { backgroundColor: "rgba(125,211,252,0.18)" },
699
565
 
700
- settingsWrap: { position: "relative", zIndex: 20 },
701
- settingsMenu: {
566
+ subWrap: { position: "relative", zIndex: 20 },
567
+ subMenu: {
702
568
  position: "absolute",
703
- bottom: 30,
569
+ bottom: 26,
704
570
  right: 0,
705
571
  zIndex: 30,
706
- width: 250,
707
- maxHeight: 420,
572
+ width: 190,
708
573
  backgroundColor: "#14161c",
709
574
  borderWidth: 1,
710
575
  borderColor: "#2a2f3a",
711
576
  borderRadius: 8,
712
577
  overflow: "hidden",
713
- paddingBottom: 6,
714
578
  elevation: 12,
715
579
  shadowColor: "#000",
716
580
  shadowOpacity: 0.5,
717
581
  shadowRadius: 10,
718
582
  shadowOffset: { width: 0, height: 3 },
719
583
  },
720
- settingsTitle: {
721
- color: "#9aa0aa",
722
- fontSize: 11,
723
- fontWeight: "800",
724
- textTransform: "uppercase",
725
- letterSpacing: 1,
726
- paddingHorizontal: 12,
727
- paddingTop: 10,
728
- paddingBottom: 6,
729
- borderBottomWidth: 1,
730
- borderBottomColor: "#262a34",
731
- },
732
-
733
- section: { paddingHorizontal: 6, paddingTop: 8 },
734
- sectionHead: {
735
- flexDirection: "row",
736
- alignItems: "center",
737
- gap: 6,
738
- paddingHorizontal: 6,
739
- paddingBottom: 2,
740
- },
741
- sectionLabel: { color: "#cbd5e1", fontSize: 12, fontWeight: "700" },
742
-
743
- menuList: { paddingVertical: 4 },
744
- menuItem: {
584
+ subTabs: { flexDirection: "row", borderBottomWidth: 1, borderBottomColor: "#262a34" },
585
+ subTab: { flex: 1, alignItems: "center", paddingVertical: 7, backgroundColor: "#1a1d24" },
586
+ subTabOn: { backgroundColor: "#232833" },
587
+ subTabText: { color: "#9aa0aa", fontSize: 12, fontWeight: "700" },
588
+ subTabTextOn: { color: "#7dd3fc", fontWeight: "800" },
589
+ subBody: { paddingVertical: 4 },
590
+ subItem: {
745
591
  flexDirection: "row",
746
592
  alignItems: "center",
747
593
  justifyContent: "space-between",
748
594
  gap: 10,
749
- paddingHorizontal: 14,
750
- paddingVertical: 6,
751
- borderRadius: 6,
595
+ paddingHorizontal: 12,
596
+ paddingVertical: 7,
752
597
  },
753
- menuItemOn: { backgroundColor: "rgba(125,211,252,0.15)" },
754
- menuItemText: { color: "#cbd5e1", fontSize: 13, fontWeight: "600" },
755
- menuItemTextOn: { color: "#7dd3fc", fontWeight: "800" },
756
-
757
- sliderRow: {
598
+ subItemOn: { backgroundColor: "rgba(125,211,252,0.15)" },
599
+ subItemText: { color: "#cbd5e1", fontSize: 13, fontWeight: "600" },
600
+ subItemTextOn: { color: "#7dd3fc", fontWeight: "800" },
601
+ sizeRow: {
758
602
  flexDirection: "row",
759
603
  alignItems: "center",
760
604
  gap: 10,
761
605
  paddingHorizontal: 10,
762
- paddingVertical: 8,
606
+ paddingVertical: 10,
763
607
  },
764
- minusBtn: {
608
+ sizeBtn: {
765
609
  width: 24,
766
610
  height: 24,
767
611
  borderRadius: 4,
@@ -769,30 +613,17 @@ const styles = StyleSheet.create({
769
613
  justifyContent: "center",
770
614
  backgroundColor: "#2a2f3a",
771
615
  },
772
- sliderTrack: { flex: 1, height: 22, justifyContent: "center" },
773
- sliderFill: { height: 4, borderRadius: 2, backgroundColor: "#7dd3fc" },
774
- sliderValue: {
616
+ sizeTrack: { flex: 1, height: 22, justifyContent: "center" },
617
+ sizeFill: { height: 4, borderRadius: 2, backgroundColor: "#7dd3fc" },
618
+ sizeLabel: {
775
619
  color: "#9aa0aa",
776
620
  fontSize: 11,
777
621
  fontWeight: "700",
778
- minWidth: 32,
779
- textAlign: "right",
622
+ textAlign: "center",
623
+ paddingBottom: 6,
780
624
  },
781
625
 
782
- switchTrack: {
783
- width: 34,
784
- height: 20,
785
- borderRadius: 10,
786
- backgroundColor: "#3a3f47",
787
- justifyContent: "center",
788
- paddingHorizontal: 2,
789
- },
790
- switchTrackOn: { backgroundColor: "rgba(229,9,20,0.85)" },
791
- switchThumb: {
792
- width: 16,
793
- height: 16,
794
- borderRadius: 8,
795
- backgroundColor: "#fff",
796
- },
797
- switchThumbOn: { alignSelf: "flex-end" },
798
- });
626
+ volTrackWrap: { width: 56, paddingVertical: 12, justifyContent: "center" },
627
+ volTrack: { height: 4, borderRadius: 2, backgroundColor: "#3a3f47", overflow: "hidden" },
628
+ volFill: { height: 4, backgroundColor: "#fff" },
629
+ });