使用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
相关产品推荐
相关产品推荐

