FLutter视频播放器

114 阅读2分钟

FLutter视频播放器

使用前的准备工作

1.添加video_player

flutter pub add video_player 

2.添加chewie

flutter pub add chewie

3.添加相应的权限

  • 如果是在android中,需要向AndroidManifest.xml文件中添加类似下面的内容 

     <uses-permission android:name="android.permission.INTERNET"/>
    
  • 在IOS中则需要在Info.plist中添加下面的内容

    <key>NSAppTransportSecurity</key>
    <dict>
      <key>NSAllowsArbitraryLoads</key>
      <true/>
    </dict>
    

在flutter中使用

import 'package:anime_video/page/player/video/video_ui_page.dart';
import 'package:flutter/material.dart';
import 'package:chewie/chewie.dart';
import 'package:video_player/video_player.dart';
class VideoPlayer extends StatefulWidget {
  // 视频URL
  final String videoUrl;
  const VideoPlayer({super.key, required this.videoUrl});
  @override
  State<VideoPlayer> createState() => _VideoPlayerState();
}
class _VideoPlayerState extends State<VideoPlayer> {
  late VideoPlayerController _videoPlayerController;
  ChewieController? _chewieController;
  bool isFullScreen = false;
  @override
  void initState() {
    super.initState();
    _initializePlayer();
  }
  Future<void> _initializePlayer() async {
    _videoPlayerController = VideoPlayerController.networkUrl(
      Uri.parse(widget.videoUrl),
    );
    await _videoPlayerController.initialize();
    _chewieController = ChewieController(
      videoPlayerController: _videoPlayerController,
      autoPlay: true,
      looping: false,
      aspectRatio: _videoPlayerController.value.aspectRatio,
      // 自定义控制栏
      customControls: VideoUiPage(
        videoPlayerController: _videoPlayerController,
        onToggleFullScreen: _toggleFullScreen,
      ),
    );
    setState(() {});
  }
  // 全屏、半屏切换
  void _toggleFullScreen() {
    if (_chewieController != null) {
      if (_chewieController!.isFullScreen) {
        _chewieController!.exitFullScreen();
      } else {
        _chewieController!.enterFullScreen();
      }
    }
  }
  @override
  Widget build(BuildContext context) {
    return Center(
      // ChewieController如果没有初始化完成,则显示加载框
      child: _chewieController != null
          ? AspectRatio(
              aspectRatio: _videoPlayerController.value.aspectRatio,
              child: Chewie(controller: _chewieController!),
            )
          : CircularProgressIndicator(),
    );
  }

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

自定义控制栏

import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:chewie/chewie.dart';

class VideoUiPage extends StatefulWidget {
  // 视频播放器控制器
  final VideoPlayerController videoPlayerController;

  // 全屏切换回调
  final VoidCallback onToggleFullScreen;

  const VideoUiPage({
    super.key,
    required this.videoPlayerController,
    required this.onToggleFullScreen,
  });

  @override
  State<VideoUiPage> createState() => _VideoUiPageState();
}

class _VideoUiPageState extends State<VideoUiPage> {
  // 播放器是否在播放
  bool get isPlaying => widget.videoPlayerController.value.isPlaying;

  // 当前播放位置
  Duration get position => widget.videoPlayerController.value.position;

  // 视频总时长
  Duration get duration => widget.videoPlayerController.value.duration;

  // 音量
  double get videoVolume => widget.videoPlayerController.value.volume;

  // 进度
  double get progress => position.inMilliseconds / duration.inMilliseconds;

  // 获取缓冲进度
  double get bufferedProgress {
    if (widget.videoPlayerController.value.buffered.isEmpty) {
      return 0.0;
    }
    // 获取最新的缓冲位置
    final buffered = widget.videoPlayerController.value.buffered.last;
    //计算缓冲结束时间占总时长的比例,作为缓冲进度返回
    return buffered.end.inMilliseconds / duration.inMilliseconds;
  }

  // 视频是否正在缓冲
  bool get isBuffering => widget.videoPlayerController.value.isBuffering;

