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

