Set up resumable uploads for Web

Learn to upload large videos to FastPix using the Web SDK, with resumable uploads that recover from interruptions and track progress in real time.

The FastPix Resumable Uploads SDK helps you efficiently upload large files from the browser by splitting them into chunks and also gives you the ability to pause and resume your uploads.



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:

  1. Divide the file: Large files are split into smaller, manageable chunks (e.g., 16 MB by default).
  2. Upload individually: Each chunk is uploaded separately. If a chunk fails, only that specific chunk needs to be re-uploaded.
  3. 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.


Step 1: Installation

To install the SDK, you can use NPM, a CDN, or your preferred package manager:


Using NPM:

npm
npm i @fastpix/resumable-uploads

Using CDN:

CDN
1<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.


1<script>
2// This endpoint provides you with the signed URL necessary for uploading your video.
3
4const url = 'https://api.fastpix.com/v1/on-demand/upload';
5
6// The FastPix Video API employs a token key pair consisting of a Token ID (username) and Token Secret (password) for authentication.
7// Generate a new Access Token in the Access Token settings located within your FastPix Organization dashboard.
8const username = '';
9const password = '';
10
11// Initialize the data object
12const data = {
13 corsOrigin: '*',
14 pushMediaSettings: {
15 accessPolicy: 'public',
16 metadata: {
17 key1: 'value1',
18 },
19 generateSubtitles: false,
20 normalizeAudio: true,
21 maxResolution: '1080p',
22 mediaQuality: 'standard',
23 },
24};
25
26const options = {
27 method: 'POST',
28 headers: {
29 'Content-Type': 'application/json',
30 Authorization: 'Basic ' + btoa(username + ':' + password),
31 },
32 body: JSON.stringify(data),
33}
34
35fetch(url, options)
36 .then(response => {
37 if (response.success) {
38
39 // You can fetch the signedUrl in the response
40 }
41 })
42</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.


Step 2: Import the SDK

Import
1import { Uploader } from "@fastpix/resumable-uploads";

Step 3: Integration

Integration
1const fileUploader = Uploader.init({
2 endpoint: 'https://example.com/signed-url', // Replace with the signed URL.
3 file: mediaFile, // Provide the media file you want to upload.
4 chunkSize: 5120, // Specify the chunk size in kilobytes (KB). Minimum allowed chunk size is 5120KB (5MB).
5
6 // Additional optional parameters can be specified here as needed
7})

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 .


1// fileUploader is the instance of the Uploader
2// Track upload progress
3fileUploader.on("progress", (event) => {
4 console.log("Upload Progress:", event.detail);
5});
6
7// Handle errors during the upload process
8fileUploader.on("error", (event) => {
9 console.error("Upload Error:", event.detail.message);
10});
11
12// Trigger actions when the upload completes successfully
13fileUploader.on("success", (event) => {
14 console.log("Upload Completed");
15});
16
17// Track the initiation of each chunk upload
18fileUploader.on("attempt", (event) => {
19 console.log("Chunk Upload Attempt:", event.detail);
20});
21
22// Track failures of each chunk upload attempt
23fileUploader.on("chunkAttemptFailure", (event) => {
24 console.log("Chunk Attempt Failure:", event.detail);
25});
26
27// Perform an action when a chunk is successfully uploaded
28fileUploader.on("chunkSuccess", (event) => {
29 console.log("Chunk Successfully Uploaded:", event.detail);
30});
31
32// Triggers when the connection is back online
33fileUploader.on("online", (event) => {
34 console.log("Connection Online");
35});
36
37// Triggers when the connection goes offline
38fileUploader.on("offline", (event) => {
39 console.log("Connection Offline");
40});

Managing video uploads

You can control the upload lifecycle with the following methods:


Pause an upload:

1// fileUploader is the instance of the Uploader
2fileUpload.pause()

Resume an upload:

1// fileUploader is the instance of the Uploader
2fileUpload.resume()

