Я работаю над приложением для видеовызовов на базе WebRTC с использованием Knockout.js и SignalR, и оно отлично работает на ПК для нескольких пользователей. Однако когда я инициирую звонок со своего iPhone (iOS Safari), получатели не видят потоки друг друга. Видеоконтейнеры добавляются, но экраны остаются пустыми у всех получателей, и они видят только меня (отправителя).
WebRTC and SignalR Test
[*]
My User ID
Loading...
My SignalR Registration ID
data-bind="text: signalrConnectionId">
Loading...
class="overflow-y-scroll bg-gray-100 p-3 border border-gray-300 rounded-md font-mono whitespace-pre-wrap h-full">
Incoming Video Call
Do you want to accept the call?
Accept Decline
[list]
⚡ Barack Obama
[*][url=https://youtube.com]YouTube[/url]
[/list]
Может кто-нибудь помочь мне решить эту проблему? Я понятия не имею, где что-то идет не так, и я не получаю никаких сообщений об ошибках в консоли. Как на стороне отправителя, так и на стороне получателя.
Я попробовал реструктурировать код. Размещение ожидания и удаление ожидания. И переупорядочение кода. Я также пробовал разные браузеры и проверял все свои разрешения (камера и микрофон).
Я работаю над приложением для видеовызовов на базе WebRTC с использованием Knockout.js и SignalR, и оно отлично работает на ПК для нескольких пользователей. Однако когда я инициирую звонок со своего iPhone (iOS Safari), получатели не видят потоки друг друга. Видеоконтейнеры добавляются, но экраны остаются пустыми у всех получателей, и они видят только меня (отправителя). [code]import * as ko from "knockout"; import * as signalR from "@microsoft/signalr"; import { Modal } from "bootstrap";
let receiverUserIds: string[] = [];
async function getUserMedia() { try { return await navigator.mediaDevices.getUserMedia({ video: true, audio: true, }); } catch (error) { throw new Error(`Error fetching user media: ${error}`); } }
function generateRandomNumber(min: number, max: number) { // Generate a random number between min and max (inclusive). return Math.floor(Math.random() * (max - min + 1)) + min; // Corrected to include max in the range. }
async function signalrStartHubConnection() { try { const connection = new signalR.HubConnectionBuilder() .withUrl("/signalr") // Specify the SignalR hub URL. .configureLogging(signalR.LogLevel.Information) // Set logging level. .withAutomaticReconnect() // Enable automatic reconnection. .build(); // Build the SignalR connection.
await connection.start(); // Start the SignalR connection. return connection; // Return the connection object. } catch (error) { throw new Error(`Error starting SignalR connection: ${error}`); // Throw an error if connection fails. } }
async function signalrRegisterWebrtcUserId( signalrHubConnection: signalR.HubConnection, webrtcUserId: string ) { try { await signalrHubConnection.invoke("RegisterUser", webrtcUserId); // Invoke SignalR method to register user. } catch (error) { throw new Error(`Error registering user: ${error}`); // Throw an error if registration fails. } }
// Interfaces interface PeerConnectionsInterface { [key: string]: RTCPeerConnection; // Map of user IDs to their respective RTCPeerConnection objects. }
interface SignalDtoInterface { signalType: SignalTypeEnum; receiverUserId: string; // The user ID of whom you want to send data to. senderUserId: string; data: string; role: RoleEnum; }
class Conference { currentSignal!: SignalDtoInterface; userMedia!: MediaStream; webrtcIceCandidates: IceCandidatesInterface; webrtcGatheredIceCandidates: KnockoutObservable; webrtcPeerConnections!: PeerConnectionsInterface; // Optional observable for peer connections. webrtcUserId: KnockoutObservable; // Observable for WebRTC user ID. webrtcReceiverUserIdsInputField: KnockoutObservable; // Observable input field for receiver user IDs.
constructor() { this.webrtcPeerConnections = {}; this.webrtcIceCandidates = {}; this.webrtcGatheredIceCandidates = ko.observable({}); this.webrtcUserId = ko.observable( generateRandomNumber(1, 19).toString() ); // Generate a random WebRTC user ID and store it as a Knockout observable. this.webrtcReceiverUserIdsInputField = ko.observable(""); // Initialize the receiver user IDs input field as an empty string. this.signalrSenderConnectionId = ko.observable(); // Initialize as undefined. Should show the ID of the original sender on the receivers screen. this.isCallToggled = ko.observable(false); // Initialize call toggle state as false.
// Bind methods to the current instance to ensure `this` context is correct. this.toggleCall = this.toggleCall.bind(this); this.startCall = this.startCall.bind(this); this.stopCall = this.stopCall.bind(this); this.acceptCall = this.acceptCall.bind(this); this.declineCall = this.declineCall.bind(this); }
addOrUpdateIceCandidate(receiverUserId: string, candidate: RTCIceCandidate) { if (!this.webrtcGatheredIceCandidates()[receiverUserId]) { this.webrtcGatheredIceCandidates()[receiverUserId] = ko.observableArray([]); } this.webrtcGatheredIceCandidates()[receiverUserId].push(candidate); // Knockout will track this. }
async startMediaStream(elementId: string, mediaStream: MediaStream) { try { const container = document.getElementById("videos"); // Target the container where you want to append the video element.
if (!container) { throw new Error("Container element not found"); }
let videoElement = document.getElementById(elementId) as HTMLVideoElement;
// If the video element doesn't exist, create it and append it to the container if (!videoElement) { videoElement = document.createElement("video"); // Create a new video element videoElement.id = elementId; // Set the element ID videoElement.autoplay = true; // Ensure the video plays automatically videoElement.muted = false; // Mute the video to avoid echo when playing local stream videoElement.playsInline = true; // For mobile devices to support inline video playback videoElement.className = "w-full bg-black rounded-lg object-contain aspect-video"; // Apply the same styling as other video elements
// Create a wrapper div if needed (like in your current structure) const videoWrapper = document.createElement("div"); videoWrapper.className = "w-full";
// Append the video element to the wrapper div videoWrapper.appendChild(videoElement);
// Append the wrapper div to the container container.appendChild(videoWrapper); }
// Assign the media stream to the video element videoElement.srcObject = mediaStream; } catch (err) { console.error("Error accessing media devices:", err); alert( "Error accessing your camera and microphone. Please check your device permissions." ); } }
async init() { this.userMedia = await getUserMedia(); this.startMediaStream("localVideo", this.userMedia); // Ensure local stream is always shown
const hubConnection = await signalrStartHubConnection(); this.signalrHubConnection = ko.observable(hubConnection); // Start the SignalR connection. await signalrRegisterWebrtcUserId(hubConnection, this.webrtcUserId()); this.signalrConnectionId = ko.observable(hubConnection.connectionId); // Store the SignalR connection ID.
// Bind view model to UI after initialization. const viewModel = { webrtcUserId: this.webrtcUserId, // Bind WebRTC user ID. signalrConnectionId: this.signalrConnectionId, // Bind SignalR connection ID. webrtcReceiverUserIdsInputField: this.webrtcReceiverUserIdsInputField, // Bind receiver user IDs input field. signalrSenderConnectionId: this.signalrSenderConnectionId, // Bind the SignalR sender connection ID. isCallToggled: this.isCallToggled, // Bind the call toggle status. toggleCall: this.toggleCall, // Bind the toggle call function. acceptCall: this.acceptCall, // Bind with current signal. declineCall: this.declineCall, // Bind the decline call function. };
ko.applyBindings(viewModel); // Apply Knockout bindings to the view model. this.listenForIncomingSignals(); }
async toggleCall() { const toggleValue = this.isCallToggled(); if (toggleValue) { receiverUserIds = this.receiverUserdIds(); // Get receiver user IDs from input. this.startCallMeshUsers(); receiverUserIds.forEach((userId) => this.startCall(userId)); } else { await this.stopCall(); } }
const peerConnection = new RTCPeerConnection(config); const mediaStream = this.userMedia;
// Add tracks to the peer connection only if they are not already added. mediaStream.getTracks().forEach((track) => { const senders = peerConnection.getSenders(); // Get the current senders. const isTrackAlreadyAdded = senders.some( (sender) => sender.track === track ); // Check if track is already added.
if (!isTrackAlreadyAdded) { peerConnection.addTrack(track, mediaStream); // Add the track if it is not already added. } });
let peerConnection = this.webrtcPeerConnections[senderUserId]; if (!peerConnection) { const config = this.configuration; this.webrtcPeerConnections[senderUserId] = new RTCPeerConnection(config); peerConnection = this.webrtcPeerConnections[senderUserId]; }
const mediaStream = this.userMedia;
// Add tracks to the peer connection only if they are not already added. mediaStream.getTracks().forEach((track) => { const senders = peerConnection.getSenders(); // Get all current senders (tracks already added). const isTrackAlreadyAdded = senders.some( (sender) => sender.track === track ); // Check if the track is already added.
if (!isTrackAlreadyAdded) { peerConnection.addTrack(track, mediaStream); // Add the track if it is not already added. } });
// Handle the remote track event to receive the incoming media streams. this.webrtcPeerConnections[senderUserId].ontrack = (event) => { const stream = event.streams; if (stream?.length) { this.startMediaStream("remoteVideo_" + senderUserId, stream[0]); // Use unique element ID. } else { console.error("No streams found in the event."); } };
// Iterate over the receiverUserIds asynchronously for (const [index, key] of receiverUserIds.entries()) { /** * "1": ["2", "3", "4"], // User 1 calls users 2, 3, and 4. "2": ["3", "4"], // User 2 only calls users 3 and 4 (not 1 because 1 has already called). "3": ["4"], // User 3 only calls user 4 (not 1 or 2 because they have already called). "4": [] // User 4 doesn't call anyone (all users have already connected).
// Filter out users that have already been "called" by this user. const mesh: string[] = receiverUserIds.slice(index + 1); // Get users after the current key.
// Only proceed if there are remaining users to call. if (mesh.length > 0) { for (const value of mesh) { // Set up the signalDto values. signalDto.receiverUserId = key; // The current user making the call. signalDto.senderUserId = key; // The same user as the sender. signalDto.data = value; // The user being called.
try { // Await the asynchronous call to SendSignal. await this.signalrHubConnection().invoke("SendSignal", signalDto); } catch (error) { // Throw an error if the call fails. throw new Error( `Error calling mesh users: ${JSON.stringify(mesh)}` ); } } } } } }
const conference = new Conference(); // Create a new Conference instance. conference.init(); // Initialize the conference.
[/code] HTML [code]
WebRTC and SignalR Test [*]
My User ID
Loading...
My SignalR Registration ID data-bind="text: signalrConnectionId"> Loading...
[/code] Может кто-нибудь помочь мне решить эту проблему? Я понятия не имею, где что-то идет не так, и я не получаю никаких сообщений об ошибках в консоли. Как на стороне отправителя, так и на стороне получателя. Я попробовал реструктурировать код. Размещение ожидания и удаление ожидания. И переупорядочение кода. Я также пробовал разные браузеры и проверял все свои разрешения (камера и микрофон).
Я работаю над приложением для видеовызовов на базе WebRTC с использованием Knockout.js и SignalR, и оно отлично работает на ПК для нескольких пользователей. Однако когда я инициирую звонок со своего iPhone (iOS Safari), получатели не видят потоки...
Я работаю над приложением для видеовызовов на базе WebRTC с использованием Knockout.js и SignalR, и оно отлично работает на ПК для нескольких пользователей. Однако когда я инициирую звонок со своего iPhone (iOS Safari), получатели не видят потоки...
Я хочу осуществлять потоковую передачу с веб-камеры на Kinesis Video Streams.
Я читал документацию и следовал инструкциям AWS.
После запуска DemoAppMain с помощью команды:
Сейчас работаю над MMO на чистой Java. Хотел сделать лаунчер для игры, который бы позволял пользователю авторизоваться. Я изо всех сил стараюсь создать безопасную систему, гарантирующую, что играть смогут только авторизованные пользователи.
Вот...
Итак, я пытался сделать такую игру, как CandyCrush, но многопользовательскую, я использую услугу лобби Unitys для многопользовательской среды P2P, поэтому мне не нужно получать выделенные серверы.
Основная проблема - это то, что когда игрок 1 (так...