Uploads SDK for Web

Let your users upload large media files easily over the web.

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.

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:

  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 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://api.fastpix.io/v1/on-demand/upload';

// 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.


Step 2: Import the SDK

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

Step 3: 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() 

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.


import React, { useState, useRef, useEffect } 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 [isOnline, setIsOnline] = useState(true);
    const fileInputRef = useRef(null);
    const fileUploadRef = useRef(null);

    useEffect(() => {
        const handleOnline = () => setIsOnline(true);
        const handleOffline = () => setIsOnline(false);
        window.addEventListener("online", handleOnline);
        window.addEventListener("offline", handleOffline);

        return () => {
            window.removeEventListener("online", handleOnline);
            window.removeEventListener("offline", handleOffline);
        };

    }, []);

    const handleUpload = (video, signedUrl) => {
        try {
            const fileUpload = Uploader.init({
                endpoint: signedUrl,
                file: video,
                chunkSize: (10 * 1024),
            });

            fileUploadRef.current = fileUpload;
            fileUpload.on("progress", (event) => {
                setStatusMessage({ status: "", message: "" });
                setProgress(event.detail.progress);
            });

            fileUpload.on("success", () => {
                setStatusMessage({
                    message: "Upload completed successfully!",
                    status: "success",
                });
            });

            fileUpload.on("offline", (event) => {
                setStatusMessage({
                    message: event.detail.message,
                    status: "error",
                });
                setIsOnline(false);
            });

            fileUpload.on("online", () => {
                setStatusMessage({ message: "", status: "" });
                setIsOnline(true);
            });

            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://api.fastpix.io/v1/on-demand/upload";
            const accessTokenId = ''; // Your FastPix Access Token ID (used as the username in Basic Auth) 
            const secretKey = ''; // Your FastPix Secret Key (used as the password in Basic Auth) 
            const data = {
                corsOrigin: "*",
                pushMediaSettings: {
                    accessPolicy: "public",
                    metadata: { key1: "value1" },
                    maxResolution: "1080p",
                },
            };

            const headersRequest = {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    Authorization: "Basic " + btoa(accessTokenId + ":" + secretKey),
                    "X-Client-Type": "web-browser"
                },
                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 && isOnline) {
            fileUploadRef.current.pause();
            setIsPaused(true);
        }
    };

    // Resume the upload  
    const handleResume = () => {
        if (fileUploadRef.current && isOnline) {
            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://api.fastpix.io/v1/on-demand/upload";
            const accessTokenId = ''; // Your FastPix Access Token ID (used as the username in Basic Auth) 
            const secretKey = ''; // Your FastPix Secret Key (used as the password in Basic Auth) 
            const data = {
                corsOrigin: "*",
                pushMediaSettings: {
                    accessPolicy: "public",
                    metadata: { key1: "value1" },
                    maxResolution: "1080p",
                },
            };

            const headersRequest = {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    Authorization: "Basic " + btoa(accessTokenId + ":" + secretKey),
                    "X-Client-Type": "web-browser"
                },
                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.progress);
    });

    // 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://api.fastpix.io/v1/on-demand/upload";
                    const accessTokenId = ''; // Your FastPix Access Token ID (used as the username in Basic Auth) 
                    const secretKey = ''; // Your FastPix Secret Key (used as the password in Basic Auth) 
                    const data = {
                        corsOrigin: "*",
                        pushMediaSettings: {
                            accessPolicy: "public",
                            metadata: { key1: "value1" },
                            maxResolution: "1080p",
                        },
                    };

                    const headersRequest = {
                        method: "POST",
                        headers: {
                            "Content-Type": "application/json",
                            Authorization: "Basic " + btoa(accessTokenId + ":" + secretKey),
                            "X-Client-Type": "web-browser"
                        },
                        body: JSON.stringify(data),
                    };

                    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.progress);
                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>



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.