Skip to content

Commit

Permalink
refactor of audio_streamer plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
bardram committed Sep 20, 2023
1 parent 0e362e0 commit 6ca7fab
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 288 deletions.
6 changes: 6 additions & 0 deletions packages/audio_streamer/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 4.0.0

* removal of permission_handler dependency - handling permission should take place in the app, not the plugin.
* major refactor of plugin code
* update of example app to handle permissions to access the microphone and other improvements.

## 3.1.0

* upgrade of permission_handler plugins
Expand Down
14 changes: 3 additions & 11 deletions packages/audio_streamer/LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,8 @@ MIT License.

Copyright 2019 Copenhagen Center for Health Technology (CACHET) at the Technical University of Denmark (DTU).

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the ”Software”), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ”Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED ”AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED ”AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
73 changes: 31 additions & 42 deletions packages/audio_streamer/README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
# Audio Streamer

Streaming of PCM audio from Android and iOS with a customizable sampling rate.
Streaming of Pulse-code modulation (PCM) audio from Android and iOS with a customizable sampling rate.

## Permissions

Using this plugin needs permission to access the microphone. Requesting this permission is **NOT** part of the plugin, but should be handled by the app. However, for the app to be able to access the microphone, the app need to have the following permission on Android and iOS.

On **Android** add the audio recording permission to `AndroidManifest.xml`.

```xml
<uses-permission android:name="android.permission.RECORD_AUDIO" />
```

On **iOS** enable the following:
On **iOS** enable the following using XCode:

- Capabilities > Background Modes > _Audio, AirPlay and Picture in Picture_
- In the Runner Xcode project edit the `Info.plist` file. Add an entry for _'Privacy - Microphone Usage Description'_

When editing the `Info.plist` file manually, the entries needed are:
If editing the `Info.plist` file manually, the entries needed are:

```xml
<key>NSMicrophoneUsageDescription</key>
Expand All @@ -26,7 +28,7 @@ When editing the `Info.plist` file manually, the entries needed are:
</array>
```

- Edit the `Podfile` to include the permission for the microphone:
Edit the `Podfile` to include the permission for the microphone:

```ruby
post_install do |installer|
Expand All @@ -43,48 +45,35 @@ post_install do |installer|
end
```

## Example
## Using the plugin

See the file `example/lib/main.dart` for a fully fledged example app using the plugin.
Note that on iOS the sample rate will not necessarily change, as there is only the option to set a preferred one.
The plugin works as a singleton and provide a simple `audioStream` to listen to.

```dart
// Note that AudioStreamer works as a singleton.
AudioStreamer streamer = AudioStreamer();
bool _isRecording = false;
List<double> _audio = [];
void onAudio(List<double> buffer) async {
_audio.addAll(buffer);
var sampleRate = await streamer.actualSampleRate;
double secondsRecorded = _audio.length.toDouble() / sampleRate;
AudioStreamer().audioStream.listen(
(List<double> buffer) {
print('Max amp: ${buffer.reduce(max)}');
print('Min amp: ${buffer.reduce(min)}');
print('$secondsRecorded seconds recorded.');
print('-' * 50);
}
void handleError(PlatformException error) {
},
onError: (Object error) {
print(error);
}
void start() async {
try {
// start streaming using default sample rate of 44100 Hz
streamer.start(onAudio, handleError);
setState(() {
_isRecording = true;
});
} catch (error) {
print(error);
}
}
void stop() async {
bool stopped = await streamer.stop();
setState(() {
_isRecording = stopped;
});
}
},
cancelOnError: true,
);
```

The sampling rate can be set and read using the `samplingRate` and `actualSampleRate` properties.

```dart
// Set the sampling rate. Must be done BEFORE listening to the audioStream.
AudioStreamer().sampleRate = 22100;
// Get the real sampling rate - may be different from the requested sampling rate.
int sampleRate = await AudioStreamer().actualSampleRate;
```

## Example

See the file `example/lib/main.dart` for an example app using the plugin.
This app also illustrates how to ask for permission to access the microphone.
Note that on iOS the sample rate will not necessarily change, as there is only the option to set a preferred one.
18 changes: 18 additions & 0 deletions packages/audio_streamer/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

include: package:flutter_lints/flutter.yaml

