Skip to content
Rapcsány Krisztián
hu
← All projects Service and API

Media and file delivery system (in-house CDN)

A standalone service for images, videos and files. It takes over uploading, processing and serving from the platform, so the main system does not have to deal with large content.

PHP Laravel 12 MySQL Message queues FFmpeg HLS streaming REST API OpenAPI
The challenge
My role

I designed and built it, starting in March 2026.

An events platform carries a huge amount of images and video: club logos, covers, galleries, stories, promo videos. If the main system stores and serves them, the site slows down, and large uploads often break.

I wanted a separate, secure service: one that resizes and serves content quickly, takes large videos in its stride, and works through a simple API.

The solution
01

Images, automatically

On upload, WebP conversion and thumbnails are produced, with per-profile sizes and quality (logo, cover, gallery, story and more).

02

Video streaming

In the background it builds an adaptive stream (480p, 720p, 1080p), an easily embeddable MP4, a thumbnail and a poster image, with volume normalisation.

03

Upload pages

One-time, time-limited upload links with drag-and-drop and a progress bar, for images, video and any file.

04

Large files without breaking

Uploads go in chunks with per-chunk retries, so even a gigabyte-sized video arrives.

05

Private and public content

Private files are only reachable through signed, time-limited addresses, while public ones can be served directly.

06

API and feedback

Key-based access with per-key permissions, a documented API, status queries and a callback (webhook) when processing ends.

Screenshots
A documented API

Endpoints for images, files and upload sessions, with key-based access and an OpenAPI description.

With brand and customer names and personal data removed (except the app store screenshots).

What makes it special

Adaptive video

480p, 720p and 1080p streams that only scale down, and work for both portrait and landscape video. A plain MP4 is made too, for anyone who just wants to embed it.

One-time upload links

Uploads go through a link valid for 30 minutes, with drag-and-drop and a progress bar.

Private files with signed links

A private file is only reachable through a signed, time-limited address, 15 minutes by default.

Challenges I ran into
01

The layer in between killed large uploads

The upload went in a single request, but the network layer in between cut it off at around 100 MB and 100 seconds. Now I upload in 20 MB chunks, and the server assembles them in the last step and checks the size. Resending a chunk is safe, so the browser retries four times, with longer and longer waits.

02

An upload must not be able to do harm

I blocked executable and server-side extensions (exe, php, js and the rest). I check type and size after assembly, and there is a limit on video length.

03

Do not lose the failure

Video processing runs in the background. On success and on failure it sends a callback to the operating system, retrying five times, and if processing fails, an alert goes out.

Under the hood

Chunked upload

20 MB chunks that the server assembles and then verifies in the final step. Resending a chunk is safe. It had to be done this way because the network layer in between limits the size and the duration of a request, and that cut off large video uploads.

Background processing chain

Video processing runs on a message queue, in several steps, with an alert on failure. The callback arrives on success and on failure, with retries at increasing intervals.

Security

Blocking executable and server-side file types, type and size checks, API-key permissions, signed URLs and a maximum video length.

Storage management

I clean up expired upload sessions every hour, and if needed I delete the originals of processed videos after a set time, so the storage does not run out.

Live code

Upload that survives interruptions

Parts arrive by index, and resending the same index overwrites the part, so a retry is harmless. After assembly it verifies the size and cleans up on failure.

UploadChunkController.php
// Re-sending the same index overwrites the part, so a retry is idempotent.
$request->file('chunk')->move($dir, sprintf('%06d.part', $index));

// ... later, in complete(): assemble the parts in order
$out = fopen($assembled, 'wb');

for ($i = 0; $i < $info['total_chunks']; $i++) {
    $part = $dir.'/'.sprintf('%06d.part', $i);

    if (! is_file($part)) {
        fclose($out);
        return response()->json(['success' => false, 'message' => "Missing chunk: {$i}"], 422);
    }

    $in = fopen($part, 'rb');
    stream_copy_to_stream($in, $out);
    fclose($in);
}
fclose($out);

if (filesize($assembled) !== $info['size']) {
    File::deleteDirectory($dir);
    return response()->json(['success' => false, 'message' => 'Size mismatch.'], 422);
}
Result

The platform and the admin manage content through one API, the heavy processing runs in the background, and large files arrive without breaking.

Are images and videos slowing your system down?

If uploads break, or the site is slow because of large content, it is worth a look.

More projects

Write to me →