Flutter音频播放插件flutter_ogg_piano的使用

发布于 1周前 作者 nodeper 来自 Flutter

Flutter音频播放插件flutter_ogg_piano的使用

获取开始

要在这个Flutter项目中使用该插件,你需要在pubspec.yaml文件的依赖部分添加以下行:

dependencies:
  flutter_ogg_piano: ^1.1.3

实现

在使用FlutterOggPiano类之前,必须首先调用init方法。默认情况下,可以同时播放的最大声音数量为128,但你可以更改此值。请注意,该插件不能处理超过5秒的声音。

从版本1.1.0开始,该插件在Android上使用了Oboe库,你可以设置性能模式为LOW_LATENCYPOWER_SAVING

  • LOW_LATENCY模式可以在渲染音频时实现最低延迟,但是同时播放太多声音会导致渲染过程无法正确完成,从而导致声音体验不佳。
  • POWER_SAVING模式需要比LOW_LATENCY模式更高的性能,但它可以同时处理更多的声音。

该插件默认使用LOW_LATENCY模式以获得更好的音频渲染时间,但你可以通过调用init()方法来更改模式。

你还可以决定音频是否将以立体声模式渲染。如果在init()方法中将isStereo参数设置为false,则所有音频将以单声道形式渲染。这也将影响设备的扬声器。如果你打开了单声道模式,即使扬声器有两通道,也会像只有一个通道一样播放任何声音。

如果你关心内存使用情况,你可以允许用户加载任何声音,而不仅仅是Android上的SoundPool

示例

以下示例展示了如何使用保存在资源文件夹中的声音文件来使用FlutterOggPiano

pubspec.yaml文件中添加资源文件路径:

assets:
  - assets/123.ogg
  - assets/456.ogg

当加载文件时,应用程序会在设备上生成一个临时的声音文件,并且不会被删除以便以后使用。如果你想要替换这个文件,需要将replace参数设置为true,否则会加载旧的数据。

你可以这样加载声音文件:

import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/services.dart';

// 在代码的某个地方...
rootBundle.load("assets/123.ogg").then((ogg) {
  // 如果你想覆盖已存在的声音...
  fop.load(src: ogg, name: "123.ogg", index: 1, forceLoad: true);
  // 如果你想正常加载...
  fop.load(src: ogg, name: "123.ogg", index: 1);
  // 如果你想替换生成的临时文件...
  fop.load(src: ogg, name: "123.ogg", index: 1, forceLoad: true, replace: true);
});

rootBundle.load("assets/456.ogg").then((ogg) {
  fop.load(src: ogg, index: 0);
});

当你想播放声音时,需要传递音符值。1个单位的差异等于1个半音的差异。负值表示降低音高,正值表示提高音高,零表示与源声音相同的音高。

// 在代码的某个地方...
fop.play(index: 1, note: -1); // 使用123.ogg声音降低1个半音
fop.play(index: 0, note: 3); // 使用456.ogg声音提高3个半音

从版本1.0.5开始,播放支持分离的左右音量。如果没有指定,左右音量的默认值为1.0。

// 在代码的某个地方...
fop.play(index: 1, note: -1, left: 0.5, right: 0.75);

从版本1.1.0开始,我们不再使用左右音量值。相反,我们只使用pan值。pan值可以是小数,但必须在-1.0到1.0之间。-1.0表示向左偏移,1.0表示向右偏移,0.0表示居中。如果没有指定pan值,默认值为0.0。

// 在代码的某个地方...
fop.play(index: 1, note: 3, pan: 1.0);

从版本1.0.6开始,你可以发送多个声音数据进行播放,但有一些限制。你需要传递一个Map<int, double[]>,每个键都是ID,双精度数组包含[pitch, left_volume, right_volume]。

Map<int, List<Float64List>> map = Map();

List<Float64List> sounds = [];

for(int i = 0; i < _number; i++) {
  Float64List list = Float64List(3);

  list[0] = _pitch;
  list[1] = _left;
  list[2] = _right;
  
  sounds.add(list);
}

map[id] = sounds;

fop.playInGroup(map);

从版本1.1.0开始,playInGroup()方法的参数有所改变。由于去除了左右音量值,双精度数组现在必须包含pan值。由于Oboe可以执行较小的延迟来播放声音,开发者可以在一次会话中多次播放相同的声音。因此,双精度数组仍然需要3个数据,但现在它将是[pitch, pan, scale]。

注意,如果scale值太大,用户可能会因为剪辑而听到糟糕的声音,所以要适当地调整这个值。pan值仍然必须在-1.0到1.0之间。

Map<int, List<Float64List>> map = Map();

List<Float64List> sounds = [];

for(int i = 0; i < _number; i++) {
  Float64List list = Float64List(3);

  list[0] = _pitch;
  list[1] = _pan;
  list[2] = _scale;
  
  sounds.add(list);
}

map[id] = sounds;

fop.playInGroup(map);

在使用完这个类之后,不要忘记释放它。

fop.release();

它会自动防止在已释放状态下播放或加载声音,但你可以通过调用以下方法简单地检查它是否已被释放。

fop.isReleased().then((r) {
  // 做一些操作
})

完整示例代码

以下是完整的示例代码,展示了如何使用FlutterOggPiano插件:

import 'dart:math';
import 'dart:typed_data';

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

import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_ogg_piano/flutter_ogg_piano.dart';

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

class MyApp extends StatefulWidget {
  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  FlutterOggPiano fop = FlutterOggPiano();

  List<String> files = ["piano.ogg", "piano2.ogg"];

  bool initialized = false;

  int _count = 1;
  int _pitch = 0;

  [@override](/user/override)
  void initState() {
    super.initState();
    initPlatformState();

    if (!initialized) {
      loadPianoSounds();
    }
  }

  // 平台消息是异步的,所以我们初始化在一个异步方法中。
  Future<void> initPlatformState() async {
    String platformVersion;
    // 平台消息可能失败,所以我们使用一个try/catch PlatformException。
    try {
      platformVersion = (await FlutterOggPiano.platformVersion) ?? _platformVersion;
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    // 如果在异步平台消息还在飞行时小部件被从树中移除,我们需要丢弃回复而不是调用setState来更新我们的非存在的外观。
    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('插件示例应用'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(onPressed: () {
                    if (initialized) {
                      fop.play(index: 0, note: 0);
                    }
                  }, child: Text("播放声音1")),
                  ElevatedButton(onPressed: () {
                    if (initialized) {
                      fop.play(index: 1, note: 0);
                    }
                  }, child: Text("播放声音2")),
                ],
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: [
                  Column(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: [
                      Center(child: Text("音高"),),
                      Row(
                        children: [
                          Ink(
                            decoration: ShapeDecoration(
                              color: Colors.lightBlue,
                              shape: CircleBorder(),
                            ),
                            child: IconButton(
                              splashRadius: 24,
                              splashColor: Colors.lightBlueAccent,
                              onPressed: () {
                                setState(() {
                                  _pitch++;
                                });
                              },
                              icon: Icon(Icons.arrow_upward, color: Colors.white70,),
                            ),
                          ),
                          Padding(padding: EdgeInsets.fromLTRB(6, 0, 6, 0)),
                          Container(
                            width: 72,
                            height: 36,
                            decoration: BoxDecoration(
                                borderRadius: BorderRadius.circular(8),
                                color: Colors.grey[100],
                                boxShadow: [
                                  BoxShadow(
                                      offset: Offset(4,4),
                                      blurRadius: 4,
                                      color: Colors.grey[600]!
                                  )
                                ]
                            ),
                            child: Center(
                              child: Text(_pitch.toString()),
                            ),
                          ),
                          Padding(padding: EdgeInsets.fromLTRB(6, 0, 6, 0)),
                          Ink(
                            decoration: ShapeDecoration(
                              color: Colors.lightBlue,
                              shape: CircleBorder(),
                            ),
                            child: IconButton(
                              splashRadius: 24,
                              splashColor: Colors.lightBlueAccent,
                              onPressed: () {
                                setState(() {
                                  _pitch--;
                                });
                              },
                              icon: Icon(Icons.arrow_downward, color: Colors.white70,),
                            ),
                          )
                        ],
                      )
                    ],
                  ),
                  Column(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: [
                      ElevatedButton(onPressed: () {
                        if (initialized) {
                          fop.play(index: 0, note: _pitch);
                        }
                      }, child: Text("播放声音1")),
                      ElevatedButton(onPressed: () {
                        fop.play(index: 1, note: _pitch);
                      }, child: Text("播放声音2")),
                    ],
                  )
                ],
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  Ink(
                    decoration: ShapeDecoration(
                      color: Colors.lightBlue,
                      shape: CircleBorder(),
                    ),
                    child: IconButton(
                      splashRadius: 24,
                      splashColor: Colors.lightBlueAccent,
                      onPressed: () {
                        setState(() {
                          _count++;
                        });
                      },
                      icon: Icon(Icons.arrow_upward, color: Colors.white70,),
                    ),
                  ),
                  Container(
                    width: 72,
                    height: 36,
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(8),
                      color: Colors.grey[100],
                      boxShadow: [
                        BoxShadow(
                            offset: Offset(4,4),
                          blurRadius: 4,
                          color: Colors.grey[600]!
                        )
                      ]
                    ),
                    child: Center(
                      child: Text(_count.toString()),
                    ),
                  ),
                  Ink(
                    decoration: ShapeDecoration(
                      color: Colors.lightBlue,
                      shape: CircleBorder(),
                    ),
                    child: IconButton(
                      splashRadius: 24,
                      splashColor: Colors.lightBlueAccent,
                      onPressed: () {
                        setState(() {
                          _count = max(--_count, 1);
                        });
                      },
                      icon: Icon(Icons.arrow_downward, color: Colors.white70,),
                    ),
                  ),
                  Column(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: [
                      ElevatedButton(onPressed: () {
                        if (initialized) {
                          Map<int, List<Float64List>> maps = Map();

                          List<Float64List> sounds = [];

                          Float64List data = Float64List(3);

                          data[0] = 0;
                          data[1] = 0;
                          data[2] = _count.toDouble();

                          sounds.add(data);

                          maps[0] = sounds;

                          fop.playInGroup(maps);
                        }
                      }, child: Text("播放声音1 ${_count}次")),
                      ElevatedButton(onPressed: () {
                        if (initialized) {
                          Map<int, List<Float64List>> maps = Map();

                          List<Float64List> sounds = [];

                          Float64List data = Float64List(3);

                          data[0] = 0;
                          data[1] = 0;
                          data[2] = _count.toDouble();

                          sounds.add(data);

                          maps[1] = sounds;

                          fop.playInGroup(maps);
                        }
                      }, child: Text("播放声音2 ${_count}次")),
                      ElevatedButton(onPressed: () {
                        if (initialized) {
                          Map<int, List<Float64List>> maps = Map();

                          List<Float64List> sounds = [];

                          Float64List data = Float64List(3);

                          data[0] = 0;
                          data[1] = 0;
                          data[2] = _count.toDouble();

                          sounds.add(data);

                          maps[1] = sounds;
                          maps[0] = sounds;

                          fop.playInGroup(maps);
                        }
                      }, child: Text("播放两个声音 ${_count}次")),
                    ],
                  )
                ],
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(onPressed: () {
                    if (initialized) {
                      fop.play(index: 0, note: 0, pan: -1.0);
                    }
                  }, child: Text("播放声音1在左边")),
                  ElevatedButton(onPressed: () {
                    if (initialized) {
                      fop.play(index: 0, note: 0, pan: 1.0);
                    }
                  }, child: Text("播放声音1在右边"))
                ],
              )
            ],
          ),
        ),
      ),
    );
  }

  Future<void> loadPianoSounds() async {
    fop.init(mode: MODE.LOW_LATENCY);

    for (int i = 0; i < files.length; i++) {
      String name = "assets/" + files[i];

      ByteData data = await rootBundle.load(name);

      await fop.load(src: data, name: files[i], index: i, forceLoad: true);
    }

    initialized = true;
  }
}

更多关于Flutter音频播放插件flutter_ogg_piano的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter音频播放插件flutter_ogg_piano的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用flutter_ogg_piano插件来播放音频的一个简单示例。请注意,flutter_ogg_piano这个插件名称看起来像是一个特定的音频播放库,但实际上在Flutter社区中并没有一个广泛认可的名为flutter_ogg_piano的官方或广泛使用的插件。不过,我可以给你一个使用类似功能的Flutter音频播放插件的示例,比如just_audio,这是一个功能强大且广泛使用的音频播放插件。

假设你使用的是just_audio插件,以下是如何实现音频播放的示例代码:

  1. 添加依赖: 首先,在你的pubspec.yaml文件中添加just_audio依赖:

    dependencies:
      flutter:
        sdk: flutter
      just_audio: ^0.9.20  # 请检查最新版本号
    
  2. 运行flutter pub get: 保存pubspec.yaml文件后,在终端中运行flutter pub get来安装依赖。

  3. 使用just_audio播放音频

    import 'package:flutter/material.dart';
    import 'package:just_audio/just_audio.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: AudioPlayerScreen(),
        );
      }
    }
    
    class AudioPlayerScreen extends StatefulWidget {
      @override
      _AudioPlayerScreenState createState() => _AudioPlayerScreenState();
    }
    
    class _AudioPlayerScreenState extends State<AudioPlayerScreen> {
      late AudioPlayer _audioPlayer;
    
      @override
      void initState() {
        super.initState();
        _audioPlayer = AudioPlayer();
      }
    
      @override
      void dispose() {
        _audioPlayer.dispose();
        super.dispose();
      }
    
      void _playAudio() async {
        // 替换为你的音频文件URL或本地路径
        final url = 'https://example.com/audio/sample.ogg';
        await _audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(url)));
        _audioPlayer.play();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Audio Player'),
          ),
          body: Center(
            child: ElevatedButton(
              onPressed: _playAudio,
              child: Text('Play Audio'),
            ),
          ),
        );
      }
    }
    

在这个示例中,我们使用了just_audio插件来创建一个简单的音频播放器。当用户点击按钮时,音频将从指定的URL开始播放。

请注意,如果你确实在寻找一个专门用于播放OGG格式音频且名为flutter_ogg_piano的插件,并且这个插件在Flutter社区中存在但不在pub.dev上广泛认知,你可能需要查阅该插件的官方文档或仓库以获取正确的使用方法。上面的代码是使用just_audio作为替代方案的示例,因为just_audio支持多种音频格式,包括OGG,并且是一个广泛使用的Flutter音频播放插件。

回到顶部