• 作者:老汪软件技巧
  • 发表时间:2024-12-27 21:06
  • 浏览量:

音视频播放是许多应用程序中的关键功能,能够提供丰富的多媒体体验。本文将介绍如何在 Flutter 中实现音视频播放功能,并提供具体的代码

视频

播放视频可以安装 video_player,最新版本可以在 pub.dev 中查看

dependencies:
  flutter:
    sdk: flutter
  video_player: ^2.9.2

要求实现的具体功能是,点击视频,显示底部的工具栏,工具栏中有一个按钮在最下方,用于切换播放和暂停,上方有进度条,支持拖拽滑动,进度条左侧是当前播放的进度,右边是总时间

以下是具体代码

import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class VideoComponent extends StatefulWidget {
  const VideoComponent({super.key});
  @override
  VideoComponentState createState() => VideoComponentState();
}
class VideoComponentState extends State<VideoComponent> {
  late VideoPlayerController _controller;
  bool _showControls = false;
  Duration _position = Duration.zero;
  @override
  void initState() {
    super.initState();
    _controller = VideoPlayerController.networkUrl(
      Uri.parse(
          'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4'),
    )..initialize().then((_) {
        setState(() {});
      });
    // 添加监听器
    _controller.addListener(() {
      setState(() {
        _position = _controller.value.position;
      });
    });
  }
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  String _formatDuration(Duration duration) {
    String twoDigits(int n) => n.toString().padLeft(2, '0');
    final hours = duration.inHours;
    final minutes = duration.inMinutes.remainder(60);
    final seconds = duration.inSeconds.remainder(60);
    return [
      if (hours > 0) twoDigits(hours),
      twoDigits(minutes),
      twoDigits(seconds),
    ].join(':');
  }
  @override
  Widget build(BuildContext context) {
    return Center(
      child: _controller.value.isInitialized
          ? Stack(
              alignment: Alignment.bottomCenter,
              children: [
                GestureDetector(
                  onTap: () {
                    setState(() {
                      _showControls = !_showControls;
                    });
                  },
                  child: AspectRatio(
                    aspectRatio: _controller.value.aspectRatio,
                    child: VideoPlayer(_controller),
                  ),
                ),
                if (_showControls) _buildControls(),
              ],
            )
          : const CircularProgressIndicator(),
    );
  }
  Widget _buildControls() {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 8.0),
              child: Text(
                _formatDuration(_position),
                style: const TextStyle(color: Colors.white),
              ),
            ),
            Expanded(
              child: VideoProgressIndicator(
                _controller,
                allowScrubbing: true,
              ),
            ),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 8.0),
              child: Text(
                _formatDuration(_controller.value.duration),
                style: const TextStyle(color: Colors.white),
              ),
            ),
          ],
        ),
        IconButton(
          onPressed: () {
            setState(() {
              _controller.value.isPlaying
                  ? _controller.pause()
                  : _controller.play();
            });
          },
          icon: Icon(
            _controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
            color: Colors.white,
            size: 30.0,
          ),
        ),
      ],
    );
  }
}

flutter 开发笔记(七):音视频_flutter 开发笔记(七):音视频_

_controller 是视频播放器的控制器,管理视频的播放、暂停等操作

_showControls 是一个布尔值,用于控制是否显示播放控件

_position 存储当前播放进度,addListener 用于监听视频播放进度的变化,并在进度更新时调用 setState,如果不监听,直接使用 _formatDuration(controller.value.position) 则无法实时获取进度

音频

播放音频采用的是 just_audio,最新版本同样在 pub.dev 中查看

要求实现的具体功能是左边是一个按钮,用于切换播放和暂停,右边有一个进度条,支持拖拽滑动,进度条左边是当前播放的进度,进度条右边是总时间

以下是具体代码

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
class SoundComponent extends StatefulWidget {
  const SoundComponent({super.key});
  @override
  SoundComponentState createState() => SoundComponentState();
}
class SoundComponentState extends State<SoundComponent> {
  late AudioPlayer _player;
  Duration _duration = Duration.zero;
  Duration _position = Duration.zero;
  late StreamSubscription<Duration?> _durationSubscription;
  late StreamSubscription<Duration> _positionSubscription;
  late StreamSubscription _playerStateSubscription;
  @override
  void initState() {
    super.initState();
    _player = AudioPlayer();
    _init();
    _durationSubscription = _player.durationStream.listen((duration) {
      setState(() {
        _duration = duration ?? Duration.zero;
      });
    });
    _positionSubscription = _player.positionStream.listen((position) {
      setState(() {
        _position = position;
      });
    });
    _playerStateSubscription = _player.playerStateStream.listen((state) {
      if (state.processingState == ProcessingState.ready) {
        setState(() {
          _duration = _player.duration ?? Duration.zero;
        });
      }
    });
  }
  Future<void> _init() async {
    try {
      await _player.setUrl(
          'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3'); // 设置音频 URL
    } catch (e) {
      print("Error loading audio: $e");
    }
  }
  @override
  void dispose() {
    _durationSubscription.cancel();
    _positionSubscription.cancel();
    _playerStateSubscription.cancel();
    _player.dispose();
    super.dispose();
  }
  String _formatDuration(Duration duration) {
    String twoDigits(int n) => n.toString().padLeft(2, '0');
    final minutes = duration.inMinutes.remainder(60);
    final seconds = duration.inSeconds.remainder(60);
    return '${twoDigits(minutes)}:${twoDigits(seconds)}';
  }
  @override
  Widget build(BuildContext context) {
    return Center(
        child: Container(
            padding: const EdgeInsets.all(10),
            color: Colors.blue,
            child: Row(
              children: [
                GestureDetector(
                  onTap: () {
                    setState(() {
                      if (_player.playing) {
                        _player.pause();
                      } else {
                        _player.play();
                      }
                    });
                  },
                  child: Container(
                    width: 48.0,
                    height: 48.0,
                    child: Icon(
                      _player.playing ? Icons.pause : Icons.play_arrow,
                      color: Colors.white,
                      size: 32.0,
                    ),
                  ),
                ),
                Text(_formatDuration(Duration(seconds: _position.inSeconds))),
                Expanded(
                    child: Slider(
                  value: _position.inSeconds.toDouble(),
                  max: _duration.inSeconds.toDouble(),
                  onChanged: (value) {
                    _player.seek(Duration(seconds: value.toInt()));
                  },
                )),
                Text(_formatDuration(Duration(seconds: _duration.inSeconds))),
              ],
            )));
  }
}

在代码中,我们创建 AudioPlayer 实例并调用 _init 方法,然后订阅 durationStream 和 positionStream,监听音频时长和位置的变化,并在变化时更新状态,为了避免内存泄漏问题,在 dispose 方法中取消流的订阅并释放音频播放器的资源

_playerStateSubscription 的作用是为了不需要通过点击左侧按钮就可以获取总时长,大部分情况下如果你设置了监听 playState 并获取总时长的操作的话,实际上是不需要 _durationSubscription 的,上述代码也一并列出