flutter开发实战-实现音效soundpool播放音频及控制播放暂停停止设置音量

flutter开发实战-实现音效soundpool播放音频

最近开发过程中遇到低配置设备时候,在Media播放音频时候出现音轨限制问题。所以将部分音频采用音效sound来播放。

一、音效类似iOS中的Sound

在iOS中使用sound来播放mp3音频示例如下

objectivec 复制代码
// 通过通知的Sound设置为voip_call.caf,这里播放一段空白音频,音频结束后结束震动
                NSString *path = [[NSBundle mainBundle] pathForResource:@"blank_call.mp3" ofType:nil];
                AudioServicesCreateSystemSoundID((__bridge CFURLRef)[NSURL fileURLWithPath:path], &soundID);

                AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallback, NULL);
                AudioServicesAddSystemSoundCompletion(kSystemSoundID_Vibrate, NULL, NULL, soundCompleteCallback, NULL);
[self startShakeSound];
objectivec 复制代码
/// 开始播放与震动
- (void)startShakeSound {
    // 声音
    AudioServicesPlaySystemSound(soundID);
    
    // 震动
    AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
}
objectivec 复制代码
/// 结束播放与震动
- (void)stopShakeSound {
    AudioServicesDisposeSystemSoundID(soundID);
    AudioServicesRemoveSystemSoundCompletion(soundID);
}
objectivec 复制代码
//AudioServicesAddSystemSoundCompletion的回调函数
void soundCompleteCallback(SystemSoundID sound,void * clientData) {
    if (sound == kSystemSoundID_Vibrate) {
        AudioServicesPlayAlertSound(sound);//重复响铃震动
    } else {
        // 移除
        AudioServicesDisposeSystemSoundID(sound);
        AudioServicesRemoveSystemSoundCompletion(sound);

        AudioServicesDisposeSystemSoundID(kSystemSoundID_Vibrate);
        AudioServicesRemoveSystemSoundCompletion(kSystemSoundID_Vibrate);
    }
}

在iOS中通过soundID,可以控制播放与暂停,当然iOS中sound播放完成有通知回调callback。soundpool暂时没找到播放完成回调callback。

二、使用soundpool播放音频

2.1、引入soundpool

在pubspec.yaml中引入

dart 复制代码
soundpool: ^2.3.0

2.2、使用soundpool来播放音频

Soundpool的初始化要用到SoundpoolOptions

dart 复制代码
SoundpoolOptions soundpoolOptions = SoundpoolOptions();
_soundpool = Soundpool.fromOptions(options: soundpoolOptions);
_soundId = await _soundpool!.loadUri(url);

播放控制需要使用AudioStreamControl,比如播放、暂停、停止、设置音量等控制操作

  • 播放
dart 复制代码
_audioStreamControl = await _soundpool!.playWithControls(_soundId!);
  • 暂停
dart 复制代码
await _audioStreamControl!.pause();
  • 停止
dart 复制代码
await _audioStreamControl!.stop();
  • 设置音量
dart 复制代码
await _audioStreamControl!.setVolume(volume: volume);

2.3、通过播放时长控制是否可以再次播放

由于soundpool没有播放音频完成回调,这里采用通过Media将音频时长获取到之后判断时间。判断当前播放时间与上次播放的时间间隔是否超过了音频时长Duration。

dart 复制代码
if (lastPlayMilliseconds == null) {
        lastPlayMilliseconds = TimeUtil.currentTimeMillis();
        _audioStreamControl = await _soundpool!.playWithControls(_soundId!);
      } else {
        int nowMilliseconds = TimeUtil.currentTimeMillis();
        bool shouldPlay = true;
        if (audioDurationInMilliseconds != null) {
          if (nowMilliseconds - lastPlayMilliseconds! <
              audioDurationInMilliseconds!) {
            shouldPlay = false;
          }
        }
        if (shouldPlay) {
          // 如果上次没有播放完成,不进行播放
          // 通过判断播放时长
          _audioStreamControl = await _soundpool!.playWithControls(_soundId!);
          lastPlayMilliseconds = nowMilliseconds;
        }
      }

整体使用soundpool播放音频的相关代码如下

dart 复制代码
import 'package:soundpool/soundpool.dart';

// 使用SoundPool
class SoundPlayer implements BaseAudioPlayer {
  // AudioStreamControl
  // Soundpool
  Soundpool? _soundpool;
  String? audioUrl;

  // 上次播放时长的毫秒数
  int? lastPlayMilliseconds;

  // 时长
  int? audioDurationInMilliseconds;

  // 优先级
  late AudioConfig __audioConfig;
  late bool _playing = false; // 是否正在播放
  int? _soundId;
  AudioStreamControl? _audioStreamControl;

