在应用开发中播放视频是一项常见的任务,Flutter 应用也不例外。为了播放视频,Flutter 团队提供了 video_player 插件。你可以使用 video_player 插件播放存储在文件系统、作为 asset 或来自互联网的视频。

在 iOS 上,video_player 插件使用 AVPlayer 来处理播放。在 Android 上,它使用 ExoPlayer

本教程演示了如何使用 video_player 包从互联网上流式传输视频,并使用以下步骤提供基本的播放和暂停控制:

  1. 添加 video_player 依赖。
  2. 为应用添加权限。
  3. 创建并初始化 VideoPlayerController
  4. 显示视频播放器。
  5. 播放和暂停视频。

1. 添加 video_player 依赖

#

本教程依赖一个 Flutter 插件:video_player。首先,将此依赖添加到你的项目中。

要将 video_player 包添加为依赖,请运行 flutter pub add

flutter pub add video_player

2. 为应用添加权限

#

接下来,更新你的 androidios 配置,以确保你的应用具有从互联网流式传输视频的正确权限。

Android

#

将以下权限添加到 <application> 定义之后的 AndroidManifest.xml 文件中。AndroidManifest.xml 文件位于 <project root>/android/app/src/main/AndroidManifest.xml

xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application ...>

    </application>

    <uses-permission android:name="android.permission.INTERNET"/>
</manifest>

iOS

#

对于 iOS,将以下内容添加到 <project root>/ios/Runner/Info.plist 文件中。

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

macOS

#

如果你使用基于网络的视频,请添加 com.apple.security.network.client 权限

Web

#

Flutter Web 不支持 dart:io,因此避免对该插件使用 VideoPlayerController.file 构造函数。使用此构造函数会尝试创建 VideoPlayerController.file,并抛出 UnimplementedError

不同的网页浏览器可能具有不同的视频播放能力,例如支持的格式或自动播放。有关更多特定于 Web 的信息,请查阅 video_player_web 包。

VideoPlayerOptions.mixWithOthers 选项目前无法在 Web 上实现。如果你在 Web 上使用此选项,它将被静默忽略。

3. 创建并初始化 VideoPlayerController

#

现在你已经安装了具有正确权限的 video_player 插件,接下来创建一个 VideoPlayerControllerVideoPlayerController 类允许你连接到不同类型的视频并控制播放。

在播放视频之前,你还必须 initialize(初始化)控制器。这会建立与视频的连接并准备控制器进行播放。

要创建和初始化 VideoPlayerController,请执行以下操作:

  1. 创建一个带有伴随 State 类的 StatefulWidget
  2. State 类添加一个变量来存储 VideoPlayerController
  3. State 类添加一个变量来存储从 VideoPlayerController.initialize 返回的 Future
  4. initState 方法中创建并初始化控制器
  5. dispose 方法中处理(释放)控制器
dart
class VideoPlayerScreen extends StatefulWidget {
  const VideoPlayerScreen({super.key});

  @override
  State<VideoPlayerScreen> createState() => _VideoPlayerScreenState();
}

class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
  late VideoPlayerController _controller;
  late Future<void> _initializeVideoPlayerFuture;

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

    // Create and store the VideoPlayerController. The VideoPlayerController
    // offers several different constructors to play videos from assets, files,
    // or the internet.
    _controller = VideoPlayerController.networkUrl(
      Uri.parse(
        'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
      ),
    );

    _initializeVideoPlayerFuture = _controller.initialize();
  }

  @override
  void dispose() {
    // Ensure disposing of the VideoPlayerController to free up resources.
    _controller.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // Complete the code in the next step.
    return Container();
  }
}

4. 显示视频播放器

#

现在,显示视频。video_player 插件提供了 VideoPlayer 组件来显示由 VideoPlayerController 初始化的视频。默认情况下,VideoPlayer 组件会占据尽可能多的空间。这通常不适合视频,因为视频旨在以特定的宽高比(例如 16x9 或 4x3)显示。

