Skip to Content

Upload con Uppy

Uppy  è un popolare uploader di file open source che gestisce automaticamente chunk, nuovi tentativi e tracciamento dell’avanzamento. Questa guida mostra come integrare Uppy con l’API Ignite Video per upload a file singolo e multipart.

Uppy gestisce automaticamente gli upload multipart per i file grandi ed è l’approccio consigliato per le applicazioni in produzione.

Per un’implementazione manuale senza dipendenze esterne, consulta la guida Upload multipart.

Installazione

Installa i pacchetti Uppy necessari:

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

Configurazione di base

Configurare upload a file singolo

Per file inferiori a 100 MB, usa l’upload diretto con un URL pre-firmato.

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 }

Configurare upload multipart

Per file superiori a 100 MB, usa l’upload multipart per maggiore affidabilità.

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 }

Esempio completo con tracciamento dell’avanzamento

Ecco un’implementazione completa che sceglie automaticamente tra upload a file singolo e multipart in base alle dimensioni del file:

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 }

Integrazione React

Ecco un componente React di esempio che usa 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

Avanzato: recupero URL in batch

Per prestazioni migliori con file grandi, recupera gli URL firmati in batch invece che uno alla volta. Riduce sensibilmente il numero di chiamate API per gli upload di grandi dimensioni.

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 }

Gestione errori e nuovi tentativi

Uppy include logica di retry integrata. Configura il comportamento dei nuovi tentativi:

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

Per una gestione personalizzata degli errori:

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) })