API Reference
Videos
Advanced Upload
Using Uppy

Uploading with Uppy

Uppy (opens in a new tab) is a popular open-source file uploader that handles chunking, retries, and progress tracking automatically. This guide shows how to integrate Uppy with the Ignite Video API for both single-file and multipart uploads.

Uppy automatically handles multipart uploads for large files, making it the recommended approach for production applications.

💡

For a manual implementation without external dependencies, see the Multipart Upload guide.

Installation

Install the required Uppy packages:

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

Basic Setup

Configure for single-file uploads

For files under 100MB, use direct upload with a pre-signed URL.

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
}

Configure for multipart uploads

For files over 100MB, use multipart upload for better reliability.

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
}

Complete Example with Progress Tracking

Here's a complete implementation that automatically chooses between single-file and multipart upload based on file size:

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
}

React Integration

Here's an example React component using 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

Advanced: Batch URL Fetching

For better performance with large files, fetch signed URLs in batches instead of one at a time. This reduces the number of API calls significantly for large uploads.

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
}

Error Handling and Retries

Uppy includes built-in retry logic. Configure retry behavior:

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

For custom error handling:

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