Flutter虚拟现实播放插件vr_player的使用

发布于 1周前 作者 vueper 来自 Flutter

Flutter虚拟现实播放插件vr_player的使用

VrPlayer

What the Flutter

Crafted with passion by What the Flutter 🦜

Pub Build Status CodeFactor


Getting Started

VrPlayer 插件基于 Kaltura Playkit SDK,允许在 Android 和 iOS 平台上流畅地播放 360° 和 VR 视频。这些视频类型通常被称为沉浸式、360 或球形视频,是通过全向摄像机或多个摄像机同时记录整个全景视图来捕捉的。

使用方法

基本用法

VrPlayer(
  x: 0,
  y: 0,
  onCreated: onViewPlayerCreated,
  width: playerWidth,
  height: playerHeight,
),

你需要实现 onViewPlayerCreated 来接收播放器事件:

void onViewPlayerCreated(
  VrPlayerController controller,
  VrPlayerObserver observer,
) {
  _viewPlayerController = controller;
  observer
    ..onStateChange = onReceiveState
    ..onDurationChange = onReceiveDuration
    ..onPositionChange = onChangePosition
    ..onFinishedChange = onReceiveEnded;
  _viewPlayerController.loadVideo(
    videoUrl:
        'https://cdn.bitmovin.com/content/assets/playhouse-vr/m3u8s/105560.m3u8',
  );
}

VrPlayerController

VrPlayerController 可用于更改 VrPlayer 的状态。注意,只有在 VrPlayer 创建后才能使用这些方法。

方法 描述
loadVideo({String? videoUrl, String? videoPath}) 根据配置初始化视频。可以通过 videoPath 加载本地文件,或通过 videoUrl 播放网络文件。本地文件仅支持 Android。
isPlaying() 检查当前播放器状态。
play() 播放视频。
pause() 暂停视频。
seekTo() 跳转到指定位置。
setVolume() 设置音量级别,从 0(无声音)到 1(最大)。
fullScreen() (Android only) 切换全屏模式。iOS 需要传递新的宽高给 VrPlayer widget。
toggleVRMode() 在 360° 模式和 VR 模式之间切换。

本地文件指南 (仅限 Android)

  1. AndroidmManifest.xml 文件中添加 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  2. 请求用户授予读取外部存储的权限。
  3. 使用正确的 videoPath 调用 loadVideo()

示例代码

以下是一个完整的示例,展示了如何使用 VrPlayer 插件创建一个 VR 视频播放器。

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:vr_player/vr_player.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: HomePage(),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => const VideoPlayerPage(),
          ),
        );
      },
      child: const Text('Start Video'),
    );
  }
}

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

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

class _VideoPlayerPageState extends State<VideoPlayerPage>
    with TickerProviderStateMixin {
  late VrPlayerController _viewPlayerController;
  late AnimationController _animationController;
  late Animation<double> _animation;
  bool _isShowingBar = false;
  bool _isPlaying = false;
  bool _isFullScreen = false;
  bool _isVideoFinished = false;
  bool _isLandscapeOrientation = false;
  bool _isVolumeSliderShown = false;
  bool _isVolumeEnabled = true;
  late double _playerWidth;
  late double _playerHeight;
  String? _duration;
  int? _intDuration;
  bool isVideoLoading = false;
  bool isVideoReady = false;
  String? _currentPosition;
  double _currentSliderValue = 0.1;
  double _seekPosition = 0;

  @override
  void initState() {
    _animationController =
        AnimationController(vsync: this, duration: const Duration(seconds: 1));
    _animation = Tween<double>(begin: 0, end: 1).animate(_animationController);
    _toggleShowingBar();
    super.initState();
  }

  void _toggleShowingBar() {
    switchVolumeSliderDisplay(show: false);

    _isShowingBar = !_isShowingBar;
    if (_isShowingBar) {
      _animationController.forward();
    } else {
      _animationController.reverse();
    }
  }

  @override
  Widget build(BuildContext context) {
    _playerWidth = MediaQuery.of(context).size.width;
    _playerHeight =
        _isFullScreen ? MediaQuery.of(context).size.height : _playerWidth / 2;
    _isLandscapeOrientation =
        MediaQuery.of(context).orientation == Orientation.landscape;

    return Scaffold(
      appBar: AppBar(
        title: const Text('VR Player'),
      ),
      body: GestureDetector(
        onTap: _toggleShowingBar,
        child: Stack(
          alignment: Alignment.bottomCenter,
          children: <Widget>[
            VrPlayer(
              x: 0,
              y: 0,
              onCreated: onViewPlayerCreated,
              width: _playerWidth,
              height: _playerHeight,
            ),
            Positioned(
              bottom: 0,
              left: 0,
              right: 0,
              child: FadeTransition(
                opacity: _animation,
                child: ColoredBox(
                  color: Colors.black,
                  child: Row(
                    children: <Widget>[
                      IconButton(
                        icon: Icon(
                          _isVideoFinished
                              ? Icons.replay
                              : _isPlaying
                                  ? Icons.pause
                                  : Icons.play_arrow,
                          color: Colors.white,
                        ),
                        onPressed: playAndPause,
                      ),
                      Text(
                        _currentPosition?.toString() ?? '00:00',
                        style: const TextStyle(color: Colors.white),
                      ),
                      Expanded(
                        child: SliderTheme(
                          data: SliderTheme.of(context).copyWith(
                            activeTrackColor: Colors.amberAccent,
                            inactiveTrackColor: Colors.grey,
                            trackHeight: 5,
                            thumbColor: Colors.white,
                            thumbShape: const RoundSliderThumbShape(
                              enabledThumbRadius: 8,
                            ),
                            overlayColor: Colors.purple.withAlpha(32),
                            overlayShape: const RoundSliderOverlayShape(
                              overlayRadius: 14,
                            ),
                          ),
                          child: Slider(
                            value: _seekPosition,
                            max: _intDuration?.toDouble() ?? 0,
                            onChangeEnd: (value) {
                              _viewPlayerController.seekTo(value.toInt());
                            },
                            onChanged: (value) {
                              onChangePosition(value.toInt());
                            },
                          ),
                        ),
                      ),
                      Text(
                        _duration?.toString() ?? '99:99',
                        style: const TextStyle(color: Colors.white),
                      ),
                      if (_isFullScreen || _isLandscapeOrientation)
                        IconButton(
                          icon: Icon(
                            _isVolumeEnabled
                                ? Icons.volume_up_rounded
                                : Icons.volume_off_rounded,
                            color: Colors.white,
                          ),
                          onPressed: () =>
                              switchVolumeSliderDisplay(show: true),
                        ),
                      IconButton(
                        icon: Icon(
                          _isFullScreen
                              ? Icons.fullscreen_exit
                              : Icons.fullscreen,
                          color: Colors.white,
                        ),
                        onPressed: fullScreenPressed,
                      ),
                      if (_isFullScreen)
                        IconButton(
                          icon: Image.asset(
                            'assets/icons/cardboard.png',
                            color: Colors.white,
                          ),
                          onPressed: cardBoardPressed,
                        )
                      else
                        Container(),
                    ],
                  ),
                ),
              ),
            ),
            Positioned(
              height: 180,
              right: 4,
              top: MediaQuery.of(context).size.height / 4,
              child: _isVolumeSliderShown
                  ? RotatedBox(
                      quarterTurns: 3,
                      child: Slider(
                        value: _currentSliderValue,
                        divisions: 10,
                        onChanged: onChangeVolumeSlider,
                      ),
                    )
                  : const SizedBox(),
            ),
          ],
        ),
      ),
    );
  }

  void cardBoardPressed() {
    _viewPlayerController.toggleVRMode();
  }

  Future<void> fullScreenPressed() async {
    await _viewPlayerController.fullScreen();
    setState(() {
      _isFullScreen = !_isFullScreen;
    });

    if (_isFullScreen) {
      SystemChrome.setPreferredOrientations([
        DeviceOrientation.landscapeRight,
        DeviceOrientation.landscapeLeft,
      ]);
      SystemChrome.setEnabledSystemUIMode(
        SystemUiMode.manual,
        overlays: [],
      );
    } else {
      SystemChrome.setPreferredOrientations([
        DeviceOrientation.landscapeRight,
        DeviceOrientation.landscapeLeft,
        DeviceOrientation.portraitUp,
        DeviceOrientation.portraitDown,
      ]);
      SystemChrome.setEnabledSystemUIMode(
        SystemUiMode.manual,
        overlays: SystemUiOverlay.values,
      );
    }
  }

  Future<void> playAndPause() async {
    if (_isVideoFinished) {
      await _viewPlayerController.seekTo(0);
    }

    if (_isPlaying) {
      await _viewPlayerController.pause();
    } else {
      await _viewPlayerController.play();
    }

    setState(() {
      _isPlaying = !_isPlaying;
      _isVideoFinished = false;
    });
  }

  void onViewPlayerCreated(
    VrPlayerController controller,
    VrPlayerObserver observer,
  ) {
    _viewPlayerController = controller;
    observer
      ..onStateChange = onReceiveState
      ..onDurationChange = onReceiveDuration
      ..onPositionChange = onChangePosition
      ..onFinishedChange = onReceiveEnded;
    _viewPlayerController.loadVideo(
      videoUrl:
          'https://cdn.bitmovin.com/content/assets/playhouse-vr/m3u8s/105560.m3u8',
    );
  }

  void onReceiveState(VrState state) {
    switch (state) {
      case VrState.loading:
        setState(() {
          isVideoLoading = true;
        });
        break;
      case VrState.ready:
        setState(() {
          isVideoLoading = false;
          isVideoReady = true;
        });
        break;
      case VrState.buffering:
      case VrState.idle:
        break;
    }
  }

  void onReceiveDuration(int millis) {
    setState(() {
      _intDuration = millis;
      _duration = millisecondsToDateTime(millis);
    });
  }

  void onChangePosition(int millis) {
    setState(() {
      _currentPosition = millisecondsToDateTime(millis);
      _seekPosition = millis.toDouble();
    });
  }

  // ignore: avoid_positional_boolean_parameters
  void onReceiveEnded(bool isFinished) {
    setState(() {
      _isVideoFinished = isFinished;
    });
  }

  void onChangeVolumeSlider(double value) {
    _viewPlayerController.setVolume(value);
    setState(() {
      _isVolumeEnabled = value != 0;
      _currentSliderValue = value;
    });
  }

  void switchVolumeSliderDisplay({required bool show}) {
    setState(() {
      _isVolumeSliderShown = show;
    });
  }

  String millisecondsToDateTime(int milliseconds) =>
      setDurationText(Duration(milliseconds: milliseconds));

  String setDurationText(Duration duration) {
    String twoDigits(int n) {
      if (n >= 10) return '$n';
      return '0$n';
    }

    final twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
    final twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
    return '${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds';
  }
}

这个示例展示了如何创建一个包含基本控制功能(如播放、暂停、全屏、音量调节等)的 VR 视频播放器。你可以根据需要进一步扩展和自定义此示例。


更多关于Flutter虚拟现实播放插件vr_player的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter虚拟现实播放插件vr_player的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用vr_player插件来实现虚拟现实视频播放的代码示例。请注意,这个示例假设你已经有一个Flutter项目,并且已经添加了vr_player插件到你的pubspec.yaml文件中。

1. 添加依赖

首先,在你的pubspec.yaml文件中添加vr_player依赖:

dependencies:
  flutter:
    sdk: flutter
  vr_player: ^最新版本号  # 替换为实际的最新版本号

然后运行flutter pub get来安装依赖。

2. 导入包

在你的Dart文件中(比如main.dart),导入vr_player包:

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

3. 创建VR播放器页面

接下来,创建一个页面来使用VR播放器。下面是一个简单的示例:

class VRPlayerPage extends StatefulWidget {
  @override
  _VRPlayerPageState createState() => _VRPlayerPageState();
}

class _VRPlayerPageState extends State<VRPlayerPage> {
  late VRPlayerController _controller;

  @override
  void initState() {
    super.initState();
    _controller = VRPlayerController();
    // 加载视频资源,可以是本地资源或网络资源
    _controller.setVideoSource('https://example.com/your-video-file.mp4');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('VR Player Demo'),
      ),
      body: Center(
        child: VRPlayer(
          controller: _controller,
          onReady: () {
            // 播放器准备就绪时回调
            print('VR Player is ready');
          },
          onError: (error) {
            // 播放器出错时回调
            print('VR Player error: $error');
          },
          onCompleted: () {
            // 视频播放完成时回调
            print('VR Player video completed');
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 控制播放/暂停
          setState(() {
            _controller.isPlaying ? _controller.pause() : _controller.play();
          });
        },
        tooltip: _controller.isPlaying ? 'Pause' : 'Play',
        child: Icon(_controller.isPlaying ? Icons.pause : Icons.play_arrow),
      ),
    );
  }

  @override
  void dispose() {
    // 释放资源
    _controller.dispose();
    super.dispose();
  }
}

4. 运行应用

最后,在你的main.dart文件中引入并使用这个页面:

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter VR Player Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: VRPlayerPage(),
    );
  }
}

注意事项

  1. 视频格式:确保你的视频文件是VR播放器支持的格式(如MP4)。
  2. 权限:如果你的视频资源是存储在设备上的,确保你的应用有访问存储的权限。
  3. 性能:虚拟现实视频播放对设备的性能要求较高,确保在支持的设备上测试你的应用。

这样,你就可以在Flutter应用中实现一个简单的VR视频播放功能了。如果你需要更多高级功能,比如自定义UI、控制音量等,可以参考vr_player的官方文档。

回到顶部