YouTube C# API V3, как возобновить прерванную загрузку?

Я не могу понять, как возобновить прерванную загрузку в V3 C# YouTube API.

Мой существующий код использует V1 и работает нормально, но я перехожу на V3.

Если я вызову UploadAsync() без каких-либо изменений, он начнется с самого начала. Используя Fiddler, я вижу, что данный здесь протокол не соблюдается, и загрузка перезапускается. .

Я попытался установить позицию в потоке в соответствии с V1, но метод ResumeAsync() недоступен.

В примере Python используется NextChunk, но метод SendNextChunk защищен и недоступен в C#.

В приведенном ниже коде и UploadVideo(), и Resume() работают нормально, если я оставляю их до завершения, но загружается все видео, а не только оставшиеся части.

Как возобновить прерванную загрузку с помощью google.apis.youtube.v3?

Вот код С#, который я пробовал до сих пор.

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". Он содержит класс ResumableUpload, используемый API YouTube.

Мне удалось успешно продолжить загрузку, пропустив первые четыре строки кода в методе UploadAsync(). Я создал новый метод под названием ResumeAsync(), копию UploadAsync() с удаленными первыми четырьмя строками кода инициализации. Все работало, и загрузка возобновилась с того места, где она была, и завершилась.

Я бы предпочел не менять код в API, поэтому, если кто-нибудь знает, как мне это использовать, дайте мне знать.

Я буду продолжать подключаться и посмотреть, смогу ли я это решить.

Это оригинальный метод UploadAsync() и мой прием ResumeAsync().

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, показывающие возобновление загрузки.


person Mick    schedule 24.01.2014    source источник


Ответы (3)


После долгих размышлений я решил изменить код API. Мое решение поддерживает обратную совместимость.

Я задокументировал свои изменения ниже, но я не рекомендую их использовать.

В методе UploadAsync() класса ResumableUpload в Google.Apis.Upload я заменил этот код.

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; }

Я также добавил «Resuming» в перечисление UploadStatus в классе IUploadProgress.

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 и потоки не были уничтожены, снова вызовите 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 восстанавливается после незначительных кратковременных сбоев, но не после перезапуска, перезагрузки или длительного простоя приложения.

Ваше здоровье. Мик.

person Mick    schedule 04.02.2014

Эта проблема устранена в версии "1.8.0.960-rc" Google. Клиентская библиотека Apis.YouTube.v3.

Они добавили новый метод под названием 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, чтобы его можно было сохранить из вызывающего приложения.

public Uri UploadUri { get; private set; }
person Mick    schedule 12.03.2014
comment
Клиентская библиотека Google API v3 .NET теперь содержит функцию сохранения UploadUri во время ResumableUpload и последующего использования этого UploadUri для возобновления загрузки в случае перезапуска программы. В github.com/google/google-api-dotnet- есть два примера ResumableUpload. образцы клиентов - person Mike Meinz; 13.08.2016

Мне удалось заставить это работать с помощью отражения и вообще избежать необходимости изменять API. Для полноты я задокументирую процесс, но это не рекомендуется. Установка приватных свойств в объекте возобновляемой загрузки — не лучшая идея.

Если ваш возобновляемый объект загрузки был уничтожен после перезапуска или перезагрузки приложения, вы все равно можете возобновить загрузку, используя версию "1.8.0.960-rc" пакета Клиентская библиотека Google.Apis.YouTube.v3.

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);
}

Вам нужно сохранить UploadUri во время события ProgressChanged.

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);
person Mick    schedule 13.03.2014