Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 11 additions & 14 deletions apps/common-app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
import React from 'react';
import type { FC } from 'react';
import Animated from 'react-native-reanimated';
import { NavigationContainer, useNavigation } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import type { FC } from 'react';
import React from 'react';
import {
FlatList,
ListRenderItem,
Pressable,
StyleSheet,
Text,
Pressable,
ListRenderItem,
} from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { NavigationContainer, useNavigation } from '@react-navigation/native';

import { Spacer } from './components';
import Container from './components/Container';
import { Example, Examples, MainStackProps } from './examples';
import { layout, colors } from './styles';
import { Spacer } from './components';
import { colors, layout } from './styles';

const Stack = createStackNavigator();

// Our slider component uses the text prop to display shared value
// We need it whitelisted in order to have it "animated".
Animated.addWhitelistedNativeProps({ text: true });

const ItemSeparatorComponent = () => <Spacer.Vertical size={16} />;

const HomeScreen: FC = () => {
Expand All @@ -35,7 +30,8 @@ const HomeScreen: FC = () => {
style={({ pressed }) => [
styles.button,
{ borderStyle: pressed ? 'solid' : 'dashed' },
]}>
]}
>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.subtitle}>{item.subtitle}</Text>
</Pressable>
Expand Down Expand Up @@ -68,7 +64,8 @@ const App: FC = () => {
headerTintColor: colors.white,
headerBackTitle: ' ',
headerBackAccessibilityLabel: 'Go back',
}}>
}}
>
<Stack.Screen
name="Home"
component={HomeScreen}
Expand Down
8 changes: 4 additions & 4 deletions apps/fabric-example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2675,7 +2675,7 @@ PODS:
- RNWorklets
- SocketRocket
- Yoga
- RNScreens (4.17.1):
- RNScreens (4.18.0):
- boost
- DoubleConversion
- fast_float
Expand All @@ -2702,10 +2702,10 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNScreens/common (= 4.17.1)
- RNScreens/common (= 4.18.0)
- SocketRocket
- Yoga
- RNScreens/common (4.17.1):
- RNScreens/common (4.18.0):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -3219,7 +3219,7 @@ SPEC CHECKSUMS:
RNAudioAPI: c763dbacdb8d89b7ce829484306df54322a7d951
RNGestureHandler: f1dd7f92a0faa2868a919ab53bb9d66eb4ebfcf5
RNReanimated: e4993dd98196c698cbacc1441a4ac5b855ae56dc
RNScreens: 833237c48c756d40764540246a501b47dadb2cac
RNScreens: d821082c6dd1cb397cc0c98b026eeafaa68be479
RNSVG: 8c0bbfa480a24b24468f1c76bd852a4aac3178e6
RNWorklets: d4553da98908962b6b834d5f2d26525b0d6840ad
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class AudioFocusListener(
}
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("interruption", body)
}

AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
playOnAudioFocus = lockScreenManager.get()?.isPlaying == true
val body =
Expand All @@ -37,6 +38,7 @@ class AudioFocusListener(
}
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("interruption", body)
}

