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

使用YouTube Data API实现分块流式上传视频问题排查

问题核心原因

你现在的代码有三个致命逻辑错误,直接导致上传首块就结束:

  • 传给YouTube上传客户端的MemoryStream绑定了固定大小的缓冲区,第一次写入256KB数据后,客户端读取到当前流的末尾位置,直接判定所有数据传输完成,触发Completed状态,根本不会等待后续数据写入。
  • 可恢复上传实例videosInsertRequest只需要调用一次UploadAsync(),它会自动持续从源流拉取数据直到流结束。你在循环里反复调用这个方法,相当于每次循环都重新发起一次全新的上传,不是续传后续块。
  • 流位置逻辑完全错误:你把MemoryStream指针移到起始位置写入数据后,没有把读取指针重置到对应位置,上传逻辑读取时直接从写入结束位置开始读,立刻碰到流末尾,自然判定上传完成。
正确实现方式

Google .NET客户端的ResumableUpload已经内置了符合YouTube要求的256KB最小分块逻辑,你根本不需要自己手写分块、维护MemoryBuffer,只要给它提供一个能顺序读取完整文件、读完所有内容才返回0字节的流即可。

第一步:封装统一读取流

写一个简单的包装流,兼容本地、S3、Drive、Mega所有来源的下载流,不需要做额外分块处理:

public class WrappedDownloadStream : Stream
{
    private readonly Stream _underlyingDownloadStream;
    private long _currentPosition;

    public WrappedDownloadStream(Stream downloadStream, long totalFileSize)
    {
        _underlyingDownloadStream = downloadStream;
        Length = totalFileSize;
    }

    public override bool CanRead => true;
    public override bool CanSeek => false;
    public override bool CanWrite => false;
    public override long Length { get; }
    public override long Position
    {
        get => _currentPosition;
        set => throw new NotSupportedException("顺序流不支持随机寻址");
    }

    public override void Flush() => _underlyingDownloadStream.Flush();
    public override Task FlushAsync(CancellationToken ct) => _underlyingDownloadStream.FlushAsync(ct);

    public override int Read(byte[] buffer, int offset, int count)
    {
        int read = _underlyingDownloadStream.Read(buffer, offset, count);
        _currentPosition += read;
        return read;
    }

    public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken ct)
    {
        int read = await _underlyingDownloadStream.ReadAsync(buffer, offset, count, ct);
        _currentPosition += read;
        return read;
    }

    public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
    public override void SetLength(long value) => throw new NotSupportedException();
    public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();

    protected override void Dispose(bool disposing)
    {
        if (disposing) _underlyingDownloadStream.Dispose();
        base.Dispose(disposing);
    }
}

第二步:简化上传逻辑

删掉你之前手写的两层循环、MemoryBuffer维护逻辑,直接把包装好的下载流传给上传客户端,只调用一次上传方法即可,客户端会自动按256KB分块拉取数据上传:

// 初始化任意来源的下载流,替换成S3/本地/Drive逻辑完全通用
var megaDownloadResult = Mega.Download(video);
using var uploadSourceStream = new WrappedDownloadStream(megaDownloadResult.BaseStream, video.FileSize);

// 初始化上传请求的逻辑和你之前保持一致
var uploadRequest = Uploader.YouTube(uploadSourceStream);
// 仅调用一次UploadAsync,等待整个上传流程完成即可
var finalUploadResult = await uploadRequest.UploadAsync();
额外说明
  • 如果你需要做限速、实时缓冲校验这类自定义逻辑,不要用覆写固定MemoryStream的方案,改用生产者-消费者模式的流:下载线程作为生产者往流里写数据,上传客户端作为消费者读数据,所有数据写完后再标记流结束,避免上传端提前读到流末尾。
  • 只要传给上传请求的流在ReadAsync返回0前能持续提供完整文件数据,客户端就不会提前触发上传完成逻辑,你之前的问题本质是流过早返回了读取结束信号。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 05:36:23