Abort an upload:

1// fileUploader is the instance of the Uploader
2fileUpload.abort()

Parameters Accepted

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.

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.


React
1import React, { useState, useRef, useEffect } from "react";
2
3import { Uploader } from "@fastpix/resumable-uploads";
4
5function Uploads() {
6 const [progress, setProgress] = useState(0);
7 const [statusMessage, setStatusMessage] = useState({
8 status: "",
9 message: "",
10 });
11 const [isPaused, setIsPaused] = useState(false);
12 const [isOnline, setIsOnline] = useState(true);
13 const fileInputRef = useRef(null);
14 const fileUploadRef = useRef(null);
15
16 useEffect(() => {
17 const handleOnline = () => setIsOnline(true);
18 const handleOffline = () => setIsOnline(false);
19 window.addEventListener("online", handleOnline);
20 window.addEventListener("offline", handleOffline);
21
22 return () => {
23 window.removeEventListener("online", handleOnline);
24 window.removeEventListener("offline", handleOffline);
25 };
26
27 }, []);
28
29 const handleUpload = (video, signedUrl) => {
30 try {
31 const fileUpload = Uploader.init({
32 endpoint: signedUrl,
33 file: video,
34 chunkSize: (10 * 1024),
35 });
36
37 fileUploadRef.current = fileUpload;
38 fileUpload.on("progress", (event) => {
39 setStatusMessage({ status: "", message: "" });
40 setProgress(event.detail.progress);
41 });
42
43 fileUpload.on("success", () => {
44 setStatusMessage({
45 message: "Upload completed successfully!",
46 status: "success",
47 });
48 });
49
50 fileUpload.on("offline", (event) => {
51 setStatusMessage({
52 message: event.detail.message,
53 status: "error",
54 });
55 setIsOnline(false);
56 });
57
58 fileUpload.on("online", () => {
59 setStatusMessage({ message: "", status: "" });
60 setIsOnline(true);
61 });
62
63 fileUpload.on("error", (event) => {
64 setStatusMessage({ message: event.detail.message, status: "error" });
65 });
66 } catch (error) {
67 setStatusMessage({ message: error?.message, status: "error" });
68 }
69 };
70
71 const handleUploadProcess = async (file) => {
72
73 if (!file) {
74 setStatusMessage({
75 message: "Please select a video to upload.",
76 status: "error",
77 });
78 return;
79 }
80
81 try {
82 const url = "https://api.fastpix.com/v1/on-demand/upload";
83 const accessTokenId = ''; // Your FastPix Access Token ID (used as the username in Basic Auth)
84 const secretKey = ''; // Your FastPix Secret Key (used as the password in Basic Auth)
85 const data = {
86 corsOrigin: "*",
87 pushMediaSettings: {
88 accessPolicy: "public",
89 metadata: { key1: "value1" },
90 maxResolution: "1080p",
91 mediaQuality: "standard",
92 },
93 };
94
95 const headersRequest = {
96 method: "POST",
97 headers: {
98 "Content-Type": "application/json",
99 Authorization: "Basic " + btoa(accessTokenId + ":" + secretKey),
100 "X-Client-Type": "web-browser"
101 },
102 body: JSON.stringify(data),
103 };
104
105 const getSignedUrl = await fetch(url, headersRequest);
106 const signedUrl = await getSignedUrl.json();
107
108 if (signedUrl.success) {
109 handleUpload(file, signedUrl.data.url);
110 } else {
111 setStatusMessage({ message: signedUrl.data.error, status: "error" });
112 }
113 } catch (error) {
114 console.error(error);
115 setStatusMessage({
116 message: "Failed to fetch signed URL.",
117 status: "error",
118 });
119 }
120 };
121
122 const handleButtonClick = () => {
123 fileInputRef.current.click();
124 };
125
126 const handleFileChange = (event) => {
127 const file = event.target.files[0];
128
129 if (file) {
130 setStatusMessage({ status: "filereceived" });
131 handleUploadProcess(file);
132 }
133 };
134
135 // Pause the upload
136 const handlePause = () => {
137 if (fileUploadRef.current && isOnline) {
138 fileUploadRef.current.pause();
139 setIsPaused(true);
140 }
141 };
142
143 // Resume the upload
144 const handleResume = () => {
145 if (fileUploadRef.current && isOnline) {
146 fileUploadRef.current.resume();
147 setIsPaused(false);
148 }
149 };
150
151 return (
152 <div
153 style={{
154 textAlign: "center",
155 width: "100%",
156 padding: "40px",
157 backgroundColor: "#f5f5f5",
158 borderRadius: "15px",
159 boxShadow: "0 4px 20px rgba(0,0,0,0.1)",
160 }}
161 >
162 <h2
163 style={{
164 fontSize: "28px",
165 marginBottom: "30px",
166 fontFamily: "Arial, sans-serif",
167 color: "#333",
168 }}
169 >
170 Fastpix Resumable Uploads
171 </h2>
172
173 {/* Upload Button */}
174 <div
175 style={{
176 marginTop: "30px",
177 marginBottom: "20px",
178 display: progress > 0 ? "none" : "block"
179 }}
180 >
181 <button
182 onClick={handleButtonClick}
183 style={{
184 padding: "15px 30px",
185 cursor: statusMessage?.status === "filereceived" ? "wait" : "pointer",
186 background: "linear-gradient(to right, #0f0f0f, #303030)",
187 color: "#ffffff",
188 borderRadius: "10px",
189 border: "none",
190 fontSize: "16px",
191 fontWeight: "600",
192 transition: "transform 0.2s ease-in-out, box-shadow 0.2s",
193 }}
194 disabled={statusMessage?.status === "filereceived"}
195 >
196 {statusMessage?.status === "filereceived" ? (
197 <span> Starting Upload...</span>
198 ) : (
199 <span>Select a Video to Upload</span>
200 )}
201 </button>
202 <input
203 type="file"
204 ref={fileInputRef}
205 onChange={handleFileChange}
206 style={{ display: "none" }}
207 />
208 </div>
209
210 {/* Progress Bar */}
211 <div style={{ display: progress > 0 ? "block" : "none" }}>
212 <p style={{ fontSize: "16px", marginBottom: "10px" }}>
213 Progress: {`${Math.round(progress)}%`}
214 </p>
215 <div
216 style={{
217 backgroundColor: "#eee",
218 width: "100%",
219 height: "10px",
220 borderRadius: "5px",
221 marginTop: "20px",
222 }}
223 >
224 <div
225 style={{
226 backgroundColor: "#0f0f0f",
227 width: `${progress}%`,
228 height: "10px",
229 borderRadius: "5px",
230 transition: "width 0.25s ease-in-out",
231 }}
232 ></div>
233 </div>
234 </div>
235 <div style={{ marginTop: "20px", display: progress > 0 && !statusMessage?.message ? "flex" : "none", justifyContent: "center", alignItems: "center" }}>
236 <button
237 onClick={handlePause}
238 style={{
239 padding: "10px 20px",
240 marginRight: "10px",
241 backgroundColor: "#FF5722",
242 color: "#fff",
243 border: "none",
244 borderRadius: "5px",
245 cursor: "pointer",
246 fontSize: "14px",
247 display: isPaused ? "none" : "block"
248 }}
249 disabled={progress === 100 || isPaused}
250 >
251 Pause
252 </button>
253 <button
254 onClick={handleResume}
255 style={{
256 padding: "10px 20px",
257 backgroundColor: "#4CAF50",
258 color: "#fff",
259 border: "none",
260 borderRadius: "5px",
261 cursor: "pointer",
262 fontSize: "14px",
263 display: !isPaused ? "none" : "block"
264 }}
265 disabled={progress === 100 || !isPaused}
266 >
267 Resume
268 </button>
269 </div>
270
271 {/* Status Message */}
272 {statusMessage?.message && (
273 <div
274 style={{
275 marginTop: "20px",
276 padding: "10px 20px",
277 borderRadius: "8px",
278 backgroundColor:
279 statusMessage?.status === "success" ? "#4CAF50" : "#FF5722",
280 color: "#fff",
281 fontWeight: "600",
282 }}
283 >
284 {statusMessage.message}
285 </div>
286 )}
287 </div>
288 );
289}
290
291export default Uploads;
1import { Uploader } from "@fastpix/resumable-uploads";
2
3const videoFile = document.getElementById("videoFile")
4
5videoFile.onchange = async (event) => {
6 const fileObject = event?.target?.files[0]
7
8 if (fileObject) {
9 try {
10
11 // The endpoint URL for getting the signed upload URL
12 const url = "https://api.fastpix.com/v1/on-demand/upload";
13 const accessTokenId = ''; // Your FastPix Access Token ID (used as the username in Basic Auth)
14 const secretKey = ''; // Your FastPix Secret Key (used as the password in Basic Auth)
15 const data = {
16 corsOrigin: "*",
17 pushMediaSettings: {
18 accessPolicy: "public",
19 metadata: { key1: "value1" },
20 maxResolution: "1080p",
21 mediaQuality: "standard",
22 },
23 };
24
25 const headersRequest = {
26 method: "POST",
27 headers: {
28 "Content-Type": "application/json",
29 Authorization: "Basic " + btoa(accessTokenId + ":" + secretKey),
30 "X-Client-Type": "web-browser"
31 },
32 body: JSON.stringify(data),
33 };
34
35 const getSignedUrl = await fetch(url, headersRequest);
36 const signedUrl = await getSignedUrl.json();
37
38 if (signedUrl.success) {
39 handleUpload(videoFile.files[0], signedUrl.data.url)
40 } else {
41 console.error(signedUrl.data.error)
42 }
43 } catch (error) {
44 console.error(error)
45 }
46 }
47}
48
49const handleUpload = (video, signedUrl) => {
50
51 const fileUpload = Uploader.init({
52 endpoint: signedUrl, // The signed URL to upload the file to
53 file: video, // The video file to upload
54 chunkSize: 5120, // The size of each chunk in KB
55 // Additional optional parameters can be specified here as needed
56 });
57
58 // Event listener for tracking upload progress.
59 fileUpload.on('progress', event => {
60 console.log("progress:", event.detail.progress);
61 });
62
63 // Event listener for handling successful upload completion.
64 fileUpload.on('success', () => {
65 console.log("upload completed")
66 });
67
68 // Event listener for handling upload errors.
69 fileUpload.on('error', event => {
70 console.log("upload error:", event?.detail?.message);
71 });
72
73 // Pause an upload
74 // fileUpload.pause()
75
76 // Resume an upload
77 // fileUpload.resume()
78
79 // Abort an upload
80 // fileUpload.abort()
81}
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>File Upload with Progress Bar</title>
8
9 <!-- Import FastPix Uploader library from CDN -->
10 <script src="https://cdn.jsdelivr.net/npm/@fastpix/resumable-uploads@latest/dist/uploads.js"></script>
11</head>
12
13<body>
14 <style>
15 .container {
16 width: 100%;
17 display: flex;
18 flex-direction: column;
19 align-items: center;
20 justify-content: center;
21 margin-top: 50px;
22 }
23
24 .progress-bar-container {
25 width: 100%;
26 background-color: #eee;
27 border-radius: 20px;
28 height: 10px;
29 margin-top: 20px;
30 }
31
32 .progress-bar {
33 height: 100%;
34 width: 0;
35 background-color: #0f0f0f;
36 border-radius: 100px;
37 transition: width 0.2s ease;
38 }
39 </style>
40
41 <div class="container">
42 <h1>FastPix Uploads</h1>
43
44 <!-- File input to select video for upload -->
45 <input id="videoFile" type="file" />
46
47 <!-- Progress indicator text -->
48 <p id="progressIndicator"></p>
49
50 <!-- Visual representation of upload progress -->
51 <div class="progress-bar-container">
52 <div class="progress-bar" id="progressBar"></div>
53 </div>
54 </div>
55
56 <script>
57 const videoFile = document.getElementById("videoFile");
58 const progressBar = document.getElementById("progressBar");
59 const progressIndicator = document.getElementById("progressIndicator")
60
61 // Initialize progress bar and indicator to 0
62 progressBar.style.width = '0%';
63 progressIndicator.innerText = '0%'
64
65 // Triggered when a file is selected
66 videoFile.onchange = async (event) => {
67 const fileObject = event?.target?.files[0];
68
69 if (fileObject) {
70 try {
71
72 // API details to get signed URL for upload
73 const url = "https://api.fastpix.com/v1/on-demand/upload";
74 const accessTokenId = ''; // Your FastPix Access Token ID (used as the username in Basic Auth)
75 const secretKey = ''; // Your FastPix Secret Key (used as the password in Basic Auth)
76 const data = {
77 corsOrigin: "*",
78 pushMediaSettings: {
79 accessPolicy: "public",
80 metadata: { key1: "value1" },
81 maxResolution: "1080p",
82 mediaQuality: "standard",
83 },
84 };
85
86 const headersRequest = {
87 method: "POST",
88 headers: {
89 "Content-Type": "application/json",
90 Authorization: "Basic " + btoa(accessTokenId + ":" + secretKey),
91 "X-Client-Type": "web-browser"
92 },
93 body: JSON.stringify(data),
94 };
95
96 const getSignedUrl = await fetch(url, headersRequest);
97 const signedUrl = await getSignedUrl.json();
98
99 // If signed URL is successfully received, proceed to upload
100 if (signedUrl.success) {
101 handleUpload(fileObject, signedUrl.data.url);
102 } else {
103 console.error(signedUrl.data.error);
104 }
105 } catch (error) {
106 console.error(error);
107 }
108 }
109 };
110
111 // Function to handle file upload using the signed URL
112 const handleUpload = (video, signedUrl) => {
113
114 const fileUpload = Uploader.init({
115 endpoint: signedUrl,
116 file: video,
117 chunkSize: 5120,
118 });
119
120 // Event listener for upload progress
121 fileUpload.on('progress', (event) => {
122 const percentage = Math.round(event.detail.progress);
123 progressIndicator.innerText = `${percentage}%`;
124 progressBar.style.width = `${percentage}%`;
125 console.log("progress:", percentage + "%");
126 });
127
128 // Event listener for successful upload
129 fileUpload.on('success', () => {
130 console.log("upload completed");
131 });
132
133 // Event listener for upload error
134 fileUpload.on('error', (event) => {
135 console.log("upload error:", event?.detail?.message);
136 });
137
138 // Pause an upload
139 // fileUpload.pause()
140 // Resume an upload
141 // fileUpload.resume()
142 // Abort an upload
143 // fileUpload.abort()
144 };
145 </script>
146</body>
147</html>



Changelog

All notable changes to the Resumable uploads SDK for web will be documented below.


Current version

v[1.0.0]

Features:

  • Chunking: Files are automatically split into chunks (default chunk size is 16MB).
  • Pause and Resume: Allows temporarily pausing the upload and resuming after a while.
  • Retry: Uploads might fail due to temporary network failures. Individual chunks are retried for 5 times with exponential backoff to recover automatically from such failures.
  • Lifecycle Event Listeners: Provides real-time feedback through various upload lifecycle events.
  • Error Handling: Comprehensive error management to notify users of issues during uploads.
  • Customizability: Options to customize chunk size and retry attempts.