You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Flutter WebView视频播放器(webview_flutter 4.13.0)的全屏控制、屏幕方向与观看历史同步问题及iOS崩溃排查

Flutter WebView视频播放器(webview_flutter 4.13.0)的全屏控制、屏幕方向与观看历史同步问题及iOS崩溃排查

看起来你已经把核心功能框架搭得很完整了,Android正常但iOS崩溃确实头疼,我结合webview_flutter 4.x的最佳实践,逐个梳理你的问题点,同时针对性排查iOS崩溃的可能原因:


1. 视频进度追踪:方法正确,但需增强错误处理

你通过JavaScriptChannel接收JS发送的currentTimeduration的方案是WebView中追踪视频进度的标准可靠方案,完全符合WebView API的使用规范。

潜在风险(iOS崩溃诱因)

你的_onJsMessage方法中直接调用json.decode(message.message),如果JS端发送的不是合法JSON格式(比如拼写错误、字段缺失、发送了非JSON字符串),iOS会因为未捕获的异常直接崩溃(Android对未捕获异常的容忍度更高,可能仅打印错误)。

优化建议

给JSON解析添加try-catch,避免崩溃:

void _onJsMessage(JavaScriptMessage message) {
  try {
    final data = json.decode(message.message);
    setState(() {
      _currentTime = (data['currentTime'] ?? 0).toDouble();
      _duration = (data['duration'] ?? 0).toDouble();
    });
  } catch (e, stackTrace) {
    print('📌 解析JS视频进度消息失败: $e\n$stackTrace');
    // 可选:上报错误到监控平台
  }
}

同时建议JS端保证发送的JSON格式严格合法,比如:

// JS端示例:每秒发送一次进度(避免过于频繁)
window.__videoInterval = setInterval(() => {
  const video = document.querySelector('video');
  if (video?.duration) {
    const progressData = JSON.stringify({
      currentTime: video.currentTime,
      duration: video.duration,
      paused: video.paused
    });
    flutterVideoPlayer.postMessage(progressData);
  }
}, 1000);

2. 隐藏全屏按钮:需完善JS实现与时机控制

你的思路是对的,但当前JS代码为空,需要补充可靠的实现,同时注意注入时机和跨域限制

可靠的JS注入代码

针对原生video元素和iframe中的视频,添加全屏按钮隐藏逻辑(兼容iOS/Android的WebKit控件):

void _hideFullscreenButton() {
  _controller.runJavaScript("""
    function hideFullscreenControls() {
      // 隐藏页面中所有video元素的全屏按钮
      const globalStyle = document.createElement('style');
      globalStyle.textContent = `
        video::-webkit-media-controls-fullscreen-button {
          display: none !important;
        }
        // 针对部分自定义播放器的全屏按钮选择器
        .fullscreen-btn, .player-fullscreen {
          display: none !important;
        }
      `;
      document.head.appendChild(globalStyle);

      // 处理iframe中的视频(注意跨域限制:仅当iframe域名与主页面一致时可操作)
      document.querySelectorAll('iframe').forEach(iframe => {
        try {
          const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
          const iframeStyle = iframeDoc.createElement('style');
          iframeStyle.textContent = `
            video::-webkit-media-controls-fullscreen-button {
              display: none !important;
            }
          `;
          iframeDoc.head.appendChild(iframeStyle);
        } catch (e) {
          console.log('⚠️ 跨域无法操作iframe内容:', e);
        }
      });
    }

    // 确保DOM完全加载后执行
    if (document.readyState === 'complete') {
      hideFullscreenControls();
    } else {
      window.addEventListener('DOMContentLoaded', hideFullscreenControls);
    }
  """);
}

注意事项

  • 必须在onPageFinished回调中调用_hideFullscreenButton(你当前的NavigationDelegate已经配置了onPageFinished,时机正确)
  • 如果视频流URL是第三方域名,跨域限制会导致无法操作iframe中的元素,此时只能隐藏主页面的控件,iframe内的全屏按钮无法通过JS控制(需联系视频流提供商修改播放器UI)

3. _handleSafeExit():逻辑正确,可微调细节

你的退出逻辑完全能防止多次pop,核心是通过_isDisposed标记位拦截重复调用,同时在mounted状态下才执行Navigator.pop(),符合Flutter的状态管理规范。

优化建议

SystemChrome的恢复操作改为异步等待(虽然不影响功能,但能保证状态完全恢复后再退出),同时明确恢复的系统UI模式:

Future<void> _handleSafeExit() async {
  if (_isDisposed) return;
  _isDisposed = true;

  // 先恢复系统UI和屏幕方向
  await SystemChrome.setEnabledSystemUIMode(
    SystemUiMode.manual,
    overlays: SystemUiOverlay.values, // 恢复所有系统UI
  );
  await SystemChrome.setPreferredOrientations(DeviceOrientation.values); // 恢复所有支持的方向

  // 异步保存观看历史,不阻塞退出
  unawaited(_handleWatchHistory());
  
  // 确保Widget仍挂载时执行退出
  if (mounted) {
    Navigator.of(context).pop();
  }
}

4. 屏幕方向与系统UI管理:需补充初始化逻辑与配置

你的方向恢复逻辑正确,但进入页面时的初始化逻辑需要完善,同时iOS需要额外配置Info.plist:

初始化逻辑补充

initState中强制设置横屏与沉浸式UI:

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

Future<void> _initVideoPlayerEnvironment() async {
  // 强制横屏(支持左右两个方向)
  await SystemChrome.setPreferredOrientations([
    DeviceOrientation.landscapeLeft,
    DeviceOrientation.landscapeRight,
  ]);
  // 沉浸式全屏(隐藏状态栏和导航栏,滑动可临时显示)
  await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
}

iOS关键配置

Info.plist中添加横屏支持(否则iOS会拒绝强制横屏的请求导致崩溃):

<key>UISupportedInterfaceOrientations</key>
<array>
  <string>UIInterfaceOrientationPortrait</string>
  <string>UIInterfaceOrientationLandscapeLeft</string>
  <string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
  <string>UIInterfaceOrientationPortrait</string>
  <string>UIInterfaceOrientationPortraitUpsideDown</string>
  <string>UIInterfaceOrientationLandscapeLeft</string>
  <string>UIInterfaceOrientationLandscapeRight</string></array>

5. 资源清理(dispose):存在重大遗漏,是iOS崩溃的核心诱因

你的dispose方法没有调用_controller.dispose(),这是webview_flutter 4.x中最常见的内存泄漏和崩溃原因!WebViewController持有原生WebView的资源,必须手动调用dispose()释放,否则iOS会因为资源无法回收直接崩溃。

优化后的dispose方法

@override
void dispose() {
  _isDisposed = true;
  // 取消本地定时器
  _hideTimer?.cancel();

  // 清理WebView相关资源
  if (_controller != null) {
    // 异步清除JS定时器(不阻塞dispose)
    unawaited(_controller.runJavaScript(
      'if (window.__videoInterval) { clearInterval(window.__videoInterval); window.__videoInterval = null; }'
    ));
    // 清除缓存
    _controller.clearCache();
    // 必须调用:释放WebView原生资源
    _controller.dispose();
  }

  // 强制恢复系统UI和方向
  unawaited(SystemChrome.setEnabledSystemUIMode(
    SystemUiMode.manual,
    overlays: SystemUiOverlay.values,
  ));
  unawaited(SystemChrome.setPreferredOrientations(DeviceOrientation.values));

  super.dispose();
}

关键说明

  • _controller.dispose()必须调用的核心步骤,webview_flutter 4.x中WebViewController不会自动销毁
  • unawaited包裹异步操作,避免dispose方法等待耗时操作(dispose不能标记为async)

6. iOS崩溃的针对性排查优先级

结合你的代码,按以下顺序排查:

  1. 是否调用了_controller.dispose():这是最可能的崩溃原因,先补全dispose中的_controller.dispose()
  2. JSON解析是否有异常:添加try-catch后观察是否仍崩溃
  3. Info.plist是否配置了横屏支持:未配置的话iOS会拒绝强制横屏请求导致崩溃
  4. JS注入是否有错误:在Safari开发者工具中连接iOS设备,查看WebView的JS控制台错误(Xcode -> Window -> Devices and Simulators -> 选中设备 -> Open Console)
  5. 跨域限制导致的JS错误:如果视频流是第三方域名,JS注入时的跨域错误可能导致WebView崩溃

总结

你的整体实现思路是正确的,符合webview_flutter的API规范,主要问题集中在资源清理的遗漏iOS平台的严格错误处理要求。按照上述建议修改后,iOS崩溃问题应该能解决,同时功能的稳定性会大幅提升。

火山引擎 最新活动