WebRTC应用屏幕共享流保存及切换异常技术求助
Hi Mitzi, let's tackle these two issues one by one—first the screen share switch-back problem, then saving the screen share stream.
From what you described, the most common culprits here are improper media stream/track management or failing to re-trigger the Chrome screen share authorization flow when switching back. Here's how to resolve it:
Common Root Causes & Fixes
- Forgot to stop/destroy old tracks: When switching to the camera, if you don't properly stop the screen share track and remove it from the PeerConnection, it can hog resources and prevent a new screen share stream from being initialized.
- Reusing a stale screen share stream: Screen share streams can't be restarted once stopped—you need to re-request access via the Chrome extension every time you want to switch back.
- PeerConnection track replacement logic is broken: You need to explicitly remove the old video track (camera) before adding the new screen share track.
Example Implementation (Frontend JS)
First, update your camera switch function to clean up the screen share resources:
function switchToCamera() { // Clean up existing screen share stream/tracks if (window.screenStream) { window.screenStream.getTracks().forEach(track => track.stop()); // Remove the screen share track from PeerConnection peerConnection.getSenders().forEach(sender => { if (sender.track?.kind === 'video' && sender.track.label.includes('screen')) { peerConnection.removeTrack(sender); } }); window.screenStream = null; } // Initialize camera stream and attach to PeerConnection navigator.mediaDevices.getUserMedia({ video: true }) .then(cameraStream => { window.cameraStream = cameraStream; const cameraTrack = cameraStream.getVideoTracks()[0]; // Add camera track to PeerConnection (replace existing if needed) const existingVideoSender = peerConnection.getSenders().find(s => s.track?.kind === 'video'); if (existingVideoSender) { existingVideoSender.replaceTrack(cameraTrack); } else { peerConnection.addTrack(cameraTrack, cameraStream); } // Update local video display localVideo.srcObject = cameraStream; }); }
Then, update the switch-back to screen share function to re-request authorization and clean up the camera resources:
function switchBackToScreenShare() { // Clean up existing camera stream/tracks if (window.cameraStream) { window.cameraStream.getTracks().forEach(track => track.stop()); const existingVideoSender = peerConnection.getSenders().find(s => s.track?.kind === 'video'); if (existingVideoSender) { peerConnection.removeTrack(existingVideoSender); } window.cameraStream = null; } // Re-trigger Chrome screen share authorization chrome.desktopCapture.chooseDesktopMedia(['screen', 'window'], streamId => { if (!streamId) return; // User canceled the selection const screenConstraints = { video: { mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: streamId, maxWidth: screen.width, maxHeight: screen.height } } }; navigator.mediaDevices.getUserMedia(screenConstraints) .then(screenStream => { window.screenStream = screenStream; const screenTrack = screenStream.getVideoTracks()[0]; // Add screen share track to PeerConnection peerConnection.addTrack(screenTrack, screenStream); // Update local video display localVideo.srcObject = screenStream; }); }); }
Additional Check
Make sure your Chrome extension's manifest includes the necessary permissions (desktopCapture, activeTab, etc.) and that you're handling any extension-specific callback logic correctly.
You can use the browser's MediaRecorder API to capture the screen share stream, then save it locally or upload it to your ASP.NET backend.
Frontend Recording Logic
Add this code to capture and save the screen share stream:
function startScreenShareRecording(screenStream) { // Choose a supported MIME type (WebM is widely supported) const recordingOptions = { mimeType: 'video/webm; codecs=vp9' }; if (!MediaRecorder.isTypeSupported(recordingOptions.mimeType)) { recordingOptions.mimeType = 'video/webm; codecs=vp8'; // Fallback to VP8 } const mediaRecorder = new MediaRecorder(screenStream, recordingOptions); const recordedChunks = []; // Collect recorded data chunks mediaRecorder.ondataavailable = (event) => { if (event.data.size > 0) recordedChunks.push(event.data); }; // On stop, create a blob and handle save/upload mediaRecorder.onstop = () => { const recordingBlob = new Blob(recordedChunks, { type: recordingOptions.mimeType }); // Option 1: Download recording to user's local machine const downloadUrl = URL.createObjectURL(recordingBlob); const downloadLink = document.createElement('a'); downloadLink.href = downloadUrl; downloadLink.download = `screen-share-${new Date().toISOString().slice(0,19)}.webm`; downloadLink.click(); URL.revokeObjectURL(downloadUrl); // Option 2: Upload to ASP.NET backend fetch('/api/ScreenShare/SaveRecording', { method: 'POST', body: recordingBlob, headers: { 'Content-Type': recordingOptions.mimeType } }) .then(response => response.json()) .then(result => console.log('Recording saved to server:', result.fileName)) .catch(err => console.error('Upload failed:', err)); }; // Start recording (save recorder reference to stop later) window.screenRecorder = mediaRecorder; mediaRecorder.start(1000); // Capture chunks every 1 second } // Call this to stop recording function stopScreenShareRecording() { if (window.screenRecorder) { window.screenRecorder.stop(); window.screenRecorder = null; } }
ASP.NET Backend Endpoint
Add this controller action to receive and save the recording:
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using System.IO; using System.Threading.Tasks; [ApiController] [Route("api/ScreenShare")] public class ScreenShareController : ControllerBase { private readonly IWebHostEnvironment _hostingEnv; public ScreenShareController(IWebHostEnvironment hostingEnv) { _hostingEnv = hostingEnv; } [HttpPost("SaveRecording")] public async Task<IActionResult> SaveRecording() { var recordingsDir = Path.Combine(_hostingEnv.WebRootPath, "ScreenRecordings"); if (!Directory.Exists(recordingsDir)) { Directory.CreateDirectory(recordingsDir); } var fileName = $"Recording_{DateTime.Now:yyyyMMddHHmmssfff}.webm"; var filePath = Path.Combine(recordingsDir, fileName); using (var stream = new FileStream(filePath, FileMode.Create)) { await Request.Body.CopyToAsync(stream); } return Ok(new { fileName = fileName }); } }
Make sure to configure your ASP.NET app to allow large file uploads if needed (update maxAllowedContentLength in web.config or Program.cs for .NET Core/.NET 5+).
内容的提问来源于stack exchange,提问作者Mitzi Lev

