Expo-AV无法播放S3上传视频,如何正确设置ContentType?
核心问题是上传到S3的视频缺少正确的Content-Type元数据,导致expo-av的Video组件无法识别视频格式。手动上传的视频能正常播放,是因为上传工具(比如AWS控制台、浏览器)会自动为文件设置正确的Content-Type,而你的代码流程里漏掉了这关键一步。
下面是具体的解决步骤和代码修改方案:
1. 从客户端获取视频的MIME类型
Expo ImagePicker返回的资产对象里已经包含了mimeType字段,你需要把这个值传递给后端,用来生成带Content-Type参数的预签名URL。
修改你的PickImage函数,获取并保存视频的MIME类型:
async PickImage(type) { let result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.All, allowsEditing: true, aspect: [4, 3], quality: 1, base64: true, }); if (!result.canceled) { if (!result.assets[0]?.fileSize) { alert("Can't determine the file size. Please choose another image/video"); this.setState({please_wait_comment: false, please_wait_post: false, please_wait_profile: false}); } else if (!this.isLessThanTheMB(result.assets[0].fileSize, 250)) { alert("The image/video must be smaller than 250MB"); this.setState({please_wait_comment: false, please_wait_post: false, please_wait_profile: false}); } else { const base64String = await FileSystem.readAsStringAsync(result.assets[0].uri, { encoding: FileSystem.EncodingType.Base64, }); const buffer = Buffer.from(base64String, "base64"); // 获取文件的MIME类型 const mimeType = result.assets[0].mimeType; this.setState({ post_image: buffer, post_image_uri: result.assets[0].uri, post_file_type: mimeType // 保存MIME类型,后续传给后端 }, () => {this.setState({please_wait_post: false})}); } } }
2. 后端生成预签名URL时添加Content-Type参数
你的后端generateUploadURL函数需要接收客户端传来的MIME类型,并将其加入S3的请求参数中。这一步是让S3为存储的对象设置正确的Content-Type元数据的关键。
修改后端代码:
async function generateUploadURL(req, res) { // 从请求体中获取客户端传来的Content-Type const { contentType } = req.body; const rawBytes = await randomBytes(16); const imageName = rawBytes.toString('hex'); const params = { Bucket: bucketName, Key: imageName, Expires: 60, // 必须添加这个字段,指定文件的Content-Type ContentType: contentType }; const uploadURL = await s3.getSignedUrlPromise('putObject', params); // 返回预签名URL给客户端 res.json({ uploadURL }); }
3. 上传文件时设置请求头的Content-Type
在客户端调用预签名URL上传文件时,必须在请求头中设置对应的Content-Type,否则S3会忽略你在预签名URL中指定的类型,依然使用默认的binary/octet-stream。
假设你是用fetch上传文件,代码示例如下:
// 调用后端接口获取预签名URL const getUploadURL = async (contentType) => { const response = await fetch('/api/generate-upload-url', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ contentType }) }); return response.json(); }; // 上传文件到S3 const uploadToS3 = async (buffer, contentType) => { const { uploadURL } = await getUploadURL(contentType); await fetch(uploadURL, { method: 'PUT', headers: { // 必须设置这个请求头,和预签名URL中的ContentType一致 'Content-Type': contentType }, body: buffer }); };
为什么之前的尝试没生效?
你提到曾尝试添加ContentType选项但S3中的“type”仍为空,大概率是因为:
- 没有从客户端传递正确的MIME类型到后端
- 上传时没有在请求头中设置Content-Type,导致S3忽略了预签名URL中的参数
完成以上修改后,S3存储的视频对象会带有正确的Content-Type元数据(比如video/quicktime对应.mov文件),expo-av的Video组件就能正常识别并播放视频了。
后续使用CloudFront时,只需要确保CloudFront的缓存策略不会篡改Content-Type响应头即可,S3的元数据会正确传递给前端。
内容的提问来源于stack exchange,提问作者Elliot Barnett

