audio_graph 0.0.5

  • Readme
  • Changelog
  • Example
  • Installing
  • 56

AudioGraph #

Flutter plugin to build custom audio graph.

This plugin inspired by AVAudioEngine and DirectShow.

Overview #

You can create graph with following steps.

  • Initialize AudioNode
  • Initialize AudioGraphBuilder
  • Connect the node's output pin to next AudioNode's input pin using AudioGraphBuilder.connect(output, input)
  • Build AudioGraph using AudioGraphBuilder.build()

To dispose audio resources, use AudioGraph.dispose().

Please note that AudioNode can only be used once. If you dispose the parent graph, you must not use the node.

Example #

See comments of example/main.dart

AudioNodes #

AudioNode is a base class. You can use these AudioNodes

  • AudioFileNode
    • Decode the audio file and pass the data to AudioFileNode.outputPin.
    • This node has play(), pause(), position to control audio state
  • AudioMixerNode
    • AudioMixerNode.appendInputPin() to get the next mixer input pin. You have to connect the same AudioFormat output pin.
  • AudioDeviceOutputNode
    • connect the output pin to AudioDeviceOutputNode.inputPin to play audio using the device's default output device.
    • You cannot use AudioDeviceOutputNode twice in a same graph.

iOS implementation #

AVAudioEngine is used in iOS.

Android implementation #

AudioFileNode is using MediaCodec and MediaExtractor. AudioDeviceOutputNode is using AudioTrack.

0.0.5 #

  • Add API documentations
  • Provide more description of this plugin

0.0.4 #

  • Use AVAudioEngine.mainMixerNode to support multiple AudioDeviceOutputNode (#1)
  • Rename AudioFileNode to AudioFilePlayerNode (#3)

0.0.3 #

Change org name

0.0.2 #

Remove web support

0.0.1 #

First release

example/lib/main.dart

import 'dart:io';
import 'dart:math';

import 'package:flutter/material.dart';
import 'dart:async';

import 'package:flutter/services.dart';
import 'package:audio_graph/audio_graph.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;

void main() => runApp(MyApp());

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

class _MyAppState extends State<MyApp> {
  AudioGraph graph;
  List<AudioFilePlayerNode> files;
  AudioMixerNode mixer;
  bool ignoreUpdate = false;
  bool playAll = true;

  @override
  void initState() {
    super.initState();
    initAudioGraph();
  }

  // Extract the asset data and export to the app's document directory
  Future<String> setupMusicFile(String assetUri) async {
    final dir = await getApplicationDocumentsDirectory();
    final file = File('${dir.path}/$assetUri');
    var data = await rootBundle.load('assets/$assetUri');
    await file.writeAsBytes(data.buffer.asInt8List());
    return file.path;
  }

  // Initialize the AudioGraph
  Future<void> initAudioGraph() async {
    final List<AudioFilePlayerNode> files = List();

    // Create AudioFilePlayerNodes from local files
    final List<String> filePaths = List();

    for (final path in filePaths) {
      files.add(await AudioFilePlayerNode.createNode(path));
    }

    // Create AudioFilePlayerNodes from assets
    // You should add these files to example/assets/ folder to test in your local workspace
    final assetFiles = [
      "test1.mp3",
      "test2.mp3",
      "test3.mp3",
    ];

    for (final asset in assetFiles) {
      final path = await setupMusicFile(asset);
      files.add(await AudioFilePlayerNode.createNode(path));
    }

    // AudioDeviceOutputNode is an output node to produce audio data to the speaker.
    final output = AudioDeviceOutputNode();

    // When all players and output node is prepared, set AudioMixerNode and rebuild UI.
    setState(() {
      mixer = AudioMixerNode();
      this.files = files;
    });

    // Add player, mixer, output nodes to the AudioGraphBuilder
    final builder = AudioGraphBuilder();
    builder.nodes.addAll(files);
    builder.nodes.addAll([mixer, output]);

    // Connect the AudioFilePlayerNode to AudioMixerNode's next input pin.
    // AudioGraph should look like this AudioFilePlayerNode -> AudioMixerNode
    for (final file in files) {
      builder.connect(file.outputPin, mixer.appendInputPin());
    }

    // Connect the AudioMixerNode's output pin to AudioDeviceOutputNode's input pin
    builder.connect(mixer.outputPin, output.inputPin);

    // Build the graph and receive it.
    // You MUST dispose the graph when you don't need it.
    try {
      graph = await builder.build();
    } on PlatformException catch (e) {
      print(e.code);
      print(e.message);
    }

    Timer.periodic(Duration(milliseconds: 200), (timer) async {
      if (ignoreUpdate) {
        return;
      }

      for (final file in files) {
        await file.updatePosition();
      }

      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: ListView(
          children: <Widget>[
            Center(
              child: Text("Main Mixer"),
            ),
            Center(
              child: IconButton(
                icon: Icon(Icons.audiotrack),
                onPressed: () {
                  for (final file in files) {
                    if (playAll) {
                      file.play();
                    } else {
                      file.pause();
                    }
                  }

                  playAll = !playAll;
                },
              ),
            ),
            Slider(
              value: mixer?.volume ?? 0,
              onChanged: (value) {
                setState(() {
                  mixer?.volume = value;
                });
              },
            ),
          ]..addAll(files?.map((f) => buildListTile(f)) ?? []),
        ),
      ),
    );
  }

  Widget buildListTile(AudioFilePlayerNode file) {
    return Column(
      children: <Widget>[
        Text(path.basename(file.path)),
        IconButton(
          icon: Icon(Icons.audiotrack),
          onPressed: () {
            if (file.isPlaying) {
              file.pause();
            } else {
              file.play();
            }
          },
        ),
        Slider(
          value: file?.volume ?? 0,
          onChanged: (value) {
            setState(() {
              file.volume = value;
            });
          },
        ),
        Slider(
          min: 0,
          max: file?.duration ?? 0,
          value: min(file?.position ?? 0, file?.duration ?? 0),
          onChangeStart: (_) {
            ignoreUpdate = true;
          },
          onChangeEnd: (_) {
            ignoreUpdate = false;
          },
          onChanged: (value) {
            if (ignoreUpdate) {
              return setState(() {
                file.position = value;
              });
            }
          },
        )
      ],
    );
  }

  @override
  void reassemble() {
    // Rebuild the graph when hot reload is executed
    graph?.dispose();
    initAudioGraph();
    super.reassemble();
  }

  @override
  void dispose() {
    graph.dispose();
    super.dispose();
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  audio_graph: ^0.0.5

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:audio_graph/audio_graph.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
16
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
90
Overall:
Weighted score of the above. [more]
56
Learn more about scoring.

We analyzed this package on Mar 31, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.7.1
  • pana: 0.13.6
  • Flutter: 1.12.13+hotfix.8

Maintenance suggestions

Package is pre-v0.1 release. (-10 points)

While nothing is inherently wrong with versions of 0.0.*, it might mean that the author is still experimenting with the general direction of the API.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
flutter 0.0.0
Transitive dependencies
collection 1.14.11 1.14.12
meta 1.1.8
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
flutter_test