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

Flutter Release包YouTube播放页启动崩溃问题求助

Flutter youtube_player_flutter Release包真机崩溃问题排查与解决

问题描述

使用youtube_player_flutter: ^8.1.2开发YouTube播放页面,Debug模式下模拟器运行完全正常,但构建Release APK后,真机点击视频按钮进入播放页时应用直接崩溃。当前Flutter版本为3.0.1,相关代码、配置如下:

播放页核心代码

late YoutubePlayerController _controller;
late String url = widget.url;
late YoutubePlayer youtubePlayer;
late String id;
late bool isPlaying, isMute;
late String author;

@override
void initState() {
  super.initState();
  
  isMute = false;
  id = YoutubePlayer.convertUrlToId(url)!;
  _controller = YoutubePlayerController(
    initialVideoId: id,
    flags: const YoutubePlayerFlags(autoPlay: false),
  );
  youtubePlayer = YoutubePlayer(
    controller: _controller,
  );
  isPlaying = _controller.value.isPlaying;
  author = _controller.metadata.author;
}

Widget setAuthor() {
  // ignore: unnecessary_null_comparison
  if (_controller.metadata.author != null) {
    setState(() {
      author = _controller.metadata.author;
    });
    return Text(
      author,
      style: const TextStyle(
        color: Colors.pink,
        fontSize: 25,
        fontWeight: FontWeight.bold,
      ),
    );
  } else {
    return setAuthor();
  }
}

// 页面布局部分
SafeArea(
  child: Scaffold(
    resizeToAvoidBottomInset: true,
    body: Padding(
      padding: const EdgeInsets.all(8.0),
      child: Padding(
        padding: const EdgeInsets.all(10.0),
        child: Stack(
          children: [
            _backAction(),
            Positioned(
              top: 130,
              left: 3,
              right: 3,
              child: SizedBox(
                height: MediaQuery.of(context).size.height * 2 / 3.0,
                child: _Player(),
              ),
            ),
          ],
        ),
      ),
    ),
  ),
);

Widget _Player() {
  return YoutubePlayerBuilder(
    player: YoutubePlayer(
      controller: _controller,
      progressIndicatorColor: dustyRose,
    ),
    builder: (BuildContext context, Widget player) {
      return Scaffold(
        body: Column(
          children: [
            const SizedBox(height: 20),
            player,
            const SizedBox(height: 10),
            Text(
              widget.title,
              style: const TextStyle(
                  color: textColorBlue,
                  fontWeight: FontWeight.bold,
                  fontSize: 30),
            ),
            const SizedBox(height: 15),
            Text(
              widget.artist,
              style: const TextStyle(color: textColorBlack, fontSize: 20),
            ),
            const SizedBox(height: 10),
            Text(
              widget.duration,
              style: const TextStyle(color: textColorBlack),
            ),
            const SizedBox(height: 10),
            // ignore: unnecessary_null_comparison
            author == null
                ? setAuthor()
                : Text(
                    _controller.metadata.author,
                    style: const TextStyle(
                        color: Colors.pink,
                        fontWeight: FontWeight.bold,
                        fontSize: 25),
                  ),
            const SizedBox(height: 15),
            buttonRowBuilder(),
          ],
        ),
      );
    },
  );
}

Widget buttonRowBuilder() {
  double size = 35;
  return Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      IconButton(
        onPressed: () {
          if (_controller.value.position <= const Duration(seconds: 30)) {
            _controller.seekTo(const Duration(seconds: 0));
          } else {
            var p = _controller.value.position - const Duration(seconds: 30);
            _controller.seekTo(p);
          }
        },
        icon: const Icon(Icons.replay_30, color: textColorBlue),
        iconSize: size,
      ),
      IconButton(
        onPressed: () {
          if (_controller.value.isPlaying) {
            _controller.pause();
          } else {
            _controller.play();
          }
          setState(() {
            isPlaying = !isPlaying;
          });
        },
        icon: isPlaying
            ? const Icon(Icons.pause, color: textColorBlue)
            : const Icon(Icons.play_arrow, color: textColorBlue),
        iconSize: size,
      ),
      IconButton(
        onPressed: () {
          var newPostion =
              _controller.value.position + const Duration(seconds: 30);
          _controller.seekTo(newPostion);
        },
        icon: const Icon(Icons.forward_30, color: textColorBlue),
        iconSize: size,
      ),
      IconButton(
        onPressed: () {
          if (isMute) {
            _controller.unMute();
          } else {
            _controller.mute();
          }
          setState(() {
            isMute = !isMute;
          });
        },
        icon: isMute
            ? const Icon(Icons.volume_off, color: textColorBlue)
            : const Icon(Icons.volume_up, color: textColorBlue),
        iconSize: size,
      ),
    ],
  );
}

build.gradle配置

defaultConfig {
    applicationId "com.youtube_player"
    minSdkVersion 21
    targetSdkVersion 34
    versionCode flutterVersionCode.toInteger()
    versionName flutterVersionName
    multiDexEnabled true
}

buildTypes {
    release {
        //    signingConfig signingConfigs.debug
        signingConfig signingConfigs.release
        minifyEnabled false
        shrinkResources false
    }
}

AndroidManifest.xml权限

<uses-permission android:name="android.permission.INTERNET" /> 
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

排查与解决步骤

1. 修复空安全强制解包问题

Release模式下空指针异常会直接导致崩溃,而Debug模式可能有容错机制:

  • 视频ID转换:YoutubePlayer.convertUrlToId(url)可能返回null(如URL无效),强制解包!会触发崩溃,替换为非空处理:
    id = YoutubePlayer.convertUrlToId(url) ?? "默认视频ID"; // 或提前判断URL有效性,提示用户
    
  • 作者信息初始化:控制器刚初始化时_controller.metadata.author为null,而late String author未正确初始化,替换为:
    author = _controller.metadata.author ?? "未知作者";
    

2. 消除无限递归调用

setAuthor()方法在作者信息为空时会递归调用自己,导致栈溢出崩溃:

  • 移除setAuthor()方法,改为监听控制器的元数据变化:
    @override
    void initState() {
      super.initState();
      // ...其他初始化代码
      _controller.addListener(() {
        if (_controller.metadata.author != null && _controller.metadata.author != author) {
          setState(() {
            author = _controller.metadata.author!;
          });
        }
      });
    }
    
  • UI部分直接使用:
    Text(
      author,
      style: const TextStyle(
          color: Colors.pink,
          fontWeight: FontWeight.bold,
          fontSize: 25),
    )
    

3. 等待控制器初始化完成

控制器初始化是异步过程,直接读取_controller.value.isPlaying和元数据会导致未初始化的异常:

  • 在_Player()中使用FutureBuilder等待控制器初始化:
    Widget _Player() {
      return YoutubePlayerBuilder(
        player: YoutubePlayer(
          controller: _controller,
          progressIndicatorColor: dustyRose,
        ),
        builder: (BuildContext context, Widget player) {
          return Scaffold(
            body: FutureBuilder(
              future: _controller.initialize(),
              builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.done) {
                  return Column(
                    // 原有的children布局
                  );
                } else {
                  return const Center(child: CircularProgressIndicator());
                }
              },
            ),
          );
        },
      );
    }
    

4. 添加ProGuard规则(可选但推荐)

即使minifyEnabled为false,添加规则避免依赖类被误处理:
在android/app/proguard-rules.pro中添加:

-keep class com.pierfrancescosoffritti.androidyoutubeplayer.core.player.** { *; }
-keep class com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.** { *; }

内容的提问来源于stack exchange,提问作者fareda nageeb

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 07:36:06