Skip to content

Commit 02ef4cf

Browse files
exzosexzos
authored andcommitted
feat: introduce FileSystem module with load method for file caching
1 parent 78d4c19 commit 02ef4cf

File tree

8 files changed

+128
-16
lines changed

8 files changed

+128
-16
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.margelo.nitro.audioanalyzer
2+
3+
import android.content.Context
4+
import com.facebook.proguard.annotations.DoNotStrip
5+
import com.facebook.react.bridge.ReactApplicationContext
6+
import java.io.File
7+
import java.net.URL
8+
import java.security.MessageDigest
9+
import com.margelo.nitro.core.Promise
10+
import com.margelo.nitro.NitroModules
11+
12+
@DoNotStrip
13+
class FileSystem(reactContext: ReactApplicationContext) :
14+
HybridFileSystemSpec() {
15+
16+
private val appContext = reactContext.applicationContext
17+
18+
override fun load(path: String): Promise<String> {
19+
return Promise.parallel {
20+
try {
21+
val url = URL(path)
22+
val connection = url.openConnection()
23+
val fileName = path.substringAfterLast('/')
24+
val fileSize = connection.contentLength
25+
val key = "$fileName-$fileSize"
26+
27+
val digest = MessageDigest.getInstance("MD5")
28+
val hash = digest.digest(key.toByteArray())
29+
.joinToString("") { "%02x".format(it) }
30+
31+
val file = File(NitroModules.applicationContext!!.cacheDir, "$hash.audio")
32+
33+
if (!file.exists()) {
34+
url.openStream().use { input ->
35+
file.outputStream().use { output -> input.copyTo(output) }
36+
}
37+
}
38+
39+
file.absolutePath
40+
} catch (e: Exception) {
41+
throw RuntimeException("Failed to load and cache file", e)
42+
}
43+
}
44+
}
45+
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
#include "AudioAnalyzer.hpp"
1+
#include "Processor.hpp"
22
#include "miniaudio.h"
33

44
#include <cmath>
55
#include <iostream>
66
#include <algorithm>
77

8-
#define LOG_TAG "HybridAudioAnalyzer"
8+
#define LOG_TAG "HybridProcessor"
99

1010
using namespace margelo::nitro::audioanalyzer;
1111

12-
bool AudioAnalyzer::decodeAudioFile(const std::string &filePath, std::vector<float> &pcmData, unsigned int &sampleRate)
12+
bool Processor::decodeAudioFile(const std::string &filePath, std::vector<float> &pcmData, unsigned int &sampleRate)
1313
{
1414
ma_decoder decoder;
1515
ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 0, 0);
@@ -51,7 +51,7 @@ bool AudioAnalyzer::decodeAudioFile(const std::string &filePath, std::vector<flo
5151
return framesRead > 0;
5252
}
5353

54-
std::vector<double> AudioAnalyzer::computeAmplitude(const std::string &filePath, double outputSampleCount = 1000)
54+
std::vector<double> Processor::computeAmplitude(const std::string &filePath, double outputSampleCount = 1000)
5555
{
5656
size_t outputSampleCountInt = static_cast<size_t>(outputSampleCount);
5757
std::vector<float> pcm;
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
#include "HybridAudioAnalyzerSpec.hpp"
1+
#include "HybridProcessorSpec.hpp"
22
#include <string>
33
#include <vector>
44

55
namespace margelo::nitro::audioanalyzer {
6-
struct AudioAnalyzer: public audioanalyzer::HybridAudioAnalyzerSpec {
7-
AudioAnalyzer() : HybridObject(TAG) {}
6+
struct Processor: public audioanalyzer::HybridProcessorSpec {
7+
Processor() : HybridObject(TAG) {}
88
std::vector<double> computeAmplitude(const std::string& filePath, double samplesPerBlock) override;
99

1010
bool decodeAudioFile(const std::string& filePath, std::vector<float>& pcmData, unsigned int& sampleRate);

ios/FileSystem.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import Foundation
2+
import CryptoKit
3+
import NitroModules
4+
5+
class FileSystem: HybridFileSystemSpec {
6+
func load(path: String) -> Promise<String> {
7+
return Promise.async {
8+
guard let url = URL(string: path) else {
9+
throw NSError(domain: "FileSystem", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])
10+
}
11+
12+
// HEAD request to get file size
13+
var request = URLRequest(url: url)
14+
request.httpMethod = "HEAD"
15+
let (_, response) = try await URLSession.shared.data(for: request)
16+
17+
var fileSize: Int64 = 0
18+
if let httpResponse = response as? HTTPURLResponse,
19+
let lengthString = httpResponse.value(forHTTPHeaderField: "Content-Length"),
20+
let length = Int64(lengthString) {
21+
fileSize = length
22+
}
23+
24+
// Compute hash based on file name and size
25+
let fileName = url.lastPathComponent
26+
let key = "\(fileName)-\(fileSize)"
27+
let hash = Insecure.MD5.hash(data: Data(key.utf8)).map { String(format: "%02x", $0) }.joined()
28+
29+
// Cache file path
30+
let cacheDir = FileManager.default.temporaryDirectory
31+
let fileURL = cacheDir.appendingPathComponent("\(hash).audio")
32+
33+
if !FileManager.default.fileExists(atPath: fileURL.path) {
34+
let (data, _) = try await URLSession.shared.data(from: url)
35+
try data.write(to: fileURL)
36+
}
37+
38+
return fileURL.path
39+
}
40+
}
41+
}

nitro.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88
"androidCxxLibName": "audioanalyzer"
99
},
1010
"autolinking": {
11-
"AudioAnalyzer": {
12-
"cpp": "AudioAnalyzer"
11+
"Processor": {
12+
"cpp": "Processor"
13+
},
14+
"FileSystem": {
15+
"swift": "FileSystem",
16+
"kotlin": "FileSystem"
1317
}
1418
},
1519
"ignorePaths": ["node_modules"]

src/FileSystem.nitro.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { HybridObject } from 'react-native-nitro-modules';
2+
3+
export interface FileSystem
4+
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
5+
/**
6+
* Downloads and caches the file.
7+
*/
8+
load(path: string): Promise<string>;
9+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { HybridObject } from 'react-native-nitro-modules';
22

3-
export interface AudioAnalyzer
3+
export interface Processor
44
extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
5-
computeAmplitude(filePath: string, outputSampleCount: number): number[];
5+
computeAmplitude(source: string, outputSampleCount: number): number[];
66
}

src/index.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
import { NitroModules } from 'react-native-nitro-modules';
2-
import type { AudioAnalyzer } from './AudioAnalyzer.nitro';
2+
import type { Processor } from './Processor.nitro';
3+
import type { FileSystem } from './FileSystem.nitro';
34

4-
const AnalyzerHybridObject =
5-
NitroModules.createHybridObject<AudioAnalyzer>('AudioAnalyzer');
5+
const AudioAnalyzerHybridObject =
6+
NitroModules.createHybridObject<Processor>('Processor');
67

7-
export function computeAmplitude(filePath: string, outputSampleCount: number) {
8-
return AnalyzerHybridObject.computeAmplitude(filePath, outputSampleCount);
8+
const FileSystemHybridObject =
9+
NitroModules.createHybridObject<FileSystem>('FileSystem');
10+
11+
function computeAmplitude(filePath: string, outputSampleCount: number) {
12+
return AudioAnalyzerHybridObject.computeAmplitude(
13+
filePath,
14+
outputSampleCount
15+
);
16+
}
17+
18+
function load(path: string) {
19+
return FileSystemHybridObject.load(path);
920
}
21+
22+
export { load, computeAmplitude };

0 commit comments

Comments
 (0)