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

Takes time for recorder to start on web #321

Closed
sit-mraasith opened this issue May 23, 2024 · 11 comments
Closed

Takes time for recorder to start on web #321

sit-mraasith opened this issue May 23, 2024 · 11 comments
Labels

Comments

@sit-mraasith
Copy link

This is my first time raising an issue on github.

Package version
record: 5.1.0
record_web: 1.1.0

Environment

  • OS: Windows - Web App
  • Chrome

When I click on the button to record, It takes about 1-2 seconds for it to start. Is this the intended behavior or is there something that i'm doing wrong.

I have a record button that calls start.
Future<void> startRecording() async { try { if (await widget._recorder.hasPermission()) { await widget._recorder.start( const RecordConfig(encoder: AudioEncoder.opus), path: '', ); setState(() { _isRecording = true; }); } } catch (e) { if (kDebugMode) { print('cannot record'); } } }
the set state sets the button color when _isrecording is true and also sets the function to stoprecording.

this is my stop record function
``
Future stopRecording() async {
try {

final path = await widget._recorder.stop();

_audioPath = path!;

setState(() {
  _isRecording = false;
});

} catch (e) {
if (kDebugMode) {
print('cannot stop');
print(e);
}
}
}
``
this is my button code ( i am not using elevated button as it caused some problems without my app layout.)

GestureDetector( onTap: () { if (!_isRecording) { startRecording(); } else { stopRecording(); } }, child: Container( margin: const EdgeInsets.all(12), decoration: const BoxDecoration( image: DecorationImage( image: AssetImage(recordIcon), ), ), ), ),

Add your record configuration RecordConfig(encoder: AudioEncoder.opus)

To Reproduce

Steps to reproduce the behavior:

  1. Click on record button
  2. recorder takes 1-2 secs to start

Expected behavior
it should have a faster response time so that the app feels responsive. If 1-2sec is the best that can be achieved. I will find a UI method to let the user know that they have to wait.

@llfbandit
Copy link
Owner

So welcome!
Just stay focus and concise on the subject you want to be resolved or implemented.

Unfortunatly, I don't reproduce the same lag on my machine (either Edge or Firefox).
It would be good to know if you experience this in release mode?

@llfbandit llfbandit added the web label May 23, 2024
@sit-mraasith
Copy link
Author

So welcome! Just stay focus and concise on the subject you want to be resolved or implemented.

Unfortunatly, I don't reproduce the same lag on my machine (either Edge or Firefox). It would be good to know if you experience this in release mode?

Yup, I experience this in release, I have tried it on both Chrome and Edge. Upon clicking the button it takes about 1-2 seconds before the recorder is active. This also occurs when trying to stop the recorder.

@llfbandit
Copy link
Owner

Are you still experiencing this issue with latest record_web 1.1.1?

@Eduardo-Mateus-Da-Costa
Copy link

In my case the start method does not work on the web because it needs a file and the web does not support this, on the web I used the startStream method receiving pcm16bits and converting to wav with the pcmtowav library

@llfbandit
Copy link
Owner

@Eduardo-Mateus-Da-Costa you can use start method on Web platform, just leave path empty. The blob Url has to be retrieved from stop method.
By the way, there's no relation with the current subject.

@sit-mraasith
Copy link
Author

Are you still experiencing this issue with latest record_web 1.1.1?

Yes, it still takes about 1-2 sec to start. I added an animation to my app that waits for the recorder to start.
image
image
image

so to go from the first to the last image. i takes about 1-2 seconds. stopping the recording also takes about 1-2 seconds. the recorded audio also seems to start a bit later. like the start of my speech and the end tends to be cut off. My project involves automatic speech recognition so having parts cut out is a big problem. I am still finding ways around the problem.

@llfbandit
Copy link
Owner

llfbandit commented Jul 4, 2024

You should rely on state stream to properly forward recorder status to UI.
Also, OP states that you record to a file, now you talk about speech recognition... This issue is unclear.
Maybe off topic, why don't you use stream in that case?

Are you experiencing the same with example project?

@sit-mraasith
Copy link
Author

You should rely on state stream to properly forward recorder status to UI. Also, OP states that you record to a file, now you talk about speech recognition... This issue is unclear. Maybe off topic, why don't you use stream in that case?

Are you experiencing the same with example project?

I save the audio as a file and allow the user to playback the audio, so I believe I don't need to use stream in this case? correct me if I'm wrong I'm still new to this.

I am going to try it with the example project to see if it happens.

@AliKarimiENT
Copy link

AliKarimiENT commented Aug 1, 2024

In my case the start method does not work on the web because it needs a file and the web does not support this, on the web I used the startStream method receiving pcm16bits and converting to wav with the pcmtowav library

Hello, could you please send your logic that you have implemented to handle the web recording audio? I have implemented it for mobile completely but on the web wasn't successful. Actually I wanted to record and then Parse into XFile?
This is something that I have implemented

import 'dart:async';
import 'dart:typed_data';
import 'dart:js_interop';

import 'package:cross_file/cross_file.dart';
import 'package:record/record.dart';
import 'package:web/web.dart' as web;

mixin AudioRecorderMixin {
  late AudioRecorder _recorder;
  final List<int> _bytes = [];
  StreamSubscription<List<int>>? _streamSubscription;

  Future<void> recordFile(AudioRecorder recorder) {
    _recorder = recorder;
    return recorder.start(recordConfig, path: '');
  }

  Future<XFile> recordStream(AudioRecorder recorder) async {
    _recorder = recorder;
    _bytes.clear(); // Clear any existing data in the buffer
    final stream = await recorder.startStream(streamConfig);

    final completer = Completer<XFile>();

    _streamSubscription = stream.listen(
      (data) => _bytes.addAll(data),
      onDone: () async {
        if (!completer.isCompleted) {
          final xFile = await _createXFileFromBytes(Uint8List.fromList(_bytes));
          completer.complete(xFile);
        }
      },
      onError: (error) {
        if (!completer.isCompleted) {
          completer.completeError(error);
        }
      },
    );

    return completer.future;
  }

  Future<XFile> stop() async {
    await _recorder.stop(); // Stop the recorder
    await _streamSubscription?.cancel(); // Cancel the stream subscription
    _streamSubscription = null;
    return _createXFileFromBytes(Uint8List.fromList(_bytes));
  }

  Future<XFile> _createXFileFromBytes(Uint8List bytes) async {
    final blob = web.Blob(<JSUint8Array>[bytes.toJS].toJS);
    final url = web.URL.createObjectURL(blob);

    // Convert the Blob URL to a Uint8List using HttpRequest
    final response = await web.HttpRequest.request(url, responseType: 'arraybuffer');
    final uint8List = Uint8List.fromList(response.response as List<int>);

    // Create an XFile from the byte array
    return XFile.fromData(uint8List, mimeType: 'audio/wav', name: 'audio.wav');
  }

  void downloadWebData(String path) {
    // Simple download code for web testing
    final anchor = web.document.createElement('a') as web.HTMLAnchorElement
      ..href = path
      ..style.display = 'none'
      ..download = 'audio.wav';
    web.document.body!.appendChild(anchor);
    anchor.click();
    web.document.body!.removeChild(anchor);
  }
}

@Eduardo-Mateus-Da-Costa
Copy link

In my case the start method does not work on the web because it needs a file and the web does not support this, on the web I used the startStream method receiving pcm16bits and converting to wav with the pcmtowav library

Hello, could you please send your logic that you have implemented to handle the web recording audio? I have implemented it for mobile completely but on the web wasn't successful. Actually I wanted to record and then Parse into XFile? This is something that I have implemented

import 'dart:async';
import 'dart:typed_data';
import 'dart:js_interop';

import 'package:cross_file/cross_file.dart';
import 'package:record/record.dart';
import 'package:web/web.dart' as web;

