You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中Audio Service如何实现音频曲目选择、播放及切换?

Flutter Audio Service 实现曲目选择、播放与切换方案

1. 添加依赖

在pubspec.yaml中加入核心依赖:

dependencies:
  audio_service: ^0.18.11
  just_audio: ^0.9.34
  rxdart: ^0.27.7

2. 实现自定义AudioHandler

AudioHandler是Audio Service的核心控制层,负责对接播放器与系统音频服务:

import 'package:audio_service/audio_service.dart';
import 'package:just_audio/just_audio.dart';
import 'package:rxdart/rxdart.dart';

class AudioPlayerHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
  final _player = AudioPlayer();
  final _playlist = ConcatenatingAudioSource(children: []);

  AudioPlayerHandler() {
    // 同步播放器状态到AudioService
    _player.playbackEventStream.map(_transformEvent).pipe(playbackState);
    // 同步当前播放曲目到UI
    _player.currentIndexStream.listen((index) {
      if (index != null && index < queue.value.length) {
        mediaItem.add(queue.value[index]);
      }
    });
  }

  // 转换JustAudio事件为AudioService标准状态
  PlaybackState _transformEvent(PlaybackEvent event) {
    return PlaybackState(
      controls: [
        MediaControl.skipToPrevious,
        _player.playing ? MediaControl.pause : MediaControl.play,
        MediaControl.skipToNext,
      ],
      systemActions: const {MediaAction.seek, MediaAction.skipToNext, MediaAction.skipToPrevious},
      androidCompactActionIndices: const [0, 1, 2],
      processingState: {
        ProcessingState.idle: AudioProcessingState.idle,
        ProcessingState.loading: AudioProcessingState.loading,
        ProcessingState.buffering: AudioProcessingState.buffering,
        ProcessingState.ready: AudioProcessingState.ready,
        ProcessingState.completed: AudioProcessingState.completed,
      }[_player.processingState]!,
      playing: _player.playing,
      updatePosition: _player.position,
      bufferedPosition: _player.bufferedPosition,
      speed: _player.speed,
      queueIndex: event.currentIndex,
    );
  }

  // 更新播放队列
  @override
  Future<void> updateQueue(List<MediaItem> queue) async {
    await _playlist.clear();
    await _playlist.addAll(queue.map((item) => AudioSource.uri(Uri.parse(item.id))));
    this.queue.add(queue);
  }

  // 跳转到指定队列索引曲目并播放
  @override
  Future<void> skipToQueueItem(int index) async {
    if (index < 0 || index >= queue.value.length) return;
    await _player.seek(Duration.zero, index: index);
    await play();
  }

  // 播放控制
  @override
  Future<void> play() => _player.play();
  @override
  Future<void> pause() => _player.pause();

  // 切换上下曲
  @override
  Future<void> skipToPrevious() async {
    _player.hasPrevious ? await _player.seekToPrevious() : await _player.seek(Duration.zero);
  }

  @override
  Future<void> skipToNext() async {
    _player.hasNext ? await _player.seekToNext() : await _player.seek(Duration.zero, index: 0);
  }

  // 资源释放
  Future<void> dispose() async {
    await _player.dispose();
    await super.dispose();
  }
}

3. 初始化Audio Service

在App启动时完成服务初始化:

void main() async {
  await AudioService.init(
    builder: () => AudioPlayerHandler(),
    config: const AudioServiceConfig(
      androidNotificationChannelId: 'com.yourdomain.audio',
      androidNotificationChannelName: '音频播放器',
      androidNotificationOngoing: true,
    ),
  );
  runApp(const MyApp());
}

4. UI层实现交互逻辑

在页面中完成曲目列表展示与播放控制:

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

class AudioPlayerPage extends StatefulWidget {
  const AudioPlayerPage({super.key});

  @override
  State<AudioPlayerPage> createState() => _AudioPlayerPageState();
}

class _AudioPlayerPageState extends State<AudioPlayerPage> {
  late AudioHandler _audioHandler;
  final List<MediaItem> _tracks = [
    MediaItem(
      id: 'https://example.com/track1.mp3',
      title: '曲目1',
      artist: '艺术家A',
      album: '专辑X',
      duration: const Duration(minutes: 3, seconds: 45),
    ),
    MediaItem(
      id: 'https://example.com/track2.mp3',
      title: '曲目2',
      artist: '艺术家B',
      album: '专辑Y',
      duration: const Duration(minutes: 4, seconds: 12),
    ),
    MediaItem(
      id: 'https://example.com/track3.mp3',
      title: '曲目3',
      artist: '艺术家C',
      album: '专辑Z',
      duration: const Duration(minutes: 2, seconds: 58),
    ),
  ];

  @override
  void initState() {
    super.initState();
    _audioHandler = AudioService.instance;
    _audioHandler.updateQueue(_tracks);
  }

  @override
  void dispose() {
    _audioHandler.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('音频播放器')),
      body: Column(
        children: [
          // 曲目选择列表
          Expanded(
            child: ListView.builder(
              itemCount: _tracks.length,
              itemBuilder: (context, index) {
                final track = _tracks[index];
                return ListTile(
                  title: Text(track.title),
                  subtitle: Text(track.artist!),
                  onTap: () => _audioHandler.skipToQueueItem(index),
                );
              },
            ),
          ),
          // 播放控制栏
          StreamBuilder<PlaybackState>(
            stream: _audioHandler.playbackState,
            builder: (context, snapshot) {
              final isPlaying = snapshot.data?.playing ?? false;
              return Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  IconButton(
                    icon: const Icon(Icons.skip_previous),
                    iconSize: 48,
                    onPressed: _audioHandler.skipToPrevious,
                  ),
                  IconButton(
                    icon: Icon(isPlaying ? Icons.pause : Icons.play_arrow),
                    iconSize: 64,
                    onPressed: isPlaying ? _audioHandler.pause : _audioHandler.play,
                  ),
                  IconButton(
                    icon: const Icon(Icons.skip_next),
                    iconSize: 48,
                    onPressed: _audioHandler.skipToNext,
                  ),
                ],
              );
            },
          ),
        ],
      ),
    );
  }
}

核心逻辑说明

  • 曲目选择:点击列表项调用skipToQueueItem(index),直接定位到指定曲目并触发播放
  • 切换逻辑:skipToPrevious()/skipToNext()自动处理首尾循环,可根据需求修改为停止播放或其他逻辑
  • 状态同步:通过StreamBuilder监听播放状态与当前曲目,实时更新UI显示

内容的提问来源于stack exchange,提问作者WD SANKA MADURANGA

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 04:15:25