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:
@@ -18,7 +18,14 @@ void _showLiveKitQualityMenu(BuildContext context, VoiceChannelService call, Off
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(pt.rBase)),
|
||||
items: <PopupMenuEntry>[
|
||||
const PopupMenuItem(enabled: false, height: 28, child: Text('SENDEN', style: TextStyle(fontSize: 10, fontWeight: FontWeight.w700, color: Colors.grey))),
|
||||
for (final q in [('sd', 'SD', '360p'), ('hd', 'HD', '720p'), ('fhd', 'Full HD', '1080p'), ('4k', '4K', '2160p')])
|
||||
for (final q in [
|
||||
('sd', 'SD', '360p'),
|
||||
('hd', 'HD', '720p'),
|
||||
('hd60', 'HD 60fps', '720p·60'),
|
||||
('fhd', 'Full HD', '1080p'),
|
||||
('fhd60', 'Full HD 60fps', '1080p·60'),
|
||||
('4k', '4K', '2160p'),
|
||||
])
|
||||
PopupMenuItem(onTap: () => call.changeQuality(q.$1), child: _QualityItem(label: q.$2, hint: q.$3, active: call.currentQualityKey == q.$1, pt: pt)),
|
||||
const PopupMenuDivider(),
|
||||
const PopupMenuItem(enabled: false, height: 28, child: Text('EMPFANGEN', style: TextStyle(fontSize: 10, fontWeight: FontWeight.w700, color: Colors.grey))),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -34,7 +34,8 @@ abstract class VoiceChannelService extends ChangeNotifier {
|
||||
String? get currentRoomId;
|
||||
String get displayName;
|
||||
|
||||
/// Sende-Qualität ('sd' | 'hd' | 'fhd' | '4k').
|
||||
/// Sende-Qualität ('sd' | 'hd' | 'hd60' | 'fhd' | 'fhd60' | '4k').
|
||||
/// Die *60-Presets sind für flüssiges Bildschirm-Streaming (M4).
|
||||
String get currentQualityKey;
|
||||
|
||||
/// Empfangs-Qualität ('auto' | 'low' | 'medium' | 'high').
|
||||
|
||||
Reference in New Issue
Block a user