Restores the live streaming controllers / views / model from the
pre-audit WIP that got stashed on 2026-10-21, then applies the Phase 6
polish items:
## /live/new form
Add a Private visibility option that mirrors the upload modal's
wording — "Only you can see this stream. Perfect for testing without
an audience." Validator in LiveStreamController@store now permits
public|unlisted|private (was public|unlisted).
## Watch page ended-state
When the stream status is "ended", the overlay is a solid "Stream
ended" card showing the stream's duration (started_at → ended_at)
and, when a recording was published, a Watch Replay CTA linking to
the saved video. Previously an ended stream showed the generic
"Waiting for the broadcast to start" placeholder forever.
## Channel name link
The uploader's name in the meta row is now a real <a href> to the
channel page.
## Viewer action row
Subscribe + Share buttons below the player, visible to anyone except
the owner. Subscribe for guests uses the Phase 7 ?intended= pattern
so after sign-in they land back on the stream.
## Live chat (DB-backed polling)
New live_chat_messages table (migration 2026_10_21_000003). Viewers
GET /live/{stream}/chat?since=N every 3 seconds; signed-in users POST
to the same path (rate-limited at 60/min). Guests see the panel in
read-only mode with a "Sign in to chat" placeholder.
Private streams reject chat for anyone but the owner (404). Non-live
status rejects chat sends (422). Author name is snapshotted at post
time so a renamed account's old messages keep the name they used.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
56 lines
2.0 KiB
PHP
56 lines
2.0 KiB
PHP
<?php
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Live streaming
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| The media plane runs as MediaMTX on loopback; nginx proxies /live-rtc and
|
|
| /live-hls to it. Nothing here is a secret except the TURN credential, which
|
|
| comes from the environment.
|
|
|
|
|
*/
|
|
|
|
return [
|
|
|
|
// MediaMTX's control API (localhost only). Used to read live publisher and
|
|
// viewer state.
|
|
'api_url' => env('LIVE_API_URL', 'http://127.0.0.1:9997'),
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| ICE
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| STUN lets a browser discover its public address. TURN relays the media
|
|
| when a direct path cannot be established — which, until a UDP port is
|
|
| forwarded to this server, is EVERY broadcaster outside the local network.
|
|
|
|
|
| With no TURN configured, publishing works from the LAN only.
|
|
|
|
|
*/
|
|
'stun_url' => env('LIVE_STUN_URL', 'stun:stun.l.google.com:19302'),
|
|
|
|
'turn_url' => env('LIVE_TURN_URL'),
|
|
'turn_username' => env('LIVE_TURN_USERNAME'),
|
|
'turn_credential' => env('LIVE_TURN_CREDENTIAL'),
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Capture defaults
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| The target the phone is ASKED for. A camera that cannot do 1080p returns
|
|
| the closest it can, and WebRTC's congestion control lowers the bitrate
|
|
| when the uplink cannot sustain it — so this is a ceiling, not a promise.
|
|
|
|
|
*/
|
|
'video_width' => (int) env('LIVE_VIDEO_WIDTH', 1920),
|
|
'video_height' => (int) env('LIVE_VIDEO_HEIGHT', 1080),
|
|
'video_fps' => (int) env('LIVE_VIDEO_FPS', 30),
|
|
|
|
// ~4.5 Mbit/s is the practical ceiling for 1080p30 over a mobile uplink.
|
|
'max_bitrate' => (int) env('LIVE_MAX_BITRATE', 4_500_000),
|
|
|
|
];
|