/* * Copyright (C) 2015 Ericsson AB. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * 3. Neither the name of Ericsson nor the names of its contributors * may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #if ENABLE(WEB_RTC) #include "MediaEndpointPeerConnection.h" #include "EventNames.h" #include "JSRTCSessionDescription.h" #include "MediaEndpointSessionConfiguration.h" #include "MediaEndpointSessionDescription.h" #include "MediaStream.h" #include "MediaStreamEvent.h" #include "MediaStreamTrack.h" #include "NotImplemented.h" #include "PeerMediaDescription.h" #include "RTCConfiguration.h" #include "RTCIceCandidate.h" #include "RTCIceCandidateEvent.h" #include "RTCOfferAnswerOptions.h" #include "RTCPeerConnection.h" #include "RTCRtpTransceiver.h" #include "RTCTrackEvent.h" #include "SDPProcessor.h" #include #include namespace WebCore { using namespace PeerConnection; using namespace PeerConnectionStates; using MediaDescriptionVector = Vector; using RtpTransceiverVector = Vector>; // We use base64 to generate the random strings so we need a size that avoids padding to get ice-chars. static const size_t cnameSize = 18; // Size range from 4 to 256 ice-chars defined in RFC 5245. static const size_t iceUfragSize = 6; // Size range from 22 to 256 ice-chars defined in RFC 5245. static const size_t icePasswordSize = 24; #if !USE(LIBWEBRTC) static std::unique_ptr createMediaEndpointPeerConnection(RTCPeerConnection& peerConnection) { return std::unique_ptr(new MediaEndpointPeerConnection(peerConnection)); } CreatePeerConnectionBackend PeerConnectionBackend::create = createMediaEndpointPeerConnection; #endif static String randomString(size_t size) { unsigned char randomValues[size]; cryptographicallyRandomValues(randomValues, size); return base64Encode(randomValues, size); } MediaEndpointPeerConnection::MediaEndpointPeerConnection(RTCPeerConnection& peerConnection) : PeerConnectionBackend(peerConnection) , m_mediaEndpoint(MediaEndpoint::create(*this)) , m_sdpProcessor(std::make_unique(m_peerConnection.scriptExecutionContext())) , m_cname(randomString(cnameSize)) , m_iceUfrag(randomString(iceUfragSize)) , m_icePassword(randomString(icePasswordSize)) { ASSERT(m_mediaEndpoint); m_defaultAudioPayloads = m_mediaEndpoint->getDefaultAudioPayloads(); m_defaultVideoPayloads = m_mediaEndpoint->getDefaultVideoPayloads(); // Tasks (see runTask()) will be deferred until we get the DTLS fingerprint. m_mediaEndpoint->generateDtlsInfo(); } static RTCRtpTransceiver* matchTransceiver(const RtpTransceiverVector& transceivers, const std::function& matchFunction) { for (auto& transceiver : transceivers) { if (matchFunction(*transceiver)) return transceiver.get(); } return nullptr; } static RTCRtpTransceiver* matchTransceiverByMid(const RtpTransceiverVector& transceivers, const String& mid) { return matchTransceiver(transceivers, [&mid] (RTCRtpTransceiver& current) { return current.mid() == mid; }); } static bool hasUnassociatedTransceivers(const RtpTransceiverVector& transceivers) { return matchTransceiver(transceivers, [] (RTCRtpTransceiver& current) { return current.mid().isNull() && !current.stopped(); }); } void MediaEndpointPeerConnection::runTask(Function&& task) { if (m_dtlsFingerprint.isNull()) { // Only one task needs to be deferred since it will hold off any others until completed. ASSERT(!m_initialDeferredTask); m_initialDeferredTask = WTFMove(task); } else callOnMainThread(WTFMove(task)); } void MediaEndpointPeerConnection::startRunningTasks() { if (!m_initialDeferredTask) return; m_initialDeferredTask(); m_initialDeferredTask = nullptr; } void MediaEndpointPeerConnection::doCreateOffer(RTCOfferOptions&& options) { runTask([this, protectedOptions = WTFMove(options)]() mutable { createOfferTask(protectedOptions); }); } void MediaEndpointPeerConnection::createOfferTask(const RTCOfferOptions&) { ASSERT(!m_dtlsFingerprint.isEmpty()); MediaEndpointSessionDescription* localDescription = internalLocalDescription(); RefPtr configurationSnapshot = localDescription ? localDescription->configuration()->clone() : MediaEndpointSessionConfiguration::create(); configurationSnapshot->setSessionVersion(m_sdpOfferSessionVersion++); auto transceivers = RtpTransceiverVector(m_peerConnection.getTransceivers()); // Remove any transceiver objects from transceivers that can be matched to an existing media description. for (auto& mediaDescription : configurationSnapshot->mediaDescriptions()) { if (!mediaDescription.port) { // This media description should be recycled. continue; } RTCRtpTransceiver* transceiver = matchTransceiverByMid(transceivers, mediaDescription.mid); if (!transceiver) continue; mediaDescription.mode = transceiver->directionString(); if (transceiver->hasSendingDirection()) { auto& sender = transceiver->sender(); mediaDescription.mediaStreamId = sender.mediaStreamIds()[0]; mediaDescription.mediaStreamTrackId = sender.trackId(); } transceivers.removeFirst(transceiver); } // Add media descriptions for remaining transceivers. for (auto& transceiver : transceivers) { PeerMediaDescription mediaDescription; auto& sender = transceiver->sender(); mediaDescription.mode = transceiver->directionString(); mediaDescription.mid = transceiver->provisionalMid(); mediaDescription.mediaStreamId = sender.mediaStreamIds()[0]; mediaDescription.type = sender.trackKind(); mediaDescription.payloads = sender.trackKind() == "audio" ? m_defaultAudioPayloads : m_defaultVideoPayloads; mediaDescription.dtlsFingerprintHashFunction = m_dtlsFingerprintFunction; mediaDescription.dtlsFingerprint = m_dtlsFingerprint; mediaDescription.cname = m_cname; mediaDescription.addSsrc(cryptographicallyRandomNumber()); mediaDescription.iceUfrag = m_iceUfrag; mediaDescription.icePassword = m_icePassword; if (sender.track()) mediaDescription.mediaStreamTrackId = sender.trackId(); configurationSnapshot->addMediaDescription(WTFMove(mediaDescription)); } String sdp; SDPProcessor::Result result = m_sdpProcessor->generate(*configurationSnapshot, sdp); if (result != SDPProcessor::Result::Success) { createOfferFailed(Exception { OperationError, "SDPProcessor internal error" }); return; } createOfferSucceeded(WTFMove(sdp)); } void MediaEndpointPeerConnection::doCreateAnswer(RTCAnswerOptions&& options) { runTask([this, protectedOptions = WTFMove(options)]() mutable { createAnswerTask(protectedOptions); }); } void MediaEndpointPeerConnection::createAnswerTask(const RTCAnswerOptions&) { ASSERT(!m_dtlsFingerprint.isEmpty()); if (!internalRemoteDescription()) { createAnswerFailed(Exception { INVALID_STATE_ERR, "No remote description set" }); return; } MediaEndpointSessionDescription* localDescription = internalLocalDescription(); RefPtr configurationSnapshot = localDescription ? localDescription->configuration()->clone() : MediaEndpointSessionConfiguration::create(); configurationSnapshot->setSessionVersion(m_sdpAnswerSessionVersion++); auto transceivers = RtpTransceiverVector(m_peerConnection.getTransceivers()); auto& remoteMediaDescriptions = internalRemoteDescription()->configuration()->mediaDescriptions(); for (unsigned i = 0; i < remoteMediaDescriptions.size(); ++i) { auto& remoteMediaDescription = remoteMediaDescriptions[i]; auto* transceiver = matchTransceiverByMid(transceivers, remoteMediaDescription.mid); if (!transceiver) { LOG_ERROR("Could not find a matching transceiver for remote description while creating answer"); continue; } if (i >= configurationSnapshot->mediaDescriptions().size()) { PeerMediaDescription newMediaDescription; auto& sender = transceiver->sender(); if (sender.track()) { if (sender.mediaStreamIds().size()) newMediaDescription.mediaStreamId = sender.mediaStreamIds()[0]; newMediaDescription.mediaStreamTrackId = sender.trackId(); newMediaDescription.addSsrc(cryptographicallyRandomNumber()); } newMediaDescription.mode = transceiver->directionString(); newMediaDescription.type = remoteMediaDescription.type; newMediaDescription.mid = remoteMediaDescription.mid; newMediaDescription.dtlsSetup = remoteMediaDescription.dtlsSetup == "active" ? "passive" : "active"; newMediaDescription.dtlsFingerprintHashFunction = m_dtlsFingerprintFunction; newMediaDescription.dtlsFingerprint = m_dtlsFingerprint; newMediaDescription.cname = m_cname; newMediaDescription.iceUfrag = m_iceUfrag; newMediaDescription.icePassword = m_icePassword; configurationSnapshot->addMediaDescription(WTFMove(newMediaDescription)); } PeerMediaDescription& localMediaDescription = configurationSnapshot->mediaDescriptions()[i]; localMediaDescription.payloads = remoteMediaDescription.payloads; localMediaDescription.rtcpMux = remoteMediaDescription.rtcpMux; if (!localMediaDescription.ssrcs.size()) localMediaDescription.addSsrc(cryptographicallyRandomNumber()); if (localMediaDescription.dtlsSetup == "actpass") localMediaDescription.dtlsSetup = "passive"; transceivers.removeFirst(transceiver); } // Unassociated (non-stopped) transceivers need to be negotiated in a follow-up offer. if (hasUnassociatedTransceivers(transceivers)) markAsNeedingNegotiation(); String sdp; SDPProcessor::Result result = m_sdpProcessor->generate(*configurationSnapshot, sdp); if (result != SDPProcessor::Result::Success) { createAnswerFailed(Exception { OperationError, "SDPProcessor internal error" }); return; } createAnswerSucceeded(WTFMove(sdp)); } static RealtimeMediaSourceMap createSourceMap(const MediaDescriptionVector& remoteMediaDescriptions, unsigned localMediaDescriptionCount, const RtpTransceiverVector& transceivers) { RealtimeMediaSourceMap sourceMap; for (unsigned i = 0; i < remoteMediaDescriptions.size() && i < localMediaDescriptionCount; ++i) { auto& remoteMediaDescription = remoteMediaDescriptions[i]; if (remoteMediaDescription.type != "audio" && remoteMediaDescription.type != "video") continue; RTCRtpTransceiver* transceiver = matchTransceiverByMid(transceivers, remoteMediaDescription.mid); if (transceiver) { if (transceiver->hasSendingDirection() && transceiver->sender().track()) sourceMap.set(transceiver->mid(), &transceiver->sender().track()->source()); } } return sourceMap; } void MediaEndpointPeerConnection::doSetLocalDescription(RTCSessionDescription& description) { runTask([this, protectedDescription = RefPtr(&description)]() mutable { setLocalDescriptionTask(WTFMove(protectedDescription)); }); } void MediaEndpointPeerConnection::setLocalDescriptionTask(RefPtr&& description) { if (m_peerConnection.internalSignalingState() == SignalingState::Closed) return; auto result = MediaEndpointSessionDescription::create(WTFMove(description), *m_sdpProcessor); if (result.hasException()) { setLocalDescriptionFailed(result.releaseException()); return; } auto newDescription = result.releaseReturnValue(); const RtpTransceiverVector& transceivers = m_peerConnection.getTransceivers(); const MediaDescriptionVector& mediaDescriptions = newDescription->configuration()->mediaDescriptions(); MediaEndpointSessionDescription* localDescription = internalLocalDescription(); unsigned previousNumberOfMediaDescriptions = localDescription ? localDescription->configuration()->mediaDescriptions().size() : 0; bool hasNewMediaDescriptions = mediaDescriptions.size() > previousNumberOfMediaDescriptions; bool isInitiator = newDescription->type() == RTCSessionDescription::SdpType::Offer; if (hasNewMediaDescriptions) { MediaEndpoint::UpdateResult result = m_mediaEndpoint->updateReceiveConfiguration(newDescription->configuration(), isInitiator); if (result == MediaEndpoint::UpdateResult::SuccessWithIceRestart) { if (m_peerConnection.internalIceGatheringState() != IceGatheringState::Gathering) m_peerConnection.updateIceGatheringState(IceGatheringState::Gathering); if (m_peerConnection.internalIceConnectionState() != IceConnectionState::Completed) m_peerConnection.updateIceConnectionState(IceConnectionState::Connected); LOG_ERROR("ICE restart is not implemented"); notImplemented(); } else if (result == MediaEndpoint::UpdateResult::Failed) { setLocalDescriptionFailed(Exception { OperationError, "Unable to apply session description" }); return; } // Associate media descriptions with transceivers (set provisional mid to 'final' mid). for (auto& mediaDescription : mediaDescriptions) { RTCRtpTransceiver* transceiver = matchTransceiver(transceivers, [&mediaDescription] (RTCRtpTransceiver& current) { return current.provisionalMid() == mediaDescription.mid; }); if (transceiver) transceiver->setMid(transceiver->provisionalMid()); } } if (internalRemoteDescription()) { MediaEndpointSessionConfiguration* remoteConfiguration = internalRemoteDescription()->configuration(); RealtimeMediaSourceMap sendSourceMap = createSourceMap(remoteConfiguration->mediaDescriptions(), mediaDescriptions.size(), transceivers); if (m_mediaEndpoint->updateSendConfiguration(remoteConfiguration, sendSourceMap, isInitiator) == MediaEndpoint::UpdateResult::Failed) { setLocalDescriptionFailed(Exception { OperationError, "Unable to apply session description" }); return; } } if (!hasUnassociatedTransceivers(transceivers)) clearNegotiationNeededState(); SignalingState newSignalingState; // Update state and local descriptions according to setLocal/RemoteDescription processing model switch (newDescription->type()) { case RTCSessionDescription::SdpType::Offer: m_pendingLocalDescription = WTFMove(newDescription); newSignalingState = SignalingState::HaveLocalOffer; break; case RTCSessionDescription::SdpType::Answer: m_currentLocalDescription = WTFMove(newDescription); m_currentRemoteDescription = m_pendingRemoteDescription; m_pendingLocalDescription = nullptr; m_pendingRemoteDescription = nullptr; newSignalingState = SignalingState::Stable; break; case RTCSessionDescription::SdpType::Rollback: m_pendingLocalDescription = nullptr; newSignalingState = SignalingState::Stable; break; case RTCSessionDescription::SdpType::Pranswer: m_pendingLocalDescription = WTFMove(newDescription); newSignalingState = SignalingState::HaveLocalPrAnswer; break; } updateSignalingState(newSignalingState); if (m_peerConnection.internalIceGatheringState() == IceGatheringState::New && mediaDescriptions.size()) m_peerConnection.updateIceGatheringState(IceGatheringState::Gathering); markAsNeedingNegotiation(); setLocalDescriptionSucceeded(); } RefPtr MediaEndpointPeerConnection::localDescription() const { return createRTCSessionDescription(internalLocalDescription()); } RefPtr MediaEndpointPeerConnection::currentLocalDescription() const { return createRTCSessionDescription(m_currentLocalDescription.get()); } RefPtr MediaEndpointPeerConnection::pendingLocalDescription() const { return createRTCSessionDescription(m_pendingLocalDescription.get()); } void MediaEndpointPeerConnection::doSetRemoteDescription(RTCSessionDescription& description) { runTask([this, protectedDescription = RefPtr(&description)]() mutable { setRemoteDescriptionTask(WTFMove(protectedDescription)); }); } void MediaEndpointPeerConnection::setRemoteDescriptionTask(RefPtr&& description) { auto result = MediaEndpointSessionDescription::create(WTFMove(description), *m_sdpProcessor); if (result.hasException()) { setRemoteDescriptionFailed(result.releaseException()); return; } auto newDescription = result.releaseReturnValue(); auto& mediaDescriptions = newDescription->configuration()->mediaDescriptions(); for (auto& mediaDescription : mediaDescriptions) { if (mediaDescription.type != "audio" && mediaDescription.type != "video") continue; mediaDescription.payloads = m_mediaEndpoint->filterPayloads(mediaDescription.payloads, mediaDescription.type == "audio" ? m_defaultAudioPayloads : m_defaultVideoPayloads); } bool isInitiator = newDescription->type() == RTCSessionDescription::SdpType::Answer; const RtpTransceiverVector& transceivers = m_peerConnection.getTransceivers(); RealtimeMediaSourceMap sendSourceMap; if (internalLocalDescription()) sendSourceMap = createSourceMap(mediaDescriptions, internalLocalDescription()->configuration()->mediaDescriptions().size(), transceivers); if (m_mediaEndpoint->updateSendConfiguration(newDescription->configuration(), sendSourceMap, isInitiator) == MediaEndpoint::UpdateResult::Failed) { setRemoteDescriptionFailed(Exception { OperationError, "Unable to apply session description" }); return; } // One legacy MediaStreamEvent will be fired for every new MediaStream created as this remote description is set. Vector> legacyMediaStreamEvents; for (auto& mediaDescription : mediaDescriptions) { RTCRtpTransceiver* transceiver = matchTransceiverByMid(transceivers, mediaDescription.mid); if (!transceiver) { bool receiveOnlyFlag = false; if (mediaDescription.mode == "sendrecv" || mediaDescription.mode == "recvonly") { // Try to match an existing transceiver. transceiver = matchTransceiver(transceivers, [&mediaDescription] (RTCRtpTransceiver& current) { return !current.stopped() && current.mid().isNull() && current.sender().trackKind() == mediaDescription.type; }); if (transceiver) { // This transceiver was created locally with a provisional mid. Its real mid will now be set by the remote // description so we need to update the mid of the transceiver's muted source to preserve the association. transceiver->setMid(mediaDescription.mid); m_mediaEndpoint->replaceMutedRemoteSourceMid(transceiver->provisionalMid(), mediaDescription.mid); } else receiveOnlyFlag = true; } if (!transceiver) { auto sender = RTCRtpSender::create(mediaDescription.type, Vector(), m_peerConnection.senderClient()); auto receiver = createReceiver(mediaDescription.mid, mediaDescription.type, mediaDescription.mediaStreamTrackId); auto newTransceiver = RTCRtpTransceiver::create(WTFMove(sender), WTFMove(receiver)); newTransceiver->setMid(mediaDescription.mid); if (receiveOnlyFlag) newTransceiver->disableSendingDirection(); transceiver = newTransceiver.ptr(); m_peerConnection.addTransceiver(WTFMove(newTransceiver)); } } if (mediaDescription.mode == "sendrecv" || mediaDescription.mode == "sendonly") { auto& receiver = transceiver->receiver(); if (receiver.isDispatched()) continue; receiver.setDispatched(true); Vector mediaStreamIds; if (!mediaDescription.mediaStreamId.isEmpty()) mediaStreamIds.append(mediaDescription.mediaStreamId); // A remote track can be associated with 0..* MediaStreams. We create a new stream for // a track in case of an unrecognized stream id, or just add the track if the stream // already exists. HashMap> trackEventMediaStreams; for (auto& id : mediaStreamIds) { if (m_remoteStreamMap.contains(id)) { RefPtr stream = m_remoteStreamMap.get(id); stream->addTrack(*receiver.track()); trackEventMediaStreams.add(id, WTFMove(stream)); } else { auto newStream = MediaStream::create(*m_peerConnection.scriptExecutionContext(), MediaStreamTrackVector({ receiver.track() })); m_remoteStreamMap.add(id, newStream.copyRef()); legacyMediaStreamEvents.append(MediaStreamEvent::create(eventNames().addstreamEvent, false, false, newStream.copyRef())); trackEventMediaStreams.add(id, WTFMove(newStream)); } } Vector> streams; copyValuesToVector(trackEventMediaStreams, streams); m_peerConnection.fireEvent(RTCTrackEvent::create(eventNames().trackEvent, false, false, &receiver, receiver.track(), WTFMove(streams), transceiver)); } } // Fire legacy addstream events. for (auto& event : legacyMediaStreamEvents) m_peerConnection.fireEvent(*event); SignalingState newSignalingState; // Update state and local descriptions according to setLocal/RemoteDescription processing model switch (newDescription->type()) { case RTCSessionDescription::SdpType::Offer: m_pendingRemoteDescription = WTFMove(newDescription); newSignalingState = SignalingState::HaveRemoteOffer; break; case RTCSessionDescription::SdpType::Answer: m_currentRemoteDescription = WTFMove(newDescription); m_currentLocalDescription = m_pendingLocalDescription; m_pendingRemoteDescription = nullptr; m_pendingLocalDescription = nullptr; newSignalingState = SignalingState::Stable; break; case RTCSessionDescription::SdpType::Rollback: m_pendingRemoteDescription = nullptr; newSignalingState = SignalingState::Stable; break; case RTCSessionDescription::SdpType::Pranswer: m_pendingRemoteDescription = WTFMove(newDescription); newSignalingState = SignalingState::HaveRemotePrAnswer; break; } updateSignalingState(newSignalingState); setRemoteDescriptionSucceeded(); } RefPtr MediaEndpointPeerConnection::remoteDescription() const { return createRTCSessionDescription(internalRemoteDescription()); } RefPtr MediaEndpointPeerConnection::currentRemoteDescription() const { return createRTCSessionDescription(m_currentRemoteDescription.get()); } RefPtr MediaEndpointPeerConnection::pendingRemoteDescription() const { return createRTCSessionDescription(m_pendingRemoteDescription.get()); } void MediaEndpointPeerConnection::setConfiguration(MediaEndpointConfiguration&& configuration) { m_mediaEndpoint->setConfiguration(WTFMove(configuration)); } void MediaEndpointPeerConnection::doAddIceCandidate(RTCIceCandidate& rtcCandidate) { runTask([this, protectedCandidate = RefPtr(&rtcCandidate)]() mutable { addIceCandidateTask(*protectedCandidate); }); } void MediaEndpointPeerConnection::addIceCandidateTask(RTCIceCandidate& rtcCandidate) { if (!internalRemoteDescription()) { addIceCandidateFailed(Exception { INVALID_STATE_ERR, "No remote description set" }); return; } auto& remoteMediaDescriptions = internalRemoteDescription()->configuration()->mediaDescriptions(); PeerMediaDescription* targetMediaDescription = nullptr; // When identifying the target media description, sdpMid takes precedence over sdpMLineIndex // if both are present. if (!rtcCandidate.sdpMid().isNull()) { const String& mid = rtcCandidate.sdpMid(); for (auto& description : remoteMediaDescriptions) { if (description.mid == mid) { targetMediaDescription = &description; break; } } if (!targetMediaDescription) { addIceCandidateFailed(Exception { OperationError, "sdpMid did not match any media description" }); return; } } else if (rtcCandidate.sdpMLineIndex()) { unsigned short sdpMLineIndex = rtcCandidate.sdpMLineIndex().value(); if (sdpMLineIndex >= remoteMediaDescriptions.size()) { addIceCandidateFailed(Exception { OperationError, "sdpMLineIndex is out of range" }); return; } targetMediaDescription = &remoteMediaDescriptions[sdpMLineIndex]; } else { ASSERT_NOT_REACHED(); return; } auto result = m_sdpProcessor->parseCandidateLine(rtcCandidate.candidate()); if (result.parsingStatus() != SDPProcessor::Result::Success) { if (result.parsingStatus() == SDPProcessor::Result::ParseError) addIceCandidateFailed(Exception { OperationError, "Invalid candidate content" }); else LOG_ERROR("SDPProcessor internal error"); return; } ASSERT(targetMediaDescription); m_mediaEndpoint->addRemoteCandidate(result.candidate(), targetMediaDescription->mid, targetMediaDescription->iceUfrag, targetMediaDescription->icePassword); targetMediaDescription->addIceCandidate(WTFMove(result.candidate())); addIceCandidateSucceeded(); } void MediaEndpointPeerConnection::getStats(MediaStreamTrack* track, Ref&& promise) { m_mediaEndpoint->getStats(track, WTFMove(promise)); } Vector> MediaEndpointPeerConnection::getRemoteStreams() const { Vector> remoteStreams; copyValuesToVector(m_remoteStreamMap, remoteStreams); return remoteStreams; } Ref MediaEndpointPeerConnection::createReceiver(const String& transceiverMid, const String& trackKind, const String& trackId) { RealtimeMediaSource::Type sourceType = trackKind == "audio" ? RealtimeMediaSource::Type::Audio : RealtimeMediaSource::Type::Video; // Create a muted remote source that will be unmuted once media starts arriving. auto remoteSource = m_mediaEndpoint->createMutedRemoteSource(transceiverMid, sourceType); auto remoteTrackPrivate = MediaStreamTrackPrivate::create(WTFMove(remoteSource), String(trackId)); auto remoteTrack = MediaStreamTrack::create(*m_peerConnection.scriptExecutionContext(), WTFMove(remoteTrackPrivate)); return RTCRtpReceiver::create(WTFMove(remoteTrack)); } std::unique_ptr MediaEndpointPeerConnection::createDataChannelHandler(const String& label, const RTCDataChannelInit& options) { return m_mediaEndpoint->createDataChannelHandler(label, options); } void MediaEndpointPeerConnection::replaceTrack(RTCRtpSender& sender, RefPtr&& withTrack, DOMPromise&& promise) { RTCRtpTransceiver* transceiver = matchTransceiver(m_peerConnection.getTransceivers(), [&sender] (RTCRtpTransceiver& current) { return ¤t.sender() == &sender; }); ASSERT(transceiver); const String& mid = transceiver->mid(); if (mid.isNull()) { // Transceiver is not associated with a media description yet. sender.setTrack(WTFMove(withTrack)); promise.resolve(); return; } runTask([this, protectedSender = RefPtr(&sender), mid, protectedTrack = WTFMove(withTrack), protectedPromise = WTFMove(promise)]() mutable { replaceTrackTask(*protectedSender, mid, WTFMove(protectedTrack), protectedPromise); }); } void MediaEndpointPeerConnection::replaceTrackTask(RTCRtpSender& sender, const String& mid, RefPtr&& withTrack, DOMPromise& promise) { if (m_peerConnection.internalSignalingState() == SignalingState::Closed) return; m_mediaEndpoint->replaceSendSource(withTrack->source(), mid); sender.setTrack(WTFMove(withTrack)); promise.resolve(); } void MediaEndpointPeerConnection::doStop() { m_mediaEndpoint->stop(); } void MediaEndpointPeerConnection::emulatePlatformEvent(const String& action) { m_mediaEndpoint->emulatePlatformEvent(action); } MediaEndpointSessionDescription* MediaEndpointPeerConnection::internalLocalDescription() const { return m_pendingLocalDescription ? m_pendingLocalDescription.get() : m_currentLocalDescription.get(); } MediaEndpointSessionDescription* MediaEndpointPeerConnection::internalRemoteDescription() const { return m_pendingRemoteDescription ? m_pendingRemoteDescription.get() : m_currentRemoteDescription.get(); } RefPtr MediaEndpointPeerConnection::createRTCSessionDescription(MediaEndpointSessionDescription* description) const { return description ? description->toRTCSessionDescription(*m_sdpProcessor) : nullptr; } void MediaEndpointPeerConnection::gotDtlsFingerprint(const String& fingerprint, const String& fingerprintFunction) { ASSERT(isMainThread()); m_dtlsFingerprint = fingerprint; m_dtlsFingerprintFunction = fingerprintFunction; startRunningTasks(); } void MediaEndpointPeerConnection::gotIceCandidate(const String& mid, IceCandidate&& candidate) { ASSERT(isMainThread()); auto& mediaDescriptions = internalLocalDescription()->configuration()->mediaDescriptions(); size_t mediaDescriptionIndex = notFound; for (size_t i = 0; i < mediaDescriptions.size(); ++i) { if (mediaDescriptions[i].mid == mid) { mediaDescriptionIndex = i; break; } } ASSERT(mediaDescriptionIndex != notFound); String candidateLine; auto result = m_sdpProcessor->generateCandidateLine(candidate, candidateLine); if (result != SDPProcessor::Result::Success) { LOG_ERROR("SDPProcessor internal error"); return; } mediaDescriptions[mediaDescriptionIndex].addIceCandidate(WTFMove(candidate)); fireICECandidateEvent(RTCIceCandidate::create(candidateLine, mid, mediaDescriptionIndex)); } void MediaEndpointPeerConnection::doneGatheringCandidates(const String& mid) { ASSERT(isMainThread()); RtpTransceiverVector transceivers = RtpTransceiverVector(m_peerConnection.getTransceivers()); RTCRtpTransceiver* notifyingTransceiver = matchTransceiverByMid(transceivers, mid); ASSERT(notifyingTransceiver); notifyingTransceiver->iceTransport().setGatheringState(RTCIceTransport::GatheringState::Complete); // Don't notify the script if there are transceivers still gathering. RTCRtpTransceiver* stillGatheringTransceiver = matchTransceiver(transceivers, [] (RTCRtpTransceiver& current) { return !current.stopped() && !current.mid().isNull() && current.iceTransport().gatheringState() != RTCIceTransport::GatheringState::Complete; }); if (!stillGatheringTransceiver) PeerConnectionBackend::doneGatheringCandidates(); } static RTCIceTransport::TransportState deriveAggregatedIceConnectionState(const Vector& states) { unsigned newCount = 0; unsigned checkingCount = 0; unsigned connectedCount = 0; unsigned completedCount = 0; unsigned failedCount = 0; unsigned disconnectedCount = 0; unsigned closedCount = 0; for (auto& state : states) { switch (state) { case RTCIceTransport::TransportState::New: ++newCount; break; case RTCIceTransport::TransportState::Checking: ++checkingCount; break; case RTCIceTransport::TransportState::Connected: ++connectedCount; break; case RTCIceTransport::TransportState::Completed: ++completedCount; break; case RTCIceTransport::TransportState::Failed: ++failedCount; break; case RTCIceTransport::TransportState::Disconnected: ++disconnectedCount; break; case RTCIceTransport::TransportState::Closed: ++closedCount; break; } } // The aggregated RTCIceConnectionState is derived from the RTCIceTransportState of all RTCIceTransports. if ((newCount > 0 && !checkingCount && !failedCount && !disconnectedCount) || (closedCount == states.size())) return RTCIceTransport::TransportState::New; if (checkingCount > 0 && !failedCount && !disconnectedCount) return RTCIceTransport::TransportState::Checking; if ((connectedCount + completedCount + closedCount) == states.size() && connectedCount > 0) return RTCIceTransport::TransportState::Connected; if ((completedCount + closedCount) == states.size() && completedCount > 0) return RTCIceTransport::TransportState::Completed; if (failedCount > 0) return RTCIceTransport::TransportState::Failed; if (disconnectedCount > 0) // Any failed caught above. return RTCIceTransport::TransportState::Disconnected; ASSERT_NOT_REACHED(); return RTCIceTransport::TransportState::New; } void MediaEndpointPeerConnection::iceTransportStateChanged(const String& mid, MediaEndpoint::IceTransportState mediaEndpointIceTransportState) { ASSERT(isMainThread()); RTCRtpTransceiver* transceiver = matchTransceiverByMid(m_peerConnection.getTransceivers(), mid); ASSERT(transceiver); RTCIceTransport::TransportState transportState = static_cast(mediaEndpointIceTransportState); transceiver->iceTransport().setTransportState(transportState); // Determine if the script needs to be notified. Vector transportStates; for (auto& transceiver : m_peerConnection.getTransceivers()) transportStates.append(transceiver->iceTransport().transportState()); RTCIceTransport::TransportState derivedState = deriveAggregatedIceConnectionState(transportStates); m_peerConnection.updateIceConnectionState(static_cast(derivedState)); } } // namespace WebCore #endif // ENABLE(WEB_RTC)