Flutter自定义YouTube播放器插件custom_youtube_player_flutter的使用
Flutter 自定义 YouTube 播放器插件 custom_youtube_player_flutter
的使用
简介
custom_youtube_player_flutter
是一个用于在 Flutter 应用中嵌入和播放 YouTube 视频的插件。该插件基于官方的 iFrame Player API,并且支持多种功能,如内联播放、字幕支持、自定义控制等。
支持平台
- Android
- iOS
对于 Web 支持,建议使用 youtube_player_iframe
插件。未来,此插件将扩展为支持 youtube_player_iframe
。
特性
- 内联播放
- 字幕支持
- 不需要 API 密钥
- 自定义控制
- 获取视频元数据
- 支持直播流视频
- 支持调整播放速率
- 适应带宽质量
- 快进和快退(水平拖动)
- 适应宽屏(缩放手势)
该插件使用了 flutter_inappwebview
来实现其功能。
要求
Android
minSdkVersion
至少设置为 17。- 需要启用
androidx
支持。
iOS
- 使用 Swift 语言
- Xcode 版本 >= 11
安装与配置
iOS
无需额外配置。更多信息可以查看 这里。
Android
在 android/app/build.gradle
文件中设置 minSdkVersion
至少为 17。
更多信息可以查看 这里。
使用示例
以下是一个完整的示例,展示了如何在 Flutter 应用中使用 custom_youtube_player_flutter
插件。
import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:custom_youtube_player_flutter/youtube_player_flutter.dart';
import 'video_list.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.blueAccent,
),
);
runApp(YoutubePlayerDemoApp());
}
/// 创建 [YoutubePlayerDemoApp] 小部件。
class YoutubePlayerDemoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'YouTube Player Flutter',
theme: ThemeData(
primarySwatch: Colors.blue,
appBarTheme: const AppBarTheme(
color: Colors.blueAccent,
titleTextStyle: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w300,
fontSize: 20,
),
),
iconTheme: const IconThemeData(
color: Colors.blueAccent,
),
),
home: MyHomePage(),
);
}
}
/// 主页
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late YoutubePlayerController _controller;
late TextEditingController _idController;
late TextEditingController _seekToController;
late PlayerState _playerState;
late YoutubeMetaData _videoMetaData;
double _volume = 100;
bool _muted = false;
bool _isPlayerReady = false;
final List<String> _ids = [
'nPt8bK2gbaU',
'gQDByCdjUXw',
'iLnmTe5Q2Qw',
'_WoCV4c6XOE',
'KmzdUeBT_-w',
'6jZDSSZZxjQ',
'p2lYr3vM_1w',
'7QUtEmBT_-w',
'34_PXCzGw1M',
];
@override
void initState() {
super.initState();
_controller = YoutubePlayerController(
initialVideoId: _ids.first,
flags: const YoutubePlayerFlags(
mute: false,
autoPlay: true,
disableDragSeek: false,
loop: false,
isLive: false,
forceHD: false,
enableCaption: true,
),
)..addListener(listener);
_idController = TextEditingController();
_seekToController = TextEditingController();
_videoMetaData = const YoutubeMetaData();
_playerState = PlayerState.unknown;
}
void listener() {
if (_isPlayerReady && mounted && !_controller.value.isFullScreen) {
setState(() {
_playerState = _controller.value.playerState;
_videoMetaData = _controller.metadata;
});
}
}
@override
void deactivate() {
// 在导航到下一页时暂停视频。
_controller.pause();
super.deactivate();
}
@override
void dispose() {
_controller.dispose();
_idController.dispose();
_seekToController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return YoutubePlayerBuilder(
onExitFullScreen: () {
// 退出全屏后,恢复设备方向。
SystemChrome.setPreferredOrientations(DeviceOrientation.values);
},
player: YoutubePlayer(
controller: _controller,
showVideoProgressIndicator: true,
progressIndicatorColor: Colors.blueAccent,
topActions: <Widget>[
const SizedBox(width: 8.0),
Expanded(
child: Text(
_controller.metadata.title,
style: const TextStyle(
color: Colors.white,
fontSize: 18.0,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
IconButton(
icon: const Icon(
Icons.settings,
color: Colors.white,
size: 25.0,
),
onPressed: () {
log('Settings Tapped!');
},
),
],
onReady: () {
_isPlayerReady = true;
},
onEnded: (data) {
_controller
.load(_ids[(_ids.indexOf(data.videoId) + 1) % _ids.length]);
_showSnackBar('Next Video Started!');
},
),
builder: (context, player) => Scaffold(
appBar: AppBar(
leading: Padding(
padding: const EdgeInsets.only(left: 12.0),
child: Image.asset(
'assets/ypf.png',
fit: BoxFit.fitWidth,
),
),
title: const Text(
'YouTube Player Flutter',
style: TextStyle(color: Colors.white),
),
actions: [
IconButton(
icon: const Icon(Icons.video_library),
onPressed: () => Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => VideoList(),
),
),
),
],
),
body: ListView(
children: [
player,
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_space,
_text('Title', _videoMetaData.title),
_space,
_text('Channel', _videoMetaData.author),
_space,
_text('Video Id', _videoMetaData.videoId),
_space,
Row(
children: [
_text(
'Playback Quality',
_controller.value.playbackQuality ?? '',
),
const Spacer(),
_text(
'Playback Rate',
'${_controller.value.playbackRate}x ',
),
],
),
_space,
TextField(
enabled: _isPlayerReady,
controller: _idController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Enter youtube <video id> or <link>',
fillColor: Colors.blueAccent.withAlpha(20),
filled: true,
hintStyle: const TextStyle(
fontWeight: FontWeight.w300,
color: Colors.blueAccent,
),
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () => _idController.clear(),
),
),
),
_space,
Row(
children: [
_loadCueButton('LOAD'),
const SizedBox(width: 10.0),
_loadCueButton('CUE'),
],
),
_space,
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: const Icon(Icons.skip_previous),
onPressed: _isPlayerReady
? () => _controller.load(_ids[
(_ids.indexOf(_controller.metadata.videoId) -
1) %
_ids.length])
: null,
),
IconButton(
icon: Icon(
_controller.value.isPlaying
? Icons.pause
: Icons.play_arrow,
),
onPressed: _isPlayerReady
? () {
_controller.value.isPlaying
? _controller.pause()
: _controller.play();
setState(() {});
}
: null,
),
IconButton(
icon: Icon(_muted ? Icons.volume_off : Icons.volume_up),
onPressed: _isPlayerReady
? () {
_muted
? _controller.unMute()
: _controller.mute();
setState(() {
_muted = !_muted;
});
}
: null,
),
FullScreenButton(
controller: _controller,
color: Colors.blueAccent,
),
IconButton(
icon: const Icon(Icons.skip_next),
onPressed: _isPlayerReady
? () => _controller.load(_ids[
(_ids.indexOf(_controller.metadata.videoId) +
1) %
_ids.length])
: null,
),
],
),
_space,
Row(
children: <Widget>[
const Text(
"Volume",
style: TextStyle(fontWeight: FontWeight.w300),
),
Expanded(
child: Slider(
inactiveColor: Colors.transparent,
value: _volume,
min: 0.0,
max: 100.0,
divisions: 10,
label: '${(_volume).round()}',
onChanged: _isPlayerReady
? (value) {
setState(() {
_volume = value;
});
_controller.setVolume(_volume.round());
}
: null,
),
),
],
),
_space,
AnimatedContainer(
duration: const Duration(milliseconds: 800),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
color: _getStateColor(_playerState),
),
padding: const EdgeInsets.all(8.0),
child: Text(
_playerState.toString(),
style: const TextStyle(
fontWeight: FontWeight.w300,
color: Colors.white,
),
textAlign: TextAlign.center,
),
),
],
),
),
],
),
),
);
}
Widget _text(String title, String value) {
return RichText(
text: TextSpan(
text: '$title : ',
style: const TextStyle(
color: Colors.blueAccent,
fontWeight: FontWeight.bold,
),
children: [
TextSpan(
text: value,
style: const TextStyle(
color: Colors.blueAccent,
fontWeight: FontWeight.w300,
),
),
],
),
);
}
Color _getStateColor(PlayerState state) {
switch (state) {
case PlayerState.unknown:
return Colors.grey[700]!;
case PlayerState.unStarted:
return Colors.pink;
case PlayerState.ended:
return Colors.red;
case PlayerState.playing:
return Colors.blueAccent;
case PlayerState.paused:
return Colors.orange;
case PlayerState.buffering:
return Colors.yellow;
case PlayerState.cued:
return Colors.blue[900]!;
default:
return Colors.blue;
}
}
Widget get _space => const SizedBox(height: 10);
Widget _loadCueButton(String action) {
return Expanded(
child: MaterialButton(
color: Colors.blueAccent,
onPressed: _isPlayerReady
? () {
if (_idController.text.isNotEmpty) {
var id = YoutubePlayer.convertUrlToId(
_idController.text,
) ??
'';
if (action == 'LOAD') _controller.load(id);
if (action == 'CUE') _controller.cue(id);
FocusScope.of(context).requestFocus(FocusNode());
} else {
_showSnackBar('Source can\'t be empty!');
}
}
: null,
disabledColor: Colors.grey,
disabledTextColor: Colors.black,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 14.0),
child: Text(
action,
style: const TextStyle(
fontSize: 18.0,
color: Colors.white,
fontWeight: FontWeight.w300,
),
textAlign: TextAlign.center,
),
),
),
);
}
void _showSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
message,
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.w300,
fontSize: 16.0,
),
),
backgroundColor: Colors.blueAccent,
behavior: SnackBarBehavior.floating,
elevation: 1.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50.0),
),
),
);
}
}
定制播放器
从 v5.x.x 及以上版本开始,可以使用 topActions
和 bottomActions
属性来自定义播放器。例如:
YoutubePlayer(
controller: _controller,
bottomActions: [
CurrentPosition(),
ProgressBar(isExpanded: true),
TotalDuration(),
],
),
使用 YouTube URL
插件还提供了 convertUrlToId()
方法,可以将 YouTube 链接转换为其对应的视频 ID。
String videoId;
videoId = YoutubePlayer.convertUrlToId("https://www.youtube.com/watch?v=BBAyRBTfsOU");
print(videoId); // 输出 BBAyRBTfsOU
更多关于Flutter自定义YouTube播放器插件custom_youtube_player_flutter的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter自定义YouTube播放器插件custom_youtube_player_flutter的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter项目中集成和使用自定义YouTube播放器插件 custom_youtube_player_flutter
的示例代码。这个插件允许你在Flutter应用中嵌入和播放YouTube视频。
第一步:添加依赖
首先,你需要在 pubspec.yaml
文件中添加 custom_youtube_player_flutter
依赖:
dependencies:
flutter:
sdk: flutter
custom_youtube_player_flutter: ^x.y.z # 替换为最新版本号
然后运行 flutter pub get
来安装依赖。
第二步:配置Android和iOS项目
Android
你需要在 android/app/src/main/AndroidManifest.xml
文件中添加以下权限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
iOS
在 ios/Runner/Info.plist
文件中,添加以下键和值来允许应用访问网络:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
第三步:使用插件
接下来,在你的 Flutter 项目中使用 custom_youtube_player_flutter
插件。以下是一个简单的示例,展示如何在你的应用中嵌入和播放YouTube视频。
import 'package:flutter/material.dart';
import 'package:custom_youtube_player_flutter/custom_youtube_player_flutter.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: YouTubePlayerScreen(),
);
}
}
class YouTubePlayerScreen extends StatefulWidget {
@override
_YouTubePlayerScreenState createState() => _YouTubePlayerScreenState();
}
class _YouTubePlayerScreenState extends State<YouTubePlayerScreen> {
late YoutubePlayerController _controller;
@override
void initState() {
super.initState();
_controller = YoutubePlayerController(
initialVideoId: 'YOUR_VIDEO_ID', // 替换为你的YouTube视频ID
flags: YoutubePlayerFlags(
autoPlay: false,
mute: false,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('YouTube Player Example'),
),
body: Center(
child: YoutubePlayerView(
controller: _controller,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_controller.loadVideoById('ANOTHER_VIDEO_ID'); // 替换为另一个视频ID
});
},
tooltip: 'Load Another Video',
child: Icon(Icons.arrow_forward),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
注意事项
- 替换视频ID:在
initialVideoId
和loadVideoById
方法中,确保替换为有效的YouTube视频ID。 - 权限处理:确保在Android和iOS项目中正确配置了网络访问权限。
- 错误处理:在实际应用中,你可能需要添加错误处理逻辑,以处理视频播放过程中可能遇到的问题,如网络错误、视频不可用等。
这个示例展示了如何在Flutter应用中集成和使用 custom_youtube_player_flutter
插件来播放YouTube视频。你可以根据需要进一步自定义和扩展此功能。