tus / tus-js-client

A pure JavaScript client for the tus resumable upload protocol
https://tus.io/
MIT License
2.1k stars 315 forks source link

Resume vimeo video not working #200

Closed jrdesire closed 2 years ago

jrdesire commented 4 years ago

Question Hi all,

I am trying to enable resuming a video on page refresh/connection lost. Right now, when I refresh a page, it restarts from the beginning instead of resuming.

Here is the code snippet:

 this.upload = new Upload(this.videoFile, {
      endpoint: undefined,
      uploadUrl: this.model.video.uploadUrl,
      resume: true,
      retryDelays: [0, threeSeconds, fiveSeconds, tenSeconds, twentySeconds],
      onError: (error: Error): void => {
        this.onError(error.message);
      },
      onProgress: (bytesUploaded: number, bytesTotal: number): void => {
        this.progress = Math.round((bytesUploaded / bytesTotal) * percent);
      },
      onSuccess: (): void => {
        console.log('success');
      }
    });

Please let me know what I am not seeing. Thank you again in advance!

Setup details Please provide following details, if applicable to your situation:

Acconut commented 4 years ago

Are you sure that you are using tus-js-client 1.8.0 and not accidentally installed the 2.1 version from NPM? That version introduced breaking changes regarding resuming.

If that does not help, please include a network recording: https://support.zendesk.com/hc/en-us/articles/204410413-Generating-a-HAR-file-for-troubleshooting

nles commented 4 years ago

I'm facing the same issue in 2.1. I did some quick testing, and it seems that when uploadUrl is provided manually (for Vimeo uploads), the fingerprint is never stored in localstorage, thus uploads won't resume after page refresh. I also tried setting the fingerprint manually, to see if tus-js-client would pick it up and resume the upload based on that, but it didn't work either (likely the fingerprint isn't even read when uploadUrl is provided).

It seems this could be a quite an easy fix, provided that the fingerprinting would work similarly in both cases (when using endpoint or uploadUrl).

Related: the uploadUrl parameter could also accept a function that could be used to dynamically create a new uploadUrl with Vimeo if there is no fingerprint stored, or use the one that is stored otherwise.

I could try to pull of a pull-request with this kind of functionality, but maybe someone more knowledgeable with the library can first let me know if I'm missing something, and if this would be a bad idea for some reason 😬

Acconut commented 4 years ago

I did some quick testing, and it seems that when uploadUrl is provided manually (for Vimeo uploads), the fingerprint is never stored in localstorage, thus uploads won't resume after page refresh.

Yes, you are right and this is intended. If you supply an uploadUrl, tus-js-client doesn't store the URL with the file's fingerprint. Using uploadUrl basically bypasses all fingerprinting and localStorage lookups, so the behavior is not a bug.

If you want to resume the Vimeo upload after the page reloads, you should store the uploadUrl on your own for now. However, we can discuss whether using uploadUrl should store the URL in localStorage.

Does that help?

nles commented 4 years ago

I tried to reuse the uploadUrl I get from Vimeo, but that does not resume the upload due to something related to the fingerprinting logic (the original topic of this issue).

Acconut commented 4 years ago

Ok, in that case I am not sure what the problem is. To be honest, this sounds like a problem on Vimeo's servers and I don't use their services so I cannot debug it on my own. Can you try to contact them?

nitingupta220 commented 4 years ago

Hey guys I'm stuck here. Anyone please help. How do we get the uploadUrl here? I don't know this.

Acconut commented 4 years ago

@nitingupta220 You get it from the Vimeo API. I don't know the details, so please consult their documentation.

sebyshalom commented 4 years ago

Hi, i am also facing kind of same problem.when i upload video on vimeo using tus protocol video uploading is working fine, But the uploading start again if we pause and resume. Using tus-js-clientd.

$(document).on("click", "button", function (e) { var file = $(this).prop("files")[0]; $.ajax({ 'url': 'https://api.vimeo.com/me/videos', 'type': 'POST', 'headers': { 'Accept': 'application/vnd.vimeo.*+json;version=3.4', 'Content-Type': 'application/json', 'Authorization': 'bearer ' + 'token' }, "data": JSON.stringify({ "upload": { "approach": "tus", "size": file.size } }), 'success': function (result) { var upload = new tus.Upload(file, { uploadUrl: result.upload.upload_link, onError: function (error) { console.log("Failed because: " + error) }, onProgress: function (bytesUploaded, bytesTotal) { var percentage = (bytesUploaded / bytesTotal * 100).toFixed(2) console.log(bytesUploaded, bytesTotal, percentage + "%") }, onSuccess: function () { console.log("Download %s from %s", upload.file.name, upload.url) } }) var pauseButton = document.querySelector("#pauseButton") var unpauseButton = document.querySelector("#unpauseButton") pauseButton.addEventListener("click", function () { upload.abort() }) unpauseButton.addEventListener("click", function () { upload.start() }) upload.start() }, 'error': function (result) { } });

Capture4

Acconut commented 4 years ago

@sebyshalom Please contact Vimeo support for this. I don't have much experience with their API.

cdelforce commented 3 years ago

Ran into this issue myself (well, just being unable to have a working pause/resume button without it restarting from 0) and thought I'd share how I fixed it for anyone else who stumbles across this page in future. I had to set the chunkSize variable within my 'new tus.Upload()'. By default, chunkSize is infinite, and it seems Vimeo's 'upload-offset' response is only for completed chunks, and so there's no completed chunks until the upload has completed. By setting it to 5000000 (5mb), I'm able to pause and resume the upload successfully. Upon resuming, it restarts at the last completed chunk, so for a small file (e.g. 30mb), there may be a noticeable drop of a few percentage after resuming. This would be less noticeable for a larger file where 5mb accounts for a smaller percentage, or you could set the chunkSize to 1mb, though the smaller the chunkSize, the slower the upload due to the increased number of http requests. Hope this helps someone.

Acconut commented 3 years ago

Thanks for sharing, @cdelforce!

websitedesignby commented 3 years ago

It was necessary for me to set the chunkSize to 1mb in order to get it to work for me uploading to Vimeo. The issue seems to be limited to Chrome. I had success in FireFox and Safari without setting a chunkSize.

Safari Version 14.0.3 (16610.4.3.1.7) - OK FireFox 88.0 - OK Chrome Version 90.0.4430.85 (Official Build) (x86_64) - ERROR (failed) net::ERR_HTTP2_PING_FAILED

Acconut commented 3 years ago

@websitedesignby Interesting, thanks for sharing. However, the error (ERR_HTTP2_PING_FAILED) looks like something that Vimeo should be looking into on their end.

stebogit commented 2 years ago

I had to set the chunkSize variable within my new tus.Upload(). By default, chunkSize is infinite, and it seems Vimeo's 'upload-offset' response is only for completed chunks, and so there's no completed chunks until the upload has completed. By setting it to 5000000 (5mb), I'm able to pause and resume the upload successfully. Upon resuming, it restarts at the last completed chunk, so for a small file (e.g. 30mb), there may be a noticeable drop of a few percentage after resuming.

I can confirm what @cdelforce said: chunkSize is required for Vimeo and it determines also the resumable "units" of the upload; e.g. with 5MB chunks, if the uploads get interrupted at say 8MB it then resumes at 5MB. @Acconut how does this fit with the tus protocol?

One thing I can't figure out is why the onProgress log is so frequent (resulting in a progress bar updating smoothly :+1: ) like @sebyshalom showed here, while the HEAD calls (and so the UrlStorage updates) when uploading to Vimeo are only every 5MB uploaded? Isn't the onProgress function executed only at every such call? If not why?

Acconut commented 2 years ago

@Acconut how does this fit with the tus protocol?

Not sure what you mean by this question. The tus protocol allows to partition the upload into PATCH requests with a variable size. It is recommended to do the upload in one request, but not required. It seems as Vimeo has an implementation which is not capable of handling requests with arbitrary size. That's quite unfortunate and can only be fixed by themselves, not tus-js-client.

Acconut commented 2 years ago

Isn't the onProgress function executed only at every such call?

onProgress is not only executed for HEAD requests, but also while PATCH requests upload data.

Tsjippy commented 2 years ago

Hi all,

I am also struggling with resumable uploads to Vimeo. To test it I start an upload to Vimeo, store the url in the database and wait till it is past 50%. Than I refresh the page and start uploading the same file to the same url. But the upload always starts completely again.

My code:

var upload = new tus.Upload(file, {
        uploadUrl: upload_url,
        chunk_size: 5000000,
        onError: function(error) {
            console.error("Failed because: " + error);
            return;
        },
        onProgress: function(bytesUploaded, bytesTotal) {
            //calculate percentage
            var percentage = (bytesUploaded / bytesTotal * 100).toFixed(2)

            //show percentage in progressbar
            document.querySelectorAll('.media-progress-bar > div').forEach(div=>{
                div.style.width = percentage+'%';
                div.innerHTML   = '<span style="width:100%;text-align:center;color:white;display:block;font-size:smaller;">'+percentage+'%</span>';
            });
        }, 
        onSuccess: function() {
            document.querySelector('[data-id="'+post_id+'"] .filename>div').textContent='Uploaded to Vimeo';
        },
        onShouldRetry: function(err, retryAttempt, options) {
          var status = err.originalResponse ? err.originalResponse.getStatus() : 0
          // If the status is a 403, we do not want to retry.
          if (status === 403) {
            return false
          }

          // For any other status code, tus-js-client should retry.
          return true
        }
})
// Start the upload
upload.start()
stebogit commented 2 years ago

@Tsjippy the TUS client for some reason does not handle the resume part of the upload if the upload URL is not created by the client itself. This is how I handled it using the library's internal components (namely the urlStorage which holds the reference to the upload URL until completion):

import * as tus from 'tus-js-client';

// 0. get a reference to the urlStorage
const { urlStorage, fingerprint: getFingerprint } = tus.defaultOptions;
let storedEntry = {};
let upload = null;

// 1. get the upload url first and save it into urlStorage
async function getUploadUrl () {
    hideErrorMessage();

    try {
        const file = input.prop('files')[0];

        if (!file) return;

        let fingerprint = await getFingerprint(file, { endpoint: 'https://www.yourserver.com' });
        let storedEntries = await urlStorage.findUploadsByFingerprint(fingerprint);

        if (storedEntries.length) {
            storedEntry = storedEntries[0];
            if (storedEntry.uploadUrl) {
                console.debug('previous URL found: ' + storedEntry.uploadUrl);
                return startUpload(file);
            }
            // cleanup
            urlStorage.removeUpload(storedEntry.urlStorageKey);
        }

        resetProgress();

        // use Vimeo API to create the upload URL
        // https://stackoverflow.com/a/65582420/4530144
        const response = await fetch('url-returning-vimeo-url');
        const data = await response.json();
        const {videoUri, iframe, uploadUrl} = data;
        console.debug('new upload URL: ' + uploadUrl);

        storedEntry = {
            size: file.size,
            metadata: {
                filename: file.name,
                filetype: file.type,
            },
            creationTime: new Date().toString(),
            uploadUrl,
            videoUri,
            iframe,
        };

        storedEntry.urlStorageKey = await urlStorage.addUpload(fingerprint, storedEntry);

        startUpload(file);

    } catch (error) {
        console.error('Error:', error);
        setErrorMessage('Sorry, an error occurred.');
    }
}

// 2. then upload, and remove the upload reference from urlStorage when done
function startUpload (file) {
    upload = new tus.Upload(file, {
        uploadUrl: storedEntry.uploadUrl,
        headers: {
            // https://developer.vimeo.com/api/upload/videos#resumable-approach-step-2
            Accept: 'application/vnd.vimeo.*+json;version=3.4' // required
        },
        chunkSize: 5 * MB, // required
        onProgress (bytesUploaded, bytesTotal) {
            const percentage = (bytesUploaded / bytesTotal * 100).toFixed(2);
            console.debug(bytesUploaded, `${percentage}%`);
            updateProgress(percentage);
        },
        async onSuccess () {
            urlStorage.removeUpload(storedEntry.urlStorageKey);
            showResults(file);
        },
        onError (error) {
            setErrorMessage('Sorry, upload failed. Please try again.');
            console.error(`Upload failed: ${error}`);
        },
    });

    upload.start();
}

// 2a. clear urlStorage on cancel
async function onCancel (e) {
    upload.abort(true);
    if (storedEntry.urlStorageKey) {
        urlStorage.removeUpload(storedEntry.urlStorageKey);
    }
    storedEntry = {};
    console.debug('aborted');
}

@Acconut can you confirm I'm not missing anything or doing something wrong? Assuming it's correct (I know it does work), I would suggest to report this in the documentation to help people interacting with Vimeo (it would also solve a few opened issues). I wonder anyway if this could be simplified by allowing the client to accept an externally generated uploadUrl and let it handle the upload/resume automatically also in this case. Or is there any specific reason not to? I'd be happy to submit a PR for the above if you'd be willing to review/accept.

Tsjippy commented 2 years ago

Thank you, I will try this out

Acconut commented 2 years ago

@stebogit Yes, this is correct. If you supply your own uploadUrl, tus-js-client assumes you will also take care of saving the URL on your own. Feel free to open an issue/PR, so we can discuss improving support for that or at least improve the documentation. Thank you for the feedback and help!

Acconut commented 2 years ago

Closing this issue due to inactivity. Feel free to leave a comment if you want to continue the discussion :)

r-a-y commented 1 year ago

Thanks to all the info in this thread, particularly by cdelforce about setting the chunkSize parameter. This addressed the resume problem for me.

Just wanted to post what Vimeo recommends for chunk size:

NOTE: This high-end pro tip might not apply to general-purpose resumable uploads. But if you're using a tus library that breaks the video file into separate chunks based on a maximum size value, we recommend setting the chunk size in the range of 128–512 MB. Smaller chunk sizes might result in slower upload speeds, especially when there are many chunks.

I think 128MB might be too high for smaller sites uploading videos with smaller resolutions and/or shorter durations, but for those uploading 4k videos, their recommendations sound about right.

It would also be good to update the tus-js-client documentation for Vimeo here about using chunkSize for resuming. I'm going to create a PR for this in a bit.

Acconut commented 1 year ago

It would also be good to update the tus-js-client documentation for Vimeo here about using chunkSize for resuming. I'm going to create a PR for this in a bit.

That would be amazing! We personally do not use Vimeo's API and thus mostly rely on community feedback like yours to provide a good documentation for interacting with their services.

jacopsd commented 5 months ago

Seems to work here by specifying any chunkSize. When leaving out the chunkSize, it does not resume properly indeed.