我无法弄清楚如何在C#YouTube API的V3中恢复中断的上传。
我现有的代码使用V1并且工作正常,但我正在切换到V3。
如果我在不改变任何内容的情况下调用UploadAsync(),它将从头开始。使用Fiddler,我可以看到未遵循protocol given here并重新启动上传。
我已尝试按照V1设置流中的位置,但没有可用的ResumeAsync()方法。
Python示例使用NextChunk,但SendNextChunk方法受到保护,在C#中不可用。
在下面的代码中,如果我将它们完成,但上传整个视频而不仅仅是其余部分,则UploadVideo()和Resume()都可以正常工作。
如何使用google.apis.youtube.v3恢复中断的上传?
这是我到目前为止尝试过的C#代码。
private ResumableUpload<Video> UploadVideo(
YouTubeService youTubeService, Video video, Stream stream, UserCredential userCredentials)
{
var resumableUpload = youTubeService.Videos.Insert(video,
"snippet,status,contentDetails", stream, "video/*");
resumableUpload.OauthToken = userCredentials.Token.AccessToken;
resumableUpload.ChunkSize = 256 * 1024;
resumableUpload.ProgressChanged += resumableUpload_ProgressChanged;
resumableUpload.ResponseReceived += resumableUpload_ResponseReceived;
resumableUpload.UploadAsync();
return resumableUpload;
}
private void Resume(ResumableUpload<Video> resumableUpload)
{
//I tried seeking like V1 but it doesn't work
//if (resumableUpload.ContentStream.CanSeek)
// resumableUpload.ContentStream.Seek(resumableUpload.ContentStream.Position, SeekOrigin.Begin);
resumableUpload.UploadAsync(); // <----This restarts the upload
}
void resumableUpload_ResponseReceived(Video obj)
{
Debug.WriteLine("Video status: {0}", obj.Status.UploadStatus);
}
void resumableUpload_ProgressChanged(IUploadProgress obj)
{
Debug.WriteLine("Position: {0}", (resumableUploadTest == null) ? 0 : resumableUploadTest.ContentStream.Position);
Debug.WriteLine("Status: {0}", obj.Status);
Debug.WriteLine("Bytes sent: {0}", obj.BytesSent);
}
private void button2_Click(object sender, EventArgs e)
{
Resume(resumableUploadTest);
}
任何解决方案/建议/演示或“google.apis.youtube.v3”源代码的链接都将非常有用。
提前致谢!
编辑:新信息
我还在努力,我相信API还没有完成。无论是那个还是我都错过了一些简单的东西。
我仍然无法找到“google.apis.youtube.v3”源代码,因此我下载了最新的“google-api-dotnet-client”源代码。它包含YouTube API使用的ResumableUpload类。
我设法通过跳过UploadAsync()方法中的前四行代码成功继续上传。我创建了一个名为ResumeAsync()的新方法,它是UploadAsync()的副本,删除了前四行初始化代码。一切正常,上传从原来的地方恢复完毕。
我宁愿不更改API中的代码,所以如果有人知道我应该如何使用它,请告诉我。
我会继续插手,看看能不能解决它。
这是原始的UploadAsync()方法和我的ResumeAsync()hack。
public async Task<IUploadProgress> UploadAsync(CancellationToken cancellationToken)
{
try
{
BytesServerReceived = 0;
UpdateProgress(new ResumableUploadProgress(UploadStatus.Starting, 0));
// Check if the stream length is known.
StreamLength = ContentStream.CanSeek ? ContentStream.Length : UnknownSize;
UploadUri = await InitializeUpload(cancellationToken).ConfigureAwait(false);
Logger.Debug("MediaUpload[{0}] - Start uploading...", UploadUri);
using (var callback = new ServerErrorCallback(this))
{
while (!await SendNextChunkAsync(ContentStream, cancellationToken).ConfigureAwait(false))
{
UpdateProgress(new ResumableUploadProgress(UploadStatus.Uploading, BytesServerReceived));
}
UpdateProgress(new ResumableUploadProgress(UploadStatus.Completed, BytesServerReceived));
}
}
catch (TaskCanceledException ex)
{
Logger.Error(ex, "MediaUpload[{0}] - Task was canceled", UploadUri);
UpdateProgress(new ResumableUploadProgress(ex, BytesServerReceived));
throw ex;
}
catch (Exception ex)
{
Logger.Error(ex, "MediaUpload[{0}] - Exception occurred while uploading media", UploadUri);
UpdateProgress(new ResumableUploadProgress(ex, BytesServerReceived));
}
return Progress;
}
public async Task<IUploadProgress> ResumeAsync(CancellationToken cancellationToken)
{
try
{
using (var callback = new ServerErrorCallback(this))
{
while (!await SendNextChunkAsync(ContentStream, cancellationToken).ConfigureAwait(false))
{
UpdateProgress(new ResumableUploadProgress(UploadStatus.Uploading, BytesServerReceived));
}
UpdateProgress(new ResumableUploadProgress(UploadStatus.Completed, BytesServerReceived));
}
}
catch (TaskCanceledException ex)
{
UpdateProgress(new ResumableUploadProgress(ex, BytesServerReceived));
throw ex;
}
catch (Exception ex)
{
UpdateProgress(new ResumableUploadProgress(ex, BytesServerReceived));
}
return Progress;
}
这些是显示上传恢复的Fiddler records。
答案 0 :(得分:2)
经过深思熟虑之后,我决定修改API代码。我的解决方案保持向后兼容性。
我已在下面记录了我的更改,但我不建议使用它们。
在“Google.Apis.Upload”中的ResumableUpload类的UploadAsync()方法中,我替换了此代码。
BytesServerReceived = 0;
UpdateProgress(new ResumableUploadProgress(UploadStatus.Starting, 0));
// Check if the stream length is known.
StreamLength = ContentStream.CanSeek ? ContentStream.Length : UnknownSize;
UploadUri = await InitializeUpload(cancellationToken).ConfigureAwait(false);
使用此代码
UpdateProgress(new ResumableUploadProgress(
BytesServerReceived == 0 ? UploadStatus.Starting : UploadStatus.Resuming, BytesServerReceived));
StreamLength = ContentStream.CanSeek ? ContentStream.Length : UnknownSize;
if (UploadUri == null) UploadUri = await InitializeUpload(cancellationToken).ConfigureAwait(false);
我还公开了UploadUri和BytesServerReceived属性。这允许在ResumableUpload对象被销毁之后或应用程序重新启动后继续上载。
您只需按正常方式重新创建ResumableUpload,设置这两个字段并调用UploadAsync()以恢复上传。在原始上传过程中,需要保存这两个字段。
public Uri UploadUri { get; set; }
public long BytesServerReceived { get; set; }
我还在IUploadProgress类的UploadStatus枚举中添加了“Resuming”。
public enum UploadStatus
{
/// <summary>
/// The upload has not started.
/// </summary>
NotStarted,
/// <summary>
/// The upload is initializing.
/// </summary>
Starting,
/// <summary>
/// Data is being uploaded.
/// </summary>
Uploading,
/// <summary>
/// Upload is being resumed.
/// </summary>
Resuming,
/// <summary>
/// The upload completed successfully.
/// </summary>
Completed,
/// <summary>
/// The upload failed.
/// </summary>
Failed
};
开始上传没有任何改变。
如果ResumableUpload Oject和流尚未销毁,请再次调用UploadAsync()以恢复中断的上载。
如果它们已被销毁,请创建新对象并设置UploadUri和BytesServerReceived属性。在原始上传期间可以保存这两个属性。视频详细信息和内容流可以按照正常情况进行配置。
这些更改允许在重新启动应用程序或重新启动后恢复上载。我不确定上传到期前多久,但是当我用我的真实应用程序进行更多测试时,我会报告回来。
为了完整起见,这是我一直在使用的测试代码,在上传过程中多次重启应用程序后,很快恢复了中断的上传。恢复和重新启动之间的唯一区别是设置UploadUri和BytesServerReceived属性。
resumableUploadTest = youTubeService.Videos.Insert(video, "snippet,status,contentDetails", fileStream, "video/*");
if (resume)
{
resumableUploadTest.UploadUri = Settings.Default.UploadUri;
resumableUploadTest.BytesServerReceived = Settings.Default.BytesServerReceived;
}
resumableUploadTest.ChunkSize = ResumableUpload<Video>.MinimumChunkSize;
resumableUploadTest.ProgressChanged += resumableUpload_ProgressChanged;
resumableUploadTest.UploadAsync();
我希望这有助于某人。我花了比预期更长的时间来解决它,我仍然希望我错过了一些简单的事情。我试图添加自己的错误处理程序已经搞砸了很多年,但API会为你完成所有这些。 API确实可以从轻微的短暂打嗝中恢复,但不能从应用程序重启,重启或长时间停机中恢复。
干杯。米克。
答案 1 :(得分:2)
我设法使用反射使其工作,并且完全无需修改API。为了完整起见,我将记录该过程,但不建议这样做。在可恢复的上传对象中设置私有属性不是一个好主意。
如果在应用程序重新启动或重新启动后销毁了可恢复的上传对象,您仍然可以使用版本&#34; 1.8.0.960-rc&#34;恢复上传。 Google.Apis.YouTube.v3 Client Library。
private static void SetPrivateProperty<T>(Object obj, string propertyName, object value)
{
var propertyInfo = typeof(T).GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance);
if (propertyInfo == null) return;
propertyInfo.SetValue(obj, value, null);
}
private static object GetPrivateProperty<T>(Object obj, string propertyName)
{
if (obj == null) return null;
var propertyInfo = typeof(T).GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance);
return propertyInfo == null ? null : propertyInfo.GetValue(obj, null);
}
您需要在ProgressChanged事件期间保存UploadUri。
Upload.ResumeUri = GetPrivateProperty<ResumableUpload<Video>>(InsertMediaUpload, "UploadUri") as Uri;
您需要在调用ResumeAsync之前设置UploadUri和StreamLength。
private const long UnknownSize = -1;
SetPrivateProperty<ResumableUpload<Video>>(InsertMediaUpload, "UploadUri", Upload.ResumeUri);
SetPrivateProperty<ResumableUpload<Video>>(InsertMediaUpload, "StreamLength", fileStream.CanSeek ? fileStream.Length : Constants.UnknownSize);
Task = InsertMediaUpload.ResumeAsync(CancellationTokenSource.Token);
答案 2 :(得分:1)
此问题已在版本&#34; 1.8.0.960-rc&#34; Google.Apis.YouTube.v3 Client Library。
他们添加了一个名为ResumeAsync的新方法,它运行正常。我希望我知道他们正在努力。
我需要解决的一个小问题是在重新启动应用程序或重新启动后恢复上传。当前的api不允许这样做,但是两个小的改动解决了这个问题。
我为ResumeAsync方法添加了一个新签名,该方法接受并设置原始的UploadUri。需要初始化StreamLength属性以避免溢出错误。
public Task<IUploadProgress> ResumeAsync(Uri uploadUri, CancellationToken cancellationToken)
{
UploadUri = uploadUri;
StreamLength = ContentStream.CanSeek ? ContentStream.Length : UnknownSize;
return ResumeAsync(cancellationToken);
}
我还公开了UploadUri的getter,因此可以从调用应用程序中保存它。
public Uri UploadUri { get; private set; }