Streaming: Qualitäts-Presets (720p30 bis 4K60)
Dropdown im Topbar zum Wählen der Stream-Qualität vor dem Start: - 720p30 (2.5 Mbit/s), 1080p30 (5), 1080p60 (8), 1440p60 (14), 4K60 (25) - Steuert getDisplayMedia-Constraints + WebRTC maxBitrate/maxFramerate - Default: 1080p60 (wie bisher) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9c286ee877
commit
9edc93a0cd
2 changed files with 45 additions and 4 deletions
|
|
@ -43,6 +43,16 @@ function formatElapsed(startedAt: string): string {
|
|||
return `${m}:${String(s).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
// ── Quality Presets ──
|
||||
|
||||
const QUALITY_PRESETS = [
|
||||
{ label: '720p30', width: 1280, height: 720, fps: 30, bitrate: 2_500_000 },
|
||||
{ label: '1080p30', width: 1920, height: 1080, fps: 30, bitrate: 5_000_000 },
|
||||
{ label: '1080p60', width: 1920, height: 1080, fps: 60, bitrate: 8_000_000 },
|
||||
{ label: '1440p60', width: 2560, height: 1440, fps: 60, bitrate: 14_000_000 },
|
||||
{ label: '4K60', width: 3840, height: 2160, fps: 60, bitrate: 25_000_000 },
|
||||
] as const;
|
||||
|
||||
// ── Component ──
|
||||
|
||||
export default function StreamingTab({ data }: { data: any }) {
|
||||
|
|
@ -51,6 +61,7 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
const [userName, setUserName] = useState(() => localStorage.getItem('streaming_name') || '');
|
||||
const [streamTitle, setStreamTitle] = useState('Screen Share');
|
||||
const [streamPassword, setStreamPassword] = useState('');
|
||||
const [qualityIdx, setQualityIdx] = useState(2); // Default: 1080p60
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [joinModal, setJoinModal] = useState<JoinModal | null>(null);
|
||||
const [myStreamId, setMyStreamId] = useState<string | null>(null);
|
||||
|
|
@ -88,8 +99,10 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
// Refs that mirror state (avoid stale closures in WS handler)
|
||||
const isBroadcastingRef = useRef(false);
|
||||
const viewingRef = useRef<ViewState | null>(null);
|
||||
const qualityRef = useRef<typeof QUALITY_PRESETS[number]>(QUALITY_PRESETS[2]);
|
||||
useEffect(() => { isBroadcastingRef.current = isBroadcasting; }, [isBroadcasting]);
|
||||
useEffect(() => { viewingRef.current = viewing; }, [viewing]);
|
||||
useEffect(() => { qualityRef.current = QUALITY_PRESETS[qualityIdx]; }, [qualityIdx]);
|
||||
|
||||
// Notify Electron about streaming status for close-warning
|
||||
useEffect(() => {
|
||||
|
|
@ -257,13 +270,13 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
if (ev.candidate) wsSend({ type: 'ice_candidate', targetId: viewerId, candidate: ev.candidate.toJSON() });
|
||||
};
|
||||
|
||||
// 60 fps + high bitrate
|
||||
// Apply quality preset to WebRTC encoding
|
||||
const videoSender = pc.getSenders().find(s => s.track?.kind === 'video');
|
||||
if (videoSender) {
|
||||
const params = videoSender.getParameters();
|
||||
if (!params.encodings || params.encodings.length === 0) params.encodings = [{}];
|
||||
params.encodings[0].maxFramerate = 60;
|
||||
params.encodings[0].maxBitrate = 8_000_000;
|
||||
params.encodings[0].maxFramerate = qualityRef.current.fps;
|
||||
params.encodings[0].maxBitrate = qualityRef.current.bitrate;
|
||||
videoSender.setParameters(params).catch(() => {});
|
||||
}
|
||||
|
||||
|
|
@ -402,8 +415,9 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
setStarting(true);
|
||||
|
||||
try {
|
||||
const q = qualityRef.current;
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia({
|
||||
video: { frameRate: { ideal: 60 }, width: { ideal: 1920 }, height: { ideal: 1080 } },
|
||||
video: { frameRate: { ideal: q.fps }, width: { ideal: q.width }, height: { ideal: q.height } },
|
||||
audio: true,
|
||||
});
|
||||
localStreamRef.current = stream;
|
||||
|
|
@ -757,6 +771,17 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
onChange={e => setStreamPassword(e.target.value)}
|
||||
disabled={isBroadcasting}
|
||||
/>
|
||||
<select
|
||||
className="stream-select-quality"
|
||||
value={qualityIdx}
|
||||
onChange={e => setQualityIdx(Number(e.target.value))}
|
||||
disabled={isBroadcasting}
|
||||
title="Stream-Qualität"
|
||||
>
|
||||
{QUALITY_PRESETS.map((p, i) => (
|
||||
<option key={p.label} value={i}>{p.label}</option>
|
||||
))}
|
||||
</select>
|
||||
{isBroadcasting ? (
|
||||
<button className="stream-btn stream-btn-stop" onClick={stopBroadcast}>
|
||||
{'\u23F9'} Stream beenden
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue