一、插件介绍

fluttertpc_just_audio 是基于 just_audio 开发的鸿蒙系统适配版本,为 Flutter 开发者提供在鸿蒙平台上进行音频播放的强大功能。该插件支持音频的加载、播放、暂停、音量控制、速度调节等核心功能,帮助开发者轻松实现音频播放需求。

主要特性

  • 支持从网络 URI 加载音频资源
  • 提供播放/暂停控制
  • 支持音量调节和播放速度控制
  • 实现音频进度条和定位功能
  • 支持多音频源的串联播放
  • 基于原生鸿蒙实现,性能优秀

二、安装与配置

1. 添加依赖

在项目的 pubspec.yaml 文件中添加以下依赖:

dependencies:
  just_audio_ohos:
    git:
      url: "https://gitcode.com/openharmony-sig/fluttertpc_just_audio"
      path: "just_audio/ohos"

执行以下命令获取依赖:

flutter pub get

2. 配置音频会话(可选)

根据需要,可以配置音频会话以适应不同的音频播放场景(如音乐、语音等):

import 'package:audio_session/audio_session.dart';

final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.speech());

三、API 说明

核心类

AudioPlayer

音频播放器的核心类,用于控制音频的加载、播放、暂停等操作。

主要方法

方法名 描述 返回值 鸿蒙支持
init() 创建一个新的平台播放器 Future<AudioPlayerPlatform> yes
load() 加载音频源 Future<LoadResponse> yes
play() 在当前位置播放音频 Future<PlayResponse> yes
pause() 暂停播放 Future<PauseResponse> yes
setVolume() 设置音量(0.0-1.0) Future<SetVolumeResponse> yes
setSpeed() 设置播放速度 Future<SetSpeedResponse> yes
seek() 定位到指定位置 Future<SeekResponse> yes
disposePlayer() 释放播放器资源 Future<DisposePlayerResponse> yes

主要流

流名称 描述 鸿蒙支持
positionStream 播放位置流 yes
bufferedPositionStream 缓冲位置流 yes
durationStream 音频时长流 yes
playbackEventStream 播放事件流 yes
playerStateStream 播放器状态流 yes

四、使用示例

完整示例代码

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:just_audio/just_audio.dart';
import 'package:rxdart/rxdart.dart';

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

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  
  MyAppState createState() => MyAppState();
}

class MyAppState extends State<MyApp> {
  final _player = AudioPlayer();

  
  void initState() {
    super.initState();
    SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
      statusBarColor: Colors.black,
    ));
    _initPlayer();
  }

  Future<void> _initPlayer() async {
    // 监听播放错误
    _player.playbackEventStream.listen((event) {},
        onError: (Object e, StackTrace stackTrace) {
      print('播放错误: $e');
    });

    // 加载音频源
    try {
      await _player.setAudioSource(AudioSource.uri(Uri.parse(
          "https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3")));
    } on PlayerException catch (e) {
      print("加载音频失败: $e");
    }
  }

  
  void dispose() {
    _player.dispose();
    super.dispose();
  }

  /// 组合位置、缓冲位置和时长流,用于显示进度条
  Stream<PositionData> get _positionDataStream =>
      Rx.combineLatest3<Duration, Duration, Duration?, PositionData>(
          _player.positionStream,
          _player.bufferedPositionStream,
          _player.durationStream,
          (position, bufferedPosition, duration) => PositionData(
              position, bufferedPosition, duration ?? Duration.zero));

  
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: SafeArea(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // 播放控制按钮
              ControlButtons(_player),
              // 进度条
              StreamBuilder<PositionData>(
                stream: _positionDataStream,
                builder: (context, snapshot) {
                  final positionData = snapshot.data;
                  return SeekBar(
                    duration: positionData?.duration ?? Duration.zero,
                    position: positionData?.position ?? Duration.zero,
                    bufferedPosition: positionData?.bufferedPosition ?? Duration.zero,
                    onChangeEnd: _player.seek,
                  );
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

/// 播放控制按钮组件
class ControlButtons extends StatelessWidget {
  final AudioPlayer player;

  const ControlButtons(this.player, {Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        // 音量控制
        IconButton(
          icon: const Icon(Icons.volume_up),
          onPressed: () {
            showSliderDialog(
              context: context,
              title: "调整音量",
              divisions: 10,
              min: 0.0,
              max: 1.0,
              value: player.volume,
              stream: player.volumeStream,
              onChanged: player.setVolume,
            );
          },
        ),

        // 播放/暂停按钮
        StreamBuilder<PlayerState>(
          stream: player.playerStateStream,
          builder: (context, snapshot) {
            final playerState = snapshot.data;
            final processingState = playerState?.processingState;
            final playing = playerState?.playing;
            
            // 加载或缓冲中显示进度指示器
            if (processingState == ProcessingState.loading ||
                processingState == ProcessingState.buffering) {
              return Container(
                margin: const EdgeInsets.all(8.0),
                width: 64.0,
                height: 64.0,
                child: const CircularProgressIndicator(),
              );
            } 
            // 未播放状态显示播放按钮
            else if (playing != true) {
              return IconButton(
                icon: const Icon(Icons.play_arrow),
                iconSize: 64.0,
                onPressed: player.play,
              );
            } 
            // 播放中显示暂停按钮
            else if (processingState != ProcessingState.completed) {
              return IconButton(
                icon: const Icon(Icons.pause),
                iconSize: 64.0,
                onPressed: player.pause,
              );
            } 
            // 播放完成显示重播按钮
            else {
              return IconButton(
                icon: const Icon(Icons.replay),
                iconSize: 64.0,
                onPressed: () => player.seek(Duration.zero),
              );
            }
          },
        ),
        
        // 播放速度控制
        StreamBuilder<double>(
          stream: player.speedStream,
          builder: (context, snapshot) => IconButton(
            icon: Text("${snapshot.data?.toStringAsFixed(1)}x",
                style: const TextStyle(fontWeight: FontWeight.bold)),
            onPressed: () {
              showSliderDialog(
                context: context,
                title: "调整播放速度",
                divisions: 10,
                min: 0.5,
                max: 1.5,
                value: player.speed,
                stream: player.speedStream,
                onChanged: player.setSpeed,
              );
            },
          ),
        ),
      ],
    );
  }
}

/// 进度条组件
class SeekBar extends StatefulWidget {
  final Duration duration;
  final Duration position;
  final Duration bufferedPosition;
  final ValueChanged<Duration>? onChanged;
  final ValueChanged<Duration>? onChangeEnd;

  const SeekBar({
    Key? key,
    required this.duration,
    required this.position,
    required this.bufferedPosition,
    this.onChanged,
    this.onChangeEnd,
  }) : super(key: key);

  
  _SeekBarState createState() => _SeekBarState();
}

class _SeekBarState extends State<SeekBar> {
  double? _dragValue;

  
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Slider(
          min: 0.0,
          max: widget.duration.inMilliseconds.toDouble(),
          value: widget.bufferedPosition.inMilliseconds.toDouble(),
          onChanged: (value) {
            setState(() {
              _dragValue = value;
            });
            widget.onChanged?.call(Duration(milliseconds: value.round()));
          },
          onChangeEnd: (value) {
            widget.onChangeEnd?.call(Duration(milliseconds: value.round()));
            _dragValue = null;
          },
          activeColor: Colors.blue.shade200,
          inactiveColor: Colors.grey.shade300,
        ),
        Slider(
          min: 0.0,
          max: widget.duration.inMilliseconds.toDouble(),
          value: _dragValue ?? widget.position.inMilliseconds.toDouble(),
          onChanged: (value) {
            setState(() {
              _dragValue = value;
            });
            widget.onChanged?.call(Duration(milliseconds: value.round()));
          },
          onChangeEnd: (value) {
            widget.onChangeEnd?.call(Duration(milliseconds: value.round()));
            _dragValue = null;
          },
          activeColor: Colors.blue,
          inactiveColor: Colors.transparent,
        ),
      ],
    );
  }
}

/// 滑块对话框组件
void showSliderDialog({
  required BuildContext context,
  required String title,
  required int divisions,
  required double min,
  required double max,
  String valueSuffix = '',
  required double value,
  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) => SizedBox(
          height: 100.0,
          child: Column(
            children: [
              Text('${snapshot.data?.toStringAsFixed(1)}$valueSuffix',
                  style: const TextStyle(
                      fontFamily: 'Fixed',
                      fontWeight: FontWeight.bold,
                      fontSize: 24.0)),
              Slider(
                divisions: divisions,
                min: min,
                max: max,
                value: snapshot.data ?? value,
                onChanged: onChanged,
              ),
            ],
          ),
        ),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('关闭'),
        ),
      ],
    ),
  );
}

/// 位置数据类
class PositionData {
  final Duration position;
  final Duration bufferedPosition;
  final Duration duration;

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

关键步骤说明

  1. 初始化播放器
    创建 AudioPlayer 实例,配置音频会话,并加载音频源。

  2. 播放控制
    通过 play()pause() 方法控制音频的播放状态。

  3. 音量和速度调节
    使用 setVolume()setSpeed() 方法调整音频的音量和播放速度。

  4. 进度控制
    通过 positionStreambufferedPositionStreamdurationStream 实时获取播放进度,并使用 seek() 方法定位到指定位置。

  5. 资源管理
    在组件销毁时调用 dispose() 方法释放播放器资源。

五、鸿蒙平台注意事项

  1. 兼容性要求

    • Flutter 版本:3.7.12-ohos-1.0.6 或 3.22.1-ohos-1.0.1
    • HarmonyOS SDK:5.0.0(12)
    • DevEco Studio:5.0.13.200
  2. 不支持的功能

    • 循环播放模式
    • 随机播放模式
    • 音调控制
    • 跳过静音功能
    • 音频效果(均衡器、响度增强器等)
    • 网络音频元数据获取
  3. 音频源支持
    目前主要支持从网络 URI 加载音频资源,本地文件支持可能有限。

六、总结

fluttertpc_just_audio 是一个功能强大的音频播放插件,为鸿蒙平台提供了核心的音频播放能力。虽然在鸿蒙平台上某些高级功能尚未支持,但已经能够满足大多数基础音频播放需求。开发者可以通过简单的 API 调用实现音频的加载、播放、暂停、音量控制等功能,快速构建音频应用。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