I have a problem connecting peer-to-peer video from native android browser to safari 11 desktop (vice versa), here is the error:
Unhandled Promise Rejection: OperationError (DOM Exception 34): Failed to set remote offer sdp: Session error code: ERROR_CONTENT. Session error description: Failed to set remote video description send parameters..
I'm currently stuck in this issue
and here is my whole client videochat code, thanks.
import app from '../../../config';
const videoChatService = app.service('participants/video-chat');
let localVid;
let remoteVid;
let localstream;
let rtcPeerConn;
let conversationId;
let userId;
let isSdpSent = false;
let hasAddTrack;
const configuration = {
iceServers: [{
urls: 'stun:stun.l.google.com:19302',
},
{
urls: 'stun:stun.services.mozilla.com',
username: '[email protected]',
credential: 'webrtcdemo',
},
// {
// urls: 'turn:mdn-samples.mozilla.org',
// username: 'webrtc',
// credential: 'turnserver' }
] };
function closeVideoCall() {
if (rtcPeerConn) {
rtcPeerConn.onaddstream = null;
rtcPeerConn.ontrack = null;
rtcPeerConn.onremovestream = null;
rtcPeerConn.onicecandidate = null;
if (remoteVid.srcObject) {
remoteVid.srcObject.getTracks().forEach(track => track.stop());
remoteVid.srcObject = null;
}
if (localVid.srcObject) {
localVid.srcObject.getTracks().forEach(track => track.stop());
localVid.srcObject = null;
}
rtcPeerConn.close();
rtcPeerConn = null;
}
}
// set local sdp
function setLocalSDP(desc) {
console.log('>>> setLocalSDP', rtcPeerConn);
return rtcPeerConn.setLocalDescription(desc);
}
function logError(error) {
console.log(`>>>>logError, ${error.name}: ${error.message}`);
}
function handleNegotiatedNeededEvent() {
console.log('>>>>> on negotiation called');
console.log('query >>>', conversationId, userId);
if (!isSdpSent) {
rtcPeerConn.createOffer()
.then(setLocalSDP)
.then(() => {
isSdpSent = true;
videoChatService.patch(null, {
data: {
type: 'video-offer',
message: JSON.stringify(rtcPeerConn.localDescription),
},
}, {
query: {
conversation_id: conversationId,
user_id: userId,
},
}).then().catch(e => { console.log('patch error', e); });
})
.catch(logError);
}
}
function handleRemoveStreamEvent() {
closeVideoCall();
}
function handleTrackEvent (evt) {
console.log('>>>>> going to add their stream...', evt);
remoteVid = document.getElementById('remoteStream');
if (!remoteVid.srcObject) {
remoteVid.srcObject = evt.streams[0];
}
}
function handleAddStreamEvent(evt) {
console.log('>>>>> stream added');
remoteVid = document.getElementById('remoteStream');
remoteVid.srcObject = event.stream;
}
function handleICECandidateEvent(evt) {
console.log('>>>> onicecandidate', evt);
console.log('query >>>', conversationId, userId);
if (evt.candidate) {
videoChatService.patch(null, {
data: {
type: 'new-ice-candidate',
message: JSON.stringify(evt.candidate),
},
}, {
query: {
conversation_id: conversationId,
user_id: userId,
},
});
}
}
function handleICEConnectionStateChangeEvent() {
console.log(`>>>>> ICE connection state changed to ${rtcPeerConn.iceConnectionState}`);
switch (rtcPeerConn.iceConnectionState) {
case 'closed':
case 'failed':
case 'disconnected':
console.log('>>>> disconnected');
closeVideoCall();
break;
}
}
function handleSignalingStateChangeEvent() {
console.log(`>>>>> WebRTC signaling state changed to: ${rtcPeerConn.signalingState}`);
switch (rtcPeerConn.signalingState) {
case 'closed':
console.log('>>>> closed');
closeVideoCall();
break;
}
}
function createPeerConnection() {
rtcPeerConn = new RTCPeerConnection(configuration);
console.log('>>>>> create peer connection', rtcPeerConn);
hasAddTrack = (rtcPeerConn.addTrack !== undefined);
rtcPeerConn.onicecandidate = handleICECandidateEvent;
rtcPeerConn.onnegotiationneeded = handleNegotiatedNeededEvent;
rtcPeerConn.oniceconnectionstatechange = handleICEConnectionStateChangeEvent;
rtcPeerConn.onsignalingstatechange = handleSignalingStateChangeEvent;
rtcPeerConn.onremovestream = handleRemoveStreamEvent;
if (hasAddTrack) {
rtcPeerConn.ontrack = handleTrackEvent;
} else {
rtcPeerConn.onaddstream = handleAddStreamEvent;
}
}
function handleGetUSerMediaError(e) {
switch (e.name) {
case 'NotFoundError':
alert('Unable to open your call because no camera and/or microphone were found.');
break;
case 'SecurityError':
case 'PermissionDeniedError':
// Do nothing; this is the same as the user canceling the call.
break;
default:
alert(`Error opening your camera and/or microphone: ${e.message}`);
break;
}
}
// add video to local and add to track
function gotStream(stream) {
console.log('>>>> gotStream', stream);
localVid.srcObject = stream;
localstream = stream;
if (hasAddTrack) {
stream.getTracks().forEach(track => rtcPeerConn.addTrack(track, localstream));
} else {
rtcPeerConn.addStream(localstream);
}
}
// start signaling
export function startSignaling(conversation_id, user_id) {
localVid = document.getElementById('localStream');
remoteVid = document.getElementById('remoteStream');
console.log('>>>>> startSignaling');
conversationId = conversation_id;
userId = user_id;
return () => {
if (!rtcPeerConn) {
createPeerConnection();
navigator.mediaDevices.getUserMedia({
audio: true,
video: {
facingMode: 'user',
},
})
.then(gotStream)
.catch(handleGetUSerMediaError);
}
};
}
export function handleVideoOfferMsg(conversation_id, user_id, message) {
console.log('>>>>> handleVideoOfferMsg');
localstream = null;
conversationId = conversation_id;
userId = user_id;
localVid = document.getElementById('localStream');
remoteVid = document.getElementById('remoteStream');
return () => {
createPeerConnection();
console.log('query >>>', conversationId, userId);
const sdp = new RTCSessionDescription(message);
// sdp.sdp = sdp.replace('a=setup:active', 'a=setup:passive');
rtcPeerConn.setRemoteDescription(sdp)
.then(() => (
navigator.mediaDevices.getUserMedia({
audio: true,
video: {
facingMode: 'user',
},
})
))
.then(gotStream)
.then(() => (
rtcPeerConn.createAnswer()
))
.then(setLocalSDP)
.then(() => {
videoChatService.patch(null, {
data: {
type: 'video-answer',
message: JSON.stringify(rtcPeerConn.localDescription),
},
}, {
query: {
conversation_id: conversationId,
user_id: userId,
},
}).then().catch(e => { console.log('patch error', e); });
});
};
}
export function handleVideoAnswerMsg(message) {
console.log('>>>>> handle video answer message', message);
return () => {
const sdp = new RTCSessionDescription(message);
rtcPeerConn.setRemoteDescription(sdp)
.catch(logError);
};
}
// Adding ice candidate
export function addIceCandidate(message) {
console.log('>>>> addIceCandidate', message);
return () => {
const candidate = new RTCIceCandidate(message);
rtcPeerConn.addIceCandidate(candidate)
.then(() => {
console.log('>>> candidate added ');
})
.catch(e => {
console.log('Error candidate', e);
});
};
}
There are two different issues making a WebRTC connection between Chrome on Android and iOS/Safari not working:
1) No H.264 implementation on device
Chrome for Android has only a hardware implementation for H.264 and there is no software implementation. At this moment H.264 works only with devices with a processor of Qualcomm (Kitkat and later) or Samsung Exynos (Lollipop and later). Since Apple only supports H.264 other Android devices can't connect with iOS and Safari.
2) There is bug in Chrome for Android:
Chrome Android does not offer/answer H.264 Constrained Baseline Profile
Because Apple only supports H.264, Android/Chrome can not connect with iOS at this moment.
This problem will be solved in Chrome Android 65 (now Canary). See this for more information.
I see your error message that is exactly this bug so I am pretty sure this is the problem. But in the end it doesn't matter. But you should be aware of both problems.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With