Skip to content

列表中播放多视频超过6个无法播放问题 #146

@ming0747

Description

@ming0747

我尝试在列表中同时初始化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))),
            ),
          )
        ],
      ),
    );
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions