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

Flutter Chewie视频播放器持续卡在加载状态的问题求助

Chewie播放器始终卡在加载界面,无法播放视频

我在Flutter应用中集成Chewie播放器,已完成自定义配置,编译正常,但在Web、Android等设备上运行时,播放器一直显示加载界面:

Chewie播放器卡在加载界面

以下是我的代码:

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 03:39:53