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
@@ -0,0 +1,289 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { ActivityIndicator, PanResponder, StyleSheet, Text, TouchableOpacity, View, } from "react-native";
4
+ import { Ionicons } from "@expo/vector-icons";
5
+ const fmt = (s) => {
6
+ if (!Number.isFinite(s) || s < 0)
7
+ s = 0;
8
+ const h = Math.floor(s / 3600);
9
+ const m = Math.floor((s % 3600) / 60);
10
+ const sec = Math.floor(s % 60);
11
+ return `${h.toString().padStart(2, "0")}:${m
12
+ .toString()
13
+ .padStart(2, "0")}:${sec.toString().padStart(2, "0")}`;
14
+ };
15
+ const clamp01 = (v) => Math.min(1, Math.max(0, v));
16
+ export function PlayerControls(props) {
17
+ const { playing, buffering, currentTime, duration, muted, title, visible, subtitlesOn, volume, subtitleTracks, subtitleTrack, onSelectSubtitleTrack, subtitleFontSize, onChangeSubtitleFontSize, loadProgress, introRegion, outroRegion, onSkipIntro, onSkipOutro, onToggleVisible, onPlayPause, onSkip, onSeek, onToggleMute, onToggleSubtitles, onVolume, onFullscreen, } = props;
18
+ const hasSubs = (subtitleTracks?.length ?? 0) > 0;
19
+ const subLang = (subtitleTrack ?? subtitleTracks?.[0] ?? "CC")
20
+ .split("-")[0]
21
+ .slice(0, 5)
22
+ .toUpperCase();
23
+ const [trackW, setTrackW] = useState(0);
24
+ const [scrubPct, setScrubPct] = useState(null);
25
+ const [volW, setVolW] = useState(0);
26
+ const [subOpen, setSubOpen] = useState(false);
27
+ const [subTab, setSubTab] = useState("lang");
28
+ const pct = duration > 0 ? clamp01(currentTime / duration) : 0;
29
+ const volPct = clamp01(volume);
30
+ const inIntro = !!introRegion &&
31
+ introRegion[1] > introRegion[0] &&
32
+ currentTime >= introRegion[0] &&
33
+ currentTime <= introRegion[1] &&
34
+ !!onSkipIntro;
35
+ const inOutro = !!outroRegion &&
36
+ outroRegion[1] > outroRegion[0] &&
37
+ currentTime >= outroRegion[0] &&
38
+ currentTime <= outroRegion[1] &&
39
+ !!onSkipOutro;
40
+ const pan = useMemo(() => PanResponder.create({
41
+ onStartShouldSetPanResponder: () => true,
42
+ onMoveShouldSetPanResponder: () => true,
43
+ onPanResponderGrant: (e) => setScrubPct(trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0),
44
+ onPanResponderMove: (e) => setScrubPct(trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0),
45
+ onPanResponderRelease: (e) => {
46
+ const p = trackW > 0 ? clamp01(e.nativeEvent.locationX / trackW) : 0;
47
+ setScrubPct(null);
48
+ onSeek(p * duration);
49
+ },
50
+ }), [trackW, duration, onSeek]);
51
+ const volPan = useMemo(() => PanResponder.create({
52
+ onStartShouldSetPanResponder: () => true,
53
+ onMoveShouldSetPanResponder: () => true,
54
+ onPanResponderGrant: (e) => onVolume(volW > 0 ? clamp01(e.nativeEvent.locationX / volW) : 1),
55
+ onPanResponderMove: (e) => onVolume(volW > 0 ? clamp01(e.nativeEvent.locationX / volW) : 1),
56
+ }), [volW, onVolume]);
57
+ const SIZE_MIN = 10;
58
+ const SIZE_MAX = 34;
59
+ const [sizeW, setSizeW] = useState(0);
60
+ const currentSize = subtitleFontSize ?? 14;
61
+ const sizePct = clamp01((currentSize - SIZE_MIN) / (SIZE_MAX - SIZE_MIN));
62
+ const sizePan = useMemo(() => PanResponder.create({
63
+ onStartShouldSetPanResponder: () => true,
64
+ onMoveShouldSetPanResponder: () => true,
65
+ onPanResponderGrant: (e) => {
66
+ if (!onChangeSubtitleFontSize)
67
+ return;
68
+ const p = sizeW > 0 ? clamp01(e.nativeEvent.locationX / sizeW) : 0;
69
+ onChangeSubtitleFontSize?.(Math.round(SIZE_MIN + p * (SIZE_MAX - SIZE_MIN)));
70
+ },
71
+ onPanResponderMove: (e) => {
72
+ if (!onChangeSubtitleFontSize)
73
+ return;
74
+ const p = sizeW > 0 ? clamp01(e.nativeEvent.locationX / sizeW) : 0;
75
+ onChangeSubtitleFontSize?.(Math.round(SIZE_MIN + p * (SIZE_MAX - SIZE_MIN)));
76
+ },
77
+ }), [sizeW, onChangeSubtitleFontSize]);
78
+ const shownPct = scrubPct ?? pct;
79
+ const volIcon = muted || volume === 0 ? "volume-mute" : volume < 0.5 ? "volume-low" : "volume-high";
80
+ return (_jsxs(View, { style: styles.root, children: [_jsx(TouchableOpacity, { style: StyleSheet.absoluteFill, activeOpacity: 1, onPress: onToggleVisible }), buffering && (_jsx(View, { style: styles.centerBadge, pointerEvents: "none", children: _jsx(ActivityIndicator, { color: "#e50914", size: "large" }) })), visible && (_jsxs(_Fragment, { children: [_jsxs(View, { style: [styles.bar, styles.top], pointerEvents: "box-none", children: [!!title && (_jsx(Text, { numberOfLines: 1, style: styles.title, children: title })), _jsx(TouchableOpacity, { onPress: onToggleMute, hitSlop: 8, children: _jsx(Ionicons, { name: muted ? "volume-mute" : "volume-high", size: 20, color: "#fff" }) })] }), _jsxs(View, { style: styles.centerRow, pointerEvents: "box-none", children: [_jsx(TouchableOpacity, { style: styles.skipBtn, onPress: () => onSkip(-10), children: _jsx(Ionicons, { name: "play-back", size: 30, color: "#fff" }) }), _jsx(TouchableOpacity, { style: styles.ppBtn, onPress: onPlayPause, children: _jsx(Ionicons, { name: buffering ? "ellipsis-horizontal" : playing ? "pause" : "play", size: 38, color: "#fff" }) }), _jsx(TouchableOpacity, { style: styles.skipBtn, onPress: () => onSkip(10), children: _jsx(Ionicons, { name: "play-forward", size: 30, color: "#fff" }) })] }), _jsxs(View, { style: [styles.bar, styles.bottom], pointerEvents: "box-none", children: [_jsx(Text, { style: styles.time, children: fmt(scrubPct !== null ? scrubPct * duration : currentTime) }), _jsxs(View, { style: styles.trackWrap, onLayout: (e) => setTrackW(e.nativeEvent.layout.width), ...pan.panHandlers, children: [_jsxs(View, { style: styles.track, children: [loadProgress != null && loadProgress > 0 && (_jsx(View, { style: [
81
+ styles.loadLine,
82
+ { width: `${clamp01(loadProgress) * 100}%` },
83
+ ] })), introRegion && duration > 0 && introRegion[1] > introRegion[0] && (_jsx(View, { style: [
84
+ styles.introHighlight,
85
+ {
86
+ left: `${clamp01(introRegion[0] / duration) * 100}%`,
87
+ width: `${clamp01((introRegion[1] - introRegion[0]) / duration) * 100}%`,
88
+ },
89
+ ] })), outroRegion && duration > 0 && outroRegion[1] > outroRegion[0] && (_jsx(View, { style: [
90
+ styles.outroHighlight,
91
+ {
92
+ left: `${clamp01(outroRegion[0] / duration) * 100}%`,
93
+ width: `${clamp01((outroRegion[1] - outroRegion[0]) / duration) * 100}%`,
94
+ },
95
+ ] })), _jsx(View, { style: [styles.fill, { width: `${shownPct * 100}%` }] })] }), _jsx(View, { style: [
96
+ styles.thumb,
97
+ { left: `${shownPct * 100}%` },
98
+ scrubPct === null && styles.thumbIdle,
99
+ ] })] }), _jsx(Text, { style: styles.time, children: fmt(duration) }), _jsx(TouchableOpacity, { style: [styles.ccBtn, subtitlesOn && styles.ccBtnOn], onPress: onToggleSubtitles, hitSlop: 6, children: _jsx(Text, { style: [styles.ccText, subtitlesOn && styles.ccTextOn], children: "CC" }) }), hasSubs ? (_jsxs(View, { style: styles.subWrap, children: [_jsxs(TouchableOpacity, { style: [styles.langBtn, subtitlesOn && styles.langBtnOn], onPress: () => setSubOpen((v) => !v), hitSlop: 6, children: [_jsx(Text, { style: [styles.ccText, subtitlesOn && styles.ccTextOn], children: subLang }), _jsx(Ionicons, { name: subOpen ? "chevron-up" : "chevron-down", size: 12, color: subtitlesOn ? "#fff" : "#9aa0aa" })] }), subOpen ? (_jsxs(View, { style: styles.subMenu, children: [_jsxs(View, { style: styles.subTabs, children: [_jsx(TouchableOpacity, { style: [styles.subTab, subTab === "lang" && styles.subTabOn], onPress: () => setSubTab("lang"), children: _jsx(Text, { style: [styles.subTabText, subTab === "lang" && styles.subTabTextOn], children: "Language" }) }), _jsx(TouchableOpacity, { style: [styles.subTab, subTab === "size" && styles.subTabOn], onPress: () => setSubTab("size"), children: _jsx(Text, { style: [styles.subTabText, subTab === "size" && styles.subTabTextOn], children: "Text Size" }) })] }), subTab === "lang" ? (_jsx(View, { style: styles.subBody, children: subtitleTracks?.map((lang) => {
100
+ const active = lang === subtitleTrack;
101
+ return (_jsxs(TouchableOpacity, { style: [styles.subItem, active && styles.subItemOn], onPress: () => {
102
+ setSubOpen(false);
103
+ onSelectSubtitleTrack?.(lang);
104
+ }, children: [_jsx(Text, { style: [styles.subItemText, active && styles.subItemTextOn], children: lang }), active ? (_jsx(Ionicons, { name: "checkmark", size: 14, color: "#fff" })) : null] }, lang));
105
+ }) })) : (_jsxs(View, { style: styles.subBody, children: [_jsxs(View, { style: styles.sizeRow, children: [_jsx(TouchableOpacity, { style: styles.sizeBtn, onPress: () => onChangeSubtitleFontSize?.(Math.max(SIZE_MIN, Math.round((subtitleFontSize ?? 14) - 2))), children: _jsx(Ionicons, { name: "remove", size: 16, color: "#fff" }) }), _jsx(View, { style: [
106
+ styles.sizeTrack,
107
+ { opacity: onChangeSubtitleFontSize ? 1 : 0.5 },
108
+ ], onLayout: (e) => setSizeW(e.nativeEvent.layout.width), ...sizePan.panHandlers, children: _jsx(View, { style: [styles.sizeFill, { width: `${sizePct * 100}%` }] }) }), _jsx(TouchableOpacity, { style: styles.sizeBtn, onPress: () => onChangeSubtitleFontSize?.(Math.min(SIZE_MAX, Math.round((subtitleFontSize ?? 14) + 2))), children: _jsx(Ionicons, { name: "add", size: 16, color: "#fff" }) })] }), _jsxs(Text, { style: styles.sizeLabel, children: [currentSize, "px"] })] }))] })) : null] })) : null, _jsx(TouchableOpacity, { onPress: onToggleMute, hitSlop: 6, children: _jsx(Ionicons, { name: volIcon, size: 17, color: "#fff" }) }), _jsx(View, { style: styles.volTrackWrap, onLayout: (e) => setVolW(e.nativeEvent.layout.width), ...volPan.panHandlers, children: _jsx(View, { style: styles.volTrack, children: _jsx(View, { style: [styles.volFill, { width: `${volPct * 100}%` }] }) }) }), _jsx(TouchableOpacity, { onPress: onFullscreen, hitSlop: 8, children: _jsx(Ionicons, { name: "expand", size: 18, color: "#fff" }) })] })] })), visible && inIntro && (_jsx(TouchableOpacity, { style: styles.skipRegionBtn, onPress: onSkipIntro, children: _jsx(Text, { style: styles.skipRegionText, children: "Skip Intro \u203A" }) })), visible && inOutro && (_jsx(TouchableOpacity, { style: styles.skipRegionBtn, onPress: onSkipOutro, children: _jsx(Text, { style: styles.skipRegionText, children: "Skip Outro \u203A" }) }))] }));
109
+ }
110
+ const styles = StyleSheet.create({
111
+ root: { ...StyleSheet.absoluteFillObject, zIndex: 2 },
112
+ centerBadge: {
113
+ ...StyleSheet.absoluteFillObject,
114
+ justifyContent: "center",
115
+ alignItems: "center",
116
+ width: "100%",
117
+ height: "100%",
118
+ },
119
+ bar: {
120
+ position: "absolute",
121
+ left: 0,
122
+ right: 0,
123
+ flexDirection: "row",
124
+ alignItems: "center",
125
+ paddingHorizontal: 12,
126
+ backgroundColor: "rgba(0,0,0,0.45)",
127
+ gap: 8,
128
+ },
129
+ top: { top: 0, height: 42 },
130
+ bottom: { bottom: 0, height: 44 },
131
+ title: { color: "#fff", fontWeight: "700", fontSize: 13, flex: 1 },
132
+ centerRow: {
133
+ ...StyleSheet.absoluteFillObject,
134
+ justifyContent: "center",
135
+ alignItems: "center",
136
+ flexDirection: "row",
137
+ gap: 34,
138
+ },
139
+ ppBtn: {
140
+ width: 64,
141
+ height: 64,
142
+ borderRadius: 32,
143
+ backgroundColor: "rgba(229,9,20,0.92)",
144
+ justifyContent: "center",
145
+ alignItems: "center",
146
+ },
147
+ skipBtn: { opacity: 0.9 },
148
+ time: {
149
+ color: "#ddd",
150
+ fontSize: 11,
151
+ fontVariant: ["tabular-nums"],
152
+ minWidth: 58,
153
+ },
154
+ trackWrap: { flex: 1, paddingVertical: 12, justifyContent: "center" },
155
+ track: {
156
+ height: 4,
157
+ borderRadius: 2,
158
+ backgroundColor: "#3a3f47",
159
+ overflow: "hidden",
160
+ },
161
+ fill: { height: 4, borderRadius: 2, backgroundColor: "#e50914" },
162
+ loadLine: {
163
+ position: "absolute",
164
+ left: 0,
165
+ top: 0,
166
+ height: 4,
167
+ borderRadius: 2,
168
+ backgroundColor: "rgba(255,255,255,0.35)",
169
+ },
170
+ introHighlight: {
171
+ position: "absolute",
172
+ top: 0,
173
+ height: 4,
174
+ backgroundColor: "rgba(0,229,255,0.55)",
175
+ },
176
+ outroHighlight: {
177
+ position: "absolute",
178
+ top: 0,
179
+ height: 4,
180
+ backgroundColor: "rgba(255,0,200,0.55)",
181
+ },
182
+ skipRegionBtn: {
183
+ position: "absolute",
184
+ right: 16,
185
+ bottom: 60,
186
+ flexDirection: "row",
187
+ alignItems: "center",
188
+ backgroundColor: "rgba(0,0,0,0.6)",
189
+ borderWidth: 1,
190
+ borderColor: "rgba(255,255,255,0.25)",
191
+ borderRadius: 20,
192
+ paddingHorizontal: 14,
193
+ paddingVertical: 8,
194
+ },
195
+ skipRegionText: { color: "#fff", fontWeight: "700", fontSize: 13 },
196
+ thumb: {
197
+ position: "absolute",
198
+ top: 12 - 7 + 2,
199
+ marginLeft: -6,
200
+ width: 12,
201
+ height: 12,
202
+ borderRadius: 6,
203
+ backgroundColor: "#e50914",
204
+ },
205
+ thumbIdle: { opacity: 0.85 },
206
+ ccBtn: {
207
+ borderWidth: 1.5,
208
+ borderColor: "#666",
209
+ borderRadius: 4,
210
+ paddingHorizontal: 5,
211
+ paddingVertical: 1,
212
+ },
213
+ ccBtnOn: { borderColor: "#e50914", backgroundColor: "rgba(229,9,20,0.25)" },
214
+ ccText: { color: "#888", fontSize: 10, fontWeight: "900" },
215
+ ccTextOn: { color: "#fff" },
216
+ langBtn: {
217
+ borderWidth: 1.5,
218
+ borderColor: "#555",
219
+ borderRadius: 4,
220
+ paddingHorizontal: 6,
221
+ paddingVertical: 1,
222
+ },
223
+ langBtnOn: {
224
+ borderColor: "#7dd3fc",
225
+ backgroundColor: "rgba(125,211,252,0.18)",
226
+ },
227
+ subWrap: { position: "relative", zIndex: 20 },
228
+ subMenu: {
229
+ position: "absolute",
230
+ bottom: 26,
231
+ right: 0,
232
+ zIndex: 30,
233
+ width: 190,
234
+ backgroundColor: "#14161c",
235
+ borderWidth: 1,
236
+ borderColor: "#2a2f3a",
237
+ borderRadius: 8,
238
+ overflow: "hidden",
239
+ elevation: 12,
240
+ shadowColor: "#000",
241
+ shadowOpacity: 0.5,
242
+ shadowRadius: 10,
243
+ shadowOffset: { width: 0, height: 3 },
244
+ },
245
+ subTabs: { flexDirection: "row", borderBottomWidth: 1, borderBottomColor: "#262a34" },
246
+ subTab: { flex: 1, alignItems: "center", paddingVertical: 7, backgroundColor: "#1a1d24" },
247
+ subTabOn: { backgroundColor: "#232833" },
248
+ subTabText: { color: "#9aa0aa", fontSize: 12, fontWeight: "700" },
249
+ subTabTextOn: { color: "#7dd3fc", fontWeight: "800" },
250
+ subBody: { paddingVertical: 4 },
251
+ subItem: {
252
+ flexDirection: "row",
253
+ alignItems: "center",
254
+ justifyContent: "space-between",
255
+ gap: 10,
256
+ paddingHorizontal: 12,
257
+ paddingVertical: 7,
258
+ },
259
+ subItemOn: { backgroundColor: "rgba(125,211,252,0.15)" },
260
+ subItemText: { color: "#cbd5e1", fontSize: 13, fontWeight: "600" },
261
+ subItemTextOn: { color: "#7dd3fc", fontWeight: "800" },
262
+ sizeRow: {
263
+ flexDirection: "row",
264
+ alignItems: "center",
265
+ gap: 10,
266
+ paddingHorizontal: 10,
267
+ paddingVertical: 10,
268
+ },
269
+ sizeBtn: {
270
+ width: 24,
271
+ height: 24,
272
+ borderRadius: 4,
273
+ alignItems: "center",
274
+ justifyContent: "center",
275
+ backgroundColor: "#2a2f3a",
276
+ },
277
+ sizeTrack: { flex: 1, height: 22, justifyContent: "center" },
278
+ sizeFill: { height: 4, borderRadius: 2, backgroundColor: "#7dd3fc" },
279
+ sizeLabel: {
280
+ color: "#9aa0aa",
281
+ fontSize: 11,
282
+ fontWeight: "700",
283
+ textAlign: "center",
284
+ paddingBottom: 6,
285
+ },
286
+ volTrackWrap: { width: 56, paddingVertical: 12, justifyContent: "center" },
287
+ volTrack: { height: 4, borderRadius: 2, backgroundColor: "#3a3f47", overflow: "hidden" },
288
+ volFill: { height: 4, backgroundColor: "#fff" },
289
+ });
@@ -1,4 +1,4 @@
1
- export type SubCueShapeWeb = {
1
+ export type SubCueShape = {
2
2
  start: number;
3
3
  end: number;
4
4
  text: string;
@@ -8,8 +8,8 @@ export type SubCueShapeWeb = {
8
8
  * absolutely with zIndex 1 (below the controls at zIndex 2, above the
9
9
  * video background at zIndex 0).
10
10
  */
11
- export declare function SubtitleDisplayWeb({ cue, enabled, fontSize, }: {
12
- cue: SubCueShapeWeb | null;
11
+ export declare function SubtitleDisplay({ cue, enabled, fontSize, }: {
12
+ cue: SubCueShape | null;
13
13
  enabled: boolean;
14
14
  fontSize: number;
15
15
  }): import("react").JSX.Element | null;
@@ -5,7 +5,7 @@ import { StyleSheet, Text, View } from "react-native";
5
5
  * absolutely with zIndex 1 (below the controls at zIndex 2, above the
6
6
  * video background at zIndex 0).
7
7
  */
8
- export function SubtitleDisplayWeb({ cue, enabled, fontSize, }) {
8
+ export function SubtitleDisplay({ cue, enabled, fontSize, }) {
9
9
  if (!enabled || !cue?.text)
10
10
  return null;
11
11
  return (_jsx(View, { pointerEvents: "none", style: styles.wrap, children: _jsx(Text, { style: [styles.text, { fontSize, lineHeight: fontSize * 1.35 }], children: cue.text }) }));
@@ -0,0 +1,3 @@
1
+ export { MegaVideoPlayer, type MegaVideoPlayerProps, type MegaVideoPlayerHandle, } from "./MegaVideoPlayer.js";
2
+ export { PlayerControls, type PlayerControlsProps } from "./PlayerControls.js";
3
+ export { SubtitleDisplay, type SubCueShape } from "./SubtitleDisplay.js";
@@ -0,0 +1,3 @@
1
+ export { MegaVideoPlayer, } from "./MegaVideoPlayer.js";
2
+ export { PlayerControls } from "./PlayerControls.js";
3
+ export { SubtitleDisplay } from "./SubtitleDisplay.js";
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
- import { type SubCue, type SubtitleFormat, type SubtitleTrack } from "../../subtitles/index.js";
3
- export type MegaVideoPlayerWebProps = {
2
+ import { type SubCue, type SubtitleFormat, type SubtitleTrack } from "../subtitles/index.js";
3
+ export type MegaVideoPlayerProps = {
4
4
  /** A playable URL — a direct stream or the first part URL. */
5
5
  uri?: string | null;
6
6
  /** Ordered additional part URLs for multi-part episodes, played in sequence. */
@@ -18,14 +18,6 @@ export type MegaVideoPlayerWebProps = {
18
18
  initialSubtitleLang?: string | null;
19
19
  /** shift the cue timestamps by an offset in seconds (can be negative) */
20
20
  subtitleOffset?: number;
21
- /** resolution labels in display order — the FIRST is the default source
22
- * (the episode URL), e.g. ["Default", "1080p", "720p", "480p"]. */
23
- resolutions?: string[];
24
- /** currently active resolution label */
25
- resolution?: string;
26
- /** select a resolution — the parent resolves the label to its URL and
27
- * updates `uri` so the new source is loaded. */
28
- onSelectResolution?: (label: string) => void;
29
21
  autoPlay?: boolean;
30
22
  onEnd?: () => void;
31
23
  onError?: (message: string) => void;
@@ -34,10 +26,8 @@ export type MegaVideoPlayerWebProps = {
34
26
  duration: number;
35
27
  }) => void;
36
28
  onFullscreenChange?: (fullscreen: boolean) => void;
37
- width?: number;
38
- height?: number;
39
29
  };
40
- type MegaVideoPlayerWebState = {
30
+ type MegaVideoPlayerState = {
41
31
  playing: boolean;
42
32
  buffering: boolean;
43
33
  loading: boolean;
@@ -52,10 +42,8 @@ type MegaVideoPlayerWebState = {
52
42
  subtitleError: string | null;
53
43
  error: string | null;
54
44
  fullscreen: boolean;
55
- width?: number;
56
- height?: number;
57
45
  };
58
- export declare class MegaVideoPlayerWeb extends React.Component<MegaVideoPlayerWebProps, MegaVideoPlayerWebState> {
46
+ export declare class MegaVideoPlayer extends React.Component<MegaVideoPlayerProps, MegaVideoPlayerState> {
59
47
  private player;
60
48
  private subs;
61
49
  private hideTimer;
@@ -64,9 +52,9 @@ export declare class MegaVideoPlayerWeb extends React.Component<MegaVideoPlayerW
64
52
  private subTrackIdx;
65
53
  private tracks;
66
54
  private sourceIndexRef;
67
- constructor(props: MegaVideoPlayerWebProps);
55
+ constructor(props: MegaVideoPlayerProps);
68
56
  componentDidMount(): void;
69
- componentDidUpdate(prevProps: MegaVideoPlayerWebProps): void;
57
+ componentDidUpdate(prevProps: MegaVideoPlayerProps): void;
70
58
  componentWillUnmount(): void;
71
59
  /** Loads a new playable URL into the player. */
72
60
  add(source: string, opts?: {
@@ -10,9 +10,10 @@ import React from "react";
10
10
  import { ActivityIndicator, StyleSheet, Text, View } from "react-native";
11
11
  import { createVideoPlayer, VideoView } from "expo-video";
12
12
  import { Ionicons } from "@expo/vector-icons";
13
- import { PlayerControlsWeb } from "./PlayerControls.js";
14
- import { loadSubtitles, } from "../../subtitles/index.js";
15
- export class MegaVideoPlayerWeb extends React.Component {
13
+ import { PlayerControls } from "./PlayerControlsWeb.js";
14
+ import { SubtitleDisplay } from "./SubtitleDisplayWeb.js";
15
+ import { loadSubtitles, } from "../subtitles/index.js";
16
+ export class MegaVideoPlayer extends React.Component {
16
17
  player;
17
18
  subs = [];
18
19
  hideTimer = null;
@@ -38,8 +39,6 @@ export class MegaVideoPlayerWeb extends React.Component {
38
39
  subtitleError: null,
39
40
  error: null,
40
41
  fullscreen: false,
41
- width: this.props.width,
42
- height: this.props.height,
43
42
  };
44
43
  this.player = createVideoPlayer(null);
45
44
  this.player.loop = false;
@@ -291,19 +290,15 @@ export class MegaVideoPlayerWeb extends React.Component {
291
290
  if (s.error) {
292
291
  return (_jsxs(View, { style: [styles.wrap, styles.center], children: [_jsx(Ionicons, { name: "alert-circle-outline", size: 40, color: "#ff6b6b" }), _jsx(Text, { style: styles.errorText, children: s.error }), _jsx(Text, { style: styles.errorHint, children: "Use \"Download & Play\" instead." })] }));
293
292
  }
294
- return (_jsxs(View, { style: [
295
- styles.wrap,
296
- { width: this.state.width, height: this.state.height },
297
- s.fullscreen && styles.fullscreen,
298
- ], children: [_jsx(VideoView, { player: this.player, style: [
299
- StyleSheet.absoluteFill,
300
- {
301
- alignSelf: "center",
302
- width: this.state.width,
303
- height: this.state.height,
304
- zIndex: 0
305
- },
306
- ], nativeControls: false, contentFit: "contain", allowsFullscreen: false }), s.loading && (_jsxs(View, { style: [styles.centerBadge, { width: this.state.width }], pointerEvents: "none", children: [_jsx(ActivityIndicator, { color: "#e50914", size: "large" }), _jsx(Text, { style: styles.loadingText, children: "Starting stream\u2026" })] })), s.subtitlesOn && s.subtitleError && !s.cues.length && (_jsx(View, { pointerEvents: "none", style: styles.subErrorWrap, children: _jsxs(Text, { style: styles.subErrorText, children: [s.subtitleError, " \u00B7 ", activeTrack?.lang ?? "no track"] }) })), _jsx(PlayerControlsWeb, { playing: s.playing, buffering: s.buffering, currentTime: s.currentTime, duration: s.duration, muted: s.muted, title: this.props.title, visible: s.visible, subtitlesOn: s.subtitlesOn, volume: s.volume, subtitleTracks: this.tracks.map((t) => t.lang), subtitleTrack: activeTrack?.lang, onSelectSubtitleTrack: this.selectSubtitleTrack, subtitleFontSize: s.subFontSize, onChangeSubtitleFontSize: this.changeSubtitleFontSize, resolutions: this.props.resolutions, resolution: this.props.resolution, onSelectResolution: this.props.onSelectResolution, onToggleVisible: this.toggleControls, onPlayPause: this.playPause, onSkip: this.skip, onSeek: this.seek, onToggleMute: this.toggleMute, onToggleSubtitles: this.toggleSubtitles, onVolume: this.changeVolume, onFullscreen: this.enterFullscreen, width: this.state.width, height: this.state.height, cue: activeCue, enabled: s.subtitlesOn, fontSize: s.subFontSize })] }));
293
+ return (_jsxs(View, { style: [styles.wrap, s.fullscreen && styles.fullscreen], children: [_jsx(View, { style: {
294
+ width: "100%",
295
+ height: "100%",
296
+ justifyContent: "center",
297
+ alignItems: "center",
298
+ }, children: _jsx(VideoView, { player: this.player, style: {
299
+ width: "100%",
300
+ height: "auto",
301
+ }, nativeControls: false, contentFit: "contain" }) }), s.loading && (_jsxs(View, { style: styles.centerBadge, pointerEvents: "none", children: [_jsx(ActivityIndicator, { color: "#e50914", size: "large" }), _jsx(Text, { style: styles.loadingText, children: "Starting stream\u2026" })] })), _jsx(SubtitleDisplay, { cue: activeCue, enabled: s.subtitlesOn, fontSize: s.subFontSize }), s.subtitlesOn && s.subtitleError && !s.cues.length && (_jsx(View, { pointerEvents: "none", style: styles.subErrorWrap, children: _jsxs(Text, { style: styles.subErrorText, children: [s.subtitleError, " \u00B7 ", activeTrack?.lang ?? "no track"] }) })), _jsx(PlayerControls, { playing: s.playing, buffering: s.buffering, currentTime: s.currentTime, duration: s.duration, muted: s.muted, title: this.props.title, visible: s.visible, subtitlesOn: s.subtitlesOn, volume: s.volume, subtitleTracks: this.tracks.map((t) => t.lang), subtitleTrack: activeTrack?.lang, onSelectSubtitleTrack: this.selectSubtitleTrack, subtitleFontSize: s.subFontSize, onChangeSubtitleFontSize: this.changeSubtitleFontSize, onToggleVisible: this.toggleControls, onPlayPause: this.playPause, onSkip: this.skip, onSeek: this.seek, onToggleMute: this.toggleMute, onToggleSubtitles: this.toggleSubtitles, onVolume: this.changeVolume, onFullscreen: this.enterFullscreen })] }));
307
302
  }
308
303
  }
309
304
  const styles = StyleSheet.create({
@@ -364,5 +359,10 @@ const styles = StyleSheet.create({
364
359
  paddingVertical: 4,
365
360
  borderRadius: 4,
366
361
  overflow: "hidden",
362
+ position: "absolute",
363
+ top: 0,
364
+ bottom: 0,
365
+ left: 0,
366
+ right: 0,
367
367
  },
368
368
  });
@@ -1,4 +1,4 @@
1
- export type PlayerControlsAndroidProps = {
1
+ export type PlayerControlsProps = {
2
2
  playing: boolean;
3
3
  buffering: boolean;
4
4
  currentTime: number;
@@ -21,14 +21,6 @@ export type PlayerControlsAndroidProps = {
21
21
  /** 0..1 — how much of the whole merged episode has been downloaded. Renders
22
22
  * a second, thinner line under the playhead track. */
23
23
  loadProgress?: number;
24
- /** resolution labels in display order — the FIRST is the default source
25
- * (usually "HD: <url>"), e.g. ["Default", "1080p", "720p"]. Shown in the
26
- * settings popup. */
27
- resolutions?: string[];
28
- /** currently active resolution label */
29
- resolution?: string;
30
- /** user picked a resolution label — resolve it to a URL and update `uri`. */
31
- onSelectResolution?: (label: string) => void;
32
24
  /** [start, end] seconds (merged timeline) of the intro — highlighted on the
33
25
  * track + shows a "Skip Intro" button while the playhead is inside it. */
34
26
  introRegion?: [number, number];
@@ -44,11 +36,5 @@ export type PlayerControlsAndroidProps = {
44
36
  onToggleSubtitles: () => void;
45
37
  onVolume: (v: number) => void;
46
38
  onFullscreen: () => void;
47
- /** current fullscreen state — switches the expand/contract icon */
48
- fullscreen?: boolean;
49
- /** any user interaction with the bar/menus — reset the auto-hide timer */
50
- onInteract?: () => void;
51
- width?: number;
52
- height?: number;
53
39
  };
54
- export declare function PlayerControlsAndroid(props: PlayerControlsAndroidProps): import("react").JSX.Element;
40
+ export declare function PlayerControls(props: PlayerControlsProps): import("react").JSX.Element;