mixin AudioRecorderMixin {
  late AudioRecorder _recorder;
  final List<int> _bytes = [];
  StreamSubscription<List<int>>? _streamSubscription;

  Future<void> recordFile(AudioRecorder recorder) {
    _recorder = recorder;
    return recorder.start(recordConfig, path: '');
  }

  Future<XFile> recordStream(AudioRecorder recorder) async {
    _recorder = recorder;
    _bytes.clear(); // Clear any existing data in the buffer
    final stream = await recorder.startStream(streamConfig);

    final completer = Completer<XFile>();

    _streamSubscription = stream.listen(
      (data) => _bytes.addAll(data),
      onDone: () async {
        if (!completer.isCompleted) {
          final xFile = await _createXFileFromBytes(Uint8List.fromList(_bytes));
          completer.complete(xFile);
        }
      },
      onError: (error) {
        if (!completer.isCompleted) {
          completer.completeError(error);
        }
      },
    );

    return completer.future;
  }

  Future<XFile> stop() async {
    await _recorder.stop(); // Stop the recorder
    await _streamSubscription?.cancel(); // Cancel the stream subscription
    _streamSubscription = null;
    return _createXFileFromBytes(Uint8List.fromList(_bytes));
  }

  Future<XFile> _createXFileFromBytes(Uint8List bytes) async {
    final blob = web.Blob(<JSUint8Array>[bytes.toJS].toJS);
    final url = web.URL.createObjectURL(blob);

    // Convert the Blob URL to a Uint8List using HttpRequest
    final response = await web.HttpRequest.request(url, responseType: 'arraybuffer');
    final uint8List = Uint8List.fromList(response.response as List<int>);

    // Create an XFile from the byte array
    return XFile.fromData(uint8List, mimeType: 'audio/wav', name: 'audio.wav');
  }

  void downloadWebData(String path) {
    // Simple download code for web testing
    final anchor = web.document.createElement('a') as web.HTMLAnchorElement
      ..href = path
      ..style.display = 'none'
      ..download = 'audio.wav';
    web.document.body!.appendChild(anchor);
    anchor.click();
    web.document.body!.removeChild(anchor);
  }
}

This is a simple example, not complete, starting the recorder can be any way you want, you just need to save the bytes of the stream and convert it to wav later, with the bytes you can see what you want to do, if you want to download it you can turn it into a Blob from these bytes with dart:html, in my case I send the bytes directly to the server

import 'package:pcmtowave/convertToWav.dart';
import 'package:record/record.dart';
import 'dart:async';

Future<Stream> startStream({
InputDevice? inputDevice,
}) async {
if (!initialized) {
await init();
}
stream = await record!.startStream(
RecordConfig(
device: inputDevice,
encoder: AudioEncoder.pcm16bits,
numChannels: 1,
bitRate: 32000,
),
);
while (await record!.isRecording() == false) {
await Future.delayed(const Duration(milliseconds: 100));
}
// pause and restart to avoid non start error
await record!.pause();
await Future.delayed(const Duration(seconds: 1));
await record!.resume();
isRecording = true;
startRecordingTime = DateTime.now();
return stream!;
}

Uint8List? bytes;
Uint8List? wavBytes;
final convertToWav pcmToWave =
convertToWav(sampleRate: 44100, converMiliSeconds: 1000, numChannels: 1);

Future awaitStreamClose() async {
if (isClosed) {
return;
} else {
await Future.delayed(const Duration(milliseconds: 100));
await awaitStreamClose();
}
}

Future awaitStreamConvert() async {
if (isConverted) {
return;
} else {
await Future.delayed(const Duration(milliseconds: 100));
await awaitStreamConvert();
}
}

@OverRide
void initState() {
super.initState();
pcmToWave.convert.listen((Uint8List event) {
wavBytes = event;
setState(() {
isConverted = true;
});
});
stream?.listen((Uint8List event) {
if (bytes == null) {
bytes = event;
} else {
bytes = Uint8List.fromList([...bytes!, ...event]);
}
}).onDone(() {
setState(() {
isClosed = true;
});
});
}

TO STOP

stopRecording()
await awaitStreamClose();
pcmToWave.run(bytes!);
await awaitStreamConvert();

@llfbandit
Copy link
Owner

You don't have to do anything to record WAVE file on web platform.
Just call start with an empty path. It will be available when calling stop on this platform.
From here, you can wrap the path with XFile('<given_path>'). That should do it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants