mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Fix track distinguishing
This commit is contained in:
@@ -132,9 +132,9 @@ addEventListener("rtctransform", (event) => {
|
|||||||
let payloadData: Uint8Array;
|
let payloadData: Uint8Array;
|
||||||
|
|
||||||
if (data.length > 20) {
|
if (data.length > 20) {
|
||||||
// For video frames, preserve only the first 2 bytes (minimal codec sync)
|
// For video frames, preserve first 8 bytes for better codec compatibility
|
||||||
// This is the absolute minimum needed for frame identification
|
// This includes frame type, keyframe info, and basic header structure
|
||||||
headerSize = 2;
|
headerSize = Math.min(8, Math.floor(data.length / 10));
|
||||||
payloadData = data.slice(headerSize);
|
payloadData = data.slice(headerSize);
|
||||||
} else {
|
} else {
|
||||||
// For small frames (likely audio), encrypt everything
|
// For small frames (likely audio), encrypt everything
|
||||||
@@ -173,6 +173,12 @@ addEventListener("rtctransform", (event) => {
|
|||||||
console.log(`E2EE ${mode} frame #${frameCount}: ${data.length} -> ${result.length} bytes (header: ${headerSize})`);
|
console.log(`E2EE ${mode} frame #${frameCount}: ${data.length} -> ${result.length} bytes (header: ${headerSize})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For video frames, check if we need to force keyframes more frequently
|
||||||
|
// This helps prevent "stuck at first frame" issues with encrypted video
|
||||||
|
if (data.length > 10000 && frameCount > 0 && frameCount % 30 === 0) {
|
||||||
|
console.log(`Large video frame #${frameCount} - suggesting keyframe for stability`);
|
||||||
|
}
|
||||||
|
|
||||||
// CRITICAL: Video frames need ArrayBuffer, not Uint8Array
|
// CRITICAL: Video frames need ArrayBuffer, not Uint8Array
|
||||||
encodedFrame.data = result.buffer;
|
encodedFrame.data = result.buffer;
|
||||||
controller.enqueue(encodedFrame);
|
controller.enqueue(encodedFrame);
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ export interface WebRTCCall {
|
|||||||
lastKeyRotation?: number;
|
lastKeyRotation?: number;
|
||||||
transformedSenders: Set<RTCRtpSender>;
|
transformedSenders: Set<RTCRtpSender>;
|
||||||
transformedReceivers: Set<RTCRtpReceiver>;
|
transformedReceivers: Set<RTCRtpReceiver>;
|
||||||
|
// Track specific senders for proper routing when both video and screen share are active
|
||||||
|
videoSender?: RTCRtpSender | null;
|
||||||
|
screenShareSender?: RTCRtpSender | null;
|
||||||
|
// Track the number of video tracks received for each type
|
||||||
|
receivedVideoTrackCount: number;
|
||||||
|
receivedScreenShareTrackCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global state
|
// Global state
|
||||||
@@ -40,6 +46,7 @@ export let onLocalVideoStream: ((userId: number, stream: MediaStream | null) =>
|
|||||||
export let onRemoteVideoStream: ((userId: number, stream: MediaStream | null) => void) | null = null;
|
export let onRemoteVideoStream: ((userId: number, stream: MediaStream | null) => void) | null = null;
|
||||||
export let onLocalScreenShare: ((userId: number, stream: MediaStream | null) => void) | null = null;
|
export let onLocalScreenShare: ((userId: number, stream: MediaStream | null) => void) | null = null;
|
||||||
export let onRemoteScreenShare: ((userId: number, stream: MediaStream | null) => void) | null = null;
|
export let onRemoteScreenShare: ((userId: number, stream: MediaStream | null) => void) | null = null;
|
||||||
|
export let onScreenShareStateChange: ((userId: number, isSharing: boolean) => void) | null = null;
|
||||||
const calls: Map<number, WebRTCCall> = new Map();
|
const calls: Map<number, WebRTCCall> = new Map();
|
||||||
|
|
||||||
export function setAuthToken(token: string) {
|
export function setAuthToken(token: string) {
|
||||||
@@ -141,7 +148,9 @@ async function createPeerConnection(userId: number): Promise<RTCPeerConnection>
|
|||||||
sessionCryptoKey: null,
|
sessionCryptoKey: null,
|
||||||
sessionId: crypto.randomUUID(),
|
sessionId: crypto.randomUUID(),
|
||||||
transformedSenders: new Set(),
|
transformedSenders: new Set(),
|
||||||
transformedReceivers: new Set()
|
transformedReceivers: new Set(),
|
||||||
|
receivedVideoTrackCount: 0,
|
||||||
|
receivedScreenShareTrackCount: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
calls.set(userId, call);
|
calls.set(userId, call);
|
||||||
@@ -265,18 +274,61 @@ async function createPeerConnection(userId: number): Promise<RTCPeerConnection>
|
|||||||
console.log(`Track received: kind=${track.kind}, isRemoteScreenSharing=${call.isRemoteScreenSharing}, isRemoteVideoEnabled=${call.isRemoteVideoEnabled}`);
|
console.log(`Track received: kind=${track.kind}, isRemoteScreenSharing=${call.isRemoteScreenSharing}, isRemoteVideoEnabled=${call.isRemoteVideoEnabled}`);
|
||||||
|
|
||||||
if (track.kind === "video") {
|
if (track.kind === "video") {
|
||||||
// Prioritize screen share over regular video
|
const receiver = call.peerConnection.getReceivers().find(r => r.track === track);
|
||||||
// If remote is screen sharing, this video track is the screen share
|
const transceiver = receiver ? call.peerConnection.getTransceivers().find(t => t.receiver === receiver) : null;
|
||||||
if (call.isRemoteScreenSharing) {
|
|
||||||
console.log("Detected screen share track (based on signaling), notifying handler");
|
console.log("Video track transceiver mid:", transceiver?.mid);
|
||||||
|
console.log("Video sender mid:", call.videoSender ? call.peerConnection.getTransceivers().find(t => t.sender === call.videoSender)?.mid : "none");
|
||||||
|
console.log("Screen share sender mid:", call.screenShareSender ? call.peerConnection.getTransceivers().find(t => t.sender === call.screenShareSender)?.mid : "none");
|
||||||
|
|
||||||
|
let isScreenShare = false;
|
||||||
|
let isVideo = false;
|
||||||
|
|
||||||
|
if (call.isRemoteScreenSharing && call.isRemoteVideoEnabled) {
|
||||||
|
// Both active - route based on which one we haven't received yet
|
||||||
|
console.log("Both features active - routing based on received track counts");
|
||||||
|
console.log("Received video tracks:", call.receivedVideoTrackCount);
|
||||||
|
console.log("Received screen share tracks:", call.receivedScreenShareTrackCount);
|
||||||
|
|
||||||
|
// Simple logic: if we haven't received video yet, this is video
|
||||||
|
// if we haven't received screen share yet, this is screen share
|
||||||
|
if (call.receivedVideoTrackCount === 0) {
|
||||||
|
isVideo = true;
|
||||||
|
call.receivedVideoTrackCount++;
|
||||||
|
console.log("Routing as video (first video track)");
|
||||||
|
} else if (call.receivedScreenShareTrackCount === 0) {
|
||||||
|
isScreenShare = true;
|
||||||
|
call.receivedScreenShareTrackCount++;
|
||||||
|
console.log("Routing as screen share (first screen share track)");
|
||||||
|
} else {
|
||||||
|
// Both already received - this shouldn't happen, log warning
|
||||||
|
console.warn("Both tracks already received, but got another video track!");
|
||||||
|
console.warn("This might be a track replacement, routing as screen share by default");
|
||||||
|
isScreenShare = true;
|
||||||
|
}
|
||||||
|
} else if (call.isRemoteScreenSharing) {
|
||||||
|
console.log("Only screen share active");
|
||||||
|
isScreenShare = true;
|
||||||
|
call.receivedScreenShareTrackCount++;
|
||||||
|
} else if (call.isRemoteVideoEnabled) {
|
||||||
|
console.log("Only video active");
|
||||||
|
isVideo = true;
|
||||||
|
call.receivedVideoTrackCount++;
|
||||||
|
} else {
|
||||||
|
console.log("Neither video nor screen share active - this shouldn't happen!");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Routing decision: isScreenShare:", isScreenShare, "isVideo:", isVideo);
|
||||||
|
|
||||||
|
if (isScreenShare) {
|
||||||
|
console.log("Detected screen share track, notifying handler");
|
||||||
if (onRemoteScreenShare) {
|
if (onRemoteScreenShare) {
|
||||||
onRemoteScreenShare(userId, remoteStream);
|
onRemoteScreenShare(userId, remoteStream);
|
||||||
} else {
|
} else {
|
||||||
console.warn("onRemoteScreenShare handler not set!");
|
console.warn("onRemoteScreenShare handler not set!");
|
||||||
}
|
}
|
||||||
} else {
|
} else if (isVideo) {
|
||||||
console.log("Detected video track (based on signaling), notifying handler");
|
console.log("Detected video track, notifying handler");
|
||||||
// Handle remote video
|
|
||||||
if (onRemoteVideoStream) {
|
if (onRemoteVideoStream) {
|
||||||
onRemoteVideoStream(userId, remoteStream);
|
onRemoteVideoStream(userId, remoteStream);
|
||||||
} else {
|
} else {
|
||||||
@@ -904,14 +956,16 @@ export async function toggleVideo(userId: number): Promise<boolean> {
|
|||||||
|
|
||||||
// Add video track to peer connection
|
// Add video track to peer connection
|
||||||
const videoTrack = videoStream.getVideoTracks()[0];
|
const videoTrack = videoStream.getVideoTracks()[0];
|
||||||
call.peerConnection.addTrack(videoTrack, videoStream);
|
const sender = call.peerConnection.addTrack(videoTrack, videoStream);
|
||||||
|
call.videoSender = sender;
|
||||||
|
|
||||||
console.log("Video track added successfully");
|
console.log("Video track added successfully");
|
||||||
console.log("Current senders:", call.peerConnection.getSenders().map(s => s.track?.kind));
|
console.log("Current senders:", call.peerConnection.getSenders().map(s => s.track?.kind));
|
||||||
console.log("Current transceivers:", call.peerConnection.getTransceivers().map(t => ({
|
console.log("Current transceivers:", call.peerConnection.getTransceivers().map(t => ({
|
||||||
sender: t.sender.track?.kind,
|
sender: t.sender.track?.kind,
|
||||||
receiver: t.receiver.track?.kind,
|
receiver: t.receiver.track?.kind,
|
||||||
direction: t.direction
|
direction: t.direction,
|
||||||
|
mid: t.mid
|
||||||
})));
|
})));
|
||||||
|
|
||||||
// Apply E2EE transform with header-preserving encryption for video
|
// Apply E2EE transform with header-preserving encryption for video
|
||||||
@@ -964,6 +1018,11 @@ export async function toggleVideo(userId: number): Promise<boolean> {
|
|||||||
const videoSender = senders.find(s => s.track === track);
|
const videoSender = senders.find(s => s.track === track);
|
||||||
if (videoSender) {
|
if (videoSender) {
|
||||||
call.peerConnection.removeTrack(videoSender);
|
call.peerConnection.removeTrack(videoSender);
|
||||||
|
call.transformedSenders.delete(videoSender);
|
||||||
|
// Clear sender reference
|
||||||
|
if (call.videoSender === videoSender) {
|
||||||
|
call.videoSender = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
call.localVideoStream = null;
|
call.localVideoStream = null;
|
||||||
@@ -1025,8 +1084,46 @@ export async function toggleScreenShare(userId: number): Promise<boolean> {
|
|||||||
const videoTrack = screenStream.getVideoTracks()[0];
|
const videoTrack = screenStream.getVideoTracks()[0];
|
||||||
|
|
||||||
// Handle when user stops sharing via browser UI
|
// Handle when user stops sharing via browser UI
|
||||||
videoTrack.addEventListener("ended", () => {
|
videoTrack.addEventListener("ended", async () => {
|
||||||
toggleScreenShare(userId);
|
console.log("Screen share track ended by browser controls");
|
||||||
|
|
||||||
|
// Clean up screen share state
|
||||||
|
if (call.screenShareStream) {
|
||||||
|
call.screenShareStream.getTracks().forEach(t => t.stop());
|
||||||
|
call.screenShareStream = null;
|
||||||
|
}
|
||||||
|
call.isScreenSharing = false;
|
||||||
|
|
||||||
|
// Remove screen share track from peer connection
|
||||||
|
const senders = call.peerConnection.getSenders();
|
||||||
|
const screenSender = senders.find(sender =>
|
||||||
|
sender.track && sender.track.kind === 'video' &&
|
||||||
|
sender.track.readyState === 'ended' &&
|
||||||
|
call.transformedSenders.has(sender)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (screenSender) {
|
||||||
|
await call.peerConnection.removeTrack(screenSender);
|
||||||
|
call.transformedSenders.delete(screenSender);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify local screen share handler
|
||||||
|
if (onLocalScreenShare) {
|
||||||
|
onLocalScreenShare(userId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify state change handler
|
||||||
|
if (onScreenShareStateChange) {
|
||||||
|
onScreenShareStateChange(userId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send signaling message to remote peer
|
||||||
|
await sendSignalingMessage({
|
||||||
|
type: "call_screen_share_toggle",
|
||||||
|
fromUserId: 0, // Will be set by server
|
||||||
|
toUserId: userId,
|
||||||
|
data: { enabled: false }
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send signaling message FIRST to notify remote peer before adding track
|
// Send signaling message FIRST to notify remote peer before adding track
|
||||||
@@ -1042,7 +1139,8 @@ export async function toggleScreenShare(userId: number): Promise<boolean> {
|
|||||||
// Small delay to ensure signaling message is processed before track arrives
|
// Small delay to ensure signaling message is processed before track arrives
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
call.peerConnection.addTrack(videoTrack, screenStream);
|
const sender = call.peerConnection.addTrack(videoTrack, screenStream);
|
||||||
|
call.screenShareSender = sender;
|
||||||
|
|
||||||
console.log("Screen share track added, immediately applying E2EE transform");
|
console.log("Screen share track added, immediately applying E2EE transform");
|
||||||
|
|
||||||
@@ -1104,6 +1202,11 @@ export async function toggleScreenShare(userId: number): Promise<boolean> {
|
|||||||
const screenSender = senders.find(s => s.track === track);
|
const screenSender = senders.find(s => s.track === track);
|
||||||
if (screenSender) {
|
if (screenSender) {
|
||||||
call.peerConnection.removeTrack(screenSender);
|
call.peerConnection.removeTrack(screenSender);
|
||||||
|
call.transformedSenders.delete(screenSender);
|
||||||
|
// Clear sender reference
|
||||||
|
if (call.screenShareSender === screenSender) {
|
||||||
|
call.screenShareSender = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
call.screenShareStream = null;
|
call.screenShareStream = null;
|
||||||
@@ -1168,6 +1271,11 @@ export function setRemoteVideoEnabled(userId: number, enabled: boolean): void {
|
|||||||
if (call) {
|
if (call) {
|
||||||
console.log(`Setting remote video enabled to ${enabled} for user ${userId}`);
|
console.log(`Setting remote video enabled to ${enabled} for user ${userId}`);
|
||||||
call.isRemoteVideoEnabled = enabled;
|
call.isRemoteVideoEnabled = enabled;
|
||||||
|
// Reset counter when feature is disabled
|
||||||
|
if (!enabled) {
|
||||||
|
call.receivedVideoTrackCount = 0;
|
||||||
|
console.log("Reset video track counter");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1179,8 +1287,17 @@ export function setRemoteScreenSharing(userId: number, enabled: boolean): void {
|
|||||||
if (call) {
|
if (call) {
|
||||||
console.log(`Setting remote screen sharing to ${enabled} for user ${userId}`);
|
console.log(`Setting remote screen sharing to ${enabled} for user ${userId}`);
|
||||||
call.isRemoteScreenSharing = enabled;
|
call.isRemoteScreenSharing = enabled;
|
||||||
|
// Reset counter when feature is disabled
|
||||||
|
if (!enabled) {
|
||||||
|
call.receivedScreenShareTrackCount = 0;
|
||||||
|
console.log("Reset screen share track counter");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setScreenShareStateChangeHandler(handler: ((userId: number, isSharing: boolean) => void) | null): void {
|
||||||
|
onScreenShareStateChange = handler;
|
||||||
|
}
|
||||||
|
|
||||||
export function cleanup(): void {
|
export function cleanup(): void {
|
||||||
// Clean up all calls
|
// Clean up all calls
|
||||||
|
|||||||
@@ -156,6 +156,10 @@
|
|||||||
|
|
||||||
.screen-share-tile {
|
.screen-share-tile {
|
||||||
min-height: 150px;
|
min-height: 150px;
|
||||||
|
|
||||||
|
.screen-share-video {
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,10 +395,13 @@
|
|||||||
min-height: 480px;
|
min-height: 480px;
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
border: 2px solid rgba($color-dark-primary, 0.6);
|
border: 2px solid rgba($color-dark-primary, 0.6);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.video-element {
|
.screen-share-video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
background: rgba(0, 0, 0, 0.9);
|
background: rgba(0, 0, 0, 0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-label {
|
.tile-label {
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ export function CallWindow() {
|
|||||||
style={{ display: call.isSharingScreen ? "flex" : "none" }}>
|
style={{ display: call.isSharingScreen ? "flex" : "none" }}>
|
||||||
<video
|
<video
|
||||||
ref={localScreenShareRef}
|
ref={localScreenShareRef}
|
||||||
className="video-element"
|
className="video-element screen-share-video"
|
||||||
autoPlay
|
autoPlay
|
||||||
playsInline
|
playsInline
|
||||||
muted />
|
muted />
|
||||||
@@ -259,7 +259,7 @@ export function CallWindow() {
|
|||||||
style={{ display: call.isRemoteScreenSharing ? "flex" : "none" }}>
|
style={{ display: call.isRemoteScreenSharing ? "flex" : "none" }}>
|
||||||
<video
|
<video
|
||||||
ref={remoteScreenShareRef}
|
ref={remoteScreenShareRef}
|
||||||
className="video-element"
|
className="video-element screen-share-video"
|
||||||
autoPlay
|
autoPlay
|
||||||
playsInline />
|
playsInline />
|
||||||
<div className="tile-label">{remoteUsername}'s Screen</div>
|
<div className="tile-label">{remoteUsername}'s Screen</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user