Skip to content

Commit 3b7ad6a

Browse files
authored
Feat/long file playback (#993)
1 parent 7f23d22 commit 3b7ad6a

40 files changed

Lines changed: 2832 additions & 321 deletions
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React, { useRef } from 'react';
2+
import { Button, View } from 'react-native';
3+
import { Audio, AudioTagHandle } from 'react-native-audio-api/development/react';
4+
5+
import { Container } from '../../components';
6+
7+
// const DEMO_AUDIO_URL = 'https://filesamples.com/samples/audio/m4a/sample4.m4a';
8+
const DEMO_AUDIO_URL = 'https://filesamples.com/samples/audio/mp3/sample4.mp3';
9+
10+
const AudioTag: React.FC = () => {
11+
const audioRef = useRef<AudioTagHandle>(null);
12+
13+
// const handlePlay = () => {
14+
// audioRef.current?.play();
15+
// };
16+
17+
// const handlePause = () => {
18+
// audioRef.current?.pause();
19+
// };
20+
21+
// const handleSeekToTime = (time: number) => {
22+
// console.log('handleSeekToTime', time);
23+
// audioRef.current?.seekToTime(time);
24+
// };
25+
26+
// const handleSetVolume = (volume: number) => {
27+
// audioRef.current?.setVolume(volume);
28+
// };
29+
30+
// const handleSetMuted = (muted: boolean) => {
31+
// audioRef.current?.setMuted(muted);
32+
// };
33+
34+
return (
35+
<Container disablePadding>
36+
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
37+
<View style={{ width: '90%' }}>
38+
<Audio source={DEMO_AUDIO_URL} ref={audioRef} controls
39+
onLoadStart={() => console.log('onLoadStart')}
40+
onLoad={() => console.log('onLoad')}
41+
onError={(error) => console.log('onError', error)}
42+
onPositionChange={(seconds) =>
43+
console.log('onPositionChange', seconds)
44+
}
45+
onEnded={() => console.log('onEnded')}
46+
onPlay={() => console.log('onPlay')}
47+
onPause={() => console.log('onPause')}
48+
onVolumeChange={(volume) => console.log('onVolumeChange', volume)}
49+
/>
50+
</View>
51+
</View>
52+
</Container>
53+
);
54+
};
55+
56+
export default AudioTag;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './AudioTag';

apps/common-app/src/examples/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import PlaybackSpeed from './PlaybackSpeed/PlaybackSpeed';
1212
import Record from './Record/Record';
1313
import Streaming from './Streaming/Streaming';
1414
import Worklets from './Worklets/Worklets';
15+
import AudioStream from './AudioTag/AudioTag';
1516

1617
type NavigationParamList = {
1718
Oscillator: undefined;
@@ -26,6 +27,7 @@ type NavigationParamList = {
2627
Record: undefined;
2728
Worklets: undefined;
2829
Streamer: undefined;
30+
AudioTag: undefined;
2931
};
3032

3133
export type ExampleKey = keyof NavigationParamList;
@@ -110,4 +112,10 @@ export const Examples: Example[] = [
110112
Icon: icons.Radio,
111113
screen: Streaming,
112114
},
115+
{
116+
key: 'AudioTag',
117+
title: 'Audio Tag',
118+
Icon: icons.Tag,
119+
screen: AudioStream,
120+
}
113121
] as const;

apps/fabric-example/metro.config.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,28 @@ const appsRoot = path.resolve(monorepoRoot, 'apps');
1313
const config = {
1414
projectRoot: __dirname,
1515
watchFolders: [monorepoRoot, appsRoot],
16+
/* we are rewriting requests because due to monorepo structure, the assets are found with '../../../' prefix
17+
and we redirect them to the correct path without relative prefixes */
18+
server: {
19+
rewriteRequestUrl: (url) => {
20+
if (!url.startsWith('/assets/../../')) {
21+
return url;
22+
}
23+
24+
const queryIndex = url.indexOf('?');
25+
const pathname = queryIndex >= 0 ? url.substring(0, queryIndex) : url;
26+
const query = queryIndex >= 0 ? url.substring(queryIndex) : '';
27+
const separator = query ? '&' : '?';
28+
29+
const relPath = pathname.startsWith('/assets/')
30+
? pathname.substring('/assets/'.length)
31+
: `../../${pathname}`;
32+
33+
const rewrittenUrl = `/assets${query}${separator}unstable_path=${encodeURIComponent(relPath)}`;
34+
35+
return rewrittenUrl;
36+
},
37+
},
1638
};
1739

1840
module.exports = mergeConfig(getDefaultConfig(__dirname), config);

packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <audioapi/HostObjects/sources/AudioBufferHostObject.h>
1515
#include <audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.h>
1616
#include <audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.h>
17+
#include <audioapi/HostObjects/sources/AudioFileSourceNodeHostObject.h>
1718
#include <audioapi/HostObjects/sources/ConstantSourceNodeHostObject.h>
1819
#include <audioapi/HostObjects/sources/OscillatorNodeHostObject.h>
1920
#include <audioapi/HostObjects/sources/RecorderAdapterNodeHostObject.h>
@@ -22,6 +23,7 @@
2223
#include <audioapi/HostObjects/utils/JsEnumParser.h>
2324
#include <audioapi/HostObjects/utils/NodeOptionsParser.h>
2425
#include <audioapi/core/BaseAudioContext.h>
26+
#include <audioapi/core/utils/AudioDecoder.h>
2527

2628
#include <memory>
2729
#include <vector>
@@ -58,6 +60,7 @@ BaseAudioContextHostObject::BaseAudioContextHostObject(
5860
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createBiquadFilter),
5961
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createIIRFilter),
6062
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createBufferSource),
63+
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createFileSource),
6164
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createBufferQueueSource),
6265
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createPeriodicWave),
6366
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createConvolver),
@@ -250,6 +253,25 @@ JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createBufferSource) {
250253
return jsi::Object::createFromHostObject(runtime, bufferSourceHostObject);
251254
}
252255

256+
JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createFileSource) {
257+
auto makeFileSourceHostObject = [&](AudioFileSourceOptions opts) -> jsi::Value {
258+
#if RN_AUDIO_API_FFMPEG_DISABLED
259+
if (opts.requiresFFmpeg) {
260+
return jsi::Value::undefined();
261+
}
262+
#endif // RN_AUDIO_API_FFMPEG_DISABLED
263+
const auto fileSourceHostObject =
264+
std::make_shared<AudioFileSourceNodeHostObject>(context_, opts);
265+
return jsi::Object::createFromHostObject(runtime, fileSourceHostObject);
266+
};
267+
268+
const auto options = args[0].asObject(runtime);
269+
270+
const auto fileSourceOptions =
271+
audioapi::option_parser::parseAudioFileSourceOptions(runtime, options);
272+
return makeFileSourceHostObject(fileSourceOptions);
273+
}
274+
253275
JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createBufferQueueSource) {
254276
const auto options = args[0].asObject(runtime);
255277
const auto baseAudioBufferSourceOptions =

packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class BaseAudioContextHostObject : public JsiHostObject {
3939
JSI_HOST_FUNCTION_DECL(createBiquadFilter);
4040
JSI_HOST_FUNCTION_DECL(createIIRFilter);
4141
JSI_HOST_FUNCTION_DECL(createBufferSource);
42+
JSI_HOST_FUNCTION_DECL(createFileSource);
4243
JSI_HOST_FUNCTION_DECL(createBufferQueueSource);
4344
JSI_HOST_FUNCTION_DECL(createPeriodicWave);
4445
JSI_HOST_FUNCTION_DECL(createAnalyser);
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#include <audioapi/HostObjects/sources/AudioFileSourceNodeHostObject.h>
2+
3+
#include <audioapi/core/BaseAudioContext.h>
4+
#include <audioapi/core/sources/AudioFileSourceNode.h>
5+
#include <audioapi/types/NodeOptions.h>
6+
#include <memory>
7+
#include <utility>
8+
#include "audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.h"
9+
10+
namespace audioapi {
11+
12+
AudioFileSourceNodeHostObject::AudioFileSourceNodeHostObject(
13+
const std::shared_ptr<BaseAudioContext> &context,
14+
const AudioFileSourceOptions &options)
15+
: AudioScheduledSourceNodeHostObject(context->createFileSource(options), options),
16+
loop_(options.loop),
17+
volume_(options.volume),
18+
duration_(std::static_pointer_cast<AudioFileSourceNode>(node_)->getDuration()) {
19+
addGetters(
20+
JSI_EXPORT_PROPERTY_GETTER(AudioFileSourceNodeHostObject, volume),
21+
JSI_EXPORT_PROPERTY_GETTER(AudioFileSourceNodeHostObject, loop),
22+
JSI_EXPORT_PROPERTY_GETTER(AudioFileSourceNodeHostObject, currentTime),
23+
JSI_EXPORT_PROPERTY_GETTER(AudioFileSourceNodeHostObject, duration));
24+
addSetters(
25+
JSI_EXPORT_PROPERTY_SETTER(AudioFileSourceNodeHostObject, onPositionChanged),
26+
JSI_EXPORT_PROPERTY_SETTER(AudioFileSourceNodeHostObject, onEnded),
27+
JSI_EXPORT_PROPERTY_SETTER(AudioFileSourceNodeHostObject, volume),
28+
JSI_EXPORT_PROPERTY_SETTER(AudioFileSourceNodeHostObject, loop));
29+
30+
addFunctions(
31+
JSI_EXPORT_FUNCTION(AudioFileSourceNodeHostObject, pause),
32+
JSI_EXPORT_FUNCTION(AudioFileSourceNodeHostObject, start),
33+
JSI_EXPORT_FUNCTION(AudioFileSourceNodeHostObject, seekToTime));
34+
}
35+
36+
AudioFileSourceNodeHostObject::~AudioFileSourceNodeHostObject() {
37+
setOnPositionChangedCallbackId(0);
38+
setOnEndedCallbackId(0);
39+
}
40+
41+
JSI_PROPERTY_GETTER_IMPL(AudioFileSourceNodeHostObject, volume) {
42+
return {volume_};
43+
}
44+
45+
JSI_PROPERTY_SETTER_IMPL(AudioFileSourceNodeHostObject, volume) {
46+
auto node = std::static_pointer_cast<AudioFileSourceNode>(node_);
47+
volume_ = static_cast<float>(value.getNumber());
48+
auto event = [node, volume = this->volume_](BaseAudioContext &ctx) {
49+
node->setVolume(volume);
50+
};
51+
node->scheduleAudioEvent(std::move(event));
52+
}
53+
54+
JSI_PROPERTY_GETTER_IMPL(AudioFileSourceNodeHostObject, loop) {
55+
return {loop_};
56+
}
57+
58+
JSI_PROPERTY_SETTER_IMPL(AudioFileSourceNodeHostObject, loop) {
59+
auto node = std::static_pointer_cast<AudioFileSourceNode>(node_);
60+
loop_ = value.getBool();
61+
auto event = [node, loop = this->loop_](BaseAudioContext &ctx) {
62+
node->setLoop(loop);
63+
};
64+
node->scheduleAudioEvent(std::move(event));
65+
}
66+
67+
JSI_PROPERTY_GETTER_IMPL(AudioFileSourceNodeHostObject, currentTime) {
68+
auto node = std::static_pointer_cast<AudioFileSourceNode>(node_);
69+
return {node->getCurrentTime()};
70+
}
71+
72+
JSI_PROPERTY_GETTER_IMPL(AudioFileSourceNodeHostObject, duration) {
73+
return {duration_};
74+
}
75+
76+
JSI_HOST_FUNCTION_IMPL(AudioFileSourceNodeHostObject, pause) {
77+
auto audioFileSourceNode = std::static_pointer_cast<AudioFileSourceNode>(node_);
78+
auto event = [audioFileSourceNode](BaseAudioContext &ctx) {
79+
audioFileSourceNode->pause();
80+
};
81+
audioFileSourceNode->scheduleAudioEvent(std::move(event));
82+
return jsi::Value::undefined();
83+
}
84+
85+
JSI_HOST_FUNCTION_IMPL(AudioFileSourceNodeHostObject, seekToTime) {
86+
auto audioFileSourceNode = std::static_pointer_cast<AudioFileSourceNode>(node_);
87+
if (count < 1 || !args[0].isNumber()) {
88+
return jsi::Value::undefined();
89+
}
90+
const double t = args[0].getNumber();
91+
92+
auto event = [audioFileSourceNode, t](BaseAudioContext &) {
93+
audioFileSourceNode->seekToTime(t);
94+
};
95+
audioFileSourceNode->scheduleAudioEvent(std::move(event));
96+
97+
return jsi::Value::undefined();
98+
}
99+
100+
JSI_PROPERTY_SETTER_IMPL(AudioFileSourceNodeHostObject, onPositionChanged) {
101+
auto callbackId = std::stoull(value.getString(runtime).utf8(runtime));
102+
setOnPositionChangedCallbackId(callbackId);
103+
}
104+
105+
void AudioFileSourceNodeHostObject::setOnPositionChangedCallbackId(uint64_t callbackId) {
106+
auto sourceNode = std::static_pointer_cast<AudioFileSourceNode>(node_);
107+
108+
auto event = [sourceNode, callbackId](BaseAudioContext &) {
109+
sourceNode->setOnPositionChangedCallbackId(callbackId);
110+
};
111+
112+
sourceNode->unregisterOnPositionChangedCallback(onPositionChangedCallbackId_);
113+
sourceNode->scheduleAudioEvent(std::move(event));
114+
onPositionChangedCallbackId_ = callbackId;
115+
}
116+
117+
void AudioFileSourceNodeHostObject::setOnEndedCallbackId(uint64_t callbackId) {
118+
auto sourceNode = std::static_pointer_cast<AudioFileSourceNode>(node_);
119+
120+
auto event = [sourceNode, callbackId](BaseAudioContext &) {
121+
sourceNode->setOnEndedCallbackId(callbackId);
122+
};
123+
124+
sourceNode->unregisterOnEndedCallback(onEndedCallbackId_);
125+
sourceNode->scheduleAudioEvent(std::move(event));
126+
onEndedCallbackId_ = callbackId;
127+
}
128+
129+
} // namespace audioapi
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#pragma once
2+
3+
#include <audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.h>
4+
#include <memory>
5+
6+
namespace audioapi {
7+
using namespace facebook;
8+
9+
struct AudioFileSourceOptions;
10+
class BaseAudioContext;
11+
12+
class AudioFileSourceNodeHostObject : public AudioScheduledSourceNodeHostObject {
13+
public:
14+
explicit AudioFileSourceNodeHostObject(
15+
const std::shared_ptr<BaseAudioContext> &context,
16+
const AudioFileSourceOptions &options);
17+
18+
~AudioFileSourceNodeHostObject() override;
19+
20+
JSI_PROPERTY_GETTER_DECL(volume);
21+
JSI_PROPERTY_GETTER_DECL(loop);
22+
JSI_PROPERTY_GETTER_DECL(currentTime);
23+
JSI_PROPERTY_GETTER_DECL(duration);
24+
25+
JSI_PROPERTY_SETTER_DECL(volume);
26+
JSI_PROPERTY_SETTER_DECL(loop);
27+
JSI_PROPERTY_SETTER_DECL(onPositionChanged);
28+
29+
JSI_HOST_FUNCTION_DECL(pause);
30+
JSI_HOST_FUNCTION_DECL(seekToStart);
31+
JSI_HOST_FUNCTION_DECL(seekToTime);
32+
33+
private:
34+
uint64_t onPositionChangedCallbackId_ = 0;
35+
uint64_t onEndedCallbackId_ = 0;
36+
37+
void setOnPositionChangedCallbackId(uint64_t callbackId);
38+
void setOnEndedCallbackId(uint64_t callbackId);
39+
40+
bool loop_;
41+
double duration_;
42+
float volume_;
43+
};
44+
45+
} // namespace audioapi

0 commit comments

Comments
 (0)