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

如何在Flutter Html Widget中展示API返回的内嵌视频?

Flutter中加载WordPress接口返回的视频内容问题

问题描述

调用WordPress REST接口 https://wrestlingworld.co/wp-json/wp/v2/posts?categories=22 后,从content->rendered字段获取到包含视频的HTML内容,使用flutter_html的Html组件渲染时,文本内容正常显示,但视频仅呈现占位样式,无法播放。当前使用的代码如下:

Padding(
  padding: const EdgeInsets.symmetric(horizontal: 5),
  child: Align(
    alignment: Alignment.center,
    child: Html(
      data: newsDetails.content!.rendered!,
      style: {
        "body": Style(
            fontSize: const FontSize(16),
            fontFamily: 'NunitoSans'),
      },
    ),
  ),
),

解决方案

flutter_html 默认不支持原生渲染<video>或<iframe>标签的视频内容,需要通过自定义渲染器或提前解析视频链接来解决,以下是两种可行方案:

方案一:自定义视频标签渲染器

通过customRender参数扩展Html组件,将HTML中的视频标签替换为Flutter原生视频播放器:

  1. 添加依赖
    在pubspec.yaml中引入所需包:
dependencies:
  flutter_html: ^3.0.0-beta.2 # 使用最新稳定版
  video_player: ^2.6.1
  # 若接口返回iframe嵌入的视频(如YouTube),需额外添加webview_flutter
  webview_flutter: ^4.2.0
  1. 实现视频播放器组件
class VideoPlayerWidget extends StatefulWidget {
  final String videoUrl;

  const VideoPlayerWidget({Key? key, required this.videoUrl}) : super(key: key);

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

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

  @override
  void initState() {
    super.initState();
    _controller = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl))
      ..initialize().then((_) => setState(() {}));
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return _controller.value.isInitialized
        ? Column(
            children: [
              AspectRatio(
                aspectRatio: _controller.value.aspectRatio,
                child: VideoPlayer(_controller),
              ),
              IconButton(
                icon: Icon(
                  _controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
                ),
                onPressed: () => setState(() {
                  _controller.value.isPlaying
                      ? _controller.pause()
                      : _controller.play();
                }),
              ),
            ],
          )
        : const Center(child: CircularProgressIndicator());
  }
}
  1. 修改Html组件代码
    添加customRender来处理视频标签:
Padding(
  padding: const EdgeInsets.symmetric(horizontal: 5),
  child: Align(
    alignment: Alignment.center,
    child: Html(
      data: newsDetails.content!.rendered!,
      style: {
        "body": Style(
          fontSize: const FontSize(16),
          fontFamily: 'NunitoSans',
        ),
      },
      customRender: {
        // 处理原生video标签
        "video": (context, attributes, _) {
          final videoSrc = attributes['src'];
          return videoSrc != null
              ? VideoPlayerWidget(videoUrl: videoSrc)
              : const SizedBox.shrink();
        },
        // 处理iframe嵌入的视频(可选)
        "iframe": (context, attributes, _) {
          final iframeSrc = attributes['src'];
          return iframeSrc != null
              ? SizedBox(
                  width: double.infinity,
                  height: 200,
                  child: WebViewWidget(
                    controller: WebViewController()
                      ..loadRequest(Uri.parse(iframeSrc)),
                  ),
                )
              : const SizedBox.shrink();
        },
      },
    ),
  ),
),

方案二:提前解析视频链接单独展示

通过正则表达式从HTML内容中提取视频链接,单独渲染视频播放器,避免HTML渲染兼容性问题:

// 提取video标签的src链接
RegExp videoRegExp = RegExp(r'<video[^>]+src="([^"]+)"');
Match? videoMatch = videoRegExp.firstMatch(newsDetails.content!.rendered!);
String? videoUrl = videoMatch?.group(1);

// 界面中先展示视频,再渲染文本内容
Column(
  children: [
    if (videoUrl != null) VideoPlayerWidget(videoUrl: videoUrl),
    Padding(
      padding: const EdgeInsets.symmetric(horizontal: 5),
      child: Align(
        alignment: Alignment.center,
        child: Html(
          data: newsDetails.content!.rendered!,
          style: {
            "body": Style(
              fontSize: const FontSize(16),
              fontFamily: 'NunitoSans',
            ),
          },
          // 隐藏原HTML中的视频占位
          customRender: {
            "video": (context, attributes, _) => const SizedBox.shrink(),
          },
        ),
      ),
    ),
  ],
)

内容的提问来源于stack exchange,提问作者Sakib ovi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 15:45:43