expo: File System Upload Async fails when sessionType is background and file is larger
Summary
I’ve been trying to upload videos to a personal server (multer on Express) or to AWS s3 using Expo File System’s uploadAsync. I’ve set the session type to be a Background upload. I can successfully upload very small videos, 5-10 seconds, but as the video gets longer, 30-60 seconds, I begin to get this unable to upload error saying that it has lost connection to the background service:
[Unhandled promise rejection: Error: Unable to upload the file: 'Error Domain=NSURLErrorDomain Code=-997 "Lost connection to background transfer service" UserInfo={NSErrorFailingURLStringKey={{serverURL}}/api/multipart-upload, NSErrorFailingURLKey={{serverURL}}/api/multipart-upload, _NSURLErrorRelatedURLSessionTaskErrorKey=(]
If the video is longer, 2ish minutes or more, than the app usually completely crashes before it even starts the upload. That’s if it doesn’t throw this error from the image picker first: Error: Video could not be picked
Managed or bare workflow? If you have ios/ or android/ directories in your project, the answer is bare!
managed
What platform(s) does this occur on?
iOS
SDK Version (managed workflow only)
44
Environment
expo-env-info 1.0.2 environment info: System: OS: macOS 12.2.1 Shell: 5.8 - /bin/zsh Binaries: Node: 16.14.0 - ~/.nvm/versions/node/v16.14.0/bin/node npm: 8.3.1 - ~/.nvm/versions/node/v16.14.0/bin/npm Managers: CocoaPods: 1.11.2 - /opt/homebrew/bin/pod SDKs: iOS SDK: Platforms: DriverKit 21.2, iOS 15.2, macOS 12.1, tvOS 15.2, watchOS 8.3 IDEs: Xcode: 13.2.1/13C100 - /usr/bin/xcodebuild npmPackages: expo: ~44.0.0 => 44.0.6 react: 17.0.1 => 17.0.1 react-dom: 17.0.1 => 17.0.1 react-native: 0.64.3 => 0.64.3 react-native-web: 0.17.1 => 0.17.1 npmGlobalPackages: expo-cli: 5.2.0 Expo Workflow: managed
Reproducible demo
Base expo App
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import * as FileSystem from 'expo-file-system'
import * as ImagePicker from 'expo-image-picker'
export default function App() {
async function uploadBackgroundVideo(localUrl) {
const response = await FileSystem.uploadAsync(`{{serverURL}}/api/multipart-upload`, localUrl, {
headers: {
"Content-Type": "multipart/form-data"
},
sessionType: FileSystem.FileSystemSessionType.BACKGROUND,
httpMethod: 'POST',
fieldName: 'video',
mimeType: 'video/quicktime',
uploadType: FileSystem.FileSystemUploadType.MULTIPART,
})
console.log(JSON.stringify(response.body), 'Response from uploading to local server')
}
async function getLibraryVideo() {
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== 'granted') {
return alert('Sorry, we need camera roll permissions to make this work!');
}
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Videos,
videoQuality: ImagePicker.UIImagePickerControllerQualityType.High,
quality: .1
})
if (!result.cancelled) {
uploadBackgroundVideo(result.uri)
}
}
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<TouchableOpacity onPress={getLibraryVideo}><Text>Get Video</Text></TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Express Multer app
const express = require('express');
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
const videoStorage = multer.diskStorage({
destination: 'uploads', // Destination to store video
filename: (req, file, cb) => {
console.log('Attempting storage')
cb(null, file.fieldname + '_' + Date.now()
+ path.extname(file.originalname))
}
});
const videoUpload = multer({
storage: videoStorage,
limits: {
fileSize: 1024 * 1024 * 1024 * 10
},
fileFilter(req, file, cb) {
console.log('Attempting file filter stuff')
if (!file.originalname.match(/\.(mp4|MPEG-4|mkv|mov|MOV)$/)) {
console.log(file.originalname, 'Video uploaded fail')
return cb(new Error('Please upload a video'))
}
cb(undefined, true)
}
})
// This method will save a "photo" field from the request as a file.
app.post('/multipart-upload', videoUpload.single('video'), (req, res) => {
console.log(req.body, 'Completed upload');
res.end('OK');
});
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 2
- Comments: 16 (2 by maintainers)
Any word on this one? Seeing this issue as well, uploading video files.
Not that I’m aware of, and I also don’t see a size limit on iOS’s NSUrlSession background upload tasks. We are looking into this now and will report back anything we find here. A quick google search of this issue shows many other developers are experiencing the same thing.