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
相关产品推荐
相关产品推荐

