3

我不知道如何在 C# YouTube API V3 中恢复中断的上传。

我现有的代码使用 V1 并且工作正常,但我正在切换到 V3。

如果我在不更改任何内容的情况下调用 UploadAsync(),它将从头开始。使用 Fiddler,我可以看到这里给出的协议没有被遵循并且上传重新开始。

我尝试按照 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 记录。

4

3 回答 3

2

此问题已在Google.Apis.YouTube.v3 客户端库的“1.8.0.960-rc”版本中得到解决。

他们添加了一个名为 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; }
于 2014-03-12T08:26:13.890 回答
2

我已经设法使用反射使其工作,并完全避免了修改 API 的需要。为了完整起见,我将记录该过程,但不建议这样做。在可恢复的上传对象中设置私有属性不是一个好主意。

当您的可恢复上传对象在应用程序重新启动或重启后被销毁时,您仍然可以使用Google.Apis.YouTube.v3 客户端库的“1.8.0.960-rc”版本恢复上传。

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);
于 2014-03-13T04:04:15.443 回答
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 确实可以从轻微的短暂故障中恢复,但不能从应用程序重新启动、重新启动或长时间中断中恢复。

干杯。米克。

于 2014-02-04T03:57:28.467 回答