Resumable uploads SDK for web
Let your users upload large media files easily over the web.
When it comes to uploading files, especially large ones like videos, using the right method is crucial for a smooth and efficient experience. This guide explains how to implement resumable web uploads effectively.
How resumable uploads work through chunking
Resumable uploads can be effectively handled through a technique called chunking. This method involves breaking down large files into smaller, more manageable pieces, or "chunks." Here's how chunking works:
- Divide the file: Large files are split into smaller, manageable chunks (e.g., 16 MB by default).
- Upload individually: Each chunk is uploaded separately. If a chunk fails, only that specific chunk needs to be re-uploaded.
- Resume capability: If the upload is interrupted, you can resume from the last successfully uploaded chunk instead of starting over.
This approach is important because:
- It reduces the risk: Uploading smaller chunks minimizes the risk of failure. If a chunk fails to upload due to network issues, only that specific chunk needs to be re-uploaded, not the entire file.
- Improves performance: Smaller chunks can be uploaded more quickly and efficiently, especially on slower connections, as they require less time to transfer.
- Easier management: Chunking allows for better tracking of upload progress, making it easier to implement features like pause and resume.
Installation
To install the SDK, you can use NPM, a CDN, or your preferred package manager:
Using NPM:
npm i @fastpix/resumable-uploads
Using CDN:
<script src="https://cdn.jsdelivr.net/npm/@fastpix/resumable-uploads@latest/dist/uploads.js"></script>
Basic usage
To get started with the SDK, you will need a signed URL.
To make API requests, you'll need a valid Access Token and Secret Key. See the Basic Authentication Guide for details on retrieving these credentials.
Once you have your credentials, use the Upload media from device API to generate a signed URL for uploading media. After fetching the the signed URL you can continue with integrating the SDK into your application.
The example below gives you an idea about how to get the signed URL using the upload media from device API. You can use the example directly or also refer to our upload videos directly guide.
<script>
// This endpoint provides you with the signed URL necessary for uploading your video.
const url = 'https://v1.fastpix.io/on-demand/uploads';
// The FastPix Video API employs a token key pair consisting of a Token ID (username) and Token Secret (password) for authentication.
// Generate a new Access Token in the Access Token settings located within your FastPix Organization dashboard.
const username = '';
const password = '';
// Initialize the data object
const data = {
corsOrigin: '*',
pushMediaSettings: {
accessPolicy: 'public',
metadata: {
key1: 'value1',
},
generateSubtitles: false,
normalizeAudio: true,
maxResolution: '1080p',
},
};
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Basic ' + btoa(username + ':' + password),
},
body: JSON.stringify(data),
}
fetch(url, options)
.then(response => {
if (response.success) {
// You can fetch the signedUrl in the response
}
})
</script>
PLEASE NOTE
When making Basic Auth API calls, securely retrieve the username and password from environment variables (.env) or a protected server endpoint to prevent unauthorized access.
In the API response, you will get a signed URL upon successful API request. Next step is to take the signed URL and pass it to the SDK.
Import the SDK
import { Uploader } from "@fastpix/resumable-uploads";
Integration
const fileUploader = Uploader.init({
endpoint: 'https://example.com/signed-url', // Replace with the signed URL.
file: mediaFile, // Provide the media file you want to upload.
chunkSize: 5120, // Specify the chunk size in kilobytes (KB). Minimum allowed chunk size is 5120KB (5MB).
// Additional optional parameters can be specified here as needed
})
Parameters to use
This SDK supports the following parameters:
endpoint
(mandatory): The URL endpoint where the file will be uploaded.file
(mandatory): The file object that you want to upload (e.g., a video file).chunkSize
(optional): chunkSize should be provided in kilobytes (KB). By default, the SDK splits files into chunks of 16 MB. You can customize this by specifying a chunkSize in kilobytes (minimum of 5 MB).maxFileSize
(optional): This parameter restricts the maximum file size that users can upload. Setting this can help prevent users from attempting to upload excessively large files that may not be manageable.retryChunkAttempt
(optional): Specifies the number of times to retry uploading a chunk in case of failure. The default value is 5.delayRetry
(optional): The delay time (in milliseconds) before retrying a chunk upload after a failure. The default value is 1000ms.
Monitor the upload progress through lifecycle events
Using the following lifecycle events lets you to build a robust and user-friendly upload experience. By tracking progress in real-time, handling errors proactively, managing chunk retries, and responding to network changes, you can ensure seamless and efficient uploads.
Integrating these event handlers into your application can enhance reliability, optimize performance, and provide users with clear, actionable feedback throughout the upload process. See detailed usage example .
// fileUploader is the instance of the Uploader
// Track upload progress
fileUploader.on("progress", (event) => {
console.log("Upload Progress:", event.detail);
});
// Handle errors during the upload process
fileUploader.on("error", (event) => {
console.error("Upload Error:", event.detail.message);
});
// Trigger actions when the upload completes successfully
fileUploader.on("success", (event) => {
console.log("Upload Completed");
});
// Track the initiation of each chunk upload
fileUploader.on("attempt", (event) => {
console.log("Chunk Upload Attempt:", event.detail);
});
// Track failures of each chunk upload attempt
fileUploader.on("chunkAttemptFailure", (event) => {
console.log("Chunk Attempt Failure:", event.detail);
});
// Perform an action when a chunk is successfully uploaded
fileUploader.on("chunkSuccess", (event) => {
console.log("Chunk Successfully Uploaded:", event.detail);
});
// Triggers when the connection is back online
fileUploader.on("online", (event) => {
console.log("Connection Online");
});
// Triggers when the connection goes offline
fileUploader.on("offline", (event) => {
console.log("Connection Offline");
});
Managing video uploads
You can control the upload lifecycle with the following methods:
Pause an upload:
// fileUploader is the instance of the Uploader
fileUpload.pause()
Resume an upload:
// fileUploader is the instance of the Uploader
fileUpload.resume()
Abort an upload:
// fileUploader is the instance of the Uploader
fileUpload.abort()
Usage example of resumable uploads SDK
The following examples give an overview of integrating the FastPix Resumable Uploads SDK into your project, enabling you to build a fully customized upload interface. By making use of the SDK's lifecycle events and configurable attributes, you can enhance functionality and optimize the upload experience.
import React, { useState, useRef } from "react";
import { Uploader } from "@fastpix/resumable-uploads";
function Uploads() {
const [progress, setProgress] = useState(0);
const [statusMessage, setStatusMessage] = useState({
status: "",
message: "",
});
const [isPaused, setIsPaused] = useState(false);
const fileInputRef = useRef(null);
const fileUploadRef = useRef(null);
const handleUpload = (video, signedUrl) => {
try {
const fileUpload = Uploader.init({
endpoint: signedUrl,
file: video,
chunkSize: 5120,
});
fileUploadRef.current = fileUpload;
fileUpload.on("progress", (event) => {
setStatusMessage({ status: "", message: "" });
setProgress(event.detail);
});
fileUpload.on("success", () => {
setStatusMessage({
message: "Upload completed successfully!",
status: "success",
});
});
fileUpload.on("offline", () => {
setStatusMessage({
message:
"Upload paused. It will resume once the network connection is restored.",
status: "error",
});
});
fileUpload.on("online", () => {
setStatusMessage({ message: "", status: "" });
});
fileUpload.on("error", (event) => {
setStatusMessage({ message: event.detail.message, status: "error" });
});
} catch (error) {
setStatusMessage({ message: error?.message, status: "error" });
}
};
const handleUploadProcess = async (file) => {
if (!file) {
setStatusMessage({
message: "Please select a video to upload.",
status: "error",
});
return;
}
try {
const url = "https://v1.fastpix.io/on-demand/uploads";
const username = "";
const password = "";
const data = {
corsOrigin: "*",
pushMediaSettings: {
accessPolicy: "public",
metadata: { key1: "value1" },
generateSubtitles: false,
normalizeAudio: true,
maxResolution: "1080p",
},
};
const headersRequest = {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Basic " + btoa(username + ":" + password),
},
body: JSON.stringify(data),
};
const getSignedUrl = await fetch(url, headersRequest);
const signedUrl = await getSignedUrl.json();
if (signedUrl.success) {
handleUpload(file, signedUrl.data.url);
} else {
setStatusMessage({ message: signedUrl.data.error, status: "error" });
}
} catch (error) {
console.error(error);
setStatusMessage({
message: "Failed to fetch signed URL.",
status: "error",
});
}
};
const handleButtonClick = () => {
fileInputRef.current.click();
};
const handleFileChange = (event) => {
const file = event.target.files[0];
if (file) {
setStatusMessage({ status: "filereceived" });
handleUploadProcess(file);
}
};
// Pause the upload
const handlePause = () => {
if (fileUploadRef.current) {
fileUploadRef.current.pause();
setIsPaused(true);
}
};
// Resume the upload
const handleResume = () => {
if (fileUploadRef.current) {
fileUploadRef.current.resume();
setIsPaused(false);
}
};
return (
<div
style={{
textAlign: "center",
width: "100%",
padding: "40px",
backgroundColor: "#f5f5f5",
borderRadius: "15px",
boxShadow: "0 4px 20px rgba(0,0,0,0.1)",
}}
>
<h2
style={{
fontSize: "28px",
marginBottom: "30px",
fontFamily: "Arial, sans-serif",
color: "#333",
}}
>
Fastpix Resumable Uploads
</h2>
{/* Upload Button */}
<div
style={{
marginTop: "30px",
marginBottom: "20px",
display: progress > 0 ? "none" : "block",
}}
>
<button
onClick={handleButtonClick}
style={{
padding: "15px 30px",
cursor:
statusMessage?.status === "filereceived" ? "wait" : "pointer",
background: "linear-gradient(to right, #0f0f0f, #303030)",
color: "#ffffff",
borderRadius: "10px",
border: "none",
fontSize: "16px",
fontWeight: "600",
transition: "transform 0.2s ease-in-out, box-shadow 0.2s",
}}
disabled={statusMessage?.status === "filereceived"}
>
{statusMessage?.status === "filereceived" ? (
<span> Starting Upload...</span>
) : (
<span>Select a Video to Upload</span>
)}
</button>
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
style={{ display: "none" }}
/>
</div>
{/* Progress Bar */}
<div style={{ display: progress > 0 ? "block" : "none" }}>
<p style={{ fontSize: "16px", marginBottom: "10px" }}>
Progress: {`${Math.round(progress)}%`}
</p>
<div
style={{
backgroundColor: "#eee",
width: "100%",
height: "10px",
borderRadius: "5px",
marginTop: "20px",
}}
>
<div
style={{
backgroundColor: "#0f0f0f",
width: `${progress}%`,
height: "10px",
borderRadius: "5px",
transition: "width 0.25s ease-in-out",
}}
></div>
</div>
</div>
<div
style={{
marginTop: "20px",
display: progress > 0 && !statusMessage?.message ? "flex" : "none",
justifyContent: "center",
alignItems: "center",
}}
>
<button
onClick={handlePause}
style={{
padding: "10px 20px",
marginRight: "10px",
backgroundColor: "#FF5722",
color: "#fff",
border: "none",
borderRadius: "5px",
cursor: "pointer",
fontSize: "14px",
display: isPaused ? "none" : "block",
}}
disabled={progress === 100 || isPaused}
>
Pause
</button>
<button
onClick={handleResume}
style={{
padding: "10px 20px",
backgroundColor: "#4CAF50",
color: "#fff",
border: "none",
borderRadius: "5px",
cursor: "pointer",
fontSize: "14px",
display: !isPaused ? "none" : "block",
}}
disabled={progress === 100 || !isPaused}
>
Resume
</button>
</div>
{/* Status Message */}
{statusMessage?.message && (
<div
style={{
marginTop: "20px",
padding: "10px 20px",
borderRadius: "8px",
backgroundColor:
statusMessage?.status === "success" ? "#4CAF50" : "#FF5722",
color: "#fff",
fontWeight: "600",
}}
>
{statusMessage.message}
</div>
)}
</div>
);
}
export default Uploads;
import { Uploader } from "@fastpix/resumable-uploads";
const videoFile = document.getElementById("videoFile")
videoFile.onchange = async (event) => {
const fileObject = event?.target?.files[0]
if (fileObject) {
try {
// The endpoint URL for getting the signed upload URL
const url = 'https://v1.fastpix.io/on-demand/uploads';
// Basic authentication credentials
const username = '';
const password = '';
// Data to send in the POST request to configure the upload
const data = {
corsOrigin: "*", // Specify allowed cross-origin requests
pushMediaSettings: {
accessPolicy: "public", // Set access policy to public
metadata: { key1: "value1" }, // Metadata associated with the video
generateSubtitles: false, // Disable automatic subtitle generation
normalizeAudio: true, // Enable audio normalization
maxResolution: "1080p", // Set the maximum video resolution
},
};
const headersRequest = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Basic ' + btoa(username + ':' + password),
},
body: JSON.stringify(data),
};
const getSignedUrl = await fetch(url, headersRequest);
const signedUrl = await getSignedUrl.json();
if (signedUrl.success) {
handleUpload(videoFile.files[0], signedUrl.data.url)
} else {
console.error(signedUrl.data.error)
}
} catch (error) {
console.error(error)
}
}
}
const handleUpload = (video, signedUrl) => {
const fileUpload = Uploader.init({
endpoint: signedUrl, // The signed URL to upload the file to
file: video, // The video file to upload
chunkSize: 5120, // The size of each chunk in KB
// Additional optional parameters can be specified here as needed
});
// Event listener for tracking upload progress.
fileUpload.on('progress', event => {
console.log("progress:",event.detail);
});
// Event listener for handling successful upload completion.
fileUpload.on('success', () => {
console.log("upload completed")
});
// Event listener for handling upload errors.
fileUpload.on('error', event => {
console.log("upload error:",event?.detail?.message);
});
// Pause an upload
// fileUpload.pause()
// Resume an upload
// fileUpload.resume()
// Abort an upload
// fileUpload.abort()
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Upload with Progress Bar</title>
<!-- Import FastPix Uploader library from CDN -->
<script src="https://cdn.jsdelivr.net/npm/@fastpix/resumable-uploads@latest/dist/uploads.js"></script>
</head>
<body>
<style>
.container {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 50px;
}
.progress-bar-container {
width: 100%;
background-color: #eee;
border-radius: 20px;
height: 10px;
margin-top: 20px;
}
.progress-bar {
height: 100%;
width: 0;
background-color: #0f0f0f;
border-radius: 100px;
transition: width 0.2s ease;
}
</style>
<div class="container">
<h1>FastPix Uploads</h1>
<!-- File input to select video for upload -->
<input id="videoFile" type="file" />
<!-- Progress indicator text -->
<p id="progressIndicator"></p>
<!-- Visual representation of upload progress -->
<div class="progress-bar-container">
<div class="progress-bar" id="progressBar"></div>
</div>
</div>
<script>
const videoFile = document.getElementById("videoFile");
const progressBar = document.getElementById("progressBar");
const progressIndicator = document.getElementById("progressIndicator")
// Initialize progress bar and indicator to 0
progressBar.style.width = '0%';
progressIndicator.innerText = '0%'
// Triggered when a file is selected
videoFile.onchange = async (event) => {
const fileObject = event?.target?.files[0];
if (fileObject) {
try {
// API details to get signed URL for upload
const url = 'https://v1.fastpix.io/on-demand/uploads';
const username = '';
const password = '';
// Request payload for upload configuration
const payload = {
corsOrigin: "*",
pushMediaSettings: {
accessPolicy: "public",
metadata: { key1: "value1" },
generateSubtitles: false,
normalizeAudio: true,
maxResolution: "1080p",
},
};
const headersRequest = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Basic ' + btoa(username + ':' + password),
},
body: JSON.stringify(payload),
};
const getSignedUrl = await fetch(url, headersRequest);
const signedUrl = await getSignedUrl.json();
// If signed URL is successfully received, proceed to upload
if (signedUrl.success) {
handleUpload(fileObject, signedUrl.data.url);
} else {
console.error(signedUrl.data.error);
}
} catch (error) {
console.error(error);
}
}
};
// Function to handle file upload using the signed URL
const handleUpload = (video, signedUrl) => {
const fileUpload = Uploader.init({
endpoint: signedUrl,
file: video,
chunkSize: 5120,
});
// Event listener for upload progress
fileUpload.on('progress', (event) => {
const percentage = Math.round(event.detail);
progressIndicator.innerText = `${percentage}%`;
progressBar.style.width = `${percentage}%`;
console.log("progress:", percentage + "%");
});
// Event listener for successful upload
fileUpload.on('success', () => {
console.log("upload completed");
});
// Event listener for upload error
fileUpload.on('error', (event) => {
console.log("upload error:", event?.detail?.message);
});
// Pause an upload
// fileUpload.pause()
// Resume an upload
// fileUpload.resume()
// Abort an upload
// fileUpload.abort()
};
</script>
</body>
</html>
Updated 9 days ago