You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter使用youtube_player_iframe遇updateAcquireFence错误无法播放视频

Flutter集成youtube_player_iframe播放异常问题排查与修复

问题现象

使用youtube_player_iframe: ^4.0.1包集成YouTube视频播放时出现以下问题:

  • 控制台输出错误提示 updateAcquireFence: Did not find frame. Flutter
  • 视频无法正常播放,等待数分钟后仅显示缩略图,点击播放按钮后持续处于加载状态

代码问题分析

  1. 视频ID提取逻辑错误

    • API返回的link字段存在空格(如https: //youtu.be/Sm3pR9FYZcY),硬编码使用substring(17)会提取到错误的视频ID
    • 固定截取位置的方式不兼容所有YouTube链接格式(如带参数的长链接)
  2. 控制器管理不当

    • 每个列表项都新建YoutubePlayerController,未进行生命周期管理,易引发资源冲突和内存泄漏
    • 外层YoutubePlayerScaffold与列表内YoutubePlayer的嵌套方式不符合插件设计规范
  3. 网络请求重复触发

    • 将getAllLinks()直接传入FutureBuilder的future参数,组件每次重建都会重新发起网络请求
  4. 无效链接未处理

    • API返回的第4条数据中link为testUrl,属于无效链接,会导致播放器初始化失败

修复方案

1. 正确提取视频ID

使用正则表达式兼容多种YouTube链接格式,同时处理链接中的空格:

String extractVideoId(String url) {
  // 移除链接中的空格
  url = url.replaceAll(' ', '');
  final regex = RegExp(r'(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})');
  final match = regex.firstMatch(url);
  return match?.group(1) ?? '';
}

2. 优化控制器生命周期管理

  • 移除外层YoutubePlayerScaffold,直接在列表项中使用YoutubePlayer
  • 在页面销毁时统一销毁所有控制器,避免内存泄漏

3. 避免重复网络请求

在initState中初始化网络请求,将Future对象赋值给变量后传入FutureBuilder:

late Future<LinkModel> _linksFuture;

@override
void initState() {
  super.initState();
  _linksFuture = getAllLinks();
}

4. 处理无效链接

在构建列表项时判断视频ID是否有效,无效则显示错误提示:

if (videoId.isEmpty) {
  return Card(
    child: Padding(
      padding: EdgeInsets.all(16),
      child: Text('无效的视频链接'),
    ),
  );
}

5. 配置平台权限

  • Android:在AndroidManifest.xml中添加网络权限
    <uses-permission android:name="android.permission.INTERNET"/>
    
  • iOS:确保Info.plist中允许HTTPS请求(YouTube链接为HTTPS,默认已支持)

完整修复代码

import 'package:flutter/material.dart';
import 'package:youtube_player_iframe/youtube_player_iframe.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

class LinkModel {
  bool? status;
  String? result;
  List<LinkData>? data;
  int? totalPosts;
  int? totalPages;
  int? perPageLimit;
  int? currentPage;
  int? hasNextPage;

  LinkModel({
    this.status,
    this.result,
    this.data,
    this.totalPosts,
    this.totalPages,
    this.perPageLimit,
    this.currentPage,
    this.hasNextPage,
  });

  LinkModel.fromJson(Map<String, dynamic> json) {
    status = json['status'];
    result = json['result'];
    if (json['data'] != null) {
      data = <LinkData>[];
      json['data'].forEach((v) {
        data!.add(LinkData.fromJson(v));
      });
    }
    totalPosts = json['totalPosts'];
    totalPages = json['totalPages'];
    perPageLimit = json['perPageLimit'];
    currentPage = json['currentPage'];
    hasNextPage = json['hasNextPage'];
  }
}

class LinkData {
  int? refId;
  String? userId;
  String? username;
  String? link;
  String? postDateTime;
  String? city;
  String? channelName;

  LinkData({
    this.refId,
    this.userId,
    this.username,
    this.link,
    this.postDateTime,
    this.city,
    this.channelName,
  });

  LinkData.fromJson(Map<String, dynamic> json) {
    refId = json['ref_id'];
    userId = json['userid'].toString();
    username = json['username'];
    link = json['link'];
    postDateTime = json['postDateTime'];
    city = json['city'];
    channelName = json['channelName'];
  }
}

class LiveProgramScreen extends StatefulWidget {
  const LiveProgramScreen({super.key});

  @override
  State<LiveProgramScreen> createState() => _LiveProgramScreenState();
}

class _LiveProgramScreenState extends State<LiveProgramScreen> {
  late Future<LinkModel> _linksFuture;
  final List<YoutubePlayerController> _controllers = [];

  @override
  void initState() {
    super.initState();
    _linksFuture = getAllLinks();
  }

  @override
  void dispose() {
    // 统一销毁所有控制器
    for (var controller in _controllers) {
      controller.close();
    }
    super.dispose();
  }

  Future<LinkModel> getAllLinks() async {
    var url = "https://url.com/MajlisLinks.php";
    var response = await http.get(Uri.parse(url));
    var jsondata = jsonDecode(response.body.toString());
    print("****" + jsondata.toString());
    return LinkModel.fromJson(jsondata);
  }

  String extractVideoId(String url) {
    url = url.replaceAll(' ', '');
    final regex = RegExp(r'(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})');
    final match = regex.firstMatch(url);
    return match?.group(1) ?? '';
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: const Text(
            "Live Program",
            style: TextStyle(
              fontWeight: FontWeight.bold,
              fontSize: 25,
            ),
          ),
          centerTitle: true,
          backgroundColor: const Color(0xFF006064),
        ),
        body: FutureBuilder<LinkModel>(
          future: _linksFuture,
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              return ListView.builder(
                itemCount: snapshot.data!.data!.length,
                itemBuilder: (context, index) {
                  var allItem = snapshot.data!.data![index];
                  final videoId = extractVideoId(allItem.link!);

                  if (videoId.isEmpty) {
                    return Card(
                      color: const Color(0xFF03A9F4),
                      elevation: 6,
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(10.0),
                      ),
                      child: Padding(
                        padding: const EdgeInsets.all(16.0),
                        child: Text(
                          '无效视频链接: ${allItem.link}',
                          style: const TextStyle(color: Colors.white),
                        ),
                      ),
                    );
                  }

                  final controller = YoutubePlayerController.fromVideoId(
                    videoId: videoId,
                    autoPlay: false,
                    params: const YoutubePlayerParams(
                      showFullscreenButton: true,
                      showControls: true,
                    ),
                  );
                  _controllers.add(controller);

                  return Card(
                    color: const Color(0xFF03A9F4),
                    elevation: 6,
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(10.0),
                    ),
                    child: Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: YoutubePlayer(
                        controller: controller,
                        aspectRatio: 16 / 9,
                      ),
                    ),
                  );
                },
              );
            } else if (snapshot.hasError) {
              return Center(child: Text('加载失败: ${snapshot.error}'));
            } else {
              return const Center(child: CircularProgressIndicator());
            }
          },
        ),
      ),
    );
  }
}

// 十六进制转Color辅助函数
int hexColors(String hex) {
  return int.parse('0xFF$hex');
}

内容的提问来源于stack exchange,提问作者Taki Rajani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 07:01:09