Flutter Chewie视频播放器持续卡在加载状态的问题求助
Chewie播放器始终卡在加载界面,无法播放视频
我在Flutter应用中集成Chewie播放器,已完成自定义配置,编译正常,但在Web、Android等设备上运行时,播放器一直显示加载界面:

以下是我的代码:
import '/backend/backend.dart'; import '/flutter_flow/flutter_flow_theme.dart'; import '/flutter_flow/flutter_flow_util.dart'; import '/custom_code/widgets/index.dart'; // Imports other custom widgets import '/custom_code/actions/index.dart'; // Imports custom actions import '/flutter_flow/custom_functions.dart'; // Imports custom functions import 'package:flutter/material.dart'; // Begin custom widget code import 'package:chewie/chewie.dart'; import 'package:video_player/video_player.dart'; import 'dart:io'; class ChewieVideoPlayer extends StatefulWidget { ChewieVideoPlayer({ Key? key, this.width, this.height, this.videoPath, this.videosList, this.isAutoPlay, this.isLooping, this.hideControlsAfter, this.currentPlayIndex, this.startTime, this.videoPlatform, this.hideControls, this.aspectRatio, this.disableFullScreen, this.isLive, this.disableZoomAndPan, this.hideOptions, this.allowScreenSleep, this.disableMuting, this.disablePlaybackSpeedChanging, this.disableAutoInitialize, this.fullScreenByDefault, this.playbackSpeedList, this.progressIndicatorDelay, this.nonDraggableProgressBar, }) : super(key: key); final double? width; final double? height; final String? videoPath; final List<String>? videosList; final bool? isAutoPlay; final bool? isLooping; final int? hideControlsAfter; int? currentPlayIndex; final double? startTime; final String? videoPlatform; final bool? hideControls; final double? aspectRatio; final bool? disableFullScreen; final bool? isLive; final bool? disableZoomAndPan; final bool? hideOptions; final bool? allowScreenSleep; final bool? disableMuting; final bool? disablePlaybackSpeedChanging; final bool? disableAutoInitialize; final bool? fullScreenByDefault; final List<double>? playbackSpeedList; final int? progressIndicatorDelay; final bool? nonDraggableProgressBar; @override State<ChewieVideoPlayer> createState() => _ChewieVideoPlayerState(); } class _ChewieVideoPlayerState extends State<ChewieVideoPlayer> { TargetPlatform? _platform; late VideoPlayerController _videoPlayerController1; late VideoPlayerController _videoPlayerController2; ChewieController? _chewieController; late List<String> srcs; //Initializing the widget state @override void initState() { super.initState(); initializePlayer(); /* srcs = widget.videosList ?? [ widget.videoPath ?? 'https://assets.mixkit.co/videos/preview/mixkit-daytime-city-traffic-aerial-view-56-large.mp4']; */ srcs = [ 'https://assets.mixkit.co/videos/preview/mixkit-daytime-city-traffic-aerial-view-56-large.mp4' ]; } //Function to clear the memory to avoid leaks @override void dispose() { _videoPlayerController1.dispose(); _videoPlayerController2.dispose(); _chewieController?.dispose(); super.dispose(); } //Function to initialize the player Future<void> initializePlayer() async { _videoPlayerController1 = VideoPlayerController.networkUrl( Uri.parse(srcs[widget.currentPlayIndex ?? 0])); _videoPlayerController2 = VideoPlayerController.networkUrl( Uri.parse(srcs[widget.currentPlayIndex ?? 0])); await Future.wait([ _videoPlayerController1.initialize(), _videoPlayerController2.initialize() ]); _createChewieController(); setState(() {}); } //Function that creates ChewieController and custmoize it (e.g. set color, autoplay, looping,etc) void _createChewieController() { // Check if videoPlatform is provided and set the platform accordingly if (widget.videoPlatform != null) { switch (widget.videoPlatform) { case 'android': _platform = TargetPlatform.android; break; case 'ios': _platform = TargetPlatform.iOS; break; case 'windows': _platform = TargetPlatform.windows; break; default: // Handle other cases or leave platform as null break; } } final subtitles = [ Subtitle( index: 0, start: Duration.zero, end: const Duration(seconds: 10), text: const TextSpan( children: [ TextSpan( text: 'Hello', style: TextStyle(color: Colors.red, fontSize: 22), ), TextSpan( text: ' from ', style: TextStyle(color: Colors.green, fontSize: 20), ), TextSpan( text: 'subtitles', style: TextStyle(color: Colors.blue, fontSize: 18), ) ], ), ), Subtitle( index: 0, start: const Duration(seconds: 10), end: const Duration(seconds: 20), text: 'Whats up? :)', ), ]; _chewieController = ChewieController( videoPlayerController: _videoPlayerController1, autoPlay: widget.isAutoPlay ?? false, looping: widget.isLooping ?? false, hideControlsTimer: Duration(seconds: widget.hideControlsAfter ?? 3), showControls: widget.hideControls ?? true, startAt: widget.startTime != null ? Duration(milliseconds: (widget.startTime! * 1000).toInt()) : null, aspectRatio: widget.aspectRatio ?? 1.7, allowFullScreen: !(widget.disableFullScreen ?? false), isLive: widget.isLive ?? false, zoomAndPan: !(widget.disableZoomAndPan ?? false), showOptions: !(widget.hideOptions ?? false), allowedScreenSleep: widget.allowScreenSleep ?? false, allowMuting: !(widget.disableMuting ?? false), allowPlaybackSpeedChanging: !(widget.disablePlaybackSpeedChanging ?? false), autoInitialize: !(widget.disableAutoInitialize ?? false), fullScreenByDefault: widget.fullScreenByDefault ?? false, draggableProgressBar: !(widget.nonDraggableProgressBar ?? false), playbackSpeeds: widget.playbackSpeedList ?? [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2], progressIndicatorDelay: widget.progressIndicatorDelay != null ? Duration(milliseconds: (widget.progressIndicatorDelay!)) : null, additionalOptions: (context) { return <OptionItem>[ OptionItem( onTap: toggleVideo, iconData: Icons.live_tv_sharp, title: 'Next Video', ), ]; }, subtitle: Subtitles(subtitles), subtitleBuilder: (context, dynamic subtitle) => Container( padding: const EdgeInsets.all(10.0), child: subtitle is InlineSpan ? RichText( text: subtitle, ) : Text( subtitle.toString(), style: const TextStyle(color: Colors.black), ), ), ); } //Function to switch to the next video in list Future<void> toggleVideo() async { await _videoPlayerController1.pause(); widget.currentPlayIndex = (widget.currentPlayIndex ?? 0) + 1; if (widget.currentPlayIndex! >= srcs.length) { widget.currentPlayIndex = 0; } await initializePlayer(); } //Building the widget @override Widget build(BuildContext context) { return MaterialApp( theme: AppTheme.light.copyWith( platform: _platform ?? Theme.of(context).platform, ), home: Scaffold( body: Column( children: <Widget>[ Expanded( child: Center( child: _chewieController != null && _chewieController! .videoPlayerController.value.isInitialized ? Chewie(controller: _chewieController!,) : const Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), SizedBox(height: 20), Text('Loading'), ], ), ), ), ], ), ), ); ); } // Theme class AppTheme { static final light = ThemeData( brightness: Brightness.light, useMaterial3: true, colorScheme: const ColorScheme.light(secondary: Colors.red), disabledColor: Colors.grey.shade400, visualDensity: VisualDensity.adaptivePlatformDensity, ); static final dark = ThemeData( brightness: Brightness.dark, colorScheme: const ColorScheme.dark(secondary: Colors.red), disabledColor: Colors.grey.shade400, useMaterial3: true, visualDensity: VisualDensity.adaptivePlatformDensity, ); }
我尝试过的操作:
- 重新安装应用,在Web和Android Studio中运行,问题未解决
- 移除视频列表逻辑,硬编码Chewie官方示例视频地址,仍无效果
- 期望:当videosList不为空时播放列表内容,为空则使用videoPath作为首个播放项
问题定位及解决方案
1. 初始化顺序错误
你在initState中先调用initializePlayer(),之后才初始化srcs列表,导致initializePlayer执行时srcs未被赋值,直接引发异常。必须先初始化视频源,再初始化播放器:
@override void initState() { super.initState(); // 先初始化视频源列表 srcs = widget.videosList ?? [ widget.videoPath ?? 'https://assets.mixkit.co/videos/preview/mixkit-daytime-city-traffic-aerial-view-56-large.mp4' ]; // 再初始化播放器 initializePlayer(); }
2. 冗余的双控制器初始化
你同时初始化了_videoPlayerController1和_videoPlayerController2,但实际只用到前者,这会浪费资源并可能引发初始化异常。移除无用的控制器:
// 修改initializePlayer方法 Future<void> initializePlayer() async { _videoPlayerController1 = VideoPlayerController.networkUrl( Uri.parse(srcs[widget.currentPlayIndex ?? 0])); await _videoPlayerController1.initialize(); _createChewieController(); setState(() {}); } // 修改dispose方法 @override void dispose() { _videoPlayerController1.dispose(); _chewieController?.dispose(); super.dispose(); }
3. 错误修改Widget成员变量
widget是不可变对象,不能在State中直接修改widget.currentPlayIndex。将播放索引移到State类中作为本地变量:
// 在_ChewieVideoPlayerState中添加本地变量 int? _currentPlayIndex; // 修改initState初始化 @override void initState() { super.initState(); _currentPlayIndex = widget.currentPlayIndex; srcs = widget.videosList ?? [ widget.videoPath ?? 'https://assets.mixkit.co/videos/preview/mixkit-daytime-city-traffic-aerial-view-56-large.mp4' ]; initializePlayer(); } // 修改initializePlayer中的引用 _videoPlayerController1 = VideoPlayerController.networkUrl( Uri.parse(srcs[_currentPlayIndex ?? 0])); // 修改toggleVideo方法 Future<void> toggleVideo() async { await _videoPlayerController1.pause(); _currentPlayIndex = (_currentPlayIndex ?? 0) + 1; if (_currentPlayIndex! >= srcs.length) { _currentPlayIndex = 0; } await initializePlayer(); }
4. 嵌套MaterialApp导致上下文冲突
自定义Widget中返回MaterialApp会覆盖外部应用的主题和导航上下文,可能导致播放器控件渲染异常,改为使用Theme包裹:
@override Widget build(BuildContext context) { return Theme( data: AppTheme.light.copyWith( platform: _platform ?? Theme.of(context).platform, ), child: Scaffold( body: Column( children: <Widget>[ Expanded( child: Center( child: _chewieController != null && _chewieController! .videoPlayerController.value.isInitialized ? Chewie(controller: _chewieController!) : const Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), SizedBox(height: 20), Text('Loading'), ], ), ), ), ], ), ), ); }
5. 补充必要权限(Android)
确保AndroidManifest.xml中添加网络权限:
<uses-permission android:name="android.permission.INTERNET"/>
6. Web端CORS问题处理
如果Web端加载失败,大概率是视频源存在CORS限制,可更换支持CORS的视频源,或在本地开发时配置CORS代理。
内容的提问来源于stack exchange,提问作者Taha Hamad
相关产品推荐
相关产品推荐