  @override
  void initState() {
    super.initState();
    widget.videoPlayerController.addListener(() {
      if (mounted) {
        setState(() {});
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    // 通过上下文获取ChewieController
    final chewieController = ChewieController.of(context);

    return Stack(
      children: [
        // 顶部控制栏
        Positioned(
          top: 0,
          left: 0,
          right: 0,
          child: Container(
            padding: const EdgeInsets.all(2),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                // 返回按钮
                IconButton(
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                  icon: Icon(Icons.arrow_back, color: Colors.white, size: 24),
                ),
                // 标题
                // 设置按钮
                IconButton(
                  onPressed: () {
                    _showSettingsDialog(context, widget.videoPlayerController);
                  },
                  icon: const Icon(
                    Icons.settings,
                    color: Colors.white,
                    size: 24,
                  ),
                ),
              ],
            ),
          ),
        ),

        // 底部控制栏
        Positioned(
          bottom: 0,
          left: 0,
          right: 0,

          child: Column(
            children: [
              // 时间显示
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 16.0),
                child: Align(
                  alignment: Alignment.centerLeft,
                  child: Text(
                    '${_formatDuration(position)} / ${_formatDuration(duration)}',
                    style: const TextStyle(color: Colors.white, fontSize: 12),
                  ),
                ),
              ),
              // 底部控制栏
              Row(
                children: [
                  // 播放/暂停按钮
                  IconButton(
                    onPressed: () {
                      if (isPlaying) {
                        widget.videoPlayerController.pause();
                      } else {
                        widget.videoPlayerController.play();
                      }
                    },
                    icon: Icon(
                      isBuffering
                          ? Icons.pause
                          : isPlaying
                          ? Icons.pause
                          : Icons.play_arrow,
                      color: Colors.white,
                      size: 35,
                    ),
                  ),

                  // 音量控制
                  IconButton(
                    onPressed: () {
                      final newVolume = videoVolume > 0 ? 0.0 : 1.0;
                      widget.videoPlayerController.setVolume(newVolume);
                    },
                    icon: Icon(
                      videoVolume > 0 ? Icons.volume_up : Icons.volume_off,
                      color: Colors.white,
                      size: 30,
                    ),
                  ),
                  // 进度条
                  Expanded(
                    child: SliderTheme(
                      data: SliderThemeData(
                        // 进度条已完成部分颜色
                        activeTrackColor: Colors.red,
                        // 进度条未完成部分颜色
                        inactiveTrackColor: Colors.white.withValues(alpha: 0.3),
                        // 缓冲进度条颜色
                        secondaryActiveTrackColor: Colors.white.withValues(
                          alpha: 0.5,
                        ),
                        // 进度条小球颜色
                        thumbColor: Colors.red,
                        // 进度条小球样式
                        thumbShape: const RoundSliderThumbShape(
                          enabledThumbRadius: 6,
                        ),
                      ),
                      child: Slider(
                        value: progress.clamp(0.0, 1.0),
                        secondaryTrackValue: bufferedProgress.clamp(0.0, 1.0),
                        onChanged: (value) {
                          final newPosition = Duration(
                            milliseconds: (value * duration.inMilliseconds)
                                .round(),
                          );
                          widget.videoPlayerController.seekTo(newPosition);
                        },
                      ),
                    ),
                  ),
                  // 全屏按钮
                  IconButton(
                    onPressed: () {
                      widget.onToggleFullScreen.call();
                    },
                    icon: Icon(
                      chewieController.isFullScreen
                          ? Icons.fullscreen_exit
                          : Icons.fullscreen,
                      color: Colors.white,
                      size: 30,
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ],
    );
  }

  // 格式化时间
  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)}';
  }

  // 显示设置对话框
  void _showSettingsDialog(
    BuildContext context,
    VideoPlayerController controller,
  ) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        backgroundColor: Colors.grey[900],
        title: const Text('播放设置', style: TextStyle(color: Colors.white)),

        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            //播放速度
            ListTile(
              title: const Text('播放速度', style: TextStyle(color: Colors.white)),
              subtitle: Text(
                '${controller.value.playbackSpeed}x',
                style: const TextStyle(color: Colors.white),
              ),
              trailing: const Icon(Icons.speed, color: Colors.white),
              onTap: () => _showSpeedDialog(context, controller),
            ),
          ],
        ),
      ),
    );
  }

  // 显示速度选择对话框
  void _showSpeedDialog(
    BuildContext context,
    VideoPlayerController controller,
  ) {
    final speeds = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0];
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        backgroundColor: Colors.grey[900],
        title: const Text('选择播放速度', style: TextStyle(color: Colors.white)),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: speeds
              .map(
                (speed) => SizedBox(
                  height: 50,
                  child: ListView(
                    children: [
                      InkWell(
                        onTap: () {
                          controller.setPlaybackSpeed(speed);
                          Navigator.of(context).pop();
                        },
                        child: Text(
                          '${speed}x',
                          style: const TextStyle(color: Colors.white),
                        ),
                      ),
                    ],
                  ),
                ),
              )
              .toList(),
        ),
      ),
    );
  }
}