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

Flutter Web如何播放后端返回的Uint8List格式MP4视频?

Flutter Web播放Uint8List格式MP4的替代方案

由于Flutter Web无法访问本地临时文件系统,针对从Websocket接收的Uint8List格式MP4视频,可通过以下两种方案实现播放:

方案一:基于video_player包结合Blob URL

利用Web平台的Blob API将Uint8List转换为内存中的二进制对象,生成可被video_player识别的Blob URL,无需写入本地文件。

跨平台实现代码

import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:video_player/video_player.dart';

class VideoPlayerWidget extends StatefulWidget {
  final List<Uint8List> videoList;
  const VideoPlayerWidget({super.key, required this.videoList});

  @override
  State<VideoPlayerWidget> createState() => _VideoPlayerWidgetState();
}

class _VideoPlayerWidgetState extends State<VideoPlayerWidget> {
  VideoPlayerController? _controller;

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

  Future<void> _setupVideoPlayer() async {
    // 合并所有Uint8List分片为完整视频数据
    final videoData = Uint8List.fromList(
      widget.videoList.fold<List<int>>([], (prev, curr) => prev..addAll(curr)),
    );

    if (kIsWeb) {
      // Web端处理逻辑
      import 'dart:html' as html;
      final blob = html.Blob([videoData], 'video/mp4');
      final blobUrl = html.Url.createObjectUrlFromBlob(blob);

      _controller = VideoPlayerController.network(blobUrl)
        ..initialize().then((_) {
          setState(() {});
          _controller?.play();
        });
    } else {
      // 移动端保留原有逻辑
      final tempDir = await getTemporaryDirectory();
      final videoFile = File('${tempDir.path}/processed_video.mp4');
      await videoFile.writeAsBytes(videoData);

      _controller = VideoPlayerController.file(videoFile)
        ..initialize().then((_) {
          setState(() {});
          _controller?.play();
        });
    }
  }

  @override
  void dispose() {
    // 释放控制器资源
    _controller?.dispose();
    // Web端需手动释放Blob URL,避免内存泄漏
    if (kIsWeb && _controller?.dataSource != null) {
      import 'dart:html' as html;
      html.Url.revokeObjectUrl(_controller!.dataSource!);
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (_controller == null || !_controller!.value.isInitialized) {
      return const Center(child: CircularProgressIndicator());
    }
    return AspectRatio(
      aspectRatio: _controller!.value.aspectRatio,
      child: VideoPlayer(_controller!),
    );
  }
}

方案二:直接嵌入Web原生Video元素

如果不想依赖video_player包,可直接通过HtmlElementView嵌入浏览器原生的<video>标签,更轻量化。

实现代码

import 'dart:html' as html;
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';

class NativeVideoPlayer extends StatefulWidget {
  final List<Uint8List> videoList;
  const NativeVideoPlayer({super.key, required this.videoList});

  @override
  State<NativeVideoPlayer> createState() => _NativeVideoPlayerState();
}

class _NativeVideoPlayerState extends State<NativeVideoPlayer> {
  String? _blobUrl;
  html.VideoElement? _videoElement;

  @override
  void initState() {
    super.initState();
    if (kIsWeb) {
      _initNativeVideo();
    }
  }

  void _initNativeVideo() {
    final videoData = Uint8List.fromList(
      widget.videoList.fold<List<int>>([], (prev, curr) => prev..addAll(curr)),
    );
    final blob = html.Blob([videoData], 'video/mp4');
    _blobUrl = html.Url.createObjectUrlFromBlob(blob);

    _videoElement = html.VideoElement()
      ..src = _blobUrl
      ..autoplay = true
      ..controls = true
      ..style.width = '100%'
      ..style.height = 'auto';

    // 注册视图工厂
    html.registerElementViewFactory('native-video', (int viewId) => _videoElement!);
  }

  @override
  void dispose() {
    if (kIsWeb) {
      _videoElement?.remove();
      if (_blobUrl != null) {
        html.Url.revokeObjectUrl(_blobUrl!);
      }
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (!kIsWeb) {
      return const SizedBox.shrink();
    }
    return HtmlElementView(viewType: 'native-video');
  }
}

关键注意事项

  • 无论哪种方案,都必须在组件销毁时调用html.Url.revokeObjectUrl()释放Blob URL,避免浏览器内存泄漏。
  • 确保后端返回的MP4编码格式符合Web浏览器支持的标准(如H.264编码),否则可能出现无法播放的情况。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 07:43:26