analyzer:
exclude: [build/**]
language:
strict-casts: true
strict-inference: true
strict-raw-types: false

linter:
rules:
cancel_subscriptions: true
constant_identifier_names: false
depend_on_referenced_packages: false
avoid_print: false
15 changes: 1 addition & 14 deletions packages/audio_streamer/example/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
# audio_streamer_example
# Audio Streamer Example

Demonstrates how to use the audio_streamer plugin.

## Getting Started

This project is a starting point for a Flutter application.

A few resources to get you started if this is your first Flutter project:

- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)

For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
147 changes: 78 additions & 69 deletions packages/audio_streamer/example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,93 +1,102 @@
import 'dart:math';
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:audio_streamer/audio_streamer.dart';
import 'dart:math';
import 'package:permission_handler/permission_handler.dart';

import 'package:flutter/services.dart';
void main() => runApp(new AudioStreamingApp());

void main() {
runApp(new MyApp());
}

class MyApp extends StatefulWidget {
class AudioStreamingApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
AudioStreamingAppState createState() => new AudioStreamingAppState();
}

class _MyAppState extends State<MyApp> {
// Note that AudioStreamer works as a singleton.
AudioStreamer streamer = AudioStreamer();
class AudioStreamingAppState extends State<AudioStreamingApp> {
int? sampleRate;
bool isRecording = false;
List<double> audio = [];
List<double>? latestBuffer;
double? recordingTime;
StreamSubscription<List<double>>? audioSubscription;

bool _isRecording = false;
List<double> _audio = [];
/// Check if microphone permission is granted.
Future<bool> checkPermission() async => await Permission.microphone.isGranted;

@override
void initState() {
super.initState();
}
/// Request the microphone permission.
Future<void> requestPermission() async =>
await Permission.microphone.request();

/// Call-back on audio sample.
void onAudio(List<double> buffer) async {
_audio.addAll(buffer);
var sampleRate = await streamer.actualSampleRate;
double secondsRecorded = _audio.length.toDouble() / sampleRate;
print('Max amp: ${buffer.reduce(max)}');
print('Min amp: ${buffer.reduce(min)}');
print('$secondsRecorded seconds recorded.');
print('-' * 50);
audio.addAll(buffer);

// Get the actual sampling rate, if not already known.
sampleRate ??= await AudioStreamer().actualSampleRate;
recordingTime = audio.length / sampleRate!;

setState(() => latestBuffer = buffer);
}

void handleError(PlatformException error) {
setState(() {
_isRecording = false;
});
print(error.message);
print(error.details);
/// Call-back on error.
void handleError(Object error) {
setState(() => isRecording = false);
print(error);
}

/// Start audio sampling.
void start() async {
try {
// Start streaming using default sample rate of 44100 Hz
streamer.start(onAudio, handleError);

setState(() {
_isRecording = true;
});
} catch (error) {
print(error);
// Check permission to use the microphone.
//
// Remember to update the AndroidManifest file (Android) and the
// Info.plist and pod files (iOS).
if (!(await checkPermission())) {
await requestPermission();
}

// Set the sampling rate - works only on Android.
AudioStreamer().sampleRate = 22100;

// Start listening to the audio stream.
audioSubscription =
AudioStreamer().audioStream.listen(onAudio, onError: handleError);

setState(() => isRecording = true);
}

/// Stop audio sampling.
void stop() async {
bool stopped = await streamer.stop();
setState(() {
_isRecording = stopped;
});
audioSubscription?.cancel();
setState(() => isRecording = false);
}

List<Widget> getContent() => <Widget>[
Container(
margin: EdgeInsets.all(25),
child: Column(children: [
Container(
child: Text(_isRecording ? "Mic: ON" : "Mic: OFF",
style: TextStyle(fontSize: 25, color: Colors.blue)),
margin: EdgeInsets.only(top: 20),
)
])),
];

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: getContent())),
floatingActionButton: FloatingActionButton(
backgroundColor: _isRecording ? Colors.red : Colors.green,
onPressed: _isRecording ? stop : start,
child: _isRecording ? Icon(Icons.stop) : Icon(Icons.mic)),
),
);
}
Widget build(BuildContext context) => MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
margin: EdgeInsets.all(25),
child: Column(children: [
Container(
child: Text(isRecording ? "Mic: ON" : "Mic: OFF",
style: TextStyle(fontSize: 25, color: Colors.blue)),
margin: EdgeInsets.only(top: 20),
),
Text(''),
Text('Max amp: ${latestBuffer?.reduce(max)}'),
Text('Min amp: ${latestBuffer?.reduce(min)}'),
Text(
'${recordingTime?.toStringAsFixed(2)} seconds recorded.'),
])),
])),
floatingActionButton: FloatingActionButton(
backgroundColor: isRecording ? Colors.red : Colors.green,
child: isRecording ? Icon(Icons.stop) : Icon(Icons.mic),
onPressed: isRecording ? stop : start,
),
),
);
}
Loading

0 comments on commit 6ca7fab

Please sign in to comment.