AudioManager.AUDIOFOCUS_GAIN -> {
if (playOnAudioFocus) {
val body =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ class LockScreenManager(
"state_playing" -> {
this.playbackState = PlaybackStateCompat.STATE_PLAYING
}

"state_paused" -> {
this.playbackState = PlaybackStateCompat.STATE_PAUSED
}
Expand Down
3 changes: 3 additions & 0 deletions packages/react-native-audio-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -164,5 +164,8 @@
"type": "module-legacy",
"languages": "cpp",
"version": "0.37.1"
},
"dependencies": {
"semver": "^7.7.3"
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use strict';

const semverPrerelease = require('semver/functions/prerelease');
const validWorkletsVersions = [
'0.6.0',
'0.6.1',
];

const validWorkletsVersions = ['0.6.0', '0.6.1'];

function validateVersion() {
let workletsVersion;
Expand All @@ -22,7 +21,7 @@ function validateVersion() {

if (!validateVersion()) {
console.warn(
'[RNAudioApi] Incompatible version of react-native-audio-worklets detected. Please install a compatible version if you want to use worklet nodes in react-native-audio-api.',
'[RNAudioApi] Incompatible version of react-native-audio-worklets detected. Please install a compatible version if you want to use worklet nodes in react-native-audio-api.'
);
process.exit(1);
}
131 changes: 131 additions & 0 deletions packages/react-native-audio-api/src/AudioAPIModule/AudioAPIModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import type { ShareableWorkletCallback } from '../interfaces';
import { NativeAudioAPIModule } from '../specs';

// Hint: Copied from react-native-worklets
// Doesn't really matter what is inside, just need a unique type
interface WorkletRuntime {
__hostObjectWorkletRuntime: never;
readonly name: string;
}

interface IWorkletsModule {
makeShareableCloneRecursive: (
workletCallback: ShareableWorkletCallback
) => ShareableWorkletCallback;

createWorkletRuntime: (runtimeName: string) => WorkletRuntime;
}

class AudioAPIModule {
#workletsModule_: IWorkletsModule | null = null;
#canUseWorklets_ = false;
#workletsVersion = 'unknown';
#workletsAvailable_ = false;
#audioRuntime: WorkletRuntime | null = null;

public supportedWorkletsVersion = ['0.6.0', '0.6.1'];

constructor() {
// Important! Verify and import worklets first
// otherwise the native module installation might crash
// if react-native-worklets is not imported before audio-api
this.#verifyWorklets();

if (this.#verifyInstallation()) {
return;
}

if (!NativeAudioAPIModule) {
throw new Error(
`Failed to install react-native-audio-api: The native module could not be found.`
);
}

NativeAudioAPIModule.install();
}

#verifyWorklets(): boolean {
try {
const workletsPackage = require('react-native-worklets');
const workletsPackageJson = require('react-native-worklets/package.json');
this.#workletsVersion = workletsPackageJson.version;
this.#workletsAvailable_ = true;

this.#canUseWorklets_ = this.supportedWorkletsVersion.includes(
workletsPackageJson.version
);

if (this.#canUseWorklets_) {
this.#workletsModule_ = workletsPackage;
}

return this.#canUseWorklets_;
} catch {
this.#canUseWorklets_ = false;
return false;
}
}

#verifyInstallation(): boolean {
return (
global.createAudioContext != null &&
global.createOfflineAudioContext != null &&
global.createAudioRecorder != null &&
global.createAudioDecoder != null &&
global.createAudioStretcher != null &&
global.AudioEventEmitter != null
);
}

get workletsModule(): IWorkletsModule | null {
return this.#workletsModule_;
}

/**
* Indicates whether react-native-worklets are installed in matching version,
* for usage with react-native-audio-api.
*/
get canUseWorklets(): boolean {
return this.#canUseWorklets_;
}

/** Returns the installed worklets version or 'unknown'. */
get workletsVersion(): string {
return this.#workletsVersion;
}

/**
* Indicates whether react-native-worklets are installed, regardless of
* version support. Useful for asserting compatibility.
*/
get areWorkletsAvailable(): boolean {
return this.#workletsAvailable_;
}

/**
* Indicates whether the installed react-native-worklets version is supported.
* Useful for asserting compatibility.
*/
get isWorkletsVersionSupported(): boolean {
// Note: if areWorkletsAvailable is true, canUseWorklets is equivalent to version check
return this.#canUseWorklets_;
}

public getOrCreateAudioRuntime(): WorkletRuntime | null {
if (!this.#canUseWorklets_) {
return null;
}

if (this.#audioRuntime) {
return this.#audioRuntime;
}

this.#audioRuntime = this.#workletsModule_!.createWorkletRuntime(
'AudioWorkletRuntime'
);

return this.#audioRuntime;
}
}

export default new AudioAPIModule();
34 changes: 34 additions & 0 deletions packages/react-native-audio-api/src/AudioAPIModule/globals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type {
IAudioContext,
IAudioDecoder,
IAudioEventEmitter,
IAudioRecorder,
IAudioStretcher,
IOfflineAudioContext,
} from '../interfaces';
import type { AudioRecorderOptions } from '../types';

/* eslint-disable no-var */
declare global {
var createAudioContext: (
sampleRate: number,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
audioWorkletRuntime: any
) => IAudioContext;
var createOfflineAudioContext: (
numberOfChannels: number,
length: number,
sampleRate: number,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
audioWorkletRuntime: any
) => IOfflineAudioContext;

var createAudioRecorder: (options: AudioRecorderOptions) => IAudioRecorder;

var createAudioDecoder: () => IAudioDecoder;

var createAudioStretcher: () => IAudioStretcher;

var AudioEventEmitter: IAudioEventEmitter;
}
/* eslint-disable no-var */
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './AudioAPIModule';
Loading