Hi! 👋
Firstly, thanks for your work on this project! 🙂
Today I used patch-package to patch react-native-audio-api@0.12.1 for the project I'm working on.
Description of Problem
The react-native-audio-api package fails to build on Windows environments because the android/build.gradle file relies on bash and chmod to execute the download-prebuilt-binaries.sh script. These commands are not natively available in standard Windows shells (PowerShell/CMD), leading to a Command failed error during the Android build or postinstall phase.
Solution
This patch replaces the shell-based download process with a cross-platform Node.js script (download-prebuilt-binaries.js). The android/build.gradle file is updated to call this Node script, ensuring that prebuilt native binaries can be downloaded and extracted successfully on Windows, macOS, and Linux without external shell dependencies.
Patch Contents (react-native-audio-api+0.12.1.patch)
diff --git a/node_modules/react-native-audio-api/android/build.gradle b/node_modules/react-native-audio-api/android/build.gradle
index e2b8442..8c36161 100644
--- a/node_modules/react-native-audio-api/android/build.gradle
+++ b/node_modules/react-native-audio-api/android/build.gradle
@@ -308,9 +308,7 @@ def assertMinimalReactNativeVersion = task assertMinimalReactNativeVersionTask {
}
task downloadPrebuiltBinaries(type: Exec) {
- commandLine 'chmod', '+x', '../scripts/download-prebuilt-binaries.sh'
- commandLine 'bash', '../scripts/download-prebuilt-binaries.sh'
- args 'android', isFFmpegDisabled() ? 'skipffmpeg' : ''
- commandLine 'node', '../scripts/download-prebuilt-binaries.js', 'android', (isFFmpegDisabled() ? 'skipffmpeg' : '')
}
// Make preBuild depend on the download task
diff --git a/node_modules/react-native-audio-api/scripts/download-prebuilt-binaries.js b/node_modules/react-native-audio-api/scripts/download-prebuilt-binaries.js
new file mode 100644
index 0000000..ee8bc83
--- /dev/null
+++ b/node_modules/react-native-audio-api/scripts/download-prebuilt-binaries.js
@@ -0,0 +1,163 @@
+const https = require('https');
+const fs = require('fs');
+const path = require('path');
+const { execSync } = require('child_process');
+const os = require('os');
+
+const MAIN_DOWNLOAD_URL = "https://github.com/software-mansion-labs/rn-audio-libs/releases/download";
+const TAG = "v2.0.0";
+const DOWNLOAD_NAMES = [
- "armeabi-v7a.zip", "arm64-v8a.zip", "x86.zip", "x86_64.zip",
- "ffmpeg_ios.zip", "iphoneos.zip", "iphonesimulator.zip", "jniLibs.zip"
+];
+const args = process.argv.slice(2);
+const isAndroid = args[0] === 'android';
+const skipFFmpeg = args[1] === 'skipffmpeg';
+
+const scriptsDir = __dirname;
+const projectRoot = isAndroid ? path.join(scriptsDir, '..') : scriptsDir;
+const tempDir = path.join(scriptsDir, 'audioapi-binaries-temp');
+
+const jniLibsDestination = path.join(projectRoot, 'android', 'src', 'main');
+const normalDestination = path.join(projectRoot, 'common', 'cpp', 'audioapi', 'external');
+
+if (!fs.existsSync(tempDir)) {
- fs.mkdirSync(tempDir, { recursive: true });
+}
+async function downloadFile(url, dest) {
- return new Promise((resolve, reject) => {
-
const file = fs.createWriteStream(dest);
-
https.get(url, (response) => {
-
if (response.statusCode === 302 || response.statusCode === 301) {
-
downloadFile(response.headers.location, dest).then(resolve).catch(reject);
-
-
-
if (response.statusCode !== 200) {
-
reject(new Error(`Failed to download ${url}: ${response.statusCode}`));
-
-
-
-
file.on('finish', () => {
-
-
// Ensure the file is flushed to disk
-
-
-
// Small delay to ensure Windows OS releases the lock
-
setTimeout(resolve, 1000);
-
-
-
}).on('error', (err) => {
-
fs.unlink(dest, () => reject(err));
-
- });
+}
+async function downloadFileWithRetry(url, dest, retries = 3) {
- for (let i = 0; i < retries; i++) {
-
-
await downloadFile(url, dest);
-
-
-
console.error(`Download failed for ${url} (attempt ${i + 1}/${retries}): ${err.message}`);
-
if (i === retries - 1) throw err;
-
const delay = Math.pow(2, i) * 2000;
-
console.log(`Retrying in ${delay}ms...`);
-
await new Promise(resolve => setTimeout(resolve, delay));
-
- }
+}
+function unzipFile(zipPath, destPath) {
- console.log(
Unzipping ${path.basename(zipPath)} to ${destPath});
- if (!fs.existsSync(destPath)) {
-
fs.mkdirSync(destPath, { recursive: true });
- }
- try {
-
if (os.platform() === 'win32') {
-
// Use 'tar' which is available in Windows 10/11 and more reliable than PowerShell for this
-
const command = `tar -xf "${zipPath}" -C "${destPath}"`;
-
-
for (let i = 0; i < 3; i++) {
-
-
execSync(command, { stdio: 'inherit' });
-
-
-
-
console.error(`Unzip attempt ${i+1} failed: ${e.message}. Retrying in 2s...`);
-
execSync('powershell -Command "Start-Sleep -Seconds 2"');
-
-
-
if (!success) throw new Error("Failed to unzip after retries.");
-
-
// Standard unzip for Unix
-
const command = `unzip -o "${zipPath}" -d "${destPath}"`;
-
execSync(command, { stdio: 'inherit' });
-
- } catch (error) {
-
console.error(`FATAL: Failed to unzip ${zipPath}: ${error.message}`);
-
process.exit(1); // Force exit if extraction fails
- }
- // Clean up __MACOSX
- const macosDir = path.join(destPath, '__MACOSX');
- if (fs.existsSync(macosDir)) {
-
try { fs.rmSync(macosDir, { recursive: true, force: true }); } catch(e) {}
- }
+}
+async function main() {
- for (const name of DOWNLOAD_NAMES) {
-
const extractedDirName = name.replace('.zip', '');
-
// Filtering irrelevant platforms to avoid network issues and bloat
-
const isIosFile = name.includes('ios') || name.includes('iphone');
-
const isAndroidFile = name.includes('v7a') || name.includes('v8a') || name === 'x86.zip' || name === 'x86_64.zip' || name === 'jniLibs.zip';
-
if (isAndroid && isIosFile) {
-
console.log(`Skipping ${name} - not required for Android build.`);
-
-
-
if (!isAndroid && isAndroidFile) {
-
console.log(`Skipping ${name} - not required for iOS build.`);
-
-
-
if ((extractedDirName === 'ffmpeg_ios' || extractedDirName === 'jniLibs') && skipFFmpeg) {
-
-
-
const outputDir = (name === 'jniLibs.zip') ? jniLibsDestination : normalDestination;
-
const finalCheckPath = path.join(outputDir, extractedDirName);
-
// Even if directory exists, we might want to re-extract if libopusfile.a is missing
-
const criticalFile = path.join(finalCheckPath, 'libopusfile.a');
-
if (fs.existsSync(finalCheckPath) && (name === 'jniLibs.zip' || fs.existsSync(criticalFile))) {
-
console.log(`Skipping ${name}, already extracted at ${finalCheckPath}`);
-
-
-
const url = `${MAIN_DOWNLOAD_URL}/${TAG}/${name}`;
-
const zipPath = path.join(tempDir, name);
-
console.log(`Downloading ${name} from ${url}...`);
-
-
await downloadFileWithRetry(url, zipPath);
-
unzipFile(zipPath, outputDir);
-
-
console.error(`Error processing ${name}: ${err.message}`);
-
-
- }
- try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch (e) {}
- console.log('Finished downloading and extracting prebuilt binaries.');
+}
+main().catch(err => {
- console.error(err);
- process.exit(1);
+});
Hi! 👋
Firstly, thanks for your work on this project! 🙂
Today I used patch-package to patch
react-native-audio-api@0.12.1for the project I'm working on.Description of Problem
The react-native-audio-api package fails to build on Windows environments because the android/build.gradle file relies on bash and chmod to execute the download-prebuilt-binaries.sh script. These commands are not natively available in standard Windows shells (PowerShell/CMD), leading to a Command failed error during the Android build or postinstall phase.
Solution
This patch replaces the shell-based download process with a cross-platform Node.js script (download-prebuilt-binaries.js). The android/build.gradle file is updated to call this Node script, ensuring that prebuilt native binaries can be downloaded and extracted successfully on Windows, macOS, and Linux without external shell dependencies.
Patch Contents (react-native-audio-api+0.12.1.patch)
diff --git a/node_modules/react-native-audio-api/android/build.gradle b/node_modules/react-native-audio-api/android/build.gradle
index e2b8442..8c36161 100644
--- a/node_modules/react-native-audio-api/android/build.gradle
+++ b/node_modules/react-native-audio-api/android/build.gradle
@@ -308,9 +308,7 @@ def assertMinimalReactNativeVersion = task assertMinimalReactNativeVersionTask {
}
task downloadPrebuiltBinaries(type: Exec) {
}
// Make preBuild depend on the download task
diff --git a/node_modules/react-native-audio-api/scripts/download-prebuilt-binaries.js b/node_modules/react-native-audio-api/scripts/download-prebuilt-binaries.js
new file mode 100644
index 0000000..ee8bc83
--- /dev/null
+++ b/node_modules/react-native-audio-api/scripts/download-prebuilt-binaries.js
@@ -0,0 +1,163 @@
+const https = require('https');
+const fs = require('fs');
+const path = require('path');
+const { execSync } = require('child_process');
+const os = require('os');
+
+const MAIN_DOWNLOAD_URL = "https://github.com/software-mansion-labs/rn-audio-libs/releases/download";
+const TAG = "v2.0.0";
+const DOWNLOAD_NAMES = [
+];
+const args = process.argv.slice(2);
+const isAndroid = args[0] === 'android';
+const skipFFmpeg = args[1] === 'skipffmpeg';
+
+const scriptsDir = __dirname;
+const projectRoot = isAndroid ? path.join(scriptsDir, '..') : scriptsDir;
+const tempDir = path.join(scriptsDir, 'audioapi-binaries-temp');
+
+const jniLibsDestination = path.join(projectRoot, 'android', 'src', 'main');
+const normalDestination = path.join(projectRoot, 'common', 'cpp', 'audioapi', 'external');
+
+if (!fs.existsSync(tempDir)) {
+}
+async function downloadFile(url, dest) {
+}
+async function downloadFileWithRetry(url, dest, retries = 3) {
+}
+function unzipFile(zipPath, destPath) {
Unzipping ${path.basename(zipPath)} to ${destPath});+}
+async function main() {
+}
+main().catch(err => {
+});