IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> flutter -- 自定义音乐播放器/视频播放器 -> 正文阅读

[移动开发]flutter -- 自定义音乐播放器/视频播放器


写在前头

flutter 自定义实现音乐播放的文章挺多的,但是在开发中还是碰见了超级无语的情况, 没想到需求竟然要音频的1倍到2倍的播放倍速, 我一度质疑这个功能的实际用途,但是既然提出来了, 开发就得撅屁股实现,这里采用了第三方的视频播放器来实现倍速效果,具体如下


一、实现效果

在这里插入图片描述

二、使用步骤

1.引入库

代码如下(示例):

just_audio: ^0.8.0//视频播放器
/*flutter_seekbar 方便实现进度条分多节的效果, 其他seekbar也能实现,可以不引入*/
flutter_seekbar:
    git: https://github.com/LiuC520/flutter_seekbar.git

2.关键代码

1. 初始化播放器关键代码如下(示例):

class _audioplayerState extends State<audioplayer> {
  //初始化播放器
  AudioPlayer audioPlayer = AudioPlayer();

  //异步获取流
  Stream<PositionData> get _positionDataStream =>
      Rx.combineLatest3<Duration, Duration, Duration?, PositionData>(
          audioPlayer.positionStream,
          audioPlayer.bufferedPositionStream,
          audioPlayer.durationStream,
              (position, bufferedPosition, duration) =>
              PositionData(
                  position, bufferedPosition, duration ?? Duration.zero));


  @override
  void initState() {
    super.initState();
    //设置播放参数
    _init();
  }

  Future<void> _init() async {
    //通知操作系统我们的应用程序的音频属性等。
    //我们为播放speech.speech的应用程序选择一个合理的默认值。
    final session = await AudioSession.instance;
    await session.configure(AudioSessionConfiguration.speech());
    //播放时收听错误。
    audioPlayer.playbackEventStream.listen((event) {},
        onError: (Object e, StackTrace stackTrace) {
          print('发生流错误: $e');
        });
    // 尝试从源加载音频并捕获任何错误。
    try {
      await audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(
          "https://demo.dj63.com//2019/user_up/dj1523281909/20201103/01363f2c17b7119f023aa17c20f1be5f.mp3")));
    } catch (e) {
      print("加载音频源时出错: $e");
    }
  }


  @override
  void dispose() {
    //页面关闭处理
    audioPlayer.dispose();
    super.dispose();
  }
}

2. 倍速进度条关键代码如下(示例):

Expanded(
            child: Container(
                padding: EdgeInsets.fromLTRB(10, 6, 0, 6),
                width: 60,
                child: SeekBar(
                  progresseight: 2,
                  min: 0,
                  max: 2,
                  //value,设置滑动条值, 从Provider取值
                  value: AudioPlayerinfo.speed_progress,
                  sectionCount: 4,
                  indicatorRadius: 8.0,
                  //sectionUnSelecteColor: Colors.white,
                  backgroundColor: Colors.white,
                  //bubbleColor: Colors.white,
                  progressColor: Colors.white,


                  onValueChanged: (v) async {
                    //用Provider去记录滑动条倍速,
                    AudioPlayerinfo.setspeed_progress(v.value);
                    //调整播放器的播放倍速
                    await audioPlayer.setSpeed(v.value+1);
                  },

                )
            ),


          )

3. 音频播放器的进度条关键代码如下,这里采用自定义的seekbar, 方便赋值状态(示例):

Scaffold(
                //设置播放器背景透明
                backgroundColor:Color(0x00000000),

               //使用StreamBuilder,异步加载WidgetUI
              body: StreamBuilder<PositionData>(
                    stream: positionDataStream,
                    builder: (context, snapshot) {
                      final positionData = snapshot.data;
                      //获取当前播放进度毫秒,并记录
                      AudioPlayerinfo.setprogress(positionData?.position.inSeconds ?? 0);
                      return SeekBarlayout(
                        duration: positionData?.duration ?? Duration.zero,
                        position: positionData?.position ?? Duration.zero,
                        bufferedPosition:
                        positionData?.bufferedPosition ?? Duration.zero,
                        onChangeEnd: audioPlayer.seek,

                      );
                    },
                  ),




              ),

4. 自定义进度条SeekBarlayout 代码(示例):

import 'dart:math';

import 'package:flutter/material.dart';

class SeekBarlayout extends StatefulWidget {
  final Duration duration;
  final Duration position;
  final Duration bufferedPosition;
  final ValueChanged<Duration>? onChanged;
  final ValueChanged<Duration>? onChangeEnd;

  SeekBarlayout({
    required this.duration,
    required this.position,
    required this.bufferedPosition,
    this.onChanged,
    this.onChangeEnd,
  });

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

class _SeekBarState extends State<SeekBarlayout> {
  double? _dragValue;
  late SliderThemeData _sliderThemeData;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();

    _sliderThemeData = SliderTheme.of(context).copyWith(
      trackHeight: 2.0,
      overlayColor: Colors.white,
      thumbColor:Colors.white,



    );
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.fromLTRB(0, 0, 0, 0),
      padding: EdgeInsets.fromLTRB(0, 0, 0, 0),
      child:  Stack(


        children: [
          SliderTheme(

            data: _sliderThemeData.copyWith(
              thumbShape: HiddenThumbComponentShape(),
              activeTrackColor: Colors.white,
              inactiveTrackColor: Colors.white,


            ),
            child: ExcludeSemantics(
              child: Slider(
                min: 0.0,
                max: widget.duration.inMilliseconds.toDouble(),
                value: min(widget.bufferedPosition.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;
                },
              ),
            ),
          ),
          SliderTheme(
            data: _sliderThemeData.copyWith(
              inactiveTrackColor: Colors.transparent,
            ),
            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;
              },
            ),
          ),
          Positioned(
            right: 16.0,
            bottom: 0.0,
            child: Text(
              RegExp(r'((^0*[1-9]\d*:)?\d{2}:\d{2})\.\d+$')
                  .firstMatch("$duration")
                  ?.group(1) ??
                  '$duration',
              style: TextStyle(
                fontSize: 14,
                fontWeight: FontWeight.w700,
                color: Colors.white,
                decoration: TextDecoration.none,
              ),),
          ),
          Positioned(
            left: 16.0,
            bottom: 0.0,
            child: Text(RegExp(r'((^0*[1-9]\d*:)?\d{2}:\d{2})\.\d+$')
                .firstMatch("$position")
                ?.group(1) ??
                '$position',
              style: TextStyle(
                fontSize: 14,
                fontWeight: FontWeight.w700,
                color: Colors.white,
                decoration: TextDecoration.none,
              ),),
          ),
        ],
      ),

    );


  }

  Duration get _remaining => widget.duration - widget.position;

  Duration get duration => widget.duration;

  Duration get position => widget.position;
}

class HiddenThumbComponentShape extends SliderComponentShape {
  @override
  Size getPreferredSize(bool isEnabled, bool isDiscrete) => Size.zero;

  @override
  void paint(
      PaintingContext context,
      Offset center, {
        required Animation<double> activationAnimation,
        required Animation<double> enableAnimation,
        required bool isDiscrete,
        required TextPainter labelPainter,
        required RenderBox parentBox,
        required SliderThemeData sliderTheme,
        required TextDirection textDirection,
        required double value,
        required double textScaleFactor,
        required Size sizeWithOverflow,
      }) {}
}

class PositionData {
  final Duration position;
  final Duration bufferedPosition;
  final Duration duration;

  PositionData(this.position, this.bufferedPosition, this.duration);
}

void showSliderDialog({
  required BuildContext context,
  required String title,
  required int divisions,
  required double min,
  required double max,
  String valueSuffix = '',
  required Stream<double> stream,
  required ValueChanged<double> onChanged,
}) {
  showDialog<void>(
    context: context,
    builder: (context) => AlertDialog(
      title: Text(title, textAlign: TextAlign.center),
      content: StreamBuilder<double>(
        stream: stream,
        builder: (context, snapshot) => Container(
          height: 60,
          width: 100,
          child: Column(
            children: [
              Text('${snapshot.data?.toStringAsFixed(1)}$valueSuffix',
                  style: TextStyle(
                      fontFamily: 'Fixed',
                      fontWeight: FontWeight.bold,
                      fontSize: 24.0)),
              Slider(
                divisions: divisions,
                min: min,
                max: max,
                value: snapshot.data ?? 1.0,
                onChanged: onChanged,
              ),
            ],
          ),
        ),
      ),
    ),
  );
}

5. 播放器暂停与播放, 以及快进5秒,回退5秒的关键代码(示例):

 GestureDetector(
                   child: Image.asset("assets/tui5.png",
                     width: 40,
                     height: 38,
                     fit: BoxFit.cover,
                     colorBlendMode: BlendMode.darken,
                   ),onTap: () async {
                      //异步回退5秒,
                     if(AudioPlayerinfo.progress > 5){
                       await audioPlayer.seek( Duration(seconds: AudioPlayerinfo.progress-5) );
                     }
                     //播放音乐

                   if(!AudioPlayerinfo.isPlaying){
                     audioPlayer.play();
                     AudioPlayerinfo.set_isPlaying(!AudioPlayerinfo.isPlaying);
                   }

                 },

                 ),Container(
                   margin: EdgeInsets.only(left: 40, right: 40),
                   child: GestureDetector(
                     onTap: () {
                       //记录播放状态
                       AudioPlayerinfo.set_isPlaying(!AudioPlayerinfo.isPlaying);
                       if(AudioPlayerinfo.isPlaying){
                         audioPlayer.stop();
                       }else{
                         audioPlayer.play();
                       }
                     },
                     child: Image.asset(AudioPlayerinfo.isPlaying
                         ? "assets/stop.png"
                         : "assets/player.png",
                       width: 54,
                       height: 54,
                       fit: BoxFit.cover,
                       colorBlendMode: BlendMode.darken,

                     ),
                   ),


                 ), GestureDetector(
                          child: Image.asset("assets/jin5.png",
                            width: 40,
                            height: 38,
                            fit: BoxFit.cover,
                            colorBlendMode: BlendMode.darken,
                          ),onTap: () async {
                           //播放快5秒
                           await audioPlayer.seek( Duration(seconds: AudioPlayerinfo.progress+5) );
                           if(!AudioPlayerinfo.isPlaying){
                             audioPlayer.play();
                             AudioPlayerinfo.set_isPlaying(!AudioPlayerinfo.isPlaying);
                           }


                      },

                        ),

总结

整体实现是非常简单的,只要对flutter的组件有所了解就能很快写出来,更多功能参数可以参考资料https://pub.flutter-io.cn/packages/just_audio,希望能够帮助到您。。。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-14 11:02:31  更:2021-07-14 11:04:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/2 2:10:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码