如何将 youtube_player_flutter 与 ListView 生成器一起使用?
How to use youtube_player_flutter with ListView builder?
我正在使用 youtube_player_flutter 包来播放 youtube 嵌入式视频。我遇到了包裹问题。根据我的理解,我尝试了几乎所有的东西,使用 init 方法、分配控制器的函数等 none 有效。
问题
YoutubeController 只接受一个 id,我有一个 URL 列表,所以我在 listview 构建器方法中分配控制器,并根据索引分配 videoId。问题是当应用程序启动时它工作正常,但如果我热重载或保存播放按钮变成无限加载,如果我再次热重载它停止并返回播放按钮。如果我播放视频,播放器播放但仍显示缩略图。如果热重新加载,缩略图将更改为正在播放的视频。如果我热重载或保存,则在初始启动后。它总是需要热重载来改变状态。
使用 onEnd() 属性 将视频重置为初始状态,即带有播放按钮的视频缩略图。但是使用 onEnded: () {_ytController.reset(); }
显示视频缩略图的无限加载。 PS。状态仅在热重载后更改。
播放视频时我不想显示标题容器,所以我使用 _isPlaying
bool 来更改状态。该值正在更改,但即使使用 setstate()
,容器也不会消失。我认为它需要再次重建。
未显示元数据标题,但在 init()
中分配控制器时有效
代码:
import 'package:flutter/material.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
class HomeScreen extends StatefulWidget {
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
// YT Controller
// late YoutubePlayerController _youtubePlayerController;
// Video Title
late String videoTitle;
// Url List
final List<String> _videoUrlList = [
'https://youtu.be/dWs3dzj4Wng',
'https://youtu.be/S3npWREXr8s',
];
/*
YoutubePlayerController _ytFN({String? url}) {
return YoutubePlayerController(
initialVideoId: YoutubePlayer.convertUrlToId(url!)!,
flags: const YoutubePlayerFlags(
autoPlay: false,
enableCaption: true,
),
);
}
//
@override
void initState() {
_ytFN(url: _videoUrlList.first);
super.initState();
}
//
@override
void dispose() {
_ytFN().dispose();
_youtubePlayerController.dispose();
super.dispose();
}
*/
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Tubeloid'),
centerTitle: true,
actions: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.menu_outlined),
)
],
),
body: Padding(
padding: const EdgeInsets.all(10.0),
child: ListView.builder(
itemCount: _videoUrlList.length,
shrinkWrap: true,
itemBuilder: (context, index) {
///-------------------------- ISSUE NO. 1
// YT Controller
final _ytController = YoutubePlayerController(
initialVideoId:
YoutubePlayer.convertUrlToId(_videoUrlList[index])!,
flags: const YoutubePlayerFlags(
autoPlay: false,
enableCaption: true,
captionLanguage: 'en',
),
);
// for container visibility
bool _isPlaying = false;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Stack(
alignment: Alignment.bottomCenter,
children: [
// Youtube Player
Container(
height: 220.0,
decoration: const BoxDecoration(
color: Color(0xfff5f5f5),
borderRadius: BorderRadius.all(Radius.circular(12)),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: YoutubePlayer(
controller: _ytController
..addListener(() {
if (_ytController.value.isPlaying) {
setState(() {
_isPlaying = true;
});
} else {
_isPlaying = false;
}
}),
showVideoProgressIndicator: true,
progressIndicatorColor: Colors.lightBlueAccent,
bottomActions: [
CurrentPosition(),
ProgressBar(isExpanded: true),
FullScreenButton(),
],
onEnded: (YoutubeMetaData _md) {
///--------------------------- ISSUE NO. 2
_ytController.reset();
// _ytController.seekTo(const Duration(seconds: 1));
// _ytController.pause();
_md.videoId;
print(_md.title);
},
),
),
),
///-------------------------- ISSUE NO. 3
// Headline
_isPlaying
? Container()
: Container(
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.9),
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(12),
bottomLeft: Radius.circular(12),
),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
///-------------------------- ISSUE NO. 4
_ytController.metadata.title,
style: const TextStyle(
fontSize: 20.0,
color: Colors.black,
),
),
),
),
],
),
);
},
),
),
);
}
}
代码前的一些注释)
- 不要放这个
final _ytController = YoutubePlayerController...
进入构建器 - 你每次都重新创建所有控制器,当 运行 setstate 时。
你必须为每个视频制作一些控制器列表并将其填充到 init override
- 重置开始你应该使用 _ytController.seekTo 命令。
- 将您的 isPlayng 状态保存到全局列表中并将其签入构建器
- 我仅在播放具体视频时从控制器获取元数据标题。更好的方法 - 之前通过通常的 http.get 请求为您的视频加载标题(类似 https://noembed.com/embed?url=https://www.youtube.com/watch?v=668nUCeBHyY)
我的代码:
import 'package:flutter/material.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
class YT extends StatefulWidget {
const YT({Key? key}) : super(key: key);
@override
_YTState createState() => _YTState();
}
class _YTState extends State<YT> {
late String videoTitle;
// Url List
final List<String> _videoUrlList = [
'https://youtu.be/dWs3dzj4Wng',
'https://www.youtube.com/watch?v=668nUCeBHyY',
'https://youtu.be/S3npWREXr8s',
];
List <YoutubePlayerController> lYTC = [];
Map<String, dynamic> cStates = {};
@override
void initState() {
super.initState();
fillYTlists();
}
fillYTlists(){
for (var element in _videoUrlList) {
String _id = YoutubePlayer.convertUrlToId(element)!;
YoutubePlayerController _ytController = YoutubePlayerController(
initialVideoId: _id,
flags: const YoutubePlayerFlags(
autoPlay: false,
enableCaption: true,
captionLanguage: 'en',
),
);
_ytController.addListener(() {
print('for $_id got isPlaying state ${_ytController.value.isPlaying}');
if (cStates[_id] != _ytController.value.isPlaying) {
if (mounted) {
setState(() {
cStates[_id] = _ytController.value.isPlaying;
});
}
}
});
lYTC.add(_ytController);
}
}
@override
void dispose() {
for (var element in lYTC) {
element.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Tubeloid'),
centerTitle: true,
actions: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.menu_outlined),
)
],
),
body: Padding(
padding: const EdgeInsets.all(10.0),
child: ListView.builder(
itemCount: _videoUrlList.length,
shrinkWrap: true,
itemBuilder: (context, index) {
YoutubePlayerController _ytController = lYTC[index];
String _id = YoutubePlayer.convertUrlToId(_videoUrlList[index])!;
String curState = 'undefined';
if (cStates[_id] != null) {
curState = cStates[_id]? 'playing':'paused';
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Stack(
alignment: Alignment.bottomCenter,
children: [
Container(
height: 220.0,
decoration: const BoxDecoration(
color: Color(0xfff5f5f5),
borderRadius: BorderRadius.all(Radius.circular(12)),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: YoutubePlayer(
controller: _ytController,
showVideoProgressIndicator: true,
progressIndicatorColor: Colors.lightBlueAccent,
bottomActions: [
CurrentPosition(),
ProgressBar(isExpanded: true),
FullScreenButton(),
],
onReady: (){
print('onReady for $index');
},
onEnded: (YoutubeMetaData _md) {
_ytController.seekTo(const Duration(seconds: 0));
},
),
),
),
Container(
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.9),
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(12),
bottomLeft: Radius.circular(12),
),
),
child: Text(curState, textScaleFactor: 1.5,),
)
],
),
);
},
),
),
);
}
}
我正在使用 youtube_player_flutter 包来播放 youtube 嵌入式视频。我遇到了包裹问题。根据我的理解,我尝试了几乎所有的东西,使用 init 方法、分配控制器的函数等 none 有效。
问题
YoutubeController 只接受一个 id,我有一个 URL 列表,所以我在 listview 构建器方法中分配控制器,并根据索引分配 videoId。问题是当应用程序启动时它工作正常,但如果我热重载或保存播放按钮变成无限加载,如果我再次热重载它停止并返回播放按钮。如果我播放视频,播放器播放但仍显示缩略图。如果热重新加载,缩略图将更改为正在播放的视频。如果我热重载或保存,则在初始启动后。它总是需要热重载来改变状态。
使用 onEnd() 属性 将视频重置为初始状态,即带有播放按钮的视频缩略图。但是使用
onEnded: () {_ytController.reset(); }
显示视频缩略图的无限加载。 PS。状态仅在热重载后更改。播放视频时我不想显示标题容器,所以我使用
_isPlaying
bool 来更改状态。该值正在更改,但即使使用setstate()
,容器也不会消失。我认为它需要再次重建。未显示元数据标题,但在
中分配控制器时有效init()
代码:
import 'package:flutter/material.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
class HomeScreen extends StatefulWidget {
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
// YT Controller
// late YoutubePlayerController _youtubePlayerController;
// Video Title
late String videoTitle;
// Url List
final List<String> _videoUrlList = [
'https://youtu.be/dWs3dzj4Wng',
'https://youtu.be/S3npWREXr8s',
];
/*
YoutubePlayerController _ytFN({String? url}) {
return YoutubePlayerController(
initialVideoId: YoutubePlayer.convertUrlToId(url!)!,
flags: const YoutubePlayerFlags(
autoPlay: false,
enableCaption: true,
),
);
}
//
@override
void initState() {
_ytFN(url: _videoUrlList.first);
super.initState();
}
//
@override
void dispose() {
_ytFN().dispose();
_youtubePlayerController.dispose();
super.dispose();
}
*/
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Tubeloid'),
centerTitle: true,
actions: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.menu_outlined),
)
],
),
body: Padding(
padding: const EdgeInsets.all(10.0),
child: ListView.builder(
itemCount: _videoUrlList.length,
shrinkWrap: true,
itemBuilder: (context, index) {
///-------------------------- ISSUE NO. 1
// YT Controller
final _ytController = YoutubePlayerController(
initialVideoId:
YoutubePlayer.convertUrlToId(_videoUrlList[index])!,
flags: const YoutubePlayerFlags(
autoPlay: false,
enableCaption: true,
captionLanguage: 'en',
),
);
// for container visibility
bool _isPlaying = false;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Stack(
alignment: Alignment.bottomCenter,
children: [
// Youtube Player
Container(
height: 220.0,
decoration: const BoxDecoration(
color: Color(0xfff5f5f5),
borderRadius: BorderRadius.all(Radius.circular(12)),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: YoutubePlayer(
controller: _ytController
..addListener(() {
if (_ytController.value.isPlaying) {
setState(() {
_isPlaying = true;
});
} else {
_isPlaying = false;
}
}),
showVideoProgressIndicator: true,
progressIndicatorColor: Colors.lightBlueAccent,
bottomActions: [
CurrentPosition(),
ProgressBar(isExpanded: true),
FullScreenButton(),
],
onEnded: (YoutubeMetaData _md) {
///--------------------------- ISSUE NO. 2
_ytController.reset();
// _ytController.seekTo(const Duration(seconds: 1));
// _ytController.pause();
_md.videoId;
print(_md.title);
},
),
),
),
///-------------------------- ISSUE NO. 3
// Headline
_isPlaying
? Container()
: Container(
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.9),
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(12),
bottomLeft: Radius.circular(12),
),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
///-------------------------- ISSUE NO. 4
_ytController.metadata.title,
style: const TextStyle(
fontSize: 20.0,
color: Colors.black,
),
),
),
),
],
),
);
},
),
),
);
}
}
代码前的一些注释)
- 不要放这个 final _ytController = YoutubePlayerController... 进入构建器 - 你每次都重新创建所有控制器,当 运行 setstate 时。 你必须为每个视频制作一些控制器列表并将其填充到 init override
- 重置开始你应该使用 _ytController.seekTo 命令。
- 将您的 isPlayng 状态保存到全局列表中并将其签入构建器
- 我仅在播放具体视频时从控制器获取元数据标题。更好的方法 - 之前通过通常的 http.get 请求为您的视频加载标题(类似 https://noembed.com/embed?url=https://www.youtube.com/watch?v=668nUCeBHyY)
我的代码:
import 'package:flutter/material.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
class YT extends StatefulWidget {
const YT({Key? key}) : super(key: key);
@override
_YTState createState() => _YTState();
}
class _YTState extends State<YT> {
late String videoTitle;
// Url List
final List<String> _videoUrlList = [
'https://youtu.be/dWs3dzj4Wng',
'https://www.youtube.com/watch?v=668nUCeBHyY',
'https://youtu.be/S3npWREXr8s',
];
List <YoutubePlayerController> lYTC = [];
Map<String, dynamic> cStates = {};
@override
void initState() {
super.initState();
fillYTlists();
}
fillYTlists(){
for (var element in _videoUrlList) {
String _id = YoutubePlayer.convertUrlToId(element)!;
YoutubePlayerController _ytController = YoutubePlayerController(
initialVideoId: _id,
flags: const YoutubePlayerFlags(
autoPlay: false,
enableCaption: true,
captionLanguage: 'en',
),
);
_ytController.addListener(() {
print('for $_id got isPlaying state ${_ytController.value.isPlaying}');
if (cStates[_id] != _ytController.value.isPlaying) {
if (mounted) {
setState(() {
cStates[_id] = _ytController.value.isPlaying;
});
}
}
});
lYTC.add(_ytController);
}
}
@override
void dispose() {
for (var element in lYTC) {
element.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Tubeloid'),
centerTitle: true,
actions: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.menu_outlined),
)
],
),
body: Padding(
padding: const EdgeInsets.all(10.0),
child: ListView.builder(
itemCount: _videoUrlList.length,
shrinkWrap: true,
itemBuilder: (context, index) {
YoutubePlayerController _ytController = lYTC[index];
String _id = YoutubePlayer.convertUrlToId(_videoUrlList[index])!;
String curState = 'undefined';
if (cStates[_id] != null) {
curState = cStates[_id]? 'playing':'paused';
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Stack(
alignment: Alignment.bottomCenter,
children: [
Container(
height: 220.0,
decoration: const BoxDecoration(
color: Color(0xfff5f5f5),
borderRadius: BorderRadius.all(Radius.circular(12)),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: YoutubePlayer(
controller: _ytController,
showVideoProgressIndicator: true,
progressIndicatorColor: Colors.lightBlueAccent,
bottomActions: [
CurrentPosition(),
ProgressBar(isExpanded: true),
FullScreenButton(),
],
onReady: (){
print('onReady for $index');
},
onEnded: (YoutubeMetaData _md) {
_ytController.seekTo(const Duration(seconds: 0));
},
),
),
),
Container(
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.9),
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(12),
bottomLeft: Radius.circular(12),
),
),
child: Text(curState, textScaleFactor: 1.5,),
)
],
),
);
},
),
),
);
}
}