Flutter中如何不使用WebView播放非.mp4格式的嵌入视频?
Flutter 实现类Chewie风格的嵌入视频播放器(替代WebView)
由于你提到的嵌入URL本质是网页视频,直接用WebView会破坏应用设计风格,这里提供一种方案:用功能更强大的WebView组件渲染视频,同时叠加自定义的类Chewie风格控制栏,兼顾播放能力和UI一致性。
步骤1:添加依赖
在pubspec.yaml中引入所需包:
dependencies: flutter: sdk: flutter flutter_inappwebview: ^6.0.0 # 替代官方WebView,支持JS回调和全屏事件
步骤2:实现自定义播放器组件
这个组件会加载嵌入URL,通过JS监听视频状态,同时叠加类Chewie的控制栏:
import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'dart:async'; class EmbeddedVideoPlayer extends StatefulWidget { final String embedUrl; const EmbeddedVideoPlayer({super.key, required this.embedUrl}); @override State<EmbeddedVideoPlayer> createState() => _EmbeddedVideoPlayerState(); } class _EmbeddedVideoPlayerState extends State<EmbeddedVideoPlayer> { late InAppWebViewController _webController; bool _isPlaying = false; double _currentPosition = 0; double _duration = 0; bool _isFullscreen = false; bool _controlsVisible = true; Timer? _hideControlsTimer; // 注入JS监听视频播放状态 final String _videoListenerJs = ''' const video = document.querySelector('video'); if (video) { video.addEventListener('play', () => window.flutter_inappwebview.callHandler('onPlay')); video.addEventListener('pause', () => window.flutter_inappwebview.callHandler('onPause')); video.addEventListener('timeupdate', () => window.flutter_inappwebview.callHandler('onTimeUpdate', video.currentTime, video.duration)); video.addEventListener('loadedmetadata', () => window.flutter_inappwebview.callHandler('onLoadedMetadata', video.duration)); } '''; @override void dispose() { _hideControlsTimer?.cancel(); super.dispose(); } void _resetHideControlsTimer() { _hideControlsTimer?.cancel(); setState(() => _controlsVisible = true); _hideControlsTimer = Timer(const Duration(seconds: 3), () { if (_isPlaying && !_isFullscreen) return; setState(() => _controlsVisible = false); }); } String _formatDuration(double seconds) { if (seconds.isNaN) return '00:00'; final mins = (seconds ~/ 60).toString().padLeft(2, '0'); final secs = (seconds % 60).toInt().toString().padLeft(2, '0'); return '$mins:$secs'; } @override Widget build(BuildContext context) { return GestureDetector( onTap: _resetHideControlsTimer, child: Stack( children: [ // 底层WebView渲染视频 InAppWebView( initialUrlRequest: URLRequest(url: Uri.parse(widget.embedUrl)), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( mediaPlaybackRequiresUserGesture: false, // 允许自动播放 transparentBackground: true, useShouldOverrideUrlLoading: true, ), ), onWebViewCreated: (controller) => _webController = controller, onLoadStop: (_, __) async { await _webController.evaluateJavascript(source: _videoListenerJs); // 注册JS回调 await _webController.addJavaScriptHandler( handlerName: 'onPlay', callback: (_) => setState(() => _isPlaying = true), ); await _webController.addJavaScriptHandler( handlerName: 'onPause', callback: (_) => setState(() => _isPlaying = false), ); await _webController.addJavaScriptHandler( handlerName: 'onTimeUpdate', callback: (args) => setState(() { _currentPosition = args[0]; _duration = args[1]; }), ); await _webController.addJavaScriptHandler( handlerName: 'onLoadedMetadata', callback: (args) => setState(() => _duration = args[0]), ); }, onEnterFullscreen: (_) => setState(() => _isFullscreen = true), onExitFullscreen: (_) => setState(() => _isFullscreen = false), ), // 类Chewie风格控制栏 if (_controlsVisible) Positioned( bottom: 0, left: 0, right: 0, child: AnimatedOpacity( opacity: _controlsVisible ? 1 : 0, duration: const Duration(milliseconds: 300), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.transparent, Colors.black54], ), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // 进度条 Slider( value: _currentPosition.clamp(0, _duration), max: _duration.isNaN ? 0 : _duration, activeColor: Colors.white, inactiveColor: Colors.white30, onChanged: (value) async { await _webController.evaluateJavascript( source: "document.querySelector('video').currentTime = $value;", ); }, ), Row( children: [ // 播放/暂停按钮 IconButton( icon: Icon( _isPlaying ? Icons.pause : Icons.play_arrow, color: Colors.white, size: 32, ), onPressed: () async { await _webController.evaluateJavascript( source: _isPlaying ? "document.querySelector('video').pause();" : "document.querySelector('video').play();", ); }, ), // 时长显示 Text( '${_formatDuration(_currentPosition)} / ${_formatDuration(_duration)}', style: const TextStyle(color: Colors.white, fontSize: 12), ), const Spacer(), // 全屏按钮 IconButton( icon: Icon( _isFullscreen ? Icons.fullscreen_exit : Icons.fullscreen, color: Colors.white, ), onPressed: () => _isFullscreen ? _webController.exitFullscreen() : _webController.enterFullscreen(), ), ], ), ], ), ), ), ), ], ), ); } }
步骤3:使用播放器
在页面中直接调用组件,传入嵌入URL即可:
// 示例:播放Dailymotion嵌入视频 EmbeddedVideoPlayer( embedUrl: 'https://www.dailymotion.com/embed/video/x8ohlkz?autoplay=1', )
注意事项
- 不同平台的嵌入页面可能需要调整视频选择器:如果
document.querySelector('video')无法找到视频元素,需要根据目标页面的DOM结构修改选择器(比如添加class或ID定位)。 - 部分视频平台可能限制自动播放,可通过修改
mediaPlaybackRequiresUserGesture或引导用户手动触发播放解决。 - 控制栏的样式可根据需求自定义,比如调整颜色、按钮布局,完全匹配Chewie的视觉风格。
内容的提问来源于stack exchange,提问作者R3x
相关产品推荐
相关产品推荐

