Cómo agregar Track en MediaStream en WebRTC
microphone audiotrack (1)
Estoy usando webrtc para comunicarme con mis compañeros. No quiero agregar una nueva pista a la transmisión generada antigua, ya que no quiero dar funcionalidad a los usuarios para que cambien sus micrófonos durante las comunicaciones de audio. El código que estoy usando es,
Deje que "pc" sea el objeto peerConnection a través del cual tiene lugar la comunicación de audio y "newStream" sea el nuevo MediaStream generado de la función getUserMedia con el nuevo dispositivo de micrófono seleccionado.
var localStreams = pc.getLocalStreams()[0];
localStreams.removeTrack(localStreams.getAudioTracks()[0]);
var audioTrack = newStream.getAudioTracks()[0];
localStreams.addTrack(audioTrack);
¿Es de alguna manera que la pista recién agregada comienza a llegar al otro par conectado anteriormente sin ofrecerle nuevamente todo el SDP?
¿Cuál sería la forma optimizada de usar en tal caso de dispositivo de medios de conmutación, es decir, micrófonos cuando las conexiones ya están establecidas entre pares?
Actualización: ejemplo de trabajo cerca del fondo.
Esto depende en gran medida del navegador que esté utilizando en este momento, debido a una evolución de las especificaciones.
En
la especificación
y Firefox, las conexiones entre pares ahora se basan fundamentalmente en pistas y no dependen de las asociaciones de flujo local.
Tiene
var sender = pc.addTrack(track, stream)
,
pc.removeTrack(sender)
e incluso
sender.replaceTrack(track)
, este último no implica renegociación en absoluto.
En Chrome, todavía tiene
pc.addStream
y
pc.removeStream
, y eliminar una pista de una transmisión local hace que cese el envío, pero volver a agregarlo no funcionó.
Tuve suerte al eliminar y volver a agregar la transmisión completa a la conexión de pares, seguido de una renegociación.
Desafortunadamente, el uso de
adapter.js
no ayuda aquí, ya que
addTrack
es difícil de rellenar.
Renegociación
La renegociación no está comenzando de nuevo. Todo lo que necesitas es:
pc.onnegotiationneeded = e => pc.createOffer()
.then(offer => pc.setLocalDescription(offer))
.then(() => signalingChannel.send(JSON.stringify({ "sdp": pc.localDescription }));
.catch(failed);
Una vez que agregue esto, la conexión entre pares se renegocia automáticamente cuando sea necesario utilizando su canal de señalización.
Esto incluso reemplaza las llamadas a
createOffer
y amigos que estás haciendo ahora, una ganancia neta.
Con esto en su lugar, puede agregar / eliminar pistas durante una conexión en vivo, y debería "funcionar".
Si eso no es lo suficientemente suave, incluso puede
pc.createDataChannel("yourOwnSignalingChannel")
Ejemplo
Aquí hay un ejemplo de todo eso (use https fiddle en Chrome):
var config = { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] };
var signalingDelayMs = 0;
var dc, sc, pc = new RTCPeerConnection(config), live = false;
pc.onaddstream = e => v2.srcObject = e.stream;
pc.ondatachannel = e => dc? scInit(sc = e.channel) : dcInit(dc = e.channel);
var streams = [];
var haveGum = navigator.mediaDevices.getUserMedia({fake:true, video:true})
.then(stream => streams[1] = stream)
.then(() => navigator.mediaDevices.getUserMedia({ video: true }))
.then(stream => v1.srcObject = streams[0] = stream);
pc.oniceconnectionstatechange = () => update(pc.iceConnectionState);
var negotiating; // Chrome workaround
pc.onnegotiationneeded = () => {
if (negotiating) return;
negotiating = true;
pc.createOffer().then(d => pc.setLocalDescription(d))
.then(() => live && sc.send(JSON.stringify({ sdp: pc.localDescription })))
.catch(log);
};
pc.onsignalingstatechange = () => negotiating = pc.signalingState != "stable";
function scInit() {
sc.onmessage = e => wait(signalingDelayMs).then(() => {
var msg = JSON.parse(e.data);
if (msg.sdp) {
var desc = new RTCSessionDescription(JSON.parse(e.data).sdp);
if (desc.type == "offer") {
pc.setRemoteDescription(desc).then(() => pc.createAnswer())
.then(answer => pc.setLocalDescription(answer)).then(() => {
sc.send(JSON.stringify({ sdp: pc.localDescription }));
}).catch(log);
} else {
pc.setRemoteDescription(desc).catch(log);
}
} else if (msg.candidate) {
pc.addIceCandidate(new RTCIceCandidate(msg.candidate)).catch(log);
}
}).catch(log);
}
function dcInit() {
dc.onopen = () => {
live = true; update("Chat:"); chat.disabled = false; chat.select();
};
dc.onmessage = e => log(e.data);
}
function createOffer() {
button.disabled = true;
pc.onicecandidate = e => {
if (live) {
sc.send(JSON.stringify({ "candidate": e.candidate }));
} else if (!e.candidate) {
offer.value = pc.localDescription.sdp;
offer.select();
answer.placeholder = "Paste answer here";
}
};
dcInit(dc = pc.createDataChannel("chat"));
scInit(sc = pc.createDataChannel("signaling"));
};
offer.onkeypress = e => {
if (e.keyCode != 13 || pc.signalingState != "stable") return;
button.disabled = offer.disabled = true;
var obj = { type:"offer", sdp:offer.value };
pc.setRemoteDescription(new RTCSessionDescription(obj))
.then(() => pc.createAnswer()).then(d => pc.setLocalDescription(d))
.catch(log);
pc.onicecandidate = e => {
if (e.candidate) return;
if (!live) {
answer.focus();
answer.value = pc.localDescription.sdp;
answer.select();
} else {
sc.send(JSON.stringify({ "candidate": e.candidate }));
}
};
};
answer.onkeypress = e => {
if (e.keyCode != 13 || pc.signalingState != "have-local-offer") return;
answer.disabled = true;
var obj = { type:"answer", sdp:answer.value };
pc.setRemoteDescription(new RTCSessionDescription(obj)).catch(log);
};
chat.onkeypress = e => {
if (e.keyCode != 13) return;
dc.send(chat.value);
log("> " + chat.value);
chat.value = "";
};
function addTrack() {
pc.addStream(streams[0]);
flipButton.disabled = false;
removeAddButton.disabled = false;
}
var flipped = 0;
function flip() {
pc.getSenders()[0].replaceTrack(streams[flipped = 1 - flipped].getVideoTracks()[0])
.catch(log);
}
function removeAdd() {
if ("removeTrack" in pc) {
pc.removeTrack(pc.getSenders()[0]);
pc.addStream(streams[flipped = 1 - flipped]);
} else {
pc.removeStream(streams[flipped]);
pc.addStream(streams[flipped = 1 - flipped]);
}
}
var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
var update = msg => div2.innerHTML = msg;
var log = msg => div.innerHTML += msg + "<br>";
<video id="v1" width="120" height="90" autoplay muted></video>
<video id="v2" width="120" height="90" autoplay></video><br>
<button id="button" onclick="createOffer()">Offer:</button>
<textarea id="offer" placeholder="Paste offer here"></textarea><br>
Answer: <textarea id="answer"></textarea><br>
<button id="button" onclick="addTrack()">AddTrack</button>
<button id="removeAddButton" onclick="removeAdd()" disabled>Remove+Add</button>
<button id="flipButton" onclick="flip()" disabled>ReplaceTrack (FF only)</button>
<div id="div"><p></div><br>
<table><tr><td><div id="div2">Not connected</div></td>
<td><input id="chat" disabled></input></td></tr></table><br>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
Instrucciones:
No hay ningún servidor involucrado, así que presione
Offer
, luego corte y pegue la oferta y responda manualmente entre dos pestañas (presione la tecla ENTER después de pegar).
Una vez hecho esto, puede chatear a través del canal de datos y presionar
addTrack
para agregar video al otro lado.
Luego puede cambiar el video que se muestra de forma remota con
Remove + Add
o
replaceTrack (FF only)
(modifique el violín en Chrome si tiene una cámara secundaria que desea usar).
La renegociación está sucediendo en todo el canal de datos ahora (no más cortar y pegar).