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

如何在Flutter中实现类似YouTube的视频质量控制器

基于Chewie+video_player实现多分辨率视频控制方案

问题解答

  1. 如何实现类似YouTube的多分辨率质量控制?

    • 先为目标视频生成不同分辨率的MP4文件,维护每个分辨率对应的URL列表。
    • 在Chewie的控制菜单中添加分辨率选择项,用户选择后,记录当前播放进度,销毁旧的VideoPlayerController和ChewieController,初始化对应分辨率的新控制器,最后恢复播放进度。
    • 可在UI上标记当前选中的分辨率,提升用户体验。
  2. 如何控制视频的分辨率与带宽?

    • 分辨率控制:直接切换对应分辨率的视频源URL,本质是加载不同码率/分辨率的MP4文件。
    • 带宽控制:MP4是单分辨率封装格式,无法像HLS/DASH那样动态自适应带宽,只能通过让用户手动选择低分辨率(低码率)视频来减少带宽消耗;也可以结合网络状态检测,自动为用户推荐合适的分辨率选项。
  3. 是否通过上传各分辨率视频并解析URL列表的方式即可实现该功能?

    • 是的,这是MP4格式实现多分辨率控制的标准方案。因为MP4不支持自适应码率流,必须为每个分辨率单独生成文件并提供独立URL,通过切换不同URL来实现质量切换。

修改后的完整代码

import 'package:flutter/material.dart';
import 'package:chewie/chewie.dart';
import 'package:video_player/video_player.dart';

class ChewieDemo extends StatefulWidget {
  const ChewieDemo({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _ChewieDemoState();
}

class _ChewieDemoState extends State<ChewieDemo> {
  late VideoPlayerController _videoPlayerController;
  ChewieController? _chewieController;
  int? bufferDelay;

  // 定义视频源结构:每个视频对应不同分辨率的URL
  final List<Map<String, dynamic>> videoSources = [
    {
      "title": "视频1",
      "resolutions": {
        "144p": "https://example.com/video1_144p.mp4",
        "360p": "https://firebasestorage.googleapis.com/v0/b/fortfolio-proj.appspot.com/o/cardHolder%2F121387692_619890725372735_3264722844580789277_n.mp4?alt=media&token=e611257b-07b3-4d07-bfc8-30b0d0e0d86c",
        "720p": "https://example.com/video1_720p.mp4"
      }
    },
    {
      "title": "视频2",
      "resolutions": {
        "360p": "https://assets.mixkit.co/videos/preview/mixkit-daytime-city-traffic-aerial-view-56-large.mp4",
        "720p": "https://example.com/video2_720p.mp4"
      }
    }
  ];

  int currVideoIndex = 0;
  String currentResolution = "360p"; // 默认分辨率

  @override
  void initState() {
    super.initState();
    initializePlayer();
  }

  @override
  void dispose() {
    _videoPlayerController.dispose();
    _chewieController?.dispose();
    super.dispose();
  }

  Future<void> initializePlayer({Duration? resumePosition}) async {
    // 获取当前选中的分辨率URL
    final videoUrl = videoSources[currVideoIndex]["resolutions"][currentResolution];
    
    _videoPlayerController = VideoPlayerController.network(videoUrl);
    await _videoPlayerController.initialize();
    
    // 如果有需要恢复的进度,跳转到对应位置
    if (resumePosition != null) {
      await _videoPlayerController.seekTo(resumePosition);
    }
    
    _createChewieController();
    setState(() {});
  }

  void _createChewieController() {
    final subtitles = [
      Subtitle(
        index: 0,
        start: Duration.zero,
        end: const Duration(seconds: 10),
        text: const TextSpan(
          children: [
            TextSpan(
              text: 'Checking sub subtitles',
              style: TextStyle(color: Colors.white, fontSize: 18),
            )
          ],
        ),
      ),
      Subtitle(
        index: 0,
        start: const Duration(seconds: 10),
        end: const Duration(seconds: 20),
        text: const TextSpan(
          text: 'Whats up? :)',
          style: TextStyle(color: Colors.white, fontSize: 18),
        ),
      ),
    ];

    _chewieController = ChewieController(
      showOptions: true,
      allowMuting: true,
      allowPlaybackSpeedChanging: false,
      allowFullScreen: true,
      videoPlayerController: _videoPlayerController,
      aspectRatio: _videoPlayerController.value.aspectRatio,
      autoPlay: resumePosition != null, // 切换后如果有进度则自动播放
      progressIndicatorDelay: bufferDelay != null ? Duration(milliseconds: bufferDelay!) : null,
      additionalOptions: (context) {
        final options = <OptionItem>[];
        
        // 添加分辨率选择选项
        videoSources[currVideoIndex]["resolutions"].forEach((res, url) {
          options.add(OptionItem(
            onTap: () => _switchResolution(res),
            iconData: Icons.high_quality,
            title: res,
            // 标记当前选中的分辨率
            trailing: currentResolution == res ? const Icon(Icons.check, color: Colors.white) : null,
          ));
        });
        
        // 添加切换视频选项
        options.add(OptionItem(
          onTap: _toggleVideo,
          iconData: Icons.live_tv_sharp,
          title: '切换视频源',
        ));
        
        return options;
      },
      subtitle: Subtitles(subtitles),
      subtitleBuilder: (context, dynamic subtitle) => Container(
        padding: const EdgeInsets.all(10.0),
        child: subtitle is InlineSpan
            ? RichText(text: subtitle)
            : Text(subtitle.toString(), style: const TextStyle(color: Colors.black)),
      ),
      hideControlsTimer: const Duration(seconds: 1),
    );
  }

  Future<void> _switchResolution(String newResolution) async {
    if (newResolution == currentResolution) return;
    
    // 记录当前播放进度
    final currentPosition = await _videoPlayerController.position;
    
    // 销毁旧控制器
    await _videoPlayerController.pause();
    _videoPlayerController.dispose();
    _chewieController?.dispose();
    
    // 更新当前分辨率
    setState(() {
      currentResolution = newResolution;
    });
    
    // 初始化新控制器并恢复进度
    await initializePlayer(resumePosition: currentPosition);
    await _videoPlayerController.play();
  }

  Future<void> _toggleVideo() async {
    // 记录当前进度
    final currentPosition = await _videoPlayerController.position;
    
    // 销毁旧控制器
    await _videoPlayerController.pause();
    _videoPlayerController.dispose();
    _chewieController?.dispose();
    
    // 切换视频索引
    currVideoIndex += 1;
    if (currVideoIndex >= videoSources.length) {
      currVideoIndex = 0;
    }
    
    // 重置为默认分辨率(可选)
    currentResolution = videoSources[currVideoIndex]["resolutions"].keys.first;
    
    // 初始化新控制器
    await initializePlayer(resumePosition: currentPosition);
  }

  @override
  Widget build(BuildContext context) {
    return _chewieController != null &&
            _chewieController!.videoPlayerController.value.isInitialized
        ? Center(
            child: AspectRatio(
              aspectRatio: _videoPlayerController.value.aspectRatio,
              child: Chewie(controller: _chewieController!),
            ),
          )
        : const Center(child: CircularProgressIndicator());
  }
}

代码说明

  • 重构视频源结构,每个视频对应多个分辨率的URL映射,便于统一管理。
  • 在additionalOptions中动态生成分辨率选择菜单,直观标记当前选中项。
  • 切换分辨率时自动记录并恢复播放进度,避免用户重新拖动进度条。
  • 保留原有的视频切换功能,同步适配新的视频源结构。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 14:20:34