> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blindcast.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Progress & Abort

> Track upload progress with the onProgress callback and cancel in-flight uploads with AbortController.

## Progress tracking

Pass an `onProgress` callback to `upload()` to track upload progress:

```typescript theme={null}
import { upload } from "@blindcast/uploader"

const result = await upload(segments, manifest, {
  contentId: "my-video-001",
  keyServerUrl: "https://keys.example.com/keys",
  presignUrl: "https://api.example.com/presign",
  auth: async () => getAccessToken(),
  onProgress: ({ completed, total, current }) => {
    const pct = Math.round((completed / total) * 100)
    updateProgressBar(pct)
    console.log(`[${pct}%] ${completed}/${total} — uploading ${current}`)
  },
})
```

### Progress payload

| Field       | Type     | Description                                      |
| ----------- | -------- | ------------------------------------------------ |
| `completed` | `number` | Number of segments uploaded so far               |
| `total`     | `number` | Total number of segments                         |
| `current`   | `string` | Filename of the segment currently being uploaded |

`onProgress` is called once per segment upload completion. It is not called for the encryption step or the manifest upload.

## Abort uploads

Use an `AbortController` to cancel an in-flight upload:

```typescript theme={null}
const controller = new AbortController()

// Start upload
const uploadPromise = upload(segments, manifest, {
  contentId: "my-video-001",
  keyServerUrl: "https://keys.example.com/keys",
  presignUrl: "https://api.example.com/presign",
  auth: async () => getAccessToken(),
  signal: controller.signal,
})

// Cancel after 30 seconds
setTimeout(() => controller.abort(), 30_000)

// Or cancel on user action
cancelButton.addEventListener("click", () => controller.abort())

const result = await uploadPromise

if (!result.ok && result.error.code === "ABORTED") {
  console.log("Upload cancelled by user")
}
```

When aborted:

* All in-flight S3 uploads are cancelled
* The upload() promise resolves with `{ ok: false, error: { code: "ABORTED" } }`
* Already-uploaded segments remain in S3 (they are not cleaned up automatically)

### Cleaning up partial uploads

If you need to clean up partially uploaded segments after an abort, use the segment URLs from the progress callback:

```typescript theme={null}
const uploadedSegments: string[] = []

const result = await upload(segments, manifest, {
  // ...
  onProgress: ({ current }) => {
    uploadedSegments.push(current)
  },
  signal: controller.signal,
})

if (!result.ok && result.error.code === "ABORTED") {
  // Clean up partial uploads via your API
  await fetch("/api/cleanup", {
    method: "POST",
    body: JSON.stringify({ segments: uploadedSegments }),
  })
}
```
