Skip to Content

Upload avec Uppy

Uppy  est un outil de téléchargement de fichiers open source très utilisé qui gère le découpage, les nouvelles tentatives et le suivi de progression automatiquement. Ce guide montre comment intégrer Uppy avec l’API Ignite Video pour les téléchargements fichier unique et multipart.

Uppy gère automatiquement les téléchargements multipart pour les gros fichiers, ce qui en fait l’approche recommandée pour les applications en production.

Pour une implémentation manuelle sans dépendance externe, voir le guide Upload multipart.

Installation

Installe les paquets Uppy nécessaires :

npm install @uppy/core @uppy/aws-s3

Configuration de base

Configurer téléchargement fichier unique

Pour les fichiers de moins de 100 Mo, utilise un téléchargement direct avec une URL pré-signée.

import Uppy from '@uppy/core' import AwsS3 from '@uppy/aws-s3' async function uploadVideo(file, title, token) { const uppy = new Uppy() // Step 1: Create video and get signed URL const response = await fetch('https://app.ignitevideo.cloud/api/videos/upload', { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ title, mimeType: file.type }) }) const { videoId, signedUrl } = await response.json() // Step 2: Configure Uppy for direct upload uppy.use(AwsS3, { shouldUseMultipart: false, getUploadParameters: () => ({ method: 'PUT', url: signedUrl, headers: { 'Content-Type': file.type } }) }) // Step 3: Add file and upload uppy.addFile({ name: file.name, type: file.type, data: file }) await uppy.upload() // Clean up uppy.destroy() return videoId }

Configurer téléchargement multipart

Pour les fichiers de plus de 100 Mo, utilise le téléchargement multipart pour une meilleure fiabilité.

import Uppy from '@uppy/core' import AwsS3 from '@uppy/aws-s3' async function uploadLargeVideo(file, title, token) { const uppy = new Uppy() // Step 1: Create video with multipart flag const response = await fetch('https://app.ignitevideo.cloud/api/videos/upload', { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ title, mimeType: file.type, useMultipart: true }) }) const { videoId, multipartUpload } = await response.json() const { uploadId, key } = multipartUpload // Step 2: Configure Uppy for multipart upload uppy.use(AwsS3, { shouldUseMultipart: true, limit: 5, // Max concurrent part uploads retryDelays: [0, 1000, 3000, 5000], // Retry delays in ms // Return the existing multipart upload info createMultipartUpload: () => Promise.resolve({ uploadId, key }), // Get signed URL for each part signPart: async (file, { partNumber }) => { const response = await fetch( `https://app.ignitevideo.cloud/api/videos/upload/s3/multipart/${uploadId}/${partNumber}?key=${encodeURIComponent(key)}`, { headers: { 'Authorization': `Bearer ${token}` } } ) const data = await response.json() return { url: data.url } }, // Complete the multipart upload completeMultipartUpload: async (file, { uploadId, key, parts }) => { const response = await fetch( `https://app.ignitevideo.cloud/api/videos/upload/s3/multipart/${uploadId}/complete?key=${encodeURIComponent(key)}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ parts }) } ) return response.json() } }) // Step 3: Add file and upload uppy.addFile({ name: file.name, type: file.type, data: file }) await uppy.upload() // Clean up uppy.destroy() return videoId }

Exemple complet avec suivi de progression

Voici une implémentation complète qui choisit automatiquement entre téléchargement fichier unique et multipart selon la taille du fichier :

import Uppy from '@uppy/core' import AwsS3 from '@uppy/aws-s3' const API_BASE = 'https://app.ignitevideo.cloud/api' const MULTIPART_THRESHOLD = 100 * 1024 * 1024 // 100MB export async function uploadVideo(file, title, token, onProgress) { const useMultipart = file.size > MULTIPART_THRESHOLD // Step 1: Initialize video on server const initResponse = await fetch(`${API_BASE}/videos/upload`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ title, mimeType: file.type, useMultipart }) }) if (!initResponse.ok) { throw new Error('Failed to initialize upload') } const initData = await initResponse.json() const { videoId } = initData // Step 2: Create and configure Uppy const uppy = new Uppy({ restrictions: { maxFileSize: 60 * 1024 * 1024 * 1024, allowedFileTypes: ['video/*'] } }) if (!useMultipart) { // Single file upload uppy.use(AwsS3, { shouldUseMultipart: false, getUploadParameters: () => ({ method: 'PUT', url: initData.signedUrl, headers: { 'Content-Type': file.type } }) }) } else { // Multipart upload const { uploadId, key } = initData.multipartUpload uppy.use(AwsS3, { shouldUseMultipart: true, limit: 5, retryDelays: [0, 1000, 3000, 5000], createMultipartUpload: () => Promise.resolve({ uploadId, key }), signPart: async (file, { partNumber }) => { const response = await fetch( `${API_BASE}/videos/upload/s3/multipart/${uploadId}/${partNumber}?key=${encodeURIComponent(key)}`, { headers: { 'Authorization': `Bearer ${token}` } } ) if (!response.ok) throw new Error(`Failed to sign part ${partNumber}`) const data = await response.json() return { url: data.url } }, completeMultipartUpload: async (file, { uploadId, key, parts }) => { const response = await fetch( `${API_BASE}/videos/upload/s3/multipart/${uploadId}/complete?key=${encodeURIComponent(key)}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ parts }) } ) if (!response.ok) throw new Error('Failed to complete multipart upload') return response.json() } }) } // Step 3: Set up event handlers uppy.on('upload-progress', (file, progress) => { const percent = (progress.bytesUploaded / progress.bytesTotal) * 100 onProgress?.(percent) }) uppy.on('upload-error', (file, error) => { console.error('Upload error:', error) }) // Step 4: Add file and upload uppy.addFile({ name: file.name, type: file.type, data: file }) await uppy.upload() // Clean up uppy.destroy() return videoId }

Intégration React

Voici un exemple de composant React utilisant Uppy :

import React, { useState, useCallback } from 'react' import { uploadVideo } from './uploadVideo' // The function from above function VideoUploader({ token }) { const [progress, setProgress] = useState(0) const [status, setStatus] = useState('idle') // idle, uploading, complete, error const [videoId, setVideoId] = useState(null) const handleFileChange = useCallback(async (event) => { const file = event.target.files?.[0] if (!file) return const title = file.name.replace(/\.[^/.]+$/, '') // Remove extension setStatus('uploading') setProgress(0) try { const id = await uploadVideo(file, title, token, (percent) => { setProgress(Math.round(percent)) }) setVideoId(id) setStatus('complete') } catch (error) { console.error('Upload failed:', error) setStatus('error') } }, [token]) return ( <div> <input type="file" accept="video/*" onChange={handleFileChange} disabled={status === 'uploading'} /> {status === 'uploading' && ( <div> <progress value={progress} max="100" /> <span>{progress}%</span> </div> )} {status === 'complete' && ( <p>Upload complete! Video ID: {videoId}</p> )} {status === 'error' && ( <p>Upload failed. Please try again.</p> )} </div> ) } export default VideoUploader

Avancé : récupération d’URL par lots

Pour de meilleures performances sur les gros fichiers, récupère les URL signées par lots plutôt qu’une par une. Cela réduit fortement le nombre d’appels API pour les gros téléchargements.

import Uppy from '@uppy/core' import AwsS3 from '@uppy/aws-s3' const API_BASE = 'https://app.ignitevideo.cloud/api' const BATCH_SIZE = 100 async function uploadWithBatchFetching(file, title, token) { // Initialize upload const initResponse = await fetch(`${API_BASE}/videos/upload`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ title, mimeType: file.type, useMultipart: true }) }) const { videoId, multipartUpload } = await initResponse.json() const { uploadId, key } = multipartUpload // Batch fetching state const urlCache = new Map() const batchPromises = new Map() const getBatchNumber = (partNumber) => Math.floor((partNumber - 1) / BATCH_SIZE) const fetchBatch = async (batchNumber) => { const startPart = batchNumber * BATCH_SIZE + 1 const endPart = startPart + BATCH_SIZE - 1 const response = await fetch( `${API_BASE}/videos/upload/s3/multipart/${uploadId}/prepare-parts?key=${encodeURIComponent(key)}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ startPart, endPart }) } ) const data = await response.json() for (const part of data.parts) { urlCache.set(part.partNumber, part.url) } } const ensureBatchLoaded = async (partNumber) => { const batchNumber = getBatchNumber(partNumber) // Start fetching this batch if not already in progress if (!batchPromises.has(batchNumber)) { batchPromises.set(batchNumber, fetchBatch(batchNumber)) } await batchPromises.get(batchNumber) // Look-ahead: pre-fetch next batch when past 50% of current batch const positionInBatch = (partNumber - 1) % BATCH_SIZE if (positionInBatch > BATCH_SIZE / 2) { const nextBatch = batchNumber + 1 if (!batchPromises.has(nextBatch)) { batchPromises.set(nextBatch, fetchBatch(nextBatch).catch(() => { batchPromises.delete(nextBatch) })) } } } // Configure Uppy const uppy = new Uppy() uppy.use(AwsS3, { shouldUseMultipart: true, limit: 5, retryDelays: [0, 1000, 3000, 5000], createMultipartUpload: () => { // Pre-fetch first batch of URLs immediately ensureBatchLoaded(1).catch(console.error) return Promise.resolve({ uploadId, key }) }, signPart: async (file, { partNumber }) => { // Check cache first let url = urlCache.get(partNumber) if (url) return { url } // Ensure batch is loaded await ensureBatchLoaded(partNumber) url = urlCache.get(partNumber) if (url) return { url } // Fallback: fetch single part const response = await fetch( `${API_BASE}/videos/upload/s3/multipart/${uploadId}/${partNumber}?key=${encodeURIComponent(key)}`, { headers: { 'Authorization': `Bearer ${token}` } } ) const data = await response.json() return { url: data.url } }, completeMultipartUpload: async (file, { uploadId, key, parts }) => { // For large uploads, let server fetch parts info const useFetchFromStorage = parts.length > 100 const response = await fetch( `${API_BASE}/videos/upload/s3/multipart/${uploadId}/complete?key=${encodeURIComponent(key)}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify( useFetchFromStorage ? { fetchPartsFromStorage: true } : { parts } ) } ) return response.json() } }) uppy.addFile({ name: file.name, type: file.type, data: file }) await uppy.upload() uppy.destroy() return videoId }

Gestion des erreurs et nouvelles tentatives

Uppy inclut une logique de nouvelle tentative intégrée. Configure le comportement :

uppy.use(AwsS3, { shouldUseMultipart: true, retryDelays: [0, 1000, 3000, 5000], // Retry delays in ms limit: 5, // Max concurrent uploads // ... other options })

Pour une gestion d’erreurs personnalisée :

uppy.on('upload-error', (file, error, response) => { console.error('Upload failed for file:', file.name) console.error('Error:', error) // You can retry the upload // uppy.retryUpload(file.id) }) uppy.on('error', (error) => { console.error('Uppy error:', error) })