自己封装flutter音频播放器

2022-01-07  本文已影响0人  X先生_未知数的X

版权声明:本文为CSDN博主「华洛」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sdta25196/article/details/115250786

1.使用方式

//  xxx.dart
  //定义控制器
  final EolAudioController controller = EolAudioController();
  // 定义mp3地址
  String _audio = "https://s3.amazonaws.com/scifri-segments/scifri201711241.mp3";
// 调用
  EolAudio(source: _audio, timer: "00:00", controller: controller),

2.封装音频类

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:zsgk/config/theme.dart';

/*
*
* @author: 田源
* @date: 2020-11-30 10:59
* @description: eol音频组件
*
*/

class EolAudio extends StatefulWidget {
  /// 音频内容 mp3链接
  final String source;

  /// 音频长度
  final String timer;

  final EolAudioController controller;

  EolAudio({Key key, @required this.timer, @required this.source, @required this.controller}) : super(key: key);
  @override
  _EolAudioState createState() => _EolAudioState();
}

class _EolAudioState extends State<EolAudio> {
  /// 音频实例
  AudioPlayer audioPlayer = AudioPlayer();

  /// 是否已经加载过这个音频
  bool loaded = false;

  /// 控制播放中动画效果的显示
  bool isPlay = false;

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

  @override
  void dispose() {
    audioPlayer?.dispose();
    super.dispose();
  }

  changePlayState(flag) {
    if (!mounted) return;
    setState(() {
      isPlay = flag;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 100.w,
      padding: EdgeInsets.all(20.w),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.all(Radius.circular(15.w)),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          // 播放动效
          Container(
            width: 80.w,
            child: Visibility(
              visible: isPlay,
              child: Image.asset(
                "assets/images/icon/music.gif",
                width: 80.w,
                height: 20.w,
              ),
              replacement: Image.asset(
                "assets/images/icon/music.png",
                width: 80.w,
                height: 20.w,
              ),
            ),
          ),
          // 进度条
          Expanded(
            flex: 1,
            child: StreamBuilder<Duration>(
              stream: audioPlayer.durationStream,
              builder: (context, snapshot) {
                final duration = snapshot.data ?? Duration.zero;
                return StreamBuilder<Duration>(
                  stream: audioPlayer.positionStream,
                  builder: (context, snapshot) {
                    var position = snapshot.data ?? Duration.zero;
                    if (position > duration) {
                      position = duration;
                    }
                    return SeekBar(
                      duration: duration,
                      position: position,
                      timer: widget.timer,
                      onChangeEnd: (newPosition) {
                        audioPlayer.seek(newPosition);
                      },
                    );
                  },
                );
              },
            ),
          ),
          // 播放按钮
          Container(
            width: 42.w,
            child: StreamBuilder<PlayerState>(
              stream: audioPlayer.playerStateStream,
              builder: (context, snapshot) {
                final playerState = snapshot.data;
                final processingState = playerState?.processingState;
                final playing = playerState?.playing;
                if (processingState == ProcessingState.loading || processingState == ProcessingState.buffering) {
                  return Transform.scale(
                    scale: 0.7,
                    child: Container(
                      width: 42.w,
                      height: 42.w,
                      child: CircularProgressIndicator(
                        strokeWidth: 5.w,
                        valueColor: AlwaysStoppedAnimation<Color>(Colors.red),
                      ),
                    ),
                  );
                } else if (playing != true) {
                  return InkWell(
                    onTap: () async {
                      if (widget.controller.activeAudio != this) {
                        widget.controller.activeAudio?.changePlayState(false);
                        widget.controller.audioPlayer?.pause();
                      }
                      // 音频只加载一次
                      if (!loaded) {
                        await audioPlayer.load(
                          AudioSource.uri(Uri.parse(widget.source)),
                        );
                        setState(() {
                          loaded = true;
                        });
                      }
                      audioPlayer.play();
                      changePlayState(true);
                      widget.controller.activeAudio = this;
                      widget.controller.audioPlayer = audioPlayer;
                    },
                    child: Container(
                      child: Icon(
                        Icons.play_circle_outline,
                        color: Colors.red,
                        size: 42.w,
                      ),
                    ),
                  );
                } else if (processingState != ProcessingState.completed) {
                  return InkWell(
                    onTap: () {
                      changePlayState(false);
                      audioPlayer.pause();
                    },
                    child: Container(
                      child: Icon(
                        Icons.pause_circle_outline,
                        color: Colors.red,
                        size: 42.w,
                      ),
                    ),
                  );
                } else {
                  return InkWell(
                    onTap: () {
                      changePlayState(false);
                      audioPlayer.seek(Duration.zero, index: 0);
                    },
                    child: Container(
                      child: Icon(
                        Icons.play_circle_outline,
                        color: Colors.red,
                        size: 42.w,
                      ),
                    ),
                  );
                }
              },
            ),
          ),
        ],
      ),
    );
  }
}

class SeekBar extends StatefulWidget {
  /// 音频长度
  final String timer;
  final Duration duration;
  final Duration position;
  final ValueChanged<Duration> onChanged;
  final ValueChanged<Duration> onChangeEnd;

  SeekBar({
    @required this.duration,
    @required this.position,
    @required this.timer,
    this.onChanged,
    this.onChangeEnd,
  });

  @override
  _SeekBarState createState() => _SeekBarState();
}

class _SeekBarState extends State<SeekBar> {
  double _dragValue;

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Container(
          child: Text(
            RegExp(r'((^0*[1-9]\d*:)?\d{2}:\d{2})\.\d+$').firstMatch('$_playing')?.group(1) ?? '$_playing"',
            style: TextStyle(color: Colors.red, fontSize: 26.sp),
          ),
        ),
        Container(
          constraints: BoxConstraints(maxWidth: 300.w),
          child: SliderTheme(
            data: SliderThemeData(
              activeTrackColor: Colors.red,
              inactiveTrackColor: Color(0xffeeeeee),
              thumbColor: Colors.red,
              thumbShape: ARoundSliderThumbShape(enabledThumbRadius: 0.w),
            ),
            child: Slider(
              min: 0.0,
              max: widget.duration.inMilliseconds.toDouble(),
              value: min(_dragValue ?? widget.position.inMilliseconds.toDouble(), widget.duration.inMilliseconds.toDouble()),
              onChanged: (value) {
                setState(() {
                  _dragValue = value;
                });
                if (widget.onChanged != null) {
                  widget.onChanged(Duration(milliseconds: value.round()));
                }
              },
              onChangeEnd: (value) {
                if (widget.onChangeEnd != null) {
                  widget.onChangeEnd(Duration(milliseconds: value.round()));
                }
                _dragValue = null;
              },
            ),
          ),
        ),
        Container(
          child: Text(
            widget.timer,
            style: TextStyle(color: Colors.red, fontSize: 26.sp),
          ),
        ),
      ],
    );
  }

  Duration get _playing => widget.position;
}

/// 重写slider指示器
class ARoundSliderThumbShape extends SliderComponentShape {
  /// Create a slider thumb that draws a circle.
  const ARoundSliderThumbShape({
    this.enabledThumbRadius = 10.0,
    this.disabledThumbRadius,
  });

  /// The preferred radius of the round thumb shape when the slider is enabled.
  ///
  /// If it is not provided, then the material default of 10 is used.
  final double enabledThumbRadius;

  /// The preferred radius of the round thumb shape when the slider is disabled.
  ///
  /// If no disabledRadius is provided, then it is equal to the
  /// [enabledThumbRadius]
  final double disabledThumbRadius;
  double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;

  @override
  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
    return Size.fromRadius(isEnabled == true ? enabledThumbRadius : _disabledThumbRadius);
  }

  @override
  void paint(
    PaintingContext context,
    Offset center, {
    Animation<double> activationAnimation,
    Animation<double> enableAnimation,
    bool isDiscrete,
    TextPainter labelPainter,
    RenderBox parentBox,
    SliderThemeData sliderTheme,
    TextDirection textDirection,
    double value,
    double textScaleFactor,
    Size sizeWithOverflow,
  }) {
    assert(context != null);
    assert(center != null);
    assert(enableAnimation != null);
    assert(sliderTheme != null);
    assert(sliderTheme.disabledThumbColor != null);
    assert(sliderTheme.thumbColor != null);

    final Canvas canvas = context.canvas;
    Rect rect = Rect.fromCenter(center: center, width: 6.w, height: 20.w);
    canvas.drawRect(rect, Paint()..color = Colors.red);
  }
}

/// 音频插件控制器
class EolAudioController {
  /// 当前活跃的音频
  _EolAudioState _activeAudio;

  _EolAudioState get activeAudio => _activeAudio;

  set activeAudio(var value) {
    _activeAudio = value;
  }

  /// 当前活跃的音频播放器
  AudioPlayer _audioPlayer;

  AudioPlayer get audioPlayer => _audioPlayer;

  set audioPlayer(var value) {
    _audioPlayer = value;
  }
}
上一篇下一篇

猜你喜欢

热点阅读