Flutter视频投屏插件flutter_cast_video的使用

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

Flutter视频投屏插件flutter_cast_video的使用

简介

flutter_cast_video 是一个Flutter插件,支持iOS和Android平台连接到如Chromecast和Apple TV等投屏设备。此插件是基于 fluter_video_cast 的克隆并增强了功能。

安装

iOS

  1. 在您的 pubspec.yaml 文件中添加 flutter_cast_video 依赖。
  2. 设置最低操作系统版本为iOS 11.0。
  3. 初始化Cast上下文,在应用代理文件 ios/Runner/AppDelegate.m 中进行如下配置:
import UIKit
import Flutter
import GoogleCast

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, GCKLoggerDelegate {
    let kReceiverAppID = kGCKDefaultMediaReceiverApplicationID
    let kDebugLoggingEnabled = true

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
        let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
        let options = GCKCastOptions(discoveryCriteria: criteria)
        GCKCastContext.setSharedInstanceWith(options)
        GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
        GCKLogger.sharedInstance().delegate = self
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}
  1. Info.plist 文件中添加键值对 <key>io.flutter.embedded_views_preview</key><true/>

Android

  1. 在您的模块(app-level)Gradle文件(通常是 android/app/build.gradle)中添加依赖:
implementation 'com.google.android.gms:play-services-cast-framework:19.0.0'
implementation 'com.google.android.exoplayer:extension-cast:2.11.5'
  1. MainActivity 的主题设置为 @style/Theme.AppCompat.NoActionBar,在 AndroidManifest.xml 中修改:
<manifest ...
  <application ...
    <meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
               android:value="com.google.android.exoplayer2.ext.cast.DefaultCastOptionsProvider"/>
    ...
    <activity android:theme="@style/Theme.AppCompat.NoActionBar" ...
  1. MainActivity 继承自 FlutterFragmentActivity 并初始化Cast上下文:
CastContext.getSharedInstance(applicationContext)

示例代码

下面是一个完整的示例应用程序,它演示了如何使用 ChromeCastButtonChromeCastController 来控制媒体播放。

import 'dart:async';
import 'dart:convert';

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

enum AppState { idle, connected, mediaLoaded, error }

String duration2String(Duration? dur, {showLive = 'Live'}) {
  Duration duration = dur ?? Duration();
  if (duration.inSeconds < 0) return showLive;
  else {
    return duration.toString().split('.').first.padLeft(8, "0");
  }
}

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

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

class CastSample extends StatefulWidget {
  static const _iconSize = 50.0;

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

class _CastSampleState extends State<CastSample> {
  late ChromeCastController _controller;
  AppState _state = AppState.idle;
  bool _playing = false;
  Map<dynamic, dynamic> _mediaInfo = {};
  Duration? position, duration;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Plugin example app'),
        actions: [
          AirPlayButton(
            size: CastSample._iconSize,
            color: Colors.white,
            activeColor: Colors.amber,
            onRoutesOpening: () => print('opening'),
            onRoutesClosed: () => print('closed'),
          ),
          ChromeCastButton(
            size: CastSample._iconSize,
            color: Colors.white,
            onButtonCreated: _onButtonCreated,
            onSessionStarted: _onSessionStarted,
            onSessionEnded: () => setState(() => _state = AppState.idle),
            onRequestCompleted: _onRequestCompleted,
            onRequestFailed: _onRequestFailed,
          ),
        ],
      ),
      body: Center(child: _handleState()),
    );
  }

  @override
  void dispose() {
    super.dispose();
    resetTimer();
  }

  Widget _handleState() {
    switch (_state) {
      case AppState.idle:
        resetTimer();
        return Text('ChromeCast not connected');
      case AppState.connected:
        return Text('No media loaded');
      case AppState.mediaLoaded:
        startTimer();
        return _mediaControls();
      case AppState.error:
        resetTimer();
        return Text('An error has occurred');
      default:
        return Container();
    }
  }

  Widget _mediaControls() {
    return Column(
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            _RoundIconButton(
              icon: Icons.replay_10,
              onPressed: () => _controller.seek(relative: true, interval: -10.0),
            ),
            _RoundIconButton(
                icon: _playing ? Icons.pause : Icons.play_arrow,
                onPressed: _playPause),
            _RoundIconButton(
              icon: Icons.forward_10,
              onPressed: () => _controller.seek(relative: true, interval: 10.0),
            ),
          ],
        ),
        Text('${duration2String(position)}/${duration2String(duration)}'),
        Text(jsonEncode(_mediaInfo)),
      ],
    );
  }

  Timer? _timer;

  Future<void> _monitor() async {
    var dur = await _controller.duration(), pos = await _controller.position();
    if (duration == null || duration!.inSeconds != dur.inSeconds) {
      setState(() {
        duration = dur;
      });
    }
    if (position == null || position!.inSeconds != pos.inSeconds) {
      setState(() {
        position = pos;
      });
    }
  }

  void resetTimer() {
    _timer?.cancel();
    _timer = null;
  }

  void startTimer() {
    if (_timer?.isActive ?? false) {
      return;
    }
    resetTimer();
    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
      _monitor();
    });
  }

  Future<void> _playPause() async {
    final playing = await _controller.isPlaying();
    if (playing == null) return;
    if (playing) {
      await _controller.pause();
    } else {
      await _controller.play();
    }
    setState(() => _playing = !playing);
  }

  Future<void> _onButtonCreated(ChromeCastController controller) async {
    _controller = controller;
    await _controller.addSessionListener();
  }

  Future<void> _onSessionStarted() async {
    setState(() => _state = AppState.connected);
    await _controller.loadMedia(
        'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
        title: "TestTitle",
        subtitle: "test Sub title",
        image:
            "https://smaller-pictures.appspot.com/images/dreamstime_xxl_65780868_small.jpg");
  }

  Future<void> _onRequestCompleted() async {
    final playing = await _controller.isPlaying();
    if (playing == null) return;
    final mediaInfo = await _controller.getMediaInfo();
    setState(() {
      _state = AppState.mediaLoaded;
      _playing = playing;
      if (mediaInfo != null) {
        _mediaInfo = mediaInfo;
      }
    });
  }

  Future<void> _onRequestFailed(String? error) async {
    setState(() => _state = AppState.error);
    print(error);
  }
}

class _RoundIconButton extends StatelessWidget {
  final IconData icon;
  final VoidCallback onPressed;

  _RoundIconButton({required this.icon, required this.onPressed});

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      child: Icon(
        icon,
        color: Colors.white,
      ),
      padding: EdgeInsets.all(16.0),
      color: Colors.blue,
      shape: CircleBorder(),
      onPressed: onPressed,
    );
  }
}

这个示例展示了如何集成 flutter_cast_video 插件来实现基本的投屏功能,包括连接、加载媒体文件以及控制播放状态。希望这能帮助您快速上手使用 flutter_cast_video 插件。


更多关于Flutter视频投屏插件flutter_cast_video的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter视频投屏插件flutter_cast_video的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何使用 flutter_cast_video 插件来进行视频投屏的示例代码。这个插件允许你将 Flutter 应用中的视频流投射到支持 DLNA/UPnP 的设备上。

首先,确保你已经在 pubspec.yaml 文件中添加了 flutter_cast_video 依赖:

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

然后,运行 flutter pub get 来获取依赖。

接下来,在你的 Flutter 应用中实现视频投屏功能。以下是一个基本的示例代码:

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

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

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

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
  late VideoPlayerController _controller;
  ChewieController? _chewieController;
  late FlutterCastVideo _flutterCastVideo;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance!.addObserver(this);
    _controller = VideoPlayerController.network(
      'https://www.example.com/path/to/your/video.mp4', // 替换为你的视频URL
    )
      ..initialize().then((_) {
        setState(() {});
      });

    _flutterCastVideo = FlutterCastVideo();
    _flutterCastVideo.initialize();

    _chewieController = ChewieController(
      videoPlayerController: _controller,
      aspectRatio: _controller.value.aspectRatio,
      autoPlay: false,
      looping: false,
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    _chewieController?.dispose();
    WidgetsBinding.instance!.removeObserver(this);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Video Casting Demo'),
      ),
      body: Center(
        child: _controller.value.isInitialized
            ? AspectRatio(
                aspectRatio: _controller.value.aspectRatio,
                child: Chewie(
                  controller: _chewieController!,
                ),
              )
            : Container(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          var devices = await _flutterCastVideo.getAvailableDevices();
          if (devices.isNotEmpty) {
            // 选择一个设备进行投屏(这里假设选择第一个设备)
            var device = devices.first;
            await _flutterCastVideo.cast(
              device,
              'https://www.example.com/path/to/your/video.mp4', // 替换为你的视频URL
              headers: {}, // 如果需要添加HTTP头,可以在这里添加
            );
          } else {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('No casting devices found.')),
            );
          }
        },
        tooltip: 'Cast Video',
        child: Icon(Icons.cast),
      ),
    );
  }
}

在这个示例中,我们做了以下几件事:

  1. 添加了 flutter_cast_videochewievideo_player 依赖。
  2. 创建了一个 VideoPlayerController 来加载视频。
  3. 使用 Chewie 来播放视频,这是一个基于 video_player 的 Flutter 视频播放器控件。
  4. 使用 FlutterCastVideo 来初始化投屏功能,并获取可用的投屏设备。
  5. 在点击浮动按钮时,选择一个设备进行投屏。

请注意,这个示例代码仅用于演示目的,实际使用时你可能需要根据具体需求进行调整,比如处理投屏过程中的错误、支持更多的视频格式等。同时,确保你的设备和应用都已正确配置以支持 DLNA/UPnP 投屏协议。

回到顶部