# Camera & media hosting

Save players' photos and videos with Fivemanage or your own server.

## How photos and videos are saved

The camera takes photos and records videos of up to 60 seconds. Each capture is uploaded straight from the player's phone to your **media host**, so the shutter never waits and nothing large travels over the game connection. A "Photo saved" or "Video saved" notification confirms each one.

The same host stores [Clips](#/docs/wd_phone/clips) videos, custom wallpapers and contact photos.

Nothing is saved until a media host is configured: the camera shows an error and `phoneadmin selftest` warns that no media host is set. Choose **one** host in `config/media_server.lua`:

```lua
Config.MediaUpload = {
    provider = 'fivemanage',   -- or 'self_hosted'
    ...
}
```

| Host | What you need | Files are stored |
| --- | --- | --- |
| `fivemanage` (recommended) | A Fivemanage account and API key. The free plan includes 10 GB | On Fivemanage's CDN |
| `self_hosted` | A domain with HTTPS and a reverse proxy | In `wd_phone/media_uploads/` on your server |

`media_server.lua` is loaded on the server only, so keys in it never reach players. To keep your choice through updates, set it in a file in `config/local/server/` instead, such as `Config.MediaUpload.provider = 'self_hosted'`. Never put media settings in `config/local/` itself, which players can read.

## Option A: Fivemanage

No server setup is needed.

1. Create an account at [fivemanage.com](https://fivemanage.com) and create an API key for media in the dashboard.
2. Add the key to `server.cfg` **above** `ensure wd_phone`:

   ```cfg server.cfg
   set FIVEMANAGE_MEDIA_API_KEY "your-key"
   ```

   You can put it in `fivemanageApiKey` in `config/media_server.lua` instead. If both are set, the config value is used.
3. Leave `provider = 'fivemanage'`.
4. Restart the resource and run `phoneadmin selftest report` in the server console. It should report `[PASS] media host fivemanage`.

The key never reaches players: for each capture, the server exchanges it for a one-time upload link. Your Fivemanage plan's storage and file-size limits apply: the free plan includes 10 GB, and a minute of video is about 19 MB.

**Separate keys per file type.** To use separate Fivemanage projects or quotas, set any of these as well. A type without its own key uses the shared key above.

```cfg server.cfg
set FIVEMANAGE_IMAGE_API_KEY "..."   # photos and video thumbnails
set FIVEMANAGE_VIDEO_API_KEY "..."   # videos
```

**Custom CDN domain.** If you set one up in Fivemanage, add it to `fivemanageHosts` in `config/media_server.lua`. Saved file links must be on one of these hosts.

## Option B: Self-hosted

Files are stored in the resource and served by your FiveM server. You need a domain with HTTPS in front of the server's HTTP port (the game port, `30120` by default).

1. **DNS:** point a hostname at the machine running the server, for example `img.yourserver.com`.
2. **Reverse proxy:** forward only `/wd_phone/upload/*` and `/wd_phone/media/*` to the server, and allow request bodies up to your largest video (40 MB by default).

   With Caddy, which gets the HTTPS certificate automatically:

   ```text Caddyfile
   img.yourserver.com {
       request_body {
           max_size 50MB
       }
       @phone path /wd_phone/upload/* /wd_phone/media/*
       handle @phone {
           reverse_proxy 127.0.0.1:30120
       }
       handle {
           respond 404
       }
   }
   ```

   With nginx and a certificate from certbot:

   ```text nginx
   server {
       listen 443 ssl http2;
       server_name img.yourserver.com;
       ssl_certificate     /etc/letsencrypt/live/img.yourserver.com/fullchain.pem;
       ssl_certificate_key /etc/letsencrypt/live/img.yourserver.com/privkey.pem;
       client_max_body_size 50m;

       location ~ ^/wd_phone/(upload|media)/ {
           proxy_pass http://127.0.0.1:30120;
           proxy_set_header Host $host;
           proxy_request_buffering off;
       }
       location / { return 404; }
   }
   ```

3. **Config** in `config/media_server.lua`:

   ```lua
   provider = 'self_hosted',
   selfHostedPublicUrl = 'https://img.yourserver.com',  -- no trailing slash, no resource name
   ```

4. **Check the proxy:** open `https://img.yourserver.com/wd_phone/media/000000000000000000000000.jpg` in a browser. `Not found` means the proxy reaches the server; a proxy error page or timeout means it does not.
5. Restart the resource and run `phoneadmin selftest report` (`[PASS] media host self_hosted`).

The proxy can listen on another HTTPS port: include it in the URL, such as `https://img.yourserver.com:8443`.

Keep the `media_uploads/` folder when you update the resource, and include it in your backups. A file is deleted when its photo or video is permanently deleted from the Gallery.

## Camera options

Edit these in `config/media.lua`:

| Option | Default | What it does |
| --- | --- | --- |
| `Config.Camera.enabled` | `true` | Turn the camera off entirely |
| `Config.Camera.video.enabled` / `maxSeconds` | `true` / `60` | Video mode and the longest recording |
| `Config.Camera.video.fps` / `bitsPerSecond` | `30` / `2500000` | Video smoothness and quality (about 19 MB per minute) |
| `Config.Camera.allowWhileInVehicle` / `allowWhileDead` / `allowWhileHandcuffed` | `true` / `false` / `false` | Where the camera can be used |
| `Config.Camera.showKeyHints` / `hideHud` | `true` / `true` | GTA key hints on screen; hide the HUD while shooting |
| `Config.Camera.zoomPresets` | 0.5, 1, 2, 3 | The zoom buttons |
| `Config.Media.notifyOnSave` | `true` | The "Photo saved" / "Video saved" notification |
| `Config.Media.quality` / `maxWidth` / `maxHeight` | `0.9` / 1920 / 1080 | Photo quality and size |
| `Config.Media.storeLocationMetadata` | `true` | Save where each photo and video was taken, for **Set waypoint** in Gallery |
| `Config.MediaLimits.maxStorageMegabytesPerPlayer` | `200` | Gallery storage per phone |
| `Config.MediaLimits.maxVideoSizeBytes` | 40 MB | Largest video; raise your proxy or host limit to match |

Players control the camera with the mouse, the zoom buttons or the scroll wheel, and these keys: **Enter** takes a photo or records, **Up** flips between the rear and selfie camera, **Left** and **Right** switch between Video and Photo, **Space** toggles the flash and **Esc** closes it. Buttons at the top switch the flash, portrait or landscape, and a 3×3 grid. The flash really lights the scene for a moment when the photo is taken. **Left Alt** releases the cursor to walk and look around. On foot, walking forward goes where the camera points.

The camera button on the lock screen, and the Camera control in Control Center, open the camera without unlocking the phone. Messages, a contact's photo and the wallpaper picker can open it too, and get the photo back once it is saved. The camera closes for an incoming call and when the player dies.

## Voices in videos

Videos record no game sound, only player voices. Each player chooses this for themselves, during the phone's setup or later in Settings → Sounds → Voice in videos. It is **off until a player turns it on**.

- **Include my voice in videos** lets phones recording nearby pick up the player's voice while they talk in voice chat. Players who leave it off are never recorded.
- **Microphone:** the player picks their microphone by name. It should be the same one FiveM uses for voice chat. With a voice changer, choose its virtual device so the video has the changed voice.
- The choice is saved on the player's PC, not on the phone item, so it applies to whichever phone they use on that PC.

Voices are placed by distance and direction from the camera and muffled through walls, and only included while pma-voice says that player is talking. Nothing is stored on the server. To turn the feature off for the whole server, set `Config.Camera.video.voice.enabled = false`.

## Test your setup

1. Restart the resource after changing upload settings.
2. Open Camera, take a photo and record a short video.
3. Open Gallery and play the video.
4. Send the photo to another player in Messages.
5. Reconnect and open the same photo again.

## Gallery behavior

Gallery has five tabs: **Photos**, grouped by day, **Albums**, **Favorites**, **Shared**, with what other players sent the phone in Messages and Mail, and **Deleted**, which keeps items for 30 days. Players can select many items at once to delete, restore, add to an album or send by NovaDrop. A storage bar shows how much of the phone's storage is used, 200 MB by default.

- The viewer swipes between items, plays videos, sets a photo as wallpaper and saves a received photo or video into the player's own gallery.
- Its info panel shows the date, size and source. For the player's own photos and videos, **Set waypoint** leads back to where it was taken.
- Deleting an album does not delete its photos, and a photo already sent in Messages or Mail keeps working after the sender deletes it.

Photos and videos belong to the phone and work without a SIM. Players can share them with someone standing nearby through **NovaDrop**. Gallery settings are in `config/gallery.lua`.

## Troubleshooting

Set `Config.MediaDebug = true` in `config/media.lua` to log each upload in the server console and F8.

| Problem | Fix |
| --- | --- |
| "Upload unavailable" when taking a photo | No host is configured. Check the Fivemanage key or `selfHostedPublicUrl`, then run `phoneadmin selftest report` |
| The log shows `presign HTTP 401` or `403` | The Fivemanage key is wrong or has no media access |
| Self-hosted photos work but videos fail | The proxy rejects large uploads. Raise `client_max_body_size` (nginx) or `max_size` (Caddy) |
| A photo shows as a broken image | Its link cannot be reached from players' games. For self-hosting, open it in a browser from outside your network; it must load over HTTPS |
| A capture failed | The thumbnail in the camera shows a red badge. Tap it to retry |
| Nobody is heard in videos | Each player must turn on "Include my voice in videos" and pick a microphone |
| Videos have the real voice, not the voice changer | Pick the voice changer's virtual device in Settings → Sounds → Voice in videos |

Never share API keys in support logs.
