react-native-video-provider 0.2.1 → 0.2.3

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 (34) hide show
  1. package/android/src/main/java/com/auvideo/AuVideoModule.kt +15 -10
  2. package/ios/AuVideo.mm +4 -4
  3. package/ios/AuVideoOrientation.swift +31 -14
  4. package/lib/module/NativeAuVideo.js.map +1 -1
  5. package/lib/module/components/FloatingPlayer.js +8 -11
  6. package/lib/module/components/FloatingPlayer.js.map +1 -1
  7. package/lib/module/components/MiniPlayer.js +10 -10
  8. package/lib/module/components/MiniPlayer.js.map +1 -1
  9. package/lib/module/components/SvgIcons.js +163 -0
  10. package/lib/module/components/SvgIcons.js.map +1 -0
  11. package/lib/module/components/VideoControls.js +83 -46
  12. package/lib/module/components/VideoControls.js.map +1 -1
  13. package/lib/module/components/icons.js +164 -0
  14. package/lib/module/components/icons.js.map +1 -0
  15. package/lib/module/core/VideoManager.js +15 -17
  16. package/lib/module/core/VideoManager.js.map +1 -1
  17. package/lib/typescript/src/NativeAuVideo.d.ts +8 -4
  18. package/lib/typescript/src/NativeAuVideo.d.ts.map +1 -1
  19. package/lib/typescript/src/components/MiniPlayer.d.ts.map +1 -1
  20. package/lib/typescript/src/components/SvgIcons.d.ts +8 -0
  21. package/lib/typescript/src/components/SvgIcons.d.ts.map +1 -0
  22. package/lib/typescript/src/components/VideoControls.d.ts.map +1 -1
  23. package/lib/typescript/src/components/icons.d.ts +13 -0
  24. package/lib/typescript/src/components/icons.d.ts.map +1 -0
  25. package/lib/typescript/src/core/VideoManager.d.ts +6 -5
  26. package/lib/typescript/src/core/VideoManager.d.ts.map +1 -1
  27. package/package.json +4 -2
  28. package/src/NativeAuVideo.ts +8 -4
  29. package/src/components/FloatingPlayer.tsx +3 -7
  30. package/src/components/MiniPlayer.tsx +7 -6
  31. package/src/components/SvgIcons.tsx +169 -0
  32. package/src/components/VideoControls.tsx +71 -36
  33. package/src/components/icons.tsx +117 -0
  34. package/src/core/VideoManager.ts +18 -17
@@ -25,8 +25,6 @@ export declare class VideoManager {
25
25
  private lastInlineSurfaceId;
26
26
  /** Per-player default for `enterFullscreen()` (VideoPlayer's prop). */
27
27
  private fullscreenOrientationDefault;
28
- /** True while a fullscreen-scoped lock is applied, so exit restores it. */
29
- private fullscreenLockApplied;
30
28
  private constructor();
31
29
  get providerConfig(): Required<VideoProviderConfig>;
32
30
  /**
@@ -86,11 +84,14 @@ export declare class VideoManager {
86
84
  /**
87
85
  * Show the built-in fullscreen host. Rotation unlocks by default; pass an
88
86
  * orientation (or set VideoPlayer's `fullscreenOrientation` prop) to force
89
- * one for the fullscreen session only it is restored on exit, so the
90
- * rest of the app never sees the lock.
87
+ * one for the fullscreen session only. That scoped orientation is the
88
+ * highest priority it's applied in the SAME native call as entering
89
+ * fullscreen (never as a separate follow-up call), so there is no
90
+ * unlocked frame where the sensor could win before the lock takes effect.
91
+ * Restored on exit; the rest of the app never sees the lock.
91
92
  */
92
93
  enterFullscreen(orientation?: OrientationLock): void;
93
- /** Restore the previous orientation lock and re-attach the previous surface. */
94
+ /** Restore the standing orientation lock (if any) and re-attach the previous surface. */
94
95
  exitFullscreen(): void;
95
96
  toggleFullscreen(orientation?: OrientationLock): void;
96
97
  showFloating(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"VideoManager.d.ts","sourceRoot":"","sources":["../../../../src/core/VideoManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"VideoManager.d.ts","sourceRoot":"","sources":["../../../../src/core/VideoManager.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-video-provider",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Singleton-engine video library for React Native (one native player, many surfaces)",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -75,6 +75,7 @@
75
75
  "react": "19.2.3",
76
76
  "react-native": "0.85.0",
77
77
  "react-native-builder-bob": "^0.43.0",
78
+ "react-native-svg": "15.15.5",
78
79
  "turbo": "^2.9.16",
79
80
  "typescript": "^6.0.3"
80
81
  },
@@ -83,7 +84,8 @@
83
84
  },
84
85
  "peerDependencies": {
85
86
  "react": "*",
86
- "react-native": "*"
87
+ "react-native": "*",
88
+ "react-native-svg": ">=13.0.0"
87
89
  },
88
90
  "workspaces": [
89
91
  "example"
@@ -81,10 +81,14 @@ export interface Spec extends TurboModule {
81
81
  attach(surfaceId: string): void;
82
82
  detach(): void;
83
83
 
84
- /** Unlock rotation + immersive mode. Does not move the player. */
85
- enterFullscreen(): void;
86
- /** Restore the previous orientation lock. */
87
- exitFullscreen(): void;
84
+ /**
85
+ * Enter fullscreen (immersive mode). `orientation` is applied in the same
86
+ * native call no intermediate unlocked frame before a lock takes effect.
87
+ * auto | portrait | inverted-portrait | landscape | inverted-landscape
88
+ */
89
+ enterFullscreen(orientation: string): void;
90
+ /** Exit fullscreen, restoring `orientation` (same value semantics) atomically. */
91
+ exitFullscreen(orientation: string): void;
88
92
 
89
93
  /**
90
94
  * Force a screen orientation, overriding the app's own lock and the
@@ -4,12 +4,12 @@ import {
4
4
  PanResponder,
5
5
  Pressable,
6
6
  StyleSheet,
7
- Text,
8
7
  useWindowDimensions,
9
8
  } from 'react-native';
10
9
  import { FLOATING_SURFACE_ID } from '../core/VideoManager';
11
10
  import { usePlayback } from '../hooks/usePlayback';
12
11
  import { useVideoManager } from '../provider/VideoContext';
12
+ import { CloseIcon, EnterFullscreenIcon } from './icons';
13
13
  import { VideoSurface } from './VideoSurface';
14
14
 
15
15
  export interface FloatingPlayerProps {
@@ -74,7 +74,7 @@ export function FloatingPlayer({ width = 200 }: FloatingPlayerProps) {
74
74
  hitSlop={8}
75
75
  onPress={() => manager.hideFloating()}
76
76
  >
77
- <Text style={styles.icon}>✕</Text>
77
+ <CloseIcon size={12} color="#fff" />
78
78
  </Pressable>
79
79
  <Pressable
80
80
  style={[styles.button, styles.expandButton]}
@@ -84,7 +84,7 @@ export function FloatingPlayer({ width = 200 }: FloatingPlayerProps) {
84
84
  manager.enterFullscreen();
85
85
  }}
86
86
  >
87
- <Text style={styles.icon}>⤢</Text>
87
+ <EnterFullscreenIcon size={12} color="#fff" />
88
88
  </Pressable>
89
89
  </Animated.View>
90
90
  );
@@ -123,8 +123,4 @@ const styles = StyleSheet.create({
123
123
  expandButton: {
124
124
  left: 4,
125
125
  },
126
- icon: {
127
- color: '#fff',
128
- fontSize: 12,
129
- },
130
126
  });
@@ -7,6 +7,7 @@ import {
7
7
  } from 'react-native';
8
8
  import { usePlayback } from '../hooks/usePlayback';
9
9
  import { useVideoManager } from '../provider/VideoContext';
10
+ import { CloseIcon, PauseIcon, PlayIcon } from './icons';
10
11
  import { VideoSurface } from './VideoSurface';
11
12
 
12
13
  export interface MiniPlayerProps extends ViewProps {
@@ -47,11 +48,15 @@ export function MiniPlayer({
47
48
  hitSlop={8}
48
49
  onPress={() => manager.toggle()}
49
50
  >
50
- <Text style={styles.icon}>{playing ? '❙❙' : '▶'}</Text>
51
+ {playing ? (
52
+ <PauseIcon size={16} color="#fff" />
53
+ ) : (
54
+ <PlayIcon size={16} color="#fff" />
55
+ )}
51
56
  </Pressable>
52
57
  {onClose ? (
53
58
  <Pressable style={styles.button} hitSlop={8} onPress={onClose}>
54
- <Text style={styles.icon}>✕</Text>
59
+ <CloseIcon size={16} color="#fff" />
55
60
  </Pressable>
56
61
  ) : null}
57
62
  </View>
@@ -86,8 +91,4 @@ const styles = StyleSheet.create({
86
91
  paddingHorizontal: 10,
87
92
  paddingVertical: 8,
88
93
  },
89
- icon: {
90
- color: '#fff',
91
- fontSize: 16,
92
- },
93
94
  });
@@ -0,0 +1,169 @@
1
+ import Svg, { G, Path } from 'react-native-svg';
2
+
3
+ const SvgIcons = ({
4
+ icon,
5
+ type,
6
+ fill,
7
+ size = 24,
8
+ }: {
9
+ icon: string;
10
+ type?: string;
11
+ fill?: string;
12
+ size?: number;
13
+ }) => {
14
+ switch (icon) {
15
+ case 'fullScreen':
16
+ return type === 'full' ? (
17
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
18
+ <G id="SVGRepo_bgCarrier" strokeWidth={0} />
19
+ <G
20
+ id="SVGRepo_tracerCarrier"
21
+ strokeLinecap="round"
22
+ strokeLinejoin="round"
23
+ />
24
+ <G id="SVGRepo_iconCarrier">
25
+ <Path
26
+ d="M4 1.5C2.61929 1.5 1.5 2.61929 1.5 4V8.5C1.5 9.05228 1.94772 9.5 2.5 9.5H3.5C4.05228 9.5 4.5 9.05228 4.5 8.5V4.5H8.5C9.05228 4.5 9.5 4.05228 9.5 3.5V2.5C9.5 1.94772 9.05228 1.5 8.5 1.5H4Z"
27
+ fill={fill || '#fff'}
28
+ />
29
+ <Path
30
+ d="M20 1.5C21.3807 1.5 22.5 2.61929 22.5 4V8.5C22.5 9.05228 22.0523 9.5 21.5 9.5H20.5C19.9477 9.5 19.5 9.05228 19.5 8.5V4.5H15.5C14.9477 4.5 14.5 4.05228 14.5 3.5V2.5C14.5 1.94772 14.9477 1.5 15.5 1.5H20Z"
31
+ fill={fill || '#fff'}
32
+ />
33
+ <Path
34
+ d="M20 22.5C21.3807 22.5 22.5 21.3807 22.5 20V15.5C22.5 14.9477 22.0523 14.5 21.5 14.5H20.5C19.9477 14.5 19.5 14.9477 19.5 15.5V19.5H15.5C14.9477 19.5 14.5 19.9477 14.5 20.5V21.5C14.5 22.0523 14.9477 22.5 15.5 22.5H20Z"
35
+ fill={fill || '#fff'}
36
+ />
37
+ <Path
38
+ d="M1.5 20C1.5 21.3807 2.61929 22.5 4 22.5H8.5C9.05228 22.5 9.5 22.0523 9.5 21.5V20.5C9.5 19.9477 9.05228 19.5 8.5 19.5H4.5V15.5C4.5 14.9477 4.05228 14.5 3.5 14.5H2.5C1.94772 14.5 1.5 14.9477 1.5 15.5V20Z"
39
+ fill={fill || '#fff'}
40
+ />
41
+ </G>
42
+ </Svg>
43
+ ) : (
44
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
45
+ <G id="SVGRepo_bgCarrier" strokeWidth={0} />
46
+ <G
47
+ id="SVGRepo_tracerCarrier"
48
+ strokeLinecap="round"
49
+ strokeLinejoin="round"
50
+ />
51
+ <G id="SVGRepo_iconCarrier">
52
+ <Path
53
+ d="M7 9.5C8.38071 9.5 9.5 8.38071 9.5 7V2.5C9.5 1.94772 9.05228 1.5 8.5 1.5H7.5C6.94772 1.5 6.5 1.94772 6.5 2.5V6.5H2.5C1.94772 6.5 1.5 6.94772 1.5 7.5V8.5C1.5 9.05228 1.94772 9.5 2.5 9.5H7Z"
54
+ fill={fill || '#fff'}
55
+ />
56
+ <Path
57
+ d="M17 9.5C15.6193 9.5 14.5 8.38071 14.5 7V2.5C14.5 1.94772 14.9477 1.5 15.5 1.5H16.5C17.0523 1.5 17.5 1.94772 17.5 2.5V6.5H21.5C22.0523 6.5 22.5 6.94772 22.5 7.5V8.5C22.5 9.05228 22.0523 9.5 21.5 9.5H17Z"
58
+ fill={fill || '#fff'}
59
+ />
60
+ <Path
61
+ d="M17 14.5C15.6193 14.5 14.5 15.6193 14.5 17V21.5C14.5 22.0523 14.9477 22.5 15.5 22.5H16.5C17.0523 22.5 17.5 22.0523 17.5 21.5V17.5H21.5C22.0523 17.5 22.5 17.0523 22.5 16.5V15.5C22.5 14.9477 22.0523 14.5 21.5 14.5H17Z"
62
+ fill={fill || '#fff'}
63
+ />
64
+ <Path
65
+ d="M9.5 17C9.5 15.6193 8.38071 14.5 7 14.5H2.5C1.94772 14.5 1.5 14.9477 1.5 15.5V16.5C1.5 17.0523 1.94772 17.5 2.5 17.5H6.5V21.5C6.5 22.0523 6.94772 22.5 7.5 22.5H8.5C9.05228 22.5 9.5 22.0523 9.5 21.5V17Z"
66
+ fill={fill || '#fff'}
67
+ />
68
+ </G>
69
+ </Svg>
70
+ );
71
+
72
+ case 'muteUnmute':
73
+ return type === 'mute' ? (
74
+ <Svg
75
+ fill={fill || '#000000'}
76
+ viewBox="0 0 24 24"
77
+ width={size || 24}
78
+ height={size || 24}
79
+ >
80
+ <G id="SVGRepo_bgCarrier" strokeWidth={0} />
81
+ <G
82
+ id="SVGRepo_tracerCarrier"
83
+ strokeLinecap="round"
84
+ strokeLinejoin="round"
85
+ />
86
+ <G id="SVGRepo_iconCarrier">
87
+ <Path
88
+ fillRule="evenodd"
89
+ d="M11.553 3.064A.75.75 0 0112 3.75v16.5a.75.75 0 01-1.255.555L5.46 16H2.75A1.75 1.75 0 011 14.25v-4.5C1 8.784 1.784 8 2.75 8h2.71l5.285-4.805a.75.75 0 01.808-.13zM10.5 5.445l-4.245 3.86a.75.75 0 01-.505.195h-3a.25.25 0 00-.25.25v4.5c0 .138.112.25.25.25h3a.75.75 0 01.505.195l4.245 3.86V5.445z"
90
+ />
91
+ <Path d="M18.718 4.222a.75.75 0 011.06 0c4.296 4.296 4.296 11.26 0 15.556a.75.75 0 01-1.06-1.06 9.5 9.5 0 000-13.436.75.75 0 010-1.06z" />
92
+ <Path d="M16.243 7.757a.75.75 0 10-1.061 1.061 4.5 4.5 0 010 6.364.75.75 0 001.06 1.06 6 6 0 000-8.485z" />
93
+ </G>
94
+ </Svg>
95
+ ) : (
96
+ <Svg
97
+ fill={fill || '#000000'}
98
+ viewBox="0 0 16 16"
99
+ width={size || 16}
100
+ height={size || 16}
101
+ >
102
+ <G id="SVGRepo_bgCarrier" strokeWidth={0} />
103
+ <G
104
+ id="SVGRepo_tracerCarrier"
105
+ strokeLinecap="round"
106
+ strokeLinejoin="round"
107
+ />
108
+ <G id="SVGRepo_iconCarrier">
109
+ <Path
110
+ d="M14.266 1.264l-13 13 .47.472 3.239-3.238L8 14.666V8.473l2.312-2.313c.467.542.737 1.227.737 1.947 0 .796-.316 1.559-.88 2.122l-.353.353.707.707.354-.353a4 4 0 0 0 1.172-2.829c0-.985-.377-1.923-1.03-2.654l1.422-1.422a5.994 5.994 0 0 1 1.608 4.076 5.999 5.999 0 0 1-1.758 4.243l-.354.353.707.707.354-.353a7 7 0 0 0 2.05-4.95 6.994 6.994 0 0 0-1.9-4.783l1.588-1.588zM8 1.334L4.5 5H1.871S1 5.894 1 8.002C1 10.11 1.871 11 1.871 11h1.422L8 6.293z"
111
+ opacity={1}
112
+ fill={fill || 'white'}
113
+ />
114
+ </G>
115
+ </Svg>
116
+ );
117
+
118
+ case 'playPause':
119
+ return type === 'play' ? (
120
+ <Svg
121
+ viewBox="0 0 24 24"
122
+ fill="none"
123
+ width={size || 24}
124
+ height={size || 24}
125
+ >
126
+ <G id="SVGRepo_bgCarrier" strokeWidth={0} />
127
+ <G
128
+ id="SVGRepo_tracerCarrier"
129
+ strokeLinecap="round"
130
+ strokeLinejoin="round"
131
+ />
132
+ <G id="SVGRepo_iconCarrier">
133
+ <Path
134
+ d="M21.4086 9.35258C23.5305 10.5065 23.5305 13.4935 21.4086 14.6474L8.59662 21.6145C6.53435 22.736 4 21.2763 4 18.9671L4 5.0329C4 2.72368 6.53435 1.26402 8.59661 2.38548L21.4086 9.35258Z"
135
+ fill={fill || '#1C274C'}
136
+ />
137
+ </G>
138
+ </Svg>
139
+ ) : (
140
+ <Svg
141
+ viewBox="0 0 24 24"
142
+ fill="none"
143
+ width={size || 24}
144
+ height={size || 24}
145
+ >
146
+ <G id="SVGRepo_bgCarrier" strokeWidth={0} />
147
+ <G
148
+ id="SVGRepo_tracerCarrier"
149
+ strokeLinecap="round"
150
+ strokeLinejoin="round"
151
+ />
152
+ <G id="SVGRepo_iconCarrier">
153
+ <Path
154
+ d="M2 6C2 4.11438 2 3.17157 2.58579 2.58579C3.17157 2 4.11438 2 6 2C7.88562 2 8.82843 2 9.41421 2.58579C10 3.17157 10 4.11438 10 6V18C10 19.8856 10 20.8284 9.41421 21.4142C8.82843 22 7.88562 22 6 22C4.11438 22 3.17157 22 2.58579 21.4142C2 20.8284 2 19.8856 2 18V6Z"
155
+ fill={fill || '#1C274C'}
156
+ />
157
+ <Path
158
+ d="M14 6C14 4.11438 14 3.17157 14.5858 2.58579C15.1716 2 16.1144 2 18 2C19.8856 2 20.8284 2 21.4142 2.58579C22 3.17157 22 4.11438 22 6V18C22 19.8856 22 20.8284 21.4142 21.4142C20.8284 22 19.8856 22 18 22C16.1144 22 15.1716 22 14.5858 21.4142C14 20.8284 14 19.8856 14 18V6Z"
159
+ fill={fill || '#1C274C'}
160
+ />
161
+ </G>
162
+ </Svg>
163
+ );
164
+ default:
165
+ return null;
166
+ }
167
+ };
168
+
169
+ export default SvgIcons;
@@ -11,6 +11,8 @@ import { useVideoManager } from '../provider/VideoContext';
11
11
  import { usePlayback } from '../hooks/usePlayback';
12
12
  import { formatTime } from '../utils/formatTime';
13
13
  import { GestureOverlay } from './GestureOverlay';
14
+ import { CloseIcon } from './icons';
15
+ import SvgIcons from './SvgIcons';
14
16
 
15
17
  export interface VideoControlsProps {
16
18
  /** Seconds jumped by double-tap. Default 10. */
@@ -35,13 +37,19 @@ export function VideoControls({
35
37
  onClose,
36
38
  }: VideoControlsProps) {
37
39
  const manager = useVideoManager();
40
+ const status = usePlayback((s) => s.status);
38
41
  const playing = usePlayback((s) => s.playing);
39
42
  const buffering = usePlayback((s) => s.buffering);
43
+ const loading = usePlayback((s) => s.loading);
40
44
  const position = usePlayback((s) => s.position);
41
45
  const duration = usePlayback((s) => s.duration);
42
46
  const muted = usePlayback((s) => s.muted);
43
47
  const fullscreen = usePlayback((s) => s.fullscreen);
44
48
 
49
+ // No known duration once loaded — a live stream or a video with no fixed
50
+ // end. There's nothing to seek, so hide the seek bar entirely.
51
+ const live = status !== 'idle' && !loading && duration <= 0;
52
+
45
53
  const [visible, setVisible] = useState(true);
46
54
  const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
47
55
  const trackWidth = useRef(0);
@@ -83,6 +91,34 @@ export function VideoControls({
83
91
 
84
92
  const progress = duration > 0 ? Math.min(position / duration, 1) : 0;
85
93
 
94
+ const muteButton = (
95
+ <Pressable
96
+ style={styles.button}
97
+ onPress={() => (muted ? manager.unmute() : manager.mute())}
98
+ hitSlop={8}
99
+ >
100
+ {muted ? (
101
+ <SvgIcons icon="muteUnmute" size={18} fill="#fff" />
102
+ ) : (
103
+ <SvgIcons icon="muteUnmute" type="mute" size={18} fill="#fff" />
104
+ )}
105
+ </Pressable>
106
+ );
107
+
108
+ const fullscreenButton = showFullscreenButton ? (
109
+ <Pressable
110
+ style={styles.button}
111
+ onPress={() => manager.toggleFullscreen()}
112
+ hitSlop={8}
113
+ >
114
+ {fullscreen ? (
115
+ <SvgIcons icon="fullScreen" size={18} fill="#fff" />
116
+ ) : (
117
+ <SvgIcons icon="fullScreen" type="full" size={18} fill="#fff" />
118
+ )}
119
+ </Pressable>
120
+ ) : null;
121
+
86
122
  return (
87
123
  <View style={StyleSheet.absoluteFill} pointerEvents="box-none">
88
124
  <GestureOverlay
@@ -95,18 +131,12 @@ export function VideoControls({
95
131
  <View style={styles.topRow}>
96
132
  {onClose ? (
97
133
  <Pressable style={styles.button} onPress={onClose} hitSlop={8}>
98
- <Text style={styles.icon}>✕</Text>
134
+ <CloseIcon size={18} color="#fff" />
99
135
  </Pressable>
100
136
  ) : (
101
137
  <View />
102
138
  )}
103
- <Pressable
104
- style={styles.button}
105
- onPress={() => (muted ? manager.unmute() : manager.mute())}
106
- hitSlop={8}
107
- >
108
- <Text style={styles.icon}>{muted ? '🔇' : '🔊'}</Text>
109
- </Pressable>
139
+ {!live ? muteButton : <View />}
110
140
  </View>
111
141
 
112
142
  <Pressable
@@ -117,33 +147,39 @@ export function VideoControls({
117
147
  }}
118
148
  hitSlop={16}
119
149
  >
120
- <Text style={styles.playIcon}>
121
- {buffering ? '…' : playing ? '❙❙' : '▶'}
122
- </Text>
150
+ {buffering ? (
151
+ <Text style={styles.playIcon}>…</Text>
152
+ ) : playing ? (
153
+ <SvgIcons icon="playPause" type="pause" size={34} fill="#fff" />
154
+ ) : (
155
+ <SvgIcons icon="playPause" type="play" size={34} fill="#fff" />
156
+ )}
123
157
  </Pressable>
124
158
 
125
159
  <View style={styles.bottomRow}>
126
- <Text style={styles.time}>{formatTime(position)}</Text>
127
- <Pressable
128
- style={styles.track}
129
- onLayout={onTrackLayout}
130
- onPress={onTrackPress}
131
- >
132
- <View style={styles.trackBg} />
133
- <View
134
- style={[styles.trackFill, { width: `${progress * 100}%` }]}
135
- />
136
- </Pressable>
137
- <Text style={styles.time}>{formatTime(duration)}</Text>
138
- {showFullscreenButton ? (
139
- <Pressable
140
- style={styles.button}
141
- onPress={() => manager.toggleFullscreen()}
142
- hitSlop={8}
143
- >
144
- <Text style={styles.icon}>{fullscreen ? '⤡' : '⤢'}</Text>
145
- </Pressable>
146
- ) : null}
160
+ {live ? (
161
+ <>
162
+ {muteButton}
163
+ <View style={styles.spacer} />
164
+ {fullscreenButton}
165
+ </>
166
+ ) : (
167
+ <>
168
+ <Text style={styles.time}>{formatTime(position)}</Text>
169
+ <Pressable
170
+ style={styles.track}
171
+ onLayout={onTrackLayout}
172
+ onPress={onTrackPress}
173
+ >
174
+ <View style={styles.trackBg} />
175
+ <View
176
+ style={[styles.trackFill, { width: `${progress * 100}%` }]}
177
+ />
178
+ </Pressable>
179
+ <Text style={styles.time}>{formatTime(duration)}</Text>
180
+ {fullscreenButton}
181
+ </>
182
+ )}
147
183
  </View>
148
184
  </View>
149
185
  ) : null}
@@ -190,6 +226,9 @@ const styles = StyleSheet.create({
190
226
  height: 24,
191
227
  justifyContent: 'center',
192
228
  },
229
+ spacer: {
230
+ flex: 1,
231
+ },
193
232
  trackBg: {
194
233
  position: 'absolute',
195
234
  left: 0,
@@ -206,8 +245,4 @@ const styles = StyleSheet.create({
206
245
  button: {
207
246
  padding: 4,
208
247
  },
209
- icon: {
210
- color: '#fff',
211
- fontSize: 18,
212
- },
213
248
  });
@@ -0,0 +1,117 @@
1
+ import Svg, { Line, Path, Polygon, Rect } from 'react-native-svg';
2
+
3
+ interface IconProps {
4
+ size: number;
5
+ color: string;
6
+ }
7
+
8
+ export function PlayIcon({ size, color }: IconProps) {
9
+ return (
10
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill={color}>
11
+ <Polygon points="6 3 20 12 6 21 6 3" />
12
+ </Svg>
13
+ );
14
+ }
15
+
16
+ export function PauseIcon({ size, color }: IconProps) {
17
+ return (
18
+ <Svg width={size} height={size} viewBox="0 0 24 24" fill={color}>
19
+ <Rect x="6" y="4" width="4" height="16" />
20
+ <Rect x="14" y="4" width="4" height="16" />
21
+ </Svg>
22
+ );
23
+ }
24
+
25
+ export function CloseIcon({ size, color }: IconProps) {
26
+ return (
27
+ <Svg
28
+ width={size}
29
+ height={size}
30
+ viewBox="0 0 24 24"
31
+ fill="none"
32
+ stroke={color}
33
+ strokeWidth={2}
34
+ strokeLinecap="round"
35
+ >
36
+ <Line x1="18" y1="6" x2="6" y2="18" />
37
+ <Line x1="6" y1="6" x2="18" y2="18" />
38
+ </Svg>
39
+ );
40
+ }
41
+
42
+ export function VolumeOnIcon({ size, color }: IconProps) {
43
+ return (
44
+ <Svg
45
+ width={size}
46
+ height={size}
47
+ viewBox="0 0 24 24"
48
+ fill="none"
49
+ stroke={color}
50
+ strokeWidth={2}
51
+ strokeLinecap="round"
52
+ strokeLinejoin="round"
53
+ >
54
+ <Polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" fill={color} />
55
+ <Path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07" />
56
+ </Svg>
57
+ );
58
+ }
59
+
60
+ export function VolumeOffIcon({ size, color }: IconProps) {
61
+ return (
62
+ <Svg
63
+ width={size}
64
+ height={size}
65
+ viewBox="0 0 24 24"
66
+ fill="none"
67
+ stroke={color}
68
+ strokeWidth={2}
69
+ strokeLinecap="round"
70
+ strokeLinejoin="round"
71
+ >
72
+ <Polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" fill={color} />
73
+ <Line x1="23" y1="9" x2="17" y2="15" />
74
+ <Line x1="17" y1="9" x2="23" y2="15" />
75
+ </Svg>
76
+ );
77
+ }
78
+
79
+ export function EnterFullscreenIcon({ size, color }: IconProps) {
80
+ return (
81
+ <Svg
82
+ width={size}
83
+ height={size}
84
+ viewBox="0 0 24 24"
85
+ fill="none"
86
+ stroke={color}
87
+ strokeWidth={2}
88
+ strokeLinecap="round"
89
+ strokeLinejoin="round"
90
+ >
91
+ <Path d="M8 3H5a2 2 0 0 0-2 2v3" />
92
+ <Path d="M16 3h3a2 2 0 0 1 2 2v3" />
93
+ <Path d="M21 16v3a2 2 0 0 1-2 2h-3" />
94
+ <Path d="M3 16v3a2 2 0 0 0 2 2h3" />
95
+ </Svg>
96
+ );
97
+ }
98
+
99
+ export function ExitFullscreenIcon({ size, color }: IconProps) {
100
+ return (
101
+ <Svg
102
+ width={size}
103
+ height={size}
104
+ viewBox="0 0 24 24"
105
+ fill="none"
106
+ stroke={color}
107
+ strokeWidth={2}
108
+ strokeLinecap="round"
109
+ strokeLinejoin="round"
110
+ >
111
+ <Path d="M8 3v3a2 2 0 0 1-2 2H3" />
112
+ <Path d="M16 3v3a2 2 0 0 0 2 2h3" />
113
+ <Path d="M21 16h-3a2 2 0 0 0-2 2v3" />
114
+ <Path d="M3 16h3a2 2 0 0 1 2 2v3" />
115
+ </Svg>
116
+ );
117
+ }