-
Notifications
You must be signed in to change notification settings - Fork 50
Open
Description
我尝试在列表中同时初始化9个视频控制器
但我会控制只有6个视频是开始播放的状态,但是监听回调会给我 -5的状态
// Invalid license, call failed.
// license 不合法,调用失败
static const PLAY_EVT_ERROR_INVALID_LICENSE = -5;
如果缩减到同时初始化6个视频控制器,只有4个视频是开始播放的状态则不会出现这个问题
class ReusableVideoListPage extends StatefulWidget {
const ReusableVideoListPage({super.key});
@override
_ReusableVideoListPageState createState() => _ReusableVideoListPageState();
}
class _ReusableVideoListPageState extends State<ReusableVideoListPage> {
ReusableVideoListController videoListController = ReusableVideoListController();
final _random = Random();
final List<String> _videos = [
"http://1500005830.vod2.myqcloud.com/43843ec0vodtranscq1500005830/48d0f1f9387702299774251236/adp.10.m3u8",
];
List<List<VideoListData>> homeHorizontalList = [];
List<VideoListData> dataList = [];
final ScrollController _scrollController = ScrollController();
// 优化:统一使用一个状态变量
bool _canBuildVideo = true;
Timer? _scrollStopTimer;
@override
void initState() {
super.initState();
_setupData();
}
void _setupData() {
for (var i = 0; i < 4; i++) {
List<VideoListData> list = [];
for (int index = 0; index < 5; index++) {
var randomVideoUrl = _videos[_random.nextInt(_videos.length)];
list.add(VideoListData("Video $index", randomVideoUrl));
}
homeHorizontalList.add(list);
}
}
bool _checkCanBuildVideo() {
return _canBuildVideo;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Reusable video list")),
body: ListView.builder(
controller: _scrollController,
itemCount: homeHorizontalList.length,
// itemCount: 3,
itemBuilder: (context, index) {
return HomeHorizontalListView(
homeHorizontalList: homeHorizontalList[index],
videoListController: videoListController,
canBuildVideo: _checkCanBuildVideo,
index: index,
);
},
),
);
}
@override
void dispose() {
_scrollController.dispose();
_scrollStopTimer?.cancel();
videoListController.dispose();
super.dispose();
}
}
class HomeHorizontalListView extends StatefulWidget {
final int index;
final List<VideoListData> homeHorizontalList; // 视频数据
final ReusableVideoListController? videoListController; // 视频控制器管理器
final Function canBuildVideo; // 回调函数,判断是否可以构建视频
const HomeHorizontalListView({super.key, required this.homeHorizontalList, this.videoListController, required this.canBuildVideo, required this.index});
@override
State<HomeHorizontalListView> createState() => _HomeHorizontalListViewState();
}
class _HomeHorizontalListViewState extends State<HomeHorizontalListView> {
// 优化:统一使用一个状态变量
bool _canBuildVideo = true;
Timer? _scrollStopTimer;
bool _checkCanBuildVideo() {
return _canBuildVideo;
}
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(vertical: 50),
height: 250,
child: ListView.builder(
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
padding: EdgeInsets.only(left: 12, right: 8),
itemCount: widget.homeHorizontalList.length,
itemBuilder: (context, index){
VideoListData item = widget.homeHorizontalList[index];
return CustomCardView(
index: "横向key:${widget.index} 自己key:${index}",
canBuildColumn: widget.canBuildVideo,
canBuildRow: _checkCanBuildVideo,
videoUrl: item.videoUrl,
);
},
),
);
}
@override
void dispose() {
_scrollStopTimer?.cancel();
super.dispose();
}
}
class CustomCardView extends StatefulWidget {
final String videoUrl;
final Function canBuildColumn; // 纵向列表,判断是否可以构建视频
final Function canBuildRow; // 横向列表,判断是否可以构建视频
final String index;
const CustomCardView({super.key, required this.index, required this.canBuildColumn, required this.canBuildRow, required this.videoUrl});
@override
State<CustomCardView> createState() => _CustomCardViewState();
}
class _CustomCardViewState extends State<CustomCardView> {
late TXVodPlayerController _controller;
StreamSubscription? playEventSubscription;
// 视频画面展示模式
final FTXPlayerRenderMode _renderMode = FTXPlayerRenderMode.ADJUST_RESOLUTION;
final FTXAndroidRenderViewType _renderType = FTXAndroidRenderViewType.TEXTURE_VIEW;
RxBool isPlay = false.obs;
Timer? _timer; // 延迟定时器,用于防止频繁切换
RxString errContent = "初始化中".obs;
Future<void> init() async {
if (!mounted) return;
await SuperPlayerPlugin.setConsoleEnabled(false);
_controller.onPlayerState.listen((val) {
debugPrint("Playback status ${val?.name}");
});
LogUtils.logOpen = true;
playEventSubscription = _controller.onPlayerEventBroadcast.listen((event){
// Subscribe to event distribution
final int code = event["event"];
if (code == TXVodPlayEvent.PLAY_EVT_RCV_FIRST_I_FRAME) {
// isPlay.value = true;
errContent.value = "获取到首帧";
// _supportedBitrates = (await _controller!.getSupportedBitrates())!;
} else if (code== TXVodPlayEvent.PLAY_EVT_PLAY_PROGRESS) {
errContent.value = "播放进度";
// bool playing = await _controller.isPlaying();
// if (playing == false) {
// await _controller.resume();
// }
// _currentProgress = event[TXVodPlayEvent.EVT_PLAY_PROGRESS].toDouble();
// double videoDuration = event[TXVodPlayEvent.EVT_PLAY_DURATION].toDouble(); // Total playback time, converted unit in seconds
// if (videoDuration == 0.0) {
// progressSliderKey.currentState?.updateProgress(0.0, 0.0);
// } else {
// progressSliderKey.currentState?.updateProgress(_currentProgress / videoDuration, videoDuration);
// }
} else if (code == TXVodPlayEvent.PLAY_EVT_PLAY_LOADING) {
errContent.value = "视频缓冲中";
// 视频缓冲中
// EasyLoading.show(status: "loading");
} else if (code == TXVodPlayEvent.PLAY_EVT_VOD_PLAY_PREPARED) {
print("预加载完成");
errContent.value = "预加载完成";
// await _controller.resume();
} else if (code == TXVodPlayEvent.PLAY_EVT_VOD_LOADING_END || code == TXVodPlayEvent.PLAY_EVT_PLAY_BEGIN) {
errContent.value = "准备播放";
isPlay.value = true;
// bool playing = await _controller.isPlaying();
// if (playing == false) {
// await _controller.resume();
// }
// EasyLoading.dismiss();
} else if (code != -100 && code < 0) {
errContent.value = "code:$event";
print("=====视频加载失败了:$event");
// EasyLoading.showToast("playError:$event");
}
});
// 3. 添加微小延迟确保监听器完全注册(可选但更安全)
await Future.delayed(Duration(milliseconds: 100));
await _controller.setConfig(FTXVodPlayConfig());
await _controller.enableHardwareDecode(true);
await _controller.setRenderMode(_renderMode);
await _controller.setLoop(true);
await _controller.setAutoPlay(isAutoPlay: false);
await _controller.startVodPlay(widget.videoUrl);
}
void startVideoController() async{
await _controller.resume();
}
@override
void initState() {
super.initState();
_controller = TXVodPlayerController();
init();
}
@override
void deactivate() {
super.deactivate();
}
@override
void dispose() {
playEventSubscription?.cancel();
_controller.dispose();
_timer?.cancel(); // 添加这行
super.dispose();
}
// 开始播放
void startPlay() async{
// errContent.value = "准备开始播放";
bool playStatus = await _controller.isPlaying();
// 判断是否存在视频控制器
if (playStatus == false) {
// errContent.value = "执行开始播放";
startVideoController();
}
}
// 暂停播放
void pausePlay() {
_controller.pause();
}
@override
Widget build(BuildContext context) {
print(widget.videoUrl);
return VisibilityDetector(
key: Key(widget.index),
onVisibilityChanged: (info) {
// 可见范围大于百分之60可见
bool isVisible = info.visibleFraction >= 0.6;
// 统一使用延迟处理,避免频繁切换
_timer?.cancel();
_timer = Timer(Duration(milliseconds: 500), () {
if (!mounted) return; // 安全检查
if (isVisible) {
startPlay();
} else {
pausePlay();
}
});
},
child: Stack(
children: [
Container(
margin: EdgeInsets.only(right: 8),
width: 120,
height: 200,
// width: 150,
// height: 300,
color: Colors.green,
child: TXPlayerVideo(
androidRenderType: _renderType,
onRenderViewCreatedListener: (viewId) {
/// 此处只展示了最基础的纹理和播放器的配置方式。 这里可记录下来 viewId,在多纹理之间进行切换,比如横竖屏切换场景,竖屏的画面,
/// 要切换到横屏的画面,可以在切换到横屏之后,拿到横屏的viewId 设置上去。回到竖屏的时候,再通过 viewId 切换回来。
/// Only the most basic configuration methods for textures and the player are shown here.
/// The `viewId` can be recorded here to switch between multiple textures. For example, in the scenario
/// of switching between portrait and landscape orientations:
/// To switch from the portrait view to the landscape view, obtain the `viewId` of the landscape view
/// after switching to landscape orientation and set it. When switching back to portrait orientation,
/// switch back using the recorded `viewId`.
_controller.setPlayerView(viewId);
},
),
),
Obx(()=>isPlay.value == false ? Container(
margin: EdgeInsets.only(right: 8),
width: 120,
height: 200,
// width: 150,
// height: 300,
color: isPlay.value ? Colors.green : Colors.red,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(widget.index, style: TextStyle(color: Colors.white, fontSize: 14)),
Text(isPlay.value ? "可以播放" : "不可以播放", style: TextStyle(color: Colors.white, fontSize: 14)),
],
),
) : const SizedBox()),
Positioned.fill(
child: Container(
alignment: Alignment.bottomCenter,
child: Obx(()=>Text(errContent.value, style: TextStyle(color: Colors.black, fontSize: 14))),
),
)
],
),
);
}
}
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels