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

Flutter开发APK在联想Tab M10 Plus上视频播放异常求助

问题解决方案

核心问题分析

从日志中的NO_MEMORY和解码器初始化失败报错来看,问题根源是视频控制器资源未正确释放,导致联想Tab M10 Plus的硬件解码器资源耗尽,无法初始化新的视频解码器。你的代码存在两个关键逻辑漏洞:

  1. _currentMedia存储的是AspectRatioUI组件而非VideoPlayerController实例,导致_showNextMedia中的控制器释放逻辑完全失效
  2. _initializeVideoPlayerFuture从未被赋值,FutureBuilder的加载逻辑存在异常

修复后的完整代码

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

class SlideShowApp extends StatefulWidget {
  const SlideShowApp({
    Key? key,
    required this.mediaUrls,
    required this.onSlideshowEnd,
  }) : super(key: key);

  final List<String> mediaUrls;
  final VoidCallback onSlideshowEnd;

  @override
  _SlideShowAppState createState() => _SlideShowAppState();
}

class _SlideShowAppState extends State<SlideShowApp> {
  VideoPlayerController? _currentVideoController;
  Widget? _currentMediaWidget;
  int currentIndex = 0;
  Timer? _mediaTimer;

  @override
  void initState() {
    super.initState();
    _loadCurrentMedia();
  }

  void _loadCurrentMedia() {
    final url = widget.mediaUrls[currentIndex];
    final fileType = getFileTypeFromUrl(url);

    // 先释放上一轮的资源
    _currentVideoController?.dispose();
    _currentVideoController = null;
    _mediaTimer?.cancel();

    if (fileType == 'video') {
      _currentVideoController = VideoPlayerController.networkUrl(Uri.parse(url))
        ..initialize().then((_) {
          setState(() {
            _currentMediaWidget = AspectRatio(
              aspectRatio: _currentVideoController!.value.aspectRatio,
              child: VideoPlayer(_currentVideoController!),
            );
          });
          _currentVideoController!.play();
          // 监听视频自然结束事件,替代定时器(更准确)
          _currentVideoController!.addListener(_checkVideoEnd);
        }).catchError((error) {
          // 捕获视频初始化失败,直接跳转到下一个媒体
          _showNextMedia();
        });
    } else if (fileType == 'image') {
      setState(() {
        _currentMediaWidget = Image.network(
          url,
          width: double.infinity,
          height: double.infinity,
          fit: BoxFit.cover,
        );
      });
      _mediaTimer = const Timer(Duration(seconds: 5), _showNextMedia);
    } else {
      setState(() {
        _currentMediaWidget = const SizedBox.shrink();
      });
      _mediaTimer = Timer(const Duration(seconds: 1), _showNextMedia);
    }
  }

  void _checkVideoEnd() {
    if (_currentVideoController != null && _currentVideoController!.value.isCompleted) {
      _currentVideoController!.removeListener(_checkVideoEnd);
      _showNextMedia();
    }
  }

  void _showNextMedia() {
    _currentVideoController?.dispose();
    _mediaTimer?.cancel();

    currentIndex++;
    if (currentIndex < widget.mediaUrls.length) {
      _loadCurrentMedia();
    } else {
      widget.onSlideshowEnd();
      Navigator.pop(context);
    }
  }

  @override
  void dispose() {
    _currentVideoController?.dispose();
    _mediaTimer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (_currentMediaWidget == null) {
      return const Center(child: CircularProgressIndicator());
    }
    return _currentMediaWidget!;
  }
}

String getFileTypeFromUrl(String url) {
  final extension = url.split('.').last.toLowerCase();
  if (extension == 'mp4') {
    return 'video';
  } else if (extension == 'jpg' || extension == 'jpeg') {
    return 'image';
  }
  return 'unknown';
}

关键修复点

  • 分离控制器与UI组件:单独维护_currentVideoController,确保每次切换媒体时都能正确释放解码器资源,避免硬件资源耗尽
  • 替换视频定时器为播放结束监听:避免手动计算时长的误差,同时准确触发下一个媒体的加载
  • 完善资源释放链路:在initState、_showNextMedia、dispose中都添加了资源释放逻辑,彻底防止内存泄漏
  • 添加错误捕获:在视频初始化失败时直接跳转下一个媒体,避免流程中断

额外优化建议

  1. 降低视频分辨率:如果设备硬件性能有限,可将视频转码为720P及以下分辨率,减少解码器负载
  2. 预加载下一个媒体:对于性能较弱的设备,可提前预加载下一个视频/图片资源,提升切换流畅度
  3. 添加加载失败占位:在图片/视频加载失败时显示占位UI,提升用户体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 16:34:53