I can\'t work out how to resume an interrupted upload in V3 of the C# YouTube API.
My existing code uses V1 and works fine but I\'m switching to V3.
If I cal
This issue has been resolved in version "1.8.0.960-rc" of the Google.Apis.YouTube.v3 Client Library.
They've added a new method called ResumeAsync and it works fine. I wish I'd known they were working on it.
One minor issue I needed to resolve was resuming an upload after restarting the application or rebooting. The current api does not allow for this but two minor changes resolved the issue.
I added a new signature for the ResumeAsync method, which accepts and sets the original UploadUri. The StreamLength property needs to be initialised to avoid an overflow error.
public Task<IUploadProgress> ResumeAsync(Uri uploadUri, CancellationToken cancellationToken)
{
UploadUri = uploadUri;
StreamLength = ContentStream.CanSeek ? ContentStream.Length : UnknownSize;
return ResumeAsync(cancellationToken);
}
I also exposed the getter for UploadUri so it can be saved from the calling application.
public Uri UploadUri { get; private set; }
After a fair bit of deliberation, I've decided to modify the API code. My solution maintains backwards compatibility.
I've documented my changes below but I don't recommend using them.
In the UploadAsync() method in the ResumableUpload Class in "Google.Apis.Upload", I replaced this code.
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);
with this code
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);
I also made the UploadUri and BytesServerReceived properties public. This allows an upload to be continued after the ResumableUpload object has been destroyed or after an application restart.
You just recreate the ResumableUpload as per normal, set these two fields and call UploadAsync() to resume an upload. Both fields need to be saved during the original upload.
public Uri UploadUri { get; set; }
public long BytesServerReceived { get; set; }
I also added "Resuming" to the UploadStatus enum in the IUploadProgress class.
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
};
Nothing has changed for starting an upload.
Provided the ResumableUpload Oject and streams have not been destroyed, call UploadAsync() again to resume an interrupted upload.
If they have been destroyed, create new objects and set the UploadUri and BytesServerReceived properties. These two properties can be saved during the original upload. The video details and content stream can be configured as per normal.
These few changes allow an upload to be resumed even after restarting your application or rebooting. I'm not sure how long before an upload expires but I'll report back when I've done some more testing with my real application.
Just for completeness, this is the test code I've been using, which happily resumes an interrupted upload after restarting the application multiple times during an upload. The only difference between resuming and restarting, is setting the UploadUri and BytesServerReceived properties.
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();
I hope this helps someone. It took me much longer than expected to work it out and I'm still hoping I've missed something simple. I messed around for ages trying to add my own error handlers but the API does all that for you. The API does recover from minor short hiccups but not from an application restart, reboot or prolonged outage.
Cheers. Mick.
I've managed to get this to work using reflection and avoided the need to modify the API at all. For completeness, I'll document the process but it isn't recommended. Setting private properties in the resumable upload object is not a great idea.
When your resumeable upload object has been destroyed after an application restart or reboot, you can still resume an upload using version "1.8.0.960-rc" of the 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);
}
You need to save the UploadUri during the ProgressChanged event.
Upload.ResumeUri = GetPrivateProperty<ResumableUpload<Video>>(InsertMediaUpload, "UploadUri") as Uri;
You need to set the UploadUri and StreamLength before calling ResumeAsync.
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);