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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 05:50:59