Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Ability to extract files simultaneously #12

Merged
merged 11 commits into from
Dec 30, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import android.os.Handler;
import java.util.List;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import java.util.HashMap;

/** JustWaveformPlugin */
public class JustWaveformPlugin implements FlutterPlugin, MethodCallHandler {
Expand All @@ -28,14 +29,21 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
case "extract":
String audioInPath = call.argument("audioInPath");
String waveOutPath = call.argument("waveOutPath");
String uuid = call.argument("uuid");
Integer samplesPerPixel = call.argument("samplesPerPixel");
Integer pixelsPerSecond = call.argument("pixelsPerSecond");
WaveformExtractor waveformExtractor = new WaveformExtractor(audioInPath, waveOutPath, samplesPerPixel, pixelsPerSecond);
waveformExtractor.start(new WaveformExtractor.OnProgressListener() {
@Override
public void onProgress(int progress) {
invokeMethod("onProgress", progress);
HashMap<String, Object> args = new HashMap();
args.put("progress", progress);
args.put("waveOutFile", waveOutPath);
args.put("uuid", uuid);

invokeMethod("onProgress", args);
}

@Override
public void onComplete() {
handler.post(new Runnable() {
Expand All @@ -45,6 +53,7 @@ public void run() {
}
});
}

@Override
public void onError(final String message) {
invokeMethod("onError", message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,7 @@ void processAudio() {

frameCount++; // not really frame count anymore
}
onProgressListener.onProgress(100);
onProgressListener.onComplete();

System.out.println("End. (" + presentationTime/1000000.0 + "sec) frameCount = " + frameCount + ", totalSampleSize = " + totalSampleSize);
System.out.println("waitingToDecode: " + waitingToDecode);
System.out.println("waitingForDecoded: " + waitingForDecoded);
Expand Down Expand Up @@ -257,6 +256,8 @@ void processAudio() {
channel.write(scaledByteSamples);
System.out.println("Total scaled samples: " + scaledSampleIdx);
}
onProgressListener.onProgress(100);
onProgressListener.onComplete();
}
catch (Exception e) {
e.printStackTrace();
Expand Down
10 changes: 7 additions & 3 deletions darwin/Classes/JustWaveformPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([@"extract" isEqualToString:call.method]) {
NSString *audioInPath = (NSString *)request[@"audioInPath"];
NSString *waveOutPath = (NSString *)request[@"waveOutPath"];
NSString *uuid = (NSString *)request[@"uuid"];
NSNumber *samplesPerPixelArg = (NSNumber *)request[@"samplesPerPixel"];
NSNumber *pixelsPerSecondArg = (NSNumber *)request[@"pixelsPerSecond"];

Expand Down Expand Up @@ -121,7 +122,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
UInt32 scaledSampleIdx = 0;
int progress = 0;

while (frameCount > 0) {
while (frameCount > 0 && progress < 100) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, what is the scenario where the second condition is required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes the progress never become 100, kind of rare times. may be frame count cause the problem. Its difficult to find

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your condition wouldn't help if the progress never became 100. Maybe you meant that frameCount never becomes 0?

status = ExtAudioFileRead(audioFileRef, &frameCount, &convertedData);
if (status != noErr) {
NSLog(@"ExtAudioFileRead error: %i", status);
Expand Down Expand Up @@ -154,9 +155,10 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
int newProgress = (int)(100 * scaledSampleIdx / waveLength);
if (newProgress != progress && newProgress <= 100) {
progress = newProgress;
if (progress >= 100) break;
//NSLog(@"Progress: %d percent", progress);
dispatch_async(dispatch_get_main_queue(), ^{
[_channel invokeMethod:@"onProgress" arguments:@(progress)];
[_channel invokeMethod:@"onProgress" arguments:@{@"progress" : @(progress), @"uuid" : uuid, @"waveOutFile" : waveOutPath}];
});
}
//NSLog(@"pixel[%d] %d: %d\t%d", scaledSampleIdx - 2, sampleIdx, minSample, maxSample);
Expand All @@ -182,7 +184,9 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
NSLog(@"Total scaled samples: %d", scaledSampleIdx);

status = ExtAudioFileDispose(audioFileRef);

dispatch_async(dispatch_get_main_queue(), ^{
[_channel invokeMethod:@"onProgress" arguments:@{@"progress" : @(100), @"uuid" : uuid, @"waveOutFile" : waveOutPath}];
});
dispatch_async(dispatch_get_main_queue(), ^{
result(nil);
});
Expand Down
36 changes: 20 additions & 16 deletions lib/just_waveform.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,49 @@ import 'package:flutter/services.dart';
/// A utility for extracting a [Waveform] from an audio file suitable for visual
/// rendering.
class JustWaveform {
static const MethodChannel _channel =
MethodChannel('com.ryanheise.just_waveform');
static final MethodChannel _channel = MethodChannel('com.ryanheise.just_waveform');

/// Extract a [Waveform] from [audioInFile] and write it to [waveOutFile] at
/// the specified [zoom] level.
// XXX: It would be better to return a stream of the actual [Waveform], with
// progress => wave.data.length / (wave.length*2)
static Map<String, StreamController<WaveformProgress>> _map = {};
static Stream<WaveformProgress> extract({
required File audioInFile,
required File waveOutFile,
WaveformZoom zoom = const WaveformZoom.pixelsPerSecond(100),
}) {
final progressController = StreamController<WaveformProgress>.broadcast();
final uuid = DateTime.now().microsecondsSinceEpoch.toString();
_map[uuid] = progressController;

progressController.add(WaveformProgress._(0.0, null));
_channel.setMethodCallHandler((MethodCall call) async {
switch (call.method) {
case 'onProgress':
// ignore: avoid_print
print("received onProgress: ${call.arguments}}");
int progress = call.arguments;
//print("_progressSubject.add($progress)");
final args = call.arguments;
int progress = args['progress'];
String file = args['waveOutFile'];
String uuid = args['uuid'];
Waveform? waveform;

if (progress == 100) {
waveform = await parse(waveOutFile);
waveform = await parse(File(file));
}
progressController.add(WaveformProgress._(progress / 100, waveform));

_map[uuid]?.add(WaveformProgress._(progress / 100, waveform));
if (progress == 100) {
progressController.close();
_map[uuid]?.close();
_map.remove(file);
}
break;
}
});
// print('Started extract $_filename');
_channel.invokeMethod('extract', {
'audioInPath': audioInFile.path,
'waveOutPath': waveOutFile.path,
'uuid': uuid,
'samplesPerPixel': zoom._samplesPerPixel,
'pixelsPerSecond': zoom._pixelsPerSecond,
}).catchError(progressController.addError);
Expand All @@ -54,9 +62,7 @@ class JustWaveform {
const headerLength = 20;
final header = Uint32List.view(bytes, 0, headerLength);
final flags = header[1];
final data = flags == 0
? Int16List.view(bytes, headerLength ~/ 2)
: Int8List.view(bytes, headerLength);
final data = flags == 0 ? Int16List.view(bytes, headerLength ~/ 2) : Int8List.view(bytes, headerLength);
return Waveform(
version: header[0],
flags: flags,
Expand Down Expand Up @@ -123,14 +129,12 @@ class Waveform {
int getPixelMax(int i) => this[2 * i + 1];

/// The duration of audio, inferred from the length of the waveform data.
Duration get duration => Duration(
microseconds: 1000 * 1000 * length * samplesPerPixel ~/ sampleRate);
Duration get duration => Duration(microseconds: 1000 * 1000 * length * samplesPerPixel ~/ sampleRate);

/// Converts an audio position to a pixel position. The returned position is a
/// [double] for accuracy, but can be converted `toInt` and used to access the
/// nearest pixel value via [getPixelMin]/[getPixelMax].
double positionToPixel(Duration position) =>
position.inMicroseconds * sampleRate / (samplesPerPixel * 1000000);
double positionToPixel(Duration position) => position.inMicroseconds * sampleRate / (samplesPerPixel * 1000000);
}

/// The resolution at which a [Waveform] should be generated.
Expand Down