Flutter视频投屏插件flutter_cast_video的使用
Flutter视频投屏插件flutter_cast_video的使用
简介
flutter_cast_video
是一个Flutter插件,支持iOS和Android平台连接到如Chromecast和Apple TV等投屏设备。此插件是基于 fluter_video_cast
的克隆并增强了功能。
安装
iOS
- 在您的
pubspec.yaml
文件中添加flutter_cast_video
依赖。 - 设置最低操作系统版本为iOS 11.0。
- 初始化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)
}
}
- 在
Info.plist
文件中添加键值对<key>io.flutter.embedded_views_preview</key><true/>
。
Android
- 在您的模块(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'
- 将
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" ...
- 让
MainActivity
继承自FlutterFragmentActivity
并初始化Cast上下文:
CastContext.getSharedInstance(applicationContext)
示例代码
下面是一个完整的示例应用程序,它演示了如何使用 ChromeCastButton
和 ChromeCastController
来控制媒体播放。
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
更多关于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),
),
);
}
}
在这个示例中,我们做了以下几件事:
- 添加了
flutter_cast_video
、chewie
和video_player
依赖。 - 创建了一个
VideoPlayerController
来加载视频。 - 使用
Chewie
来播放视频,这是一个基于video_player
的 Flutter 视频播放器控件。 - 使用
FlutterCastVideo
来初始化投屏功能,并获取可用的投屏设备。 - 在点击浮动按钮时,选择一个设备进行投屏。
请注意,这个示例代码仅用于演示目的,实际使用时你可能需要根据具体需求进行调整,比如处理投屏过程中的错误、支持更多的视频格式等。同时,确保你的设备和应用都已正确配置以支持 DLNA/UPnP 投屏协议。