视频播放器

一个视频和音频播放器,可以从本地资源、本地文件和网络 URL 播放,并具有强大的控件。

如何在 Flutter 中播放视频?

Flutter 团队直接提供了一个名为 `video_player` 的库。但是,这个库非常基础。虽然它可以播放视频,但视频播放控件的添加和样式设计则由您自己完成。有一个更好的选择,它能像您期望的那样在 Android 和 iOS 上都附带 UI——Chewie。 - Chewie 在后台使用了第一方的 `video_player` 包。它只是简化了视频播放过程。

项目设置

导入包,

dependencies:
  flutter:
    sdk: flutter
  chewie: ^0.9.7

播放视频

Chewie(以及 `video_player`)可以从 3 个来源播放视频——资源、文件和网络。它的好处在于,您不需要写太多代码就可以更改数据源。从资源切换到网络视频只需敲几下键盘。我们先来看看资源。

  • 资源视频设置

资源是应用程序可以直接使用的文件。在发布时,它们会与您的应用程序文件一起打包。要设置资源,只需在项目根目录中创建一个文件夹,例如 `videos`。然后将您想要的视频文件拖入其中。

vss

为了让 Flutter 知道所有可用的资源,您必须在 `pubspec` 文件中指定它们。这里我添加了项目中所有需要的依赖项。

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.3
  chewie: ^0.9.10
  file_picker: ^1.13.0+1
  audioplayers: ^0.15.1
  fluttertoast: ^7.0.2

使用 Chewie 小部件

现在是时候开始播放视频了。为此,Chewie 提供了自己的小部件,正如我之前提到的,它只是 Flutter 团队提供的 `VideoPlayer` 的一个封装。

因为我们要在一个 ListView 中播放多个视频,所以最好将所有与视频播放相关的部分放在它自己的小部件中。另外,由于视频播放器的资源需要释放,您需要创建一个 `StatefulWidget` 来获取 `dispose()` 方法。

mp2-Apple-iPhone-7-Plus-Gold--shadow-

mp1-Apple-iPhone-7-Plus-Gold--shadow-

让我们创建 `videos_list.dart`,

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

class VideosList extends StatefulWidget {
  final VideoPlayerController videoPlayerController;
  final bool looping;

  const VideosList(
      {Key key, @required this.videoPlayerController, this.looping})
      : super(key: key);

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

class _VideosListState extends State<VideosList> {
  ChewieController videosController;

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

    videosController = ChewieController(
      videoPlayerController: widget.videoPlayerController,
      aspectRatio: 16 / 9,
      autoInitialize: true,
      looping: widget.looping,
      errorBuilder: (context, errorMessage) {
        return Center(child: progressBar()
            );
      },
    );
  }

  Widget progressBar() {
    return CircularProgressIndicator();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Chewie(
        controller: videosController,
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    // IMPORTANT to dispose of all the used resources
    // widget.videoPlayerController.dispose();
    videosController.dispose();
  }
}

要在 ListView 中播放视频,我们不需要做很多额外的工作。将所有 Chewie 相关内容放在一个单独的小部件中后,让我们创建一个 `MyHomePage` StatelessWidget。

在 `main.dart` 中,添加了项目中所有必需的组件

import 'package:audioplayers/audioplayers.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:video_app/videos_list.dart';
import 'package:video_player/video_player.dart';
import 'package:fluttertoast/fluttertoast.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

int status = 0;
AudioPlayer player = AudioPlayer();

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Colors.purple[50],
        appBar: AppBar(
          leading: Icon(
            Icons.play_circle_filled,
            size: 45,
          ),
          backgroundColor: Colors.deepPurple,
          title: Text("Music Player"),
          actions: <Widget>[
            IconButton(icon: Icon(Icons.next_week), onPressed: () {}),
            IconButton(
                icon: Icon(Icons.new_releases),
                onPressed: () {
                  Fluttertoast.showToast(
                      msg: "New features coming soon!",
                      toastLength: Toast.LENGTH_LONG,
                      gravity: ToastGravity.BOTTOM,
                      timeInSecForIosWeb: 1,
                      backgroundColor: Colors.deepPurple,
                      textColor: Colors.white,
                      fontSize: 16.0);
                }),
          ],
        ),
        body: Stack(children: <Widget>[
          ListView(
            children: <Widget>[
              VideosList(
                videoPlayerController: VideoPlayerController.asset(
                  'videos/Ansible.MP4',
                ),
                looping: true,
              ),
              VideosList(
                videoPlayerController: VideoPlayerController.asset(
                  'videos/Specialist_In_Python.MP4',
                ),
                looping: true,
              ),
              VideosList(
                videoPlayerController: VideoPlayerController.asset(
                  'videos/AnsiblePro.MP4',
                ),
                looping: true,
              ),
              VideosList(
                videoPlayerController: VideoPlayerController.network(
                  'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
                ),
                looping: true,
              ),
              VideosList(
                videoPlayerController: VideoPlayerController.asset(
                  'videos/OpenShift.MP4',
                ),
                looping: true,
              ),
              VideosList(
                videoPlayerController: VideoPlayerController.asset(
                  'videos/Flutter.MP4',
                ),
                looping: true,
              ),
            ],
          ),
        ]),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.audiotrack),
          elevation: 15,
          backgroundColor: Colors.deepPurple,
          onPressed: () async {
            if (status == 1) {
              status = await player.stop();
              status = 0;
            } else {
              String filePath = await FilePicker.getFilePath();
              status = await player.play(filePath, isLocal: true);
              //also can be played from the assets...
              //but users must have choices so local file is used!!
            }
          },
        ));
  }
}

这里我们添加了一些来自资源和一些来自网络的视频

              VideosList(
                videoPlayerController: VideoPlayerController.asset(
                  'videos/Ansible.MP4',
                ),
                looping: true,
              ),
              VideosList(
                videoPlayerController: VideoPlayerController.asset(
                  'videos/Specialist_In_Python.MP4',
                ),
                looping: true,
              ),
              VideosList(
                videoPlayerController: VideoPlayerController.asset(
                  'videos/AnsiblePro.MP4',
                ),
                looping: true,
              ),
              VideosList(
                videoPlayerController: VideoPlayerController.network(
                  'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
                ),
                looping: true,
              ),
              VideosList(
                videoPlayerController: VideoPlayerController.asset(
                  'videos/OpenShift.MP4',
                ),
                looping: true,
              ),
              VideosList(
                videoPlayerController: VideoPlayerController.asset(
                  'videos/Flutter.MP4',
                ),
                looping: true,
              ),

播放音频

您有两个选择

我们使用了 audioplayers 插件。

1) 实现起来很简单,但在这个项目中我们使用了不同的方法来处理 audioplayer。由于我想让这个应用程序成为一个单页应用,我没有使用 Intent 在两个活动之间切换,为了播放和停止音频,我使用了一个带有该级别功能的浮动按钮,这是代码片段:
floatingActionButton: FloatingActionButton(
          child: Icon(Icons.audiotrack),
          elevation: 15,
          backgroundColor: Colors.deepPurple,
          onPressed: () async {
            if (status == 1) {
              status = await player.stop();
              status = 0;
            } else {
              String filePath = await FilePicker.getFilePath();
              status = await player.play(filePath, isLocal: true);
              //also can be played from the assets...
              //but users must have choices so local file is used!!
            }
          },
        )
2) 在这里,我实现了专用的音频 UI 及其功能,如播放、暂停和停止按钮以及持续时间;

对于音频封面,我们目前使用的是静态图片,请将您的图片放在 `images` 根文件夹中,如下所示:
images

同时将资产路径添加到您的 `pubspec.yml` 文件中:
assets

要实现音频功能和 UI,请参考 `audio.dart`,

import 'package:audioplayers/audioplayers.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

myApp() {
  return MaterialApp(
    home: HomePage(),
    debugShowCheckedModeBanner: false,
  );
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  AudioPlayer player = AudioPlayer();
  bool isPlaying = false;
  String currentTime = "0:00:00";
  String completeTime = "0:00:00";

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

    player.onAudioPositionChanged.listen((Duration duration) {
      setState(() {
        currentTime = duration.toString().split(".")[0];
      });
    });

    player.onDurationChanged.listen((Duration duration) {
      setState(() {
        completeTime = duration.toString().split(".")[0];
      });
    });
  }

  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        width: double.infinity,
        height: double.infinity,
        color: Colors.blueGrey[200],
        child: Column(
          children: <Widget>[
            Card(
              shadowColor: Colors.deepPurple[900],
              elevation: 20,
              margin: EdgeInsets.only(top: 40, left: 30, right: 30),
              child: Image.asset("images/wallhaven.jpg"),
            ),
            Container(
                margin: EdgeInsets.only(top: 50),
                width: 240,
                height: 50,
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.circular(80),
                ),
                child: Row(
                  // mainAxisAlignment: MainAxisAlignment.spaceAround,
                  // mainAxisSize: MainAxisSize.max,
                  // mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    IconButton(
                        icon: Icon(
                          isPlaying
                              ? Icons.pause_circle_filled
                              : Icons.play_circle_filled,
                          color: Colors.black,
                          size: 30,
                        ),
                        onPressed: () {
                          if (isPlaying) {
                            player.pause();

                            setState(() {
                              isPlaying = false;
                            });
                          } else {
                            player.resume();
                            setState(() {
                              isPlaying = true;
                            });
                          }
                        }),
                    IconButton(
                      icon: Icon(
                        Icons.stop,
                        color: Colors.black,
                        size: 25,
                      ),
                      onPressed: () {
                        player.stop();

                        setState(() {
                          isPlaying = false;
                        });
                      },
                    ),
                    Text(
                      "   " + currentTime,
                      style: TextStyle(fontWeight: FontWeight.w700),
                    ),
                    Text(" | "),
                    Text(
                      completeTime,
                      style: TextStyle(fontWeight: FontWeight.w300),
                    ),
                  ],
                )),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.audiotrack),
        elevation: 10,
        backgroundColor: Colors.deepPurple,
        onPressed: () async {
          String filePath = await FilePicker.getFilePath();

          int status = await player.play(filePath, isLocal: true);

          if (status == 1) {
            setState(() {
              isPlaying = true;
            });
          }
        },
      ),
    );
  }
}

我们使用 `audioCache` 流的持续时间和位置,为此必须订阅这些流,

 player.onAudioPositionChanged.listen((Duration duration) {
      setState(() {
        currentTime = duration.toString().split(".")[0];
      });
    });

    player.onDurationChanged.listen((Duration duration) {
      setState(() {
        completeTime = duration.toString().split(".")[0];
      });
    });

我们的音频 UI 将如下所示:

NewAudio

Chewie 是一个 Flutter 包,旨在简化视频播放过程。它为您处理了开始、停止和暂停按钮,以及显示视频进度的叠加层。

这是我们项目的最终输出

finalMock
点击 这里 查看带声音的输出。

未来要添加的功能

  • 可以从网络 URL 添加音频/视频(用户输入)。
  • 在音频部分添加以分钟为单位的播放/暂停和持续时间。
  • 分享应用的截图。
  • 通知当前播放的内容。

GitHub

https://github.com/hrshmistry/Video-Player