  SoundPlayer(this.__audioConfig, {Duration? duration}) {
    if (duration != null) {
      audioDurationInMilliseconds = duration!.inMilliseconds;
    }
    print("SoundPlayer audioDurationInMilliseconds:${audioDurationInMilliseconds}");
    SoundpoolOptions soundpoolOptions = SoundpoolOptions();
    _soundpool = Soundpool.fromOptions(options: soundpoolOptions);
    setAudioUrl(__audioConfig.audioUrl);
  }

  Future<void> setAudioUrl(String url) async {
    if (_soundpool != null) {
      _soundId = await _soundpool!.loadUri(url);
    }
  }

  AudioConfig get_audioConfig() {
    return __audioConfig;
  }

  @override
  void play() async {
    // Usually you don't want to wait for playback to finish.
    if (__audioConfig.audioUrl != null &&
        __audioConfig.audioUrl.isNotEmpty &&
        _soundId != null &&
        _soundpool != null) {
      if (lastPlayMilliseconds == null) {
        lastPlayMilliseconds = TimeUtil.currentTimeMillis();
        _audioStreamControl = await _soundpool!.playWithControls(_soundId!);
      } else {
        int nowMilliseconds = TimeUtil.currentTimeMillis();
        bool shouldPlay = true;
        if (audioDurationInMilliseconds != null) {
          if (nowMilliseconds - lastPlayMilliseconds! <
              audioDurationInMilliseconds!) {
            shouldPlay = false;
          }
        }
        if (shouldPlay) {
          // 如果上次没有播放完成,不进行播放
          // 通过判断播放时长
          _audioStreamControl = await _soundpool!.playWithControls(_soundId!);
          lastPlayMilliseconds = nowMilliseconds;
        }
      }
    }
  }

  @override
  void pause() async {
    if (__audioConfig.audioUrl != null &&
        __audioConfig.audioUrl.isNotEmpty &&
        _audioStreamControl != null) {
      try {

        await _audioStreamControl!.pause();
      } catch(e) {
        print("audioStreamControl pause e:${e.toString()}");
      }
    }
  }

  @override
  void stop() async {
    if (__audioConfig.audioUrl != null &&
        __audioConfig.audioUrl.isNotEmpty &&
        _audioStreamControl != null) {
      try {
        await _audioStreamControl!.stop();
      } catch(e) {
        print("audioStreamControl stop e:${e.toString()}");
      }
    }
  }

  @override
  void setVolume(double volume) async {
    if (__audioConfig.audioUrl != null &&
        __audioConfig.audioUrl.isNotEmpty &&
        _audioStreamControl != null) {
      try {
        await _audioStreamControl!.setVolume(volume: volume);
      } catch(e) {
        print("audioStreamControl setVolume e:${e.toString()}");
      }
    }
  }

  // 不需要该播放器,则需要调用该方法
  @override
  void dispose() {
    if (_soundpool != null) {
      _soundpool!.dispose();
    }
  }
}

三、小结

flutter开发实战-实现音效soundpool播放音频及控制播放、暂停、停止、设置音量等控制操作。

学习记录,每天不停进步。

相关推荐
Android系统攻城狮5 小时前
Linux Gstreamer深度解析之gst_audio_encoder_set_frame_max调用流程与实战(六十一)
android·linux·运维·音视频·gstreamer音视频·音视频进阶·gstreamer音视频进阶
传奇开心果编程14 小时前
【现代声明式UI学与练】第4课 列表渲染与 key——如何高效渲染列表、key 的作用、列表重排时的状态保持
学习·flutter·react native·ui·swiftui·android jetpack
redfred16 小时前
HyperFrames 部署教程:5.6 万 Star 开源「写 HTML 出视频」框架,Windows 从零上手(CLI 三条命令 + 7 个避坑)
ffmpeg·开源·自动化·html·音视频
m0_7381858216 小时前
Flutter 鸿蒙化实战:media_info 适配 OpenHarmony,媒体信息与缩略图
flutter·华为·harmonyos·鸿蒙·媒体
martindelophy16 小时前
用自然语言剪视频:我们在 Timeline Studio 中实现了 ChatCut 对话剪辑
android·音视频
特视界说17 小时前
没有完整 3D 模型,能做产品三维动画吗?
3d·微信·音视频·新浪微博
特视界说17 小时前
产品宣传动画和企业宣传片有什么区别,应该先做哪个?
微信·音视频·新浪微博·新人首发
bluebonnet2717 小时前
【docker】容器内映射外部音频驱动
docker·容器·音视频
m0_7381858217 小时前
Flutter 鸿蒙化实战:just_audio 适配 OpenHarmony,功能强大的播放器
flutter·华为·harmonyos·鸿蒙
可乐鸡翅yeah_1 天前
FFmpeg 生成 HLS 独立分片 independent‑segments 参数到底有什么用
javascript·ffmpeg·音视频·safari·m3u8