因此,将 VideoPlayer 组件包装在 AspectRatio 组件中,以确保视频具有正确的比例。

此外,你必须在 _initializeVideoPlayerFuture() 完成后显示 VideoPlayer 组件。使用 FutureBuilder 显示加载指示器,直到控制器完成初始化。注意:初始化控制器并不会开始播放。

dart
// Use a FutureBuilder to display a loading spinner while waiting for the
// VideoPlayerController to finish initializing.
FutureBuilder(
  future: _initializeVideoPlayerFuture,
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.done) {
      // If the VideoPlayerController has finished initialization, use
      // the data it provides to limit the aspect ratio of the video.
      return AspectRatio(
        aspectRatio: _controller.value.aspectRatio,
        // Use the VideoPlayer widget to display the video.
        child: VideoPlayer(_controller),
      );
    } else {
      // If the VideoPlayerController is still initializing, show a
      // loading spinner.
      return const Center(child: CircularProgressIndicator());
    }
  },
)

5. 播放和暂停视频

#

默认情况下,视频以暂停状态开始。要开始播放,请调用 VideoPlayerController 提供的 play() 方法。要暂停播放,请调用 pause() 方法。

对于本示例,向你的应用添加一个 FloatingActionButton,它根据情况显示播放或暂停图标。当用户点击按钮时,如果视频当前已暂停,则播放视频;如果视频正在播放,则暂停视频。

dart
FloatingActionButton(
  onPressed: () {
    // Wrap the play or pause in a call to `setState`. This ensures the
    // correct icon is shown.
    setState(() {
      // If the video is playing, pause it.
      if (_controller.value.isPlaying) {
        _controller.pause();
      } else {
        // If the video is paused, play it.
        _controller.play();
      }
    });
  },
  // Display the correct icon depending on the state of the player.
  child: Icon(
    _controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
  ),
)

完整示例

#
import 'dart:async';

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

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

class VideoPlayerApp extends StatelessWidget {
  const VideoPlayerApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Video Player Demo',
      home: VideoPlayerScreen(),
    );
  }
}

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

  @override
  State<VideoPlayerScreen> createState() => _VideoPlayerScreenState();
}

class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
  late VideoPlayerController _controller;
  late Future<void> _initializeVideoPlayerFuture;

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

    // Create and store the VideoPlayerController. The VideoPlayerController
    // offers several different constructors to play videos from assets, files,
    // or the internet.
    _controller = VideoPlayerController.networkUrl(
      Uri.parse(
        'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
      ),
    );

    // Initialize the controller and store the Future for later use.
    _initializeVideoPlayerFuture = _controller.initialize();

    // Use the controller to loop the video.
    _controller.setLooping(true);
  }

  @override
  void dispose() {
    // Ensure disposing of the VideoPlayerController to free up resources.
    _controller.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Butterfly Video')),
      // Use a FutureBuilder to display a loading spinner while waiting for the
      // VideoPlayerController to finish initializing.
      body: FutureBuilder(
        future: _initializeVideoPlayerFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            // If the VideoPlayerController has finished initialization, use
            // the data it provides to limit the aspect ratio of the video.
            return AspectRatio(
              aspectRatio: _controller.value.aspectRatio,
              // Use the VideoPlayer widget to display the video.
              child: VideoPlayer(_controller),
            );
          } else {
            // If the VideoPlayerController is still initializing, show a
            // loading spinner.
            return const Center(child: CircularProgressIndicator());
          }
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // Wrap the play or pause in a call to `setState`. This ensures the
          // correct icon is shown.
          setState(() {
            // If the video is playing, pause it.
            if (_controller.value.isPlaying) {
              _controller.pause();
            } else {
              // If the video is paused, play it.
              _controller.play();
            }
          });
        },
        // Display the correct icon depending on the state of the player.
        child: Icon(
          _controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
        ),
      ),
    );
  }
}