feat: 60-FPS-Streaming-Presets (hd60/fhd60) fuer Bildschirm-Streams (M4)

Nach docs/STREAMING_60FPS.md: neue Presets 720p/1080p mit 60 FPS und
hoher Bitrate (5/8 Mbit/s), Preset-Aufloesung+FPS werden jetzt bis in
die Capture-Constraints durchgereicht (vorher blieb der LiveKit-Default
1080p/15 aktiv), 60-FPS-Presets publishen ohne Simulcast mit
DegradationPreference.maintainFramerate (unter Last Aufloesung statt
Frames opfern). contentHint 'motion' ist in flutter_webrtc 1.4.1 aus
Dart nicht setzbar (geprueft) - im Code dokumentiert.
4 neue Vertragstests fuer die Preset-Tabellen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bernd Steckmeister
2026-07-06 22:19:25 +02:00
parent 9f797279eb
commit ffd70caffd
4 changed files with 101 additions and 10 deletions
@@ -21,17 +21,30 @@ class LiveKitCallManager extends VoiceChannelService {
static final Map<String, VideoParameters> qualityPresets = {
'sd': VideoParametersPresets.h360_169,
'hd': VideoParametersPresets.h720_169,
'hd60': VideoParametersPresets.h720_169,
'fhd': VideoParametersPresets.h1080_169,
'fhd60': VideoParametersPresets.h1080_169,
'4k': VideoParametersPresets.h2160_169,
};
static const Map<String, VideoEncoding> _screenShareEncodings = {
'sd': VideoEncoding(maxBitrate: 500_000, maxFramerate: 10),
'hd': VideoEncoding(maxBitrate: 1_500_000, maxFramerate: 15),
'fhd': VideoEncoding(maxBitrate: 2_500_000, maxFramerate: 20),
'4k': VideoEncoding(maxBitrate: 4_000_000, maxFramerate: 30),
/// FPS + Bitrate pro Screenshare-Preset. Die 60-FPS-Presets (M4,
/// `docs/STREAMING_60FPS.md`) brauchen deutlich mehr Bitrate, sonst
/// verwirft der Encoder Frames und es ruckelt trotz hoher Framerate.
@visibleForTesting
static const Map<String, VideoEncoding> screenShareEncodings = {
'sd': VideoEncoding(maxBitrate: 500_000, maxFramerate: 10),
'hd': VideoEncoding(maxBitrate: 1_500_000, maxFramerate: 15),
'hd60': VideoEncoding(maxBitrate: 5_000_000, maxFramerate: 60),
'fhd': VideoEncoding(maxBitrate: 2_500_000, maxFramerate: 20),
'fhd60': VideoEncoding(maxBitrate: 8_000_000, maxFramerate: 60),
'4k': VideoEncoding(maxBitrate: 4_000_000, maxFramerate: 30),
};
/// 60-FPS-Presets bekommen beim Publish eine andere Strategie
/// (kein Simulcast, Framerate vor Auflösung halten).
@visibleForTesting
static bool isHighFpsPreset(String key) => key.endsWith('60');
Room? _room;
EventsListener<RoomEvent>? _roomEventListener;
String _displayName = '';
@@ -445,7 +458,7 @@ class LiveKitCallManager extends VoiceChannelService {
}
VideoEncoding _screenShareEncoding() {
return _screenShareEncodings[_localQualityKey] ??
return screenShareEncodings[_localQualityKey] ??
const VideoEncoding(maxBitrate: 1_500_000, maxFramerate: 15);
}
@@ -454,10 +467,18 @@ class LiveKitCallManager extends VoiceChannelService {
if (isScreenSharing) return;
try {
final encoding = _screenShareEncoding();
final highFps = isHighFpsPreset(_localQualityKey);
final track = await LocalVideoTrack.createScreenShareTrack(
ScreenShareCaptureOptions(
sourceId: sourceId,
maxFrameRate: encoding.maxFramerate.toDouble(),
// Ohne explizite params bleibt die Capture-Auflösung beim
// LiveKit-Default (1080p/15) hängen das Preset (Auflösung UND
// FPS) muss bis in die Capture-Constraints durchgereicht werden.
params: VideoParameters(
dimensions: _currentQuality.dimensions,
encoding: encoding,
),
),
);
@@ -466,8 +487,15 @@ class LiveKitCallManager extends VoiceChannelService {
await _room!.localParticipant!.publishVideoTrack(
track,
publishOptions: VideoPublishOptions(
simulcast: true,
degradationPreference: DegradationPreference.balanced,
// 60-FPS-Presets: EIN Layer mit voller Bitrate statt drei
// Simulcast-Encodes (Software-Encoder!), und unter Last lieber
// Auflösung senken als Frames verlieren. contentHint 'motion'
// wäre ein weiterer Hebel, ist aber in flutter_webrtc 1.4.1 aus
// Dart nicht setzbar (Pub-Cache geprüft, 2026-07-06).
simulcast: !highFps,
degradationPreference: highFps
? DegradationPreference.maintainFramerate
: DegradationPreference.balanced,
screenShareEncoding: encoding,
),
);