Appearance
Video Upload Pipeline
Upload Flow
- File selection --
FileUploader.vueaccepts video files via drag-and-drop or file picker - Client-side validation -- checks file size (
VIDEOSIZE.limit) and duration (VIDEODURATION.limit) - Metadata extraction -- reads video duration, width, height, aspect ratio from the file
- Auto-streaming -- streaming is automatically enabled for videos longer than 4 seconds
- Bunny upload token --
chatbotService.getBunnyUploadToken()calls backend to get TUS upload credentials - TUS upload -- file uploaded to
https://video.bunnycdn.com/tusuploadusing TUS resumable protocol - Transcoding -- Bunny CDN transcodes the video into multiple renditions (H.264 + AV1, various bitrates)
- Status polling --
videoProxy.tspolls/upload_video/statusuntil encoding completes - M3U8 parsing --
streamParser.tsfetches and parses the HLS master playlist to extract stream data
Bunny Transcoding Status Codes
| Code | Status |
|---|---|
| 0 | Created |
| 1 | Uploaded |
| 2 | Processing |
| 3 | Transcoding |
| 4 | Finished |
| 5 | Error |
| 6 | Upload Failed |
| 7 | JIT Segmenting |
| 8 | JIT Playlists Created |
Soft Progress Bar
The upload status polling in videoProxy.ts uses a "soft progress" algorithm. Instead of jumping between status checkpoints, the progress bar eases toward the actual target percentage and drifts slightly past it, giving a smooth visual experience. It never reaches 100% until the transcoding is actually finished.
Stream Parser Output
After transcoding, streamParser(streamId) fetches the HLS manifest and returns:
typescript
{
baseUrl: string,
length: number, // duration in seconds
bufferTime: number,
audio: {
codecs: string,
bandwidth: number,
timescale: number,
duration: number,
init: string, // e.g. "aac_128k/init-stream0.m4s"
media: string
},
video: [ // sorted by bandwidth ascending
{
codecs: string, // e.g. "avc1.64001f" (H.264) or "av01.0.08M.08" (AV1)
bandwidth: number,
height: number,
width: number,
duration: number,
timescale: number,
init: string, // e.g. "avc1_300k/init-stream0.m4s" or "av01_300k/init-stream0.m4s"
media: string
},
// ... more renditions at increasing bitrates
]
}The init URI prefix reveals the codec and bitrate: avc1_300k/ = H.264 at 300kbps, av01_300k/ = AV1 at 300kbps. This is how the codebase distinguishes between codec types when filtering.
API Endpoints
POST get_upload_url/bunny -> { token, expiration, videoId, libraryId, collectionId }
DELETE /api/chatbot/video/{id} -> removes video from Bunny library
POST /creatives/{id}/migrate-videos -> backend migration (not yet used in AF)Video Delete
When a video is removed from a block, chatbotService.deleteVideo(streamId) is called to clean up the Bunny library entry. This prevents orphaned videos from accumulating storage costs.