Clean up stream audio on ending stream
This commit is contained in:
@@ -623,7 +623,7 @@ class DebugNetworkSnapshotBuilder {
|
|||||||
node.isMuted = user.voiceState?.isMuted === true;
|
node.isMuted = user.voiceState?.isMuted === true;
|
||||||
node.isDeafened = user.voiceState?.isDeafened === true;
|
node.isDeafened = user.voiceState?.isDeafened === true;
|
||||||
node.isSpeaking = user.voiceState?.isSpeaking === true || node.isSpeaking;
|
node.isSpeaking = user.voiceState?.isSpeaking === true || node.isSpeaking;
|
||||||
node.isStreaming = user.screenShareState?.isSharing === true || node.isStreaming;
|
node.isStreaming = user.screenShareState?.isSharing === true;
|
||||||
|
|
||||||
if (user.voiceState?.isConnected !== true)
|
if (user.voiceState?.isConnected !== true)
|
||||||
node.streams.audio = 0;
|
node.streams.audio = 0;
|
||||||
|
|||||||
@@ -924,6 +924,11 @@ export class WebRTCService implements OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.type === P2P_TYPE_SCREEN_STATE && event.isScreenSharing === false) {
|
||||||
|
this.peerManager.clearRemoteScreenShareStream(event.fromPeerId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.type === P2P_TYPE_SCREEN_SHARE_REQUEST) {
|
if (event.type === P2P_TYPE_SCREEN_SHARE_REQUEST) {
|
||||||
this.screenShareManager.requestScreenShareForPeer(event.fromPeerId);
|
this.screenShareManager.requestScreenShareForPeer(event.fromPeerId);
|
||||||
return;
|
return;
|
||||||
@@ -951,15 +956,12 @@ export class WebRTCService implements OnDestroy {
|
|||||||
const connectedPeerIds = new Set(this.peerManager.getConnectedPeerIds());
|
const connectedPeerIds = new Set(this.peerManager.getConnectedPeerIds());
|
||||||
|
|
||||||
for (const peerId of peerIds) {
|
for (const peerId of peerIds) {
|
||||||
if (!this.activeRemoteScreenSharePeers.has(peerId)) {
|
if (this.activeRemoteScreenSharePeers.has(peerId) && connectedPeerIds.has(peerId)) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connectedPeerIds.has(peerId)) {
|
|
||||||
this.peerManager.sendToPeer(peerId, { type: P2P_TYPE_SCREEN_SHARE_STOP });
|
this.peerManager.sendToPeer(peerId, { type: P2P_TYPE_SCREEN_SHARE_STOP });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeRemoteScreenSharePeers.delete(peerId);
|
this.activeRemoteScreenSharePeers.delete(peerId);
|
||||||
|
this.peerManager.clearRemoteScreenShareStream(peerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import {
|
|||||||
schedulePeerReconnect,
|
schedulePeerReconnect,
|
||||||
trackDisconnectedPeer
|
trackDisconnectedPeer
|
||||||
} from './recovery/peer-recovery';
|
} from './recovery/peer-recovery';
|
||||||
import { handleRemoteTrack } from './streams/remote-streams';
|
import { clearRemoteScreenShareStream as clearManagedRemoteScreenShareStream, handleRemoteTrack } from './streams/remote-streams';
|
||||||
import {
|
import {
|
||||||
ConnectionLifecycleHandlers,
|
ConnectionLifecycleHandlers,
|
||||||
createPeerConnectionManagerState,
|
createPeerConnectionManagerState,
|
||||||
@@ -223,6 +223,11 @@ export class PeerConnectionManager {
|
|||||||
return getConnectedPeerIds(this.state);
|
return getConnectedPeerIds(this.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Remove any cached remote screen-share tracks for a peer. */
|
||||||
|
clearRemoteScreenShareStream(peerId: string): void {
|
||||||
|
clearManagedRemoteScreenShareStream(this.context, peerId);
|
||||||
|
}
|
||||||
|
|
||||||
/** Reset the connected peers list to empty and notify subscribers. */
|
/** Reset the connected peers list to empty and notify subscribers. */
|
||||||
resetConnectedPeers(): void {
|
resetConnectedPeers(): void {
|
||||||
resetConnectedPeers(this.state);
|
resetConnectedPeers(this.state);
|
||||||
|
|||||||
@@ -53,22 +53,33 @@ export function handleRemoteTrack(
|
|||||||
state.remotePeerScreenShareStreams.set(remotePeerId, screenShareStream);
|
state.remotePeerScreenShareStreams.set(remotePeerId, screenShareStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.remoteStream$.next({
|
publishRemoteStreamUpdate(context, remotePeerId, compositeStream);
|
||||||
peerId: remotePeerId,
|
}
|
||||||
stream: compositeStream
|
|
||||||
});
|
|
||||||
|
|
||||||
recordDebugNetworkStreams(remotePeerId, {
|
export function clearRemoteScreenShareStream(
|
||||||
audio: compositeStream.getAudioTracks().length,
|
context: PeerConnectionManagerContext,
|
||||||
video: compositeStream.getVideoTracks().length
|
remotePeerId: string
|
||||||
});
|
): void {
|
||||||
|
const { state } = context;
|
||||||
|
const screenShareStream = state.remotePeerScreenShareStreams.get(remotePeerId);
|
||||||
|
|
||||||
logger.info('Remote stream updated', {
|
if (!screenShareStream) {
|
||||||
audioTrackCount: compositeStream.getAudioTracks().length,
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const screenShareTrackIds = new Set(
|
||||||
|
screenShareStream.getTracks().map((track) => track.id)
|
||||||
|
);
|
||||||
|
const compositeStream = removeTracksFromStreamMap(
|
||||||
|
state.remotePeerStreams,
|
||||||
remotePeerId,
|
remotePeerId,
|
||||||
trackCount: compositeStream.getTracks().length,
|
screenShareTrackIds
|
||||||
videoTrackCount: compositeStream.getVideoTracks().length
|
);
|
||||||
});
|
|
||||||
|
removeTracksFromStreamMap(state.remotePeerVoiceStreams, remotePeerId, screenShareTrackIds);
|
||||||
|
state.remotePeerScreenShareStreams.delete(remotePeerId);
|
||||||
|
|
||||||
|
publishRemoteStreamUpdate(context, remotePeerId, compositeStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildCompositeRemoteStream(
|
function buildCompositeRemoteStream(
|
||||||
@@ -140,48 +151,27 @@ function removeRemoteTrack(
|
|||||||
remotePeerId: string,
|
remotePeerId: string,
|
||||||
trackId: string
|
trackId: string
|
||||||
): void {
|
): void {
|
||||||
const { logger, state } = context;
|
const { state } = context;
|
||||||
const compositeStream = removeTrackFromStreamMap(state.remotePeerStreams, remotePeerId, trackId);
|
const compositeStream = removeTrackFromStreamMap(state.remotePeerStreams, remotePeerId, trackId);
|
||||||
|
|
||||||
removeTrackFromStreamMap(state.remotePeerVoiceStreams, remotePeerId, trackId);
|
removeTrackFromStreamMap(state.remotePeerVoiceStreams, remotePeerId, trackId);
|
||||||
removeTrackFromStreamMap(state.remotePeerScreenShareStreams, remotePeerId, trackId);
|
removeTrackFromStreamMap(state.remotePeerScreenShareStreams, remotePeerId, trackId);
|
||||||
|
|
||||||
if (!compositeStream) {
|
publishRemoteStreamUpdate(context, remotePeerId, compositeStream);
|
||||||
recordDebugNetworkStreams(remotePeerId, { audio: 0,
|
|
||||||
video: 0 });
|
|
||||||
|
|
||||||
logger.info('Remote stream updated', {
|
|
||||||
audioTrackCount: 0,
|
|
||||||
remotePeerId,
|
|
||||||
trackCount: 0,
|
|
||||||
videoTrackCount: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.remoteStream$.next({
|
|
||||||
peerId: remotePeerId,
|
|
||||||
stream: compositeStream
|
|
||||||
});
|
|
||||||
|
|
||||||
recordDebugNetworkStreams(remotePeerId, {
|
|
||||||
audio: compositeStream.getAudioTracks().length,
|
|
||||||
video: compositeStream.getVideoTracks().length
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info('Remote stream updated', {
|
|
||||||
audioTrackCount: compositeStream.getAudioTracks().length,
|
|
||||||
remotePeerId,
|
|
||||||
trackCount: compositeStream.getTracks().length,
|
|
||||||
videoTrackCount: compositeStream.getVideoTracks().length
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeTrackFromStreamMap(
|
function removeTrackFromStreamMap(
|
||||||
streamMap: Map<string, MediaStream>,
|
streamMap: Map<string, MediaStream>,
|
||||||
remotePeerId: string,
|
remotePeerId: string,
|
||||||
trackId: string
|
trackId: string
|
||||||
|
): MediaStream | null {
|
||||||
|
return removeTracksFromStreamMap(streamMap, remotePeerId, new Set([trackId]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeTracksFromStreamMap(
|
||||||
|
streamMap: Map<string, MediaStream>,
|
||||||
|
remotePeerId: string,
|
||||||
|
trackIds: ReadonlySet<string>
|
||||||
): MediaStream | null {
|
): MediaStream | null {
|
||||||
const currentStream = streamMap.get(remotePeerId);
|
const currentStream = streamMap.get(remotePeerId);
|
||||||
|
|
||||||
@@ -191,7 +181,7 @@ function removeTrackFromStreamMap(
|
|||||||
|
|
||||||
const remainingTracks = currentStream
|
const remainingTracks = currentStream
|
||||||
.getTracks()
|
.getTracks()
|
||||||
.filter((existingTrack) => existingTrack.id !== trackId && existingTrack.readyState === 'live');
|
.filter((existingTrack) => !trackIds.has(existingTrack.id) && existingTrack.readyState === 'live');
|
||||||
|
|
||||||
if (remainingTracks.length === currentStream.getTracks().length) {
|
if (remainingTracks.length === currentStream.getTracks().length) {
|
||||||
return currentStream;
|
return currentStream;
|
||||||
@@ -208,6 +198,32 @@ function removeTrackFromStreamMap(
|
|||||||
return nextStream;
|
return nextStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function publishRemoteStreamUpdate(
|
||||||
|
context: PeerConnectionManagerContext,
|
||||||
|
remotePeerId: string,
|
||||||
|
compositeStream: MediaStream | null
|
||||||
|
): void {
|
||||||
|
const { logger, state } = context;
|
||||||
|
const stream = compositeStream ?? new MediaStream();
|
||||||
|
|
||||||
|
state.remoteStream$.next({
|
||||||
|
peerId: remotePeerId,
|
||||||
|
stream
|
||||||
|
});
|
||||||
|
|
||||||
|
recordDebugNetworkStreams(remotePeerId, {
|
||||||
|
audio: stream.getAudioTracks().length,
|
||||||
|
video: stream.getVideoTracks().length
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('Remote stream updated', {
|
||||||
|
audioTrackCount: stream.getAudioTracks().length,
|
||||||
|
remotePeerId,
|
||||||
|
trackCount: stream.getTracks().length,
|
||||||
|
videoTrackCount: stream.getVideoTracks().length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function isVoiceAudioTrack(
|
function isVoiceAudioTrack(
|
||||||
context: PeerConnectionManagerContext,
|
context: PeerConnectionManagerContext,
|
||||||
event: RTCTrackEvent,
|
event: RTCTrackEvent,
|
||||||
|
|||||||
@@ -141,9 +141,17 @@ export class ScreenShareViewerComponent implements OnDestroy {
|
|||||||
// Subscribe to remote streams with video (screen shares)
|
// Subscribe to remote streams with video (screen shares)
|
||||||
// NOTE: We no longer auto-display remote streams. Users must click "Live" to view.
|
// NOTE: We no longer auto-display remote streams. Users must click "Live" to view.
|
||||||
// This subscription is kept for potential future use (e.g., tracking available streams)
|
// This subscription is kept for potential future use (e.g., tracking available streams)
|
||||||
this.remoteStreamSub = this.webrtcService.onRemoteStream.subscribe(({ peerId, stream }) => {
|
this.remoteStreamSub = this.webrtcService.onRemoteStream.subscribe(({ peerId }) => {
|
||||||
// Do nothing on remote stream - user must explicitly click "Live" to view
|
if (peerId !== this.watchingUserId() || this.isLocalShare()) {
|
||||||
// The stream is still stored in webrtcService.remoteStreams and can be accessed via getRemoteStream()
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = this.webrtcService.getRemoteScreenShareStream(peerId);
|
||||||
|
const hasActiveVideo = stream?.getVideoTracks().some((track) => track.readyState === 'live') ?? false;
|
||||||
|
|
||||||
|
if (!hasActiveVideo) {
|
||||||
|
this.stopWatching();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for focus events dispatched by other components
|
// Listen for focus events dispatched by other components
|
||||||
|
|||||||
Reference in New Issue
Block a user