tus / tus-js-client

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

tus-js-client doesn't upload file larger than 100Mb for Android #508

Open Xotonori opened 1 year ago

Xotonori commented 1 year ago

tus-js-client doesn't upload file larger than 100Mb for Android. I've got next error - "Failed to upload because: Error: tus: cannot fetch file.uri as Blob, make sure the uri is correct and accessible. [object Object]"

I get video file data by "react-native-document-picker" library.

Than i get data like:

const assets = [{
"fileCopyUri": "file:///data/user/0/...projectName...development/files/9467e842-4db4-4bf7-849d-f3c400120665/fileName.mp4", 
"name": "basketball2k168MB.mp4", 
"size": 155686888, 
"type": "video/mp4", 
"uri":"content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2FfileName.mp4"
}];

Than i push assets to tus upload component and get presight params:

const uploadFile = (file: any, idx: number) => {
  const fileSize = file?.size || 0;

  dispatch(GameVimeoPresignUrl(idx, fileSize)).then(
    (data: VimeoPresignUrl) => {

      const {uploadLink, chunkSize, uri} = data;

      const uploader = new tus.Upload(file, {
        uploadUrl: uploadLink,
        endpoint: uploadLink,
        retryDelays: [0, 3000, 5000, 10000, 20000],
        chunkSize,
        metadata: {
          filename: `video.${file.name}`,
          filetype: file.type,
        },

        onError: onUploadError,
        onProgress: (bytesUploaded, bytesTotal) => {
          const percent = bytesUploaded / bytesTotal;
          onProgress(percent, idx);
        },
        onSuccess: () => {
          console.log('Successfully uploaded to:', uri);
          onUploadSuccess(uri);
        },
      });
      uploader.start();
    },
  );
};

And when uploading started, I get this error - "Failed to upload because: Error: tus: cannot fetch file.uri as Blob, make sure the uri is correct and accessible. [object Object]"

Files lower than 100Mb uploading very well, but larger don't( This problem only for an Android, IOS is ok.

Valere3162 commented 1 year ago

Did you found any solution for this? I am also facing the exact same issue.

Xotonori commented 1 year ago

Unfortunatly no =(

nh2 commented 1 year ago

I cannot confirm this issue. I can successfully upload a 500 MB video file with tus-js-client 3.0.1 to my own TUS server implementation, with both Firefox and Chrome on Android 11 (LineageOS on a Google Pixel 1).


Separate: In your code:

      const uploader = new tus.Upload(file, {
//...
        chunkSize,

It looks like here you're passing the large file's full size as chunkSize.

According to chunkSize docs this will pull the whole file into RAM:

A large chunk size (more than a GB) is problematic when a reader/readable stream is used as the input file. In these cases, tus-js-client will create an in-memory buffer with the size of chunkSize

On Android devices you usually don't have a lot of RAM, so this might be problematic.

H4mxa commented 1 year ago

When attempting to upload a large file on iOS, the app's memory usage rapidly increases to 2GB and subsequently causes the app to crash.

Acconut commented 1 year ago

@H4mxa Are you using React Native on iOS? Or are you using it in the browser? Because the original question was using React Native.

Tanush-J commented 5 months ago

I am also facing the same issue with React-Native and I am using expo-image-picker to select a video.

 LOG  {"assetId": "33737", "base64": null, "duration": 180032, "exif": null, "fileName": "77541d9f-259a-4fde-8e3b-a5d98d940223.mp4", "filesize": 451514586, "height": 1080, "mimeType": "video/mp4", "rotation": 90, "type": "video", "uri": "file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252FnativeChunking-94c8fbbb-5bd2-4a23-be0e-a8c6ef78365f/ImagePicker/4c885de7-a3c5-4759-8156-b8ade72214e7.mp4", "width": 1920}
 LOG  Failed because: Error: tus: cannot fetch `file.uri` as Blob, make sure the uri is correrrrect and accessible. [object Object]

I am getting this error when file size is around 400MB

Acconut commented 5 months ago

My first thought is that this could be an memory issue. When a file URI in React Native is passed to tus-js-client, we attempt to load the entire file into a blob in memory:

https://github.com/tus/tus-js-client/blob/b147b53aadbbdb3b9d63bdba7c0484cfb2bedf74/lib/browser/uriToBlob.js#L6

It is quite possible that this operation is not possible for larger file sizes as it would exhaust the available memory. We should investigate whether it is possible to only load parts of the file into memory and then tus-js-client can use its chunking logic to divide the file into multiple, smaller chunks.

wfern commented 4 months ago

I think @Acconut is right. I saw in some place about a fetch() limitation on Android but I didn't find it to link.

Maybe a custom FileReader using expo-file-system and react-native-quick-base64 can solve the issue. Something like:

export default class TusFileReader {
  async openFile(input, chunkSize): {
    const fileInfo = await FileSystem.getInfoAsync(input.uri);
    if (fileInfo.exists) {
      return new FileSource(input, fileInfo.size);
    }
  }
}
class FileSource {
  private _file;
  private _size;

  get size(): number {
    return this._size;
  }

  constructor(file, size) {
    this._file = file;
    this._size = size;
  }

  async slice(start, end) {
    if (start >= this._size) {
      return { value: new Uint8Array(0), done: true };
    }

    end = Math.min(end, this._size);
    const length = end - start;

    const data = await FileSystem.readAsStringAsync(this._file.uri, {
      encoding: FileSystem.EncodingType.Base64,
      length: length,
      position: start,
    });

    const value = toByteArray(data);

    return { value, done: false };
  }
}
Acconut commented 4 months ago

@wfern Thank you for the suggestion. Something like you suggested will likely help here. Do you have an opinion on what is the best way to access files that works well with React Native and Expo? You mentioned expo-file-system, but react-native-fs also has method for reading files at offsets: https://github.com/itinance/react-native-fs?tab=readme-ov-file#readfilepath-string-length--0-position--0-encodingoroptions-any-promisestring. I am no React Native developer and thus I am wondering that the preferred approach for file access currently is.

wfern commented 4 months ago

@Acconut This is debatable because there is no preferred way. Most people should use expo nowadays but there are still many cases of people not using it and not liking using expo libraries because of the extra setup (even though it is minimal).

A good option would be to leave it as it is because:

Document the possible limitation and add a snippet with that code on how to create FileReader for RN and mention that it is also possible to do it with "react-native-fs".

This would work for me because first I would test it the way it is today (no extra setup or libraries, just works™) and if it didn't work I would add FileReader based on this documentation snippet.