react-native-audio-api 0.13.2 → 0.13.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/RNAudioAPI.podspec +53 -16
- package/android/src/main/java/com/swmansion/audioapi/system/AudioEvent.kt +1 -0
- package/common/cpp/audioapi/HostObjects/sources/AudioFileSourceNodeHostObject.cpp +8 -2
- package/common/cpp/audioapi/HostObjects/sources/AudioFileSourceNodeHostObject.h +1 -0
- package/common/cpp/audioapi/HostObjects/utils/JsEnumParser.cpp +2 -0
- package/common/cpp/audioapi/core/sources/AudioFileSourceNode.cpp +15 -1
- package/common/cpp/audioapi/core/sources/AudioFileSourceNode.h +19 -0
- package/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp +12 -1
- package/common/cpp/audioapi/core/sources/RecorderAdapterNode.h +7 -0
- package/common/cpp/audioapi/core/utils/param/ParamQueueBase.hpp +1 -1
- package/common/cpp/audioapi/core/utils/param/ParamRenderQueue.cpp +35 -12
- package/common/cpp/audioapi/core/utils/param/ParamRenderQueue.h +9 -0
- package/common/cpp/audioapi/core/utils/worklets/SafeIncludes.h +3 -3
- package/common/cpp/audioapi/core/utils/worklets/WorkletsRunner.cpp +2 -2
- package/common/cpp/audioapi/events/AudioEvent.h +1 -0
- package/common/cpp/audioapi/events/AudioEventPayload.h +11 -0
- package/common/cpp/audioapi/events/AudioEventPayloadMapping.h +1 -0
- package/common/cpp/audioapi/utils/events/BufferingStateDispatcher.cpp +49 -0
- package/common/cpp/audioapi/utils/events/BufferingStateDispatcher.h +42 -0
- package/ios/audioapi/ios/core/IOSAudioRecorder.h +12 -1
- package/ios/audioapi/ios/core/IOSAudioRecorder.mm +179 -63
- package/ios/audioapi/ios/core/NativeAudioRecorder.h +2 -0
- package/ios/audioapi/ios/core/NativeAudioRecorder.m +37 -12
- package/ios/audioapi/ios/core/utils/IOSFileWriter.h +3 -0
- package/ios/audioapi/ios/core/utils/IOSFileWriter.mm +17 -0
- package/ios/audioapi/ios/core/utils/IOSRotatingFileWriter.h +3 -0
- package/ios/audioapi/ios/core/utils/IOSRotatingFileWriter.mm +21 -0
- package/ios/audioapi/ios/system/AudioEngine.h +2 -1
- package/ios/audioapi/ios/system/AudioEngine.mm +49 -23
- package/ios/audioapi/ios/system/SystemNotificationManager.mm +10 -0
- package/lib/commonjs/Audio/Audio.js +30 -3
- package/lib/commonjs/Audio/Audio.js.map +1 -1
- package/lib/commonjs/Audio/Audio.web.js +21 -3
- package/lib/commonjs/Audio/Audio.web.js.map +1 -1
- package/lib/commonjs/Audio/AudioFileSourceNode.js +20 -0
- package/lib/commonjs/Audio/AudioFileSourceNode.js.map +1 -1
- package/lib/commonjs/Audio/controls/AudioControls.js +90 -94
- package/lib/commonjs/Audio/controls/AudioControls.js.map +1 -1
- package/lib/commonjs/Audio/controls/audioControlUtils.js +48 -18
- package/lib/commonjs/Audio/controls/audioControlUtils.js.map +1 -1
- package/lib/commonjs/Audio/index.js +7 -0
- package/lib/commonjs/Audio/index.js.map +1 -1
- package/lib/commonjs/Audio/utils.js +16 -5
- package/lib/commonjs/Audio/utils.js.map +1 -1
- package/lib/commonjs/AudioAPIModule/AudioAPIModule.js +1 -1
- package/lib/commonjs/core/AudioDecoder.js +4 -1
- package/lib/commonjs/core/AudioDecoder.js.map +1 -1
- package/lib/commonjs/core/AudioParam.js +1 -0
- package/lib/commonjs/core/AudioParam.js.map +1 -1
- package/lib/module/Audio/Audio.js +30 -3
- package/lib/module/Audio/Audio.js.map +1 -1
- package/lib/module/Audio/Audio.web.js +21 -3
- package/lib/module/Audio/Audio.web.js.map +1 -1
- package/lib/module/Audio/AudioFileSourceNode.js +20 -0
- package/lib/module/Audio/AudioFileSourceNode.js.map +1 -1
- package/lib/module/Audio/controls/AudioControls.js +92 -96
- package/lib/module/Audio/controls/AudioControls.js.map +1 -1
- package/lib/module/Audio/controls/audioControlUtils.js +48 -19
- package/lib/module/Audio/controls/audioControlUtils.js.map +1 -1
- package/lib/module/Audio/index.js +1 -0
- package/lib/module/Audio/index.js.map +1 -1
- package/lib/module/Audio/utils.js +15 -5
- package/lib/module/Audio/utils.js.map +1 -1
- package/lib/module/AudioAPIModule/AudioAPIModule.js +1 -1
- package/lib/module/core/AudioDecoder.js +4 -1
- package/lib/module/core/AudioDecoder.js.map +1 -1
- package/lib/module/core/AudioParam.js +1 -0
- package/lib/module/core/AudioParam.js.map +1 -1
- package/lib/typescript/Audio/Audio.d.ts.map +1 -1
- package/lib/typescript/Audio/Audio.web.d.ts.map +1 -1
- package/lib/typescript/Audio/AudioFileSourceNode.d.ts +2 -0
- package/lib/typescript/Audio/AudioFileSourceNode.d.ts.map +1 -1
- package/lib/typescript/Audio/controls/AudioControls.d.ts.map +1 -1
- package/lib/typescript/Audio/controls/audioControlUtils.d.ts +11 -4
- package/lib/typescript/Audio/controls/audioControlUtils.d.ts.map +1 -1
- package/lib/typescript/Audio/index.d.ts +1 -0
- package/lib/typescript/Audio/index.d.ts.map +1 -1
- package/lib/typescript/Audio/types.d.ts +15 -1
- package/lib/typescript/Audio/types.d.ts.map +1 -1
- package/lib/typescript/Audio/utils.d.ts +2 -1
- package/lib/typescript/Audio/utils.d.ts.map +1 -1
- package/lib/typescript/core/AudioDecoder.d.ts.map +1 -1
- package/lib/typescript/core/AudioParam.d.ts.map +1 -1
- package/lib/typescript/events/types.d.ts +5 -0
- package/lib/typescript/events/types.d.ts.map +1 -1
- package/lib/typescript/jsi-interfaces.d.ts +1 -0
- package/lib/typescript/jsi-interfaces.d.ts.map +1 -1
- package/package.json +8 -8
- package/scripts/download-prebuilt-binaries.sh +94 -6
- package/scripts/validate-worklets-version.js +1 -1
- package/src/Audio/Audio.tsx +38 -2
- package/src/Audio/Audio.web.tsx +26 -2
- package/src/Audio/AudioFileSourceNode.ts +28 -0
- package/src/Audio/controls/AudioControls.tsx +117 -116
- package/src/Audio/controls/audioControlUtils.ts +63 -31
- package/src/Audio/index.ts +1 -0
- package/src/Audio/types.ts +15 -1
- package/src/Audio/utils.ts +20 -2
- package/src/AudioAPIModule/AudioAPIModule.ts +1 -1
- package/src/core/AudioDecoder.ts +5 -3
- package/src/core/AudioParam.ts +1 -0
- package/src/events/types.ts +6 -0
- package/src/jsi-interfaces.ts +1 -0
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
ActivityIndicator,
|
|
4
|
+
Animated,
|
|
4
5
|
Image,
|
|
6
|
+
PanResponder,
|
|
5
7
|
Platform,
|
|
6
8
|
Pressable,
|
|
7
9
|
StyleSheet,
|
|
8
10
|
Text,
|
|
9
11
|
View,
|
|
10
12
|
} from 'react-native';
|
|
11
|
-
import {
|
|
12
|
-
import Animated, {
|
|
13
|
-
useAnimatedRef,
|
|
14
|
-
useSharedValue,
|
|
15
|
-
} from 'react-native-reanimated';
|
|
13
|
+
import type { LayoutChangeEvent } from 'react-native';
|
|
16
14
|
import { useAudioTagContext } from '../AudioTagContext';
|
|
15
|
+
import { isPlaybackActive } from '../utils';
|
|
17
16
|
import {
|
|
18
17
|
formatTime,
|
|
19
18
|
timeFromLocationX,
|
|
19
|
+
useBufferingSweep,
|
|
20
20
|
useExpandableTrackHeight,
|
|
21
21
|
} from './audioControlUtils';
|
|
22
22
|
|
|
@@ -28,7 +28,11 @@ import MuteIcon from './icons/speaker-x.png';
|
|
|
28
28
|
const TRACK_BAR_HEIGHT = 12;
|
|
29
29
|
const TRACK_BAR_HEIGHT_PRESSED = 18;
|
|
30
30
|
const TRACK_BAR_ANIM_MS = 150;
|
|
31
|
-
const
|
|
31
|
+
const BUFFERING_SWEEP_MS = 1100;
|
|
32
|
+
const BUFFERING_SWEEP_WIDTH_RATIO = 0.35;
|
|
33
|
+
const BUFFERING_SWEEP_SLICE_OPACITIES = [
|
|
34
|
+
0.04, 0.18, 0.45, 0.6, 0.45, 0.18, 0.04,
|
|
35
|
+
];
|
|
32
36
|
|
|
33
37
|
const AudioControls: React.FC = () => {
|
|
34
38
|
const {
|
|
@@ -50,107 +54,83 @@ const AudioControls: React.FC = () => {
|
|
|
50
54
|
);
|
|
51
55
|
|
|
52
56
|
const [scrubTime, setScrubTime] = useState<number | null>(null);
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
progressTrackAnim.expand();
|
|
61
|
-
const d = durationRef.current;
|
|
62
|
-
progressTrackRef.current?.measureInWindow((_left, _y, width, _h) => {
|
|
63
|
-
progressMetricsWidth.value = width;
|
|
64
|
-
setScrubTime(timeFromLocationX(x, width, d));
|
|
65
|
-
});
|
|
66
|
-
},
|
|
67
|
-
[progressTrackAnim, progressMetricsWidth, setScrubTime, progressTrackRef]
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
const onUpdate = useCallback(
|
|
71
|
-
(x: number) => {
|
|
72
|
-
const d = durationRef.current;
|
|
73
|
-
const w = progressMetricsWidth.value;
|
|
74
|
-
setScrubTime(timeFromLocationX(x, w, d));
|
|
75
|
-
},
|
|
76
|
-
[progressMetricsWidth]
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
const seekTo = useCallback(
|
|
80
|
-
(x: number) => {
|
|
81
|
-
const d = durationRef.current;
|
|
82
|
-
const w = progressMetricsWidth.value;
|
|
83
|
-
const t = timeFromLocationX(x, w, d);
|
|
84
|
-
seekToTime(t);
|
|
85
|
-
},
|
|
86
|
-
[progressMetricsWidth, seekToTime]
|
|
57
|
+
const [trackWidth, setTrackWidth] = useState(0);
|
|
58
|
+
const isBuffering = playbackState === 'buffering';
|
|
59
|
+
const bufferingSweepStyle = useBufferingSweep(
|
|
60
|
+
isBuffering,
|
|
61
|
+
trackWidth,
|
|
62
|
+
BUFFERING_SWEEP_WIDTH_RATIO,
|
|
63
|
+
BUFFERING_SWEEP_MS
|
|
87
64
|
);
|
|
88
65
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}, [progressTrackAnim]);
|
|
66
|
+
const scrub = useRef({
|
|
67
|
+
startX: 0,
|
|
68
|
+
trackWidth: 0,
|
|
69
|
+
duration,
|
|
70
|
+
seekToTime,
|
|
71
|
+
expand: progressTrackAnim.expand,
|
|
72
|
+
collapse: progressTrackAnim.collapse,
|
|
73
|
+
});
|
|
74
|
+
scrub.current.duration = duration;
|
|
75
|
+
scrub.current.seekToTime = seekToTime;
|
|
76
|
+
scrub.current.expand = progressTrackAnim.expand;
|
|
77
|
+
scrub.current.collapse = progressTrackAnim.collapse;
|
|
102
78
|
|
|
103
|
-
const
|
|
104
|
-
(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
79
|
+
const panResponder = useMemo(
|
|
80
|
+
() =>
|
|
81
|
+
PanResponder.create({
|
|
82
|
+
onStartShouldSetPanResponder: () => true,
|
|
83
|
+
onMoveShouldSetPanResponder: () => true,
|
|
84
|
+
onPanResponderTerminationRequest: () => false,
|
|
85
|
+
onPanResponderGrant: (event) => {
|
|
86
|
+
const s = scrub.current;
|
|
87
|
+
s.startX = event.nativeEvent.locationX;
|
|
88
|
+
s.expand();
|
|
89
|
+
setScrubTime(timeFromLocationX(s.startX, s.trackWidth, s.duration));
|
|
90
|
+
},
|
|
91
|
+
onPanResponderMove: (_event, gestureState) => {
|
|
92
|
+
const s = scrub.current;
|
|
93
|
+
setScrubTime(
|
|
94
|
+
timeFromLocationX(
|
|
95
|
+
s.startX + gestureState.dx,
|
|
96
|
+
s.trackWidth,
|
|
97
|
+
s.duration
|
|
98
|
+
)
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
onPanResponderRelease: (_event, gestureState) => {
|
|
102
|
+
const s = scrub.current;
|
|
103
|
+
s.seekToTime(
|
|
104
|
+
timeFromLocationX(
|
|
105
|
+
s.startX + gestureState.dx,
|
|
106
|
+
s.trackWidth,
|
|
107
|
+
s.duration
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
s.collapse();
|
|
111
|
+
setScrubTime(null);
|
|
112
|
+
},
|
|
113
|
+
onPanResponderTerminate: () => {
|
|
114
|
+
scrub.current.collapse();
|
|
115
|
+
setScrubTime(null);
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
[]
|
|
108
119
|
);
|
|
109
120
|
|
|
110
|
-
const scrubGesture = useMemo(() => {
|
|
111
|
-
const panGesture = Gesture.Pan()
|
|
112
|
-
.runOnJS(true)
|
|
113
|
-
.minDistance(SCRUB_PAN_MIN_DISTANCE)
|
|
114
|
-
.onStart((e) => {
|
|
115
|
-
onStart(e.x);
|
|
116
|
-
})
|
|
117
|
-
.onUpdate((e) => {
|
|
118
|
-
onUpdate(e.x);
|
|
119
|
-
})
|
|
120
|
-
.onEnd((e) => {
|
|
121
|
-
onEnd(e.x);
|
|
122
|
-
})
|
|
123
|
-
.onFinalize((_e, success) => {
|
|
124
|
-
if (!success) {
|
|
125
|
-
onCancel();
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
const tapGesture = Gesture.Tap()
|
|
130
|
-
.runOnJS(true)
|
|
131
|
-
.maxDistance(14)
|
|
132
|
-
.onEnd((e, success) => {
|
|
133
|
-
if (success) {
|
|
134
|
-
onTapSeek(e.x);
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
return Gesture.Race(panGesture, tapGesture);
|
|
139
|
-
}, [onStart, onUpdate, onEnd, onCancel, onTapSeek]);
|
|
140
|
-
|
|
141
121
|
const onPlayPausePress = useCallback(() => {
|
|
142
|
-
if (playbackState
|
|
122
|
+
if (isPlaybackActive(playbackState)) {
|
|
143
123
|
pause();
|
|
144
124
|
} else {
|
|
145
125
|
play();
|
|
146
126
|
}
|
|
147
127
|
}, [playbackState, pause, play]);
|
|
148
128
|
|
|
149
|
-
const onProgressTrackLayout = useCallback(() => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}, [
|
|
129
|
+
const onProgressTrackLayout = useCallback((event: LayoutChangeEvent) => {
|
|
130
|
+
const { width } = event.nativeEvent.layout;
|
|
131
|
+
scrub.current.trackWidth = width;
|
|
132
|
+
setTrackWidth(width);
|
|
133
|
+
}, []);
|
|
154
134
|
|
|
155
135
|
if (!ready) {
|
|
156
136
|
return (
|
|
@@ -167,10 +147,10 @@ const AudioControls: React.FC = () => {
|
|
|
167
147
|
<View style={styles.container}>
|
|
168
148
|
<View style={styles.topRow}>
|
|
169
149
|
<Pressable style={styles.playPause} onPress={onPlayPausePress}>
|
|
170
|
-
{playbackState
|
|
171
|
-
<Image source={PauseIcon} style={
|
|
150
|
+
{isPlaybackActive(playbackState) ? (
|
|
151
|
+
<Image source={PauseIcon} style={styles.icon} />
|
|
172
152
|
) : (
|
|
173
|
-
<Image source={PlayIcon} style={
|
|
153
|
+
<Image source={PlayIcon} style={styles.icon} />
|
|
174
154
|
)}
|
|
175
155
|
</Pressable>
|
|
176
156
|
|
|
@@ -178,28 +158,34 @@ const AudioControls: React.FC = () => {
|
|
|
178
158
|
{formatTime(displayTime)} / {formatTime(duration)}
|
|
179
159
|
</Text>
|
|
180
160
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
style={[styles.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
161
|
+
{/* prettier-ignore */}
|
|
162
|
+
<View
|
|
163
|
+
onLayout={onProgressTrackLayout}
|
|
164
|
+
style={styles.progressTrack}
|
|
165
|
+
{...panResponder.panHandlers}>
|
|
166
|
+
<Animated.View
|
|
167
|
+
style={[styles.trackInner, progressTrackAnim.animatedStyle]}>
|
|
168
|
+
<View style={[styles.trackFill, { width: `${progress * 100}%` }]} />
|
|
169
|
+
{isBuffering && (
|
|
170
|
+
<Animated.View
|
|
171
|
+
pointerEvents="none"
|
|
172
|
+
style={[styles.bufferingSweep, bufferingSweepStyle]}>
|
|
173
|
+
{BUFFERING_SWEEP_SLICE_OPACITIES.map((opacity, index) => (
|
|
174
|
+
<View
|
|
175
|
+
key={index}
|
|
176
|
+
style={[styles.bufferingSweepSlice, { opacity }]}
|
|
177
|
+
/>
|
|
178
|
+
))}
|
|
179
|
+
</Animated.View>
|
|
180
|
+
)}
|
|
181
|
+
</Animated.View>
|
|
182
|
+
</View>
|
|
197
183
|
|
|
198
184
|
<Pressable style={styles.volumeIcon} onPress={() => setMuted(!muted)}>
|
|
199
185
|
{muted ? (
|
|
200
|
-
<Image source={MuteIcon} style={
|
|
186
|
+
<Image source={MuteIcon} style={styles.icon} />
|
|
201
187
|
) : (
|
|
202
|
-
<Image source={VolumeIcon} style={
|
|
188
|
+
<Image source={VolumeIcon} style={styles.icon} />
|
|
203
189
|
)}
|
|
204
190
|
</Pressable>
|
|
205
191
|
</View>
|
|
@@ -234,6 +220,10 @@ const styles = StyleSheet.create({
|
|
|
234
220
|
flexDirection: 'row',
|
|
235
221
|
alignItems: 'center',
|
|
236
222
|
},
|
|
223
|
+
icon: {
|
|
224
|
+
width: 24,
|
|
225
|
+
height: 24,
|
|
226
|
+
},
|
|
237
227
|
playPause: {
|
|
238
228
|
padding: 4,
|
|
239
229
|
marginRight: 12,
|
|
@@ -259,6 +249,17 @@ const styles = StyleSheet.create({
|
|
|
259
249
|
height: '100%',
|
|
260
250
|
backgroundColor: '#000',
|
|
261
251
|
},
|
|
252
|
+
bufferingSweep: {
|
|
253
|
+
position: 'absolute',
|
|
254
|
+
top: 0,
|
|
255
|
+
bottom: 0,
|
|
256
|
+
left: 0,
|
|
257
|
+
flexDirection: 'row',
|
|
258
|
+
},
|
|
259
|
+
bufferingSweepSlice: {
|
|
260
|
+
flex: 1,
|
|
261
|
+
backgroundColor: '#000',
|
|
262
|
+
},
|
|
262
263
|
volumeIcon: {
|
|
263
264
|
padding: 4,
|
|
264
265
|
marginRight: 12,
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
useSharedValue,
|
|
4
|
-
useAnimatedStyle,
|
|
5
|
-
withTiming,
|
|
6
|
-
} from 'react-native-reanimated';
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { Animated, Easing } from 'react-native';
|
|
7
3
|
|
|
8
4
|
export function formatTime(seconds: number): string {
|
|
9
5
|
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
@@ -36,32 +32,68 @@ export function useExpandableTrackHeight(
|
|
|
36
32
|
trackBarHeightPressed: number,
|
|
37
33
|
trackBarAnimMs: number
|
|
38
34
|
) {
|
|
39
|
-
const height =
|
|
40
|
-
const animatedStyle = useAnimatedStyle(() => ({
|
|
41
|
-
height: height.value,
|
|
42
|
-
borderRadius: height.value / 2,
|
|
43
|
-
}));
|
|
35
|
+
const height = useRef(new Animated.Value(trackBarHeight)).current;
|
|
44
36
|
|
|
45
|
-
return useMemo(
|
|
46
|
-
() =>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
37
|
+
return useMemo(() => {
|
|
38
|
+
const animateTo = (toValue: number) => {
|
|
39
|
+
Animated.timing(height, {
|
|
40
|
+
toValue,
|
|
41
|
+
duration: trackBarAnimMs,
|
|
42
|
+
useNativeDriver: false,
|
|
43
|
+
}).start();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
animatedStyle: {
|
|
48
|
+
height,
|
|
49
|
+
borderRadius: Animated.divide(height, 2),
|
|
52
50
|
},
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
expand: () => animateTo(trackBarHeightPressed),
|
|
52
|
+
collapse: () => animateTo(trackBarHeight),
|
|
53
|
+
};
|
|
54
|
+
}, [height, trackBarHeight, trackBarHeightPressed, trackBarAnimMs]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function useBufferingSweep(
|
|
58
|
+
isBuffering: boolean,
|
|
59
|
+
trackWidth: number,
|
|
60
|
+
sweepWidthRatio: number,
|
|
61
|
+
sweepDurationMs: number
|
|
62
|
+
) {
|
|
63
|
+
const sweepProgress = useRef(new Animated.Value(0)).current;
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!isBuffering) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const sweep = Animated.loop(
|
|
71
|
+
Animated.timing(sweepProgress, {
|
|
72
|
+
toValue: 1,
|
|
73
|
+
duration: sweepDurationMs,
|
|
74
|
+
easing: Easing.inOut((t) => Easing.quad(t)),
|
|
75
|
+
useNativeDriver: true,
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
sweep.start();
|
|
79
|
+
|
|
80
|
+
return () => {
|
|
81
|
+
sweep.stop();
|
|
82
|
+
sweepProgress.setValue(0);
|
|
83
|
+
};
|
|
84
|
+
}, [isBuffering, sweepDurationMs, sweepProgress]);
|
|
85
|
+
|
|
86
|
+
const sweepWidth = trackWidth * sweepWidthRatio;
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
width: sweepWidth,
|
|
90
|
+
transform: [
|
|
91
|
+
{
|
|
92
|
+
translateX: sweepProgress.interpolate({
|
|
93
|
+
inputRange: [0, 1],
|
|
94
|
+
outputRange: [-sweepWidth, trackWidth],
|
|
95
|
+
}),
|
|
57
96
|
},
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
animatedStyle,
|
|
61
|
-
height,
|
|
62
|
-
trackBarHeight,
|
|
63
|
-
trackBarHeightPressed,
|
|
64
|
-
trackBarAnimMs,
|
|
65
|
-
]
|
|
66
|
-
);
|
|
97
|
+
],
|
|
98
|
+
};
|
|
67
99
|
}
|
package/src/Audio/index.ts
CHANGED
package/src/Audio/types.ts
CHANGED
|
@@ -23,7 +23,11 @@ export type AudioSource = AudioURISource | AudioRequireSource | string;
|
|
|
23
23
|
|
|
24
24
|
export type PreloadType = 'auto' | 'metadata' | 'none';
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* `'buffering'` is a sub-state of `'playing'` — stalled on decoded data. See
|
|
28
|
+
* `onWaiting`/`onPlaying`.
|
|
29
|
+
*/
|
|
30
|
+
export type AudioTagPlaybackState = 'idle' | 'playing' | 'paused' | 'buffering';
|
|
27
31
|
|
|
28
32
|
export interface AudioTagHandle {
|
|
29
33
|
play: () => void;
|
|
@@ -83,6 +87,16 @@ interface AudioEventProps {
|
|
|
83
87
|
onPlay: TMPEmptyEventHandler;
|
|
84
88
|
onPause: TMPEmptyEventHandler;
|
|
85
89
|
onVolumeChange: TMPNumberEventHandler;
|
|
90
|
+
/**
|
|
91
|
+
* Fires on a genuine stall (not a deliberate pause) — mirrors HTML media's
|
|
92
|
+
* `waiting`.
|
|
93
|
+
*/
|
|
94
|
+
onWaiting: TMPEmptyEventHandler;
|
|
95
|
+
/**
|
|
96
|
+
* Fires when playback resumes after `onWaiting` — mirrors HTML media's
|
|
97
|
+
* `playing`.
|
|
98
|
+
*/
|
|
99
|
+
onPlaying: TMPEmptyEventHandler;
|
|
86
100
|
}
|
|
87
101
|
|
|
88
102
|
export interface AudioPropsBase
|
package/src/Audio/utils.ts
CHANGED
|
@@ -2,7 +2,16 @@ import { useMemo } from 'react';
|
|
|
2
2
|
import { Image, Platform } from 'react-native';
|
|
3
3
|
import AudioContext from '../core/AudioContext';
|
|
4
4
|
import type BaseAudioContext from '../core/BaseAudioContext';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
AudioProps,
|
|
7
|
+
AudioPropsBase,
|
|
8
|
+
AudioSource,
|
|
9
|
+
AudioTagPlaybackState,
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
export function isPlaybackActive(state: AudioTagPlaybackState): boolean {
|
|
13
|
+
return state === 'playing' || state === 'buffering';
|
|
14
|
+
}
|
|
6
15
|
|
|
7
16
|
const noop = () => {};
|
|
8
17
|
const noopError = (_error: Error) => {};
|
|
@@ -38,6 +47,8 @@ export function withPropsDefaults(
|
|
|
38
47
|
onPlay: props.onPlay ?? noop,
|
|
39
48
|
onPause: props.onPause ?? noop,
|
|
40
49
|
onVolumeChange: props.onVolumeChange ?? noopNumber,
|
|
50
|
+
onWaiting: props.onWaiting ?? noop,
|
|
51
|
+
onPlaying: props.onPlaying ?? noop,
|
|
41
52
|
};
|
|
42
53
|
}
|
|
43
54
|
|
|
@@ -72,6 +83,8 @@ export function useStableAudioProps(props: AudioProps): AudioPropsBase {
|
|
|
72
83
|
onPlay,
|
|
73
84
|
onPause,
|
|
74
85
|
onVolumeChange,
|
|
86
|
+
onWaiting,
|
|
87
|
+
onPlaying,
|
|
75
88
|
} = withPropsDefaults(props, resolvedContext);
|
|
76
89
|
|
|
77
90
|
return useMemo(
|
|
@@ -98,6 +111,8 @@ export function useStableAudioProps(props: AudioProps): AudioPropsBase {
|
|
|
98
111
|
onPlay,
|
|
99
112
|
onPause,
|
|
100
113
|
onVolumeChange,
|
|
114
|
+
onWaiting,
|
|
115
|
+
onPlaying,
|
|
101
116
|
}),
|
|
102
117
|
[
|
|
103
118
|
autoPlay,
|
|
@@ -119,6 +134,8 @@ export function useStableAudioProps(props: AudioProps): AudioPropsBase {
|
|
|
119
134
|
onPlay,
|
|
120
135
|
onPause,
|
|
121
136
|
onVolumeChange,
|
|
137
|
+
onWaiting,
|
|
138
|
+
onPlaying,
|
|
122
139
|
]
|
|
123
140
|
);
|
|
124
141
|
}
|
|
@@ -129,7 +146,8 @@ export function resolveSourcePath(source: AudioSource): string {
|
|
|
129
146
|
}
|
|
130
147
|
|
|
131
148
|
if (typeof source === 'number') {
|
|
132
|
-
|
|
149
|
+
const uri = Image.resolveAssetSource(source)?.uri;
|
|
150
|
+
return uri ?? '';
|
|
133
151
|
}
|
|
134
152
|
|
|
135
153
|
return source.uri ?? '';
|
|
@@ -12,7 +12,7 @@ class AudioAPIModule implements IAudioAPIModule {
|
|
|
12
12
|
#canUseWorklets_ = false;
|
|
13
13
|
#workletsVersion = 'unknown';
|
|
14
14
|
#workletsAvailable_ = false;
|
|
15
|
-
private static readonly MIN_WORKLETS_VERSION = '0.
|
|
15
|
+
private static readonly MIN_WORKLETS_VERSION = '0.7.0';
|
|
16
16
|
|
|
17
17
|
constructor() {
|
|
18
18
|
// Important! Verify and import worklets first
|
package/src/core/AudioDecoder.ts
CHANGED
|
@@ -55,9 +55,11 @@ class AudioDecoder {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
private resolveStringSource(input: number | string): string | number {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
if (typeof input !== 'number') {
|
|
59
|
+
return input;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return Image.resolveAssetSource(input)?.uri ?? '';
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
private assertSupportedStringSource(
|
package/src/core/AudioParam.ts
CHANGED
package/src/events/types.ts
CHANGED
|
@@ -7,6 +7,10 @@ export interface EventTypeWithValue {
|
|
|
7
7
|
value: number;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export interface EventTypeWithBool {
|
|
11
|
+
value: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
export type InterruptionType = 'began' | 'ended';
|
|
11
15
|
|
|
12
16
|
export interface OnInterruptionEventType {
|
|
@@ -75,6 +79,8 @@ interface AudioAPIEvents {
|
|
|
75
79
|
positionChanged: EventTypeWithValue;
|
|
76
80
|
bufferEnded: OnBufferEndEventType;
|
|
77
81
|
recorderError: OnRecorderErrorEventType;
|
|
82
|
+
/** `value` is true while an `<Audio>` source is stalled on decoded data. */
|
|
83
|
+
bufferingStateChanged: EventTypeWithBool;
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
type AudioEvents = SystemEvents & AudioAPIEvents & NotificationEvents;
|
package/src/jsi-interfaces.ts
CHANGED
|
@@ -247,6 +247,7 @@ export interface IAudioFileSourceNode extends IAudioScheduledSourceNode {
|
|
|
247
247
|
|
|
248
248
|
// passing subscriptionId(uint_64 in cpp, string in js) to the cpp
|
|
249
249
|
onPositionChanged: string;
|
|
250
|
+
onBufferingStateChanged: string;
|
|
250
251
|
}
|
|
251
252
|
|
|
252
253
|
export interface IMediaElementAudioSourceNode extends IAudioNode {}
|