| /* |
| * Copyright (C) 2012 Google Inc. All rights reserved. |
| * Copyright (C) 2013 Nokia Corporation and/or its subsidiary(-ies). |
| * 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 Google Inc. 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(MEDIA_STREAM) |
| |
| #include "RTCPeerConnection.h" |
| |
| #include "DOMError.h" |
| #include "Document.h" |
| #include "Event.h" |
| #include "ExceptionCode.h" |
| #include "Frame.h" |
| #include "JSDOMError.h" |
| #include "MediaStream.h" |
| #include "MediaStreamTrack.h" |
| #include "RTCConfiguration.h" |
| #include "RTCDataChannel.h" |
| #include "RTCIceCandidate.h" |
| #include "RTCIceCandidateEvent.h" |
| #include "RTCOfferAnswerOptions.h" |
| #include "RTCSessionDescription.h" |
| #include "RTCTrackEvent.h" |
| #include <wtf/MainThread.h> |
| #include <wtf/text/Base64.h> |
| |
| namespace WebCore { |
| |
| using namespace PeerConnection; |
| using namespace PeerConnectionStates; |
| |
| RefPtr<RTCPeerConnection> RTCPeerConnection::create(ScriptExecutionContext& context, const Dictionary& rtcConfiguration, ExceptionCode& ec) |
| { |
| RefPtr<RTCConfiguration> configuration = RTCConfiguration::create(rtcConfiguration, ec); |
| if (ec) |
| return nullptr; |
| |
| RefPtr<RTCPeerConnection> peerConnection = adoptRef(new RTCPeerConnection(context, WTFMove(configuration), ec)); |
| peerConnection->suspendIfNeeded(); |
| if (ec) |
| return nullptr; |
| |
| return peerConnection; |
| } |
| |
| RTCPeerConnection::RTCPeerConnection(ScriptExecutionContext& context, RefPtr<RTCConfiguration>&& configuration, ExceptionCode& ec) |
| : ActiveDOMObject(&context) |
| , m_signalingState(SignalingState::Stable) |
| , m_iceGatheringState(IceGatheringState::New) |
| , m_iceConnectionState(IceConnectionState::New) |
| , m_configuration(WTFMove(configuration)) |
| , m_stopped(false) |
| { |
| Document& document = downcast<Document>(context); |
| |
| if (!document.frame()) { |
| ec = NOT_SUPPORTED_ERR; |
| return; |
| } |
| |
| m_backend = PeerConnectionBackend::create(this); |
| if (!m_backend) { |
| ec = NOT_SUPPORTED_ERR; |
| return; |
| } |
| |
| m_backend->setConfiguration(*m_configuration); |
| } |
| |
| RTCPeerConnection::~RTCPeerConnection() |
| { |
| stop(); |
| } |
| |
| RefPtr<RTCRtpSender> RTCPeerConnection::addTrack(RefPtr<MediaStreamTrack>&& track, Vector<MediaStream*> streams, ExceptionCode& ec) |
| { |
| if (!track) { |
| ec = TypeError; |
| return nullptr; |
| } |
| |
| if (m_signalingState == SignalingState::Closed) { |
| ec = INVALID_STATE_ERR; |
| return nullptr; |
| } |
| |
| // Require at least one stream until https://github.com/w3c/webrtc-pc/issues/288 is resolved |
| if (!streams.size()) { |
| ec = NOT_SUPPORTED_ERR; |
| return nullptr; |
| } |
| |
| for (auto& sender : m_senderSet) { |
| if (sender->trackId() == track->id()) { |
| // FIXME: Spec says InvalidParameter |
| ec = INVALID_MODIFICATION_ERR; |
| return nullptr; |
| } |
| } |
| |
| Vector<String> mediaStreamIds; |
| for (auto stream : streams) |
| mediaStreamIds.append(stream->id()); |
| |
| RefPtr<RTCRtpSender> sender = RTCRtpSender::create(WTFMove(track), WTFMove(mediaStreamIds), *this); |
| m_senderSet.append(sender); |
| |
| m_backend->markAsNeedingNegotiation(); |
| |
| return sender; |
| } |
| |
| void RTCPeerConnection::removeTrack(RTCRtpSender* sender, ExceptionCode& ec) |
| { |
| if (!sender) { |
| ec = TypeError; |
| return; |
| } |
| |
| if (m_signalingState == SignalingState::Closed) { |
| ec = INVALID_STATE_ERR; |
| return; |
| } |
| |
| if (!m_senderSet.contains(sender)) |
| return; |
| |
| sender->stop(); |
| |
| m_backend->markAsNeedingNegotiation(); |
| } |
| |
| void RTCPeerConnection::queuedCreateOffer(const Dictionary& offerOptions, SessionDescriptionPromise&& promise) |
| { |
| if (m_signalingState == SignalingState::Closed) { |
| promise.reject(DOMError::create("InvalidStateError")); |
| return; |
| } |
| |
| ExceptionCode ec = 0; |
| RefPtr<RTCOfferOptions> options = RTCOfferOptions::create(offerOptions, ec); |
| if (ec) { |
| promise.reject(DOMError::create("Invalid createOffer argument")); |
| return; |
| } |
| ASSERT(options); |
| |
| m_backend->createOffer(*options, WTFMove(promise)); |
| } |
| |
| void RTCPeerConnection::queuedCreateAnswer(const Dictionary& answerOptions, SessionDescriptionPromise&& promise) |
| { |
| if (m_signalingState == SignalingState::Closed) { |
| promise.reject(DOMError::create("InvalidStateError")); |
| return; |
| } |
| |
| ExceptionCode ec = 0; |
| RefPtr<RTCAnswerOptions> options = RTCAnswerOptions::create(answerOptions, ec); |
| if (ec) { |
| promise.reject(DOMError::create("Invalid createAnswer argument")); |
| return; |
| } |
| |
| m_backend->createAnswer(*options, WTFMove(promise)); |
| } |
| |
| void RTCPeerConnection::queuedSetLocalDescription(RTCSessionDescription* description, PeerConnection::VoidPromise&& promise) |
| { |
| if (m_signalingState == SignalingState::Closed) { |
| promise.reject(DOMError::create("InvalidStateError")); |
| return; |
| } |
| |
| ASSERT(description); |
| m_backend->setLocalDescription(*description, WTFMove(promise)); |
| } |
| |
| RefPtr<RTCSessionDescription> RTCPeerConnection::localDescription() const |
| { |
| return m_backend->localDescription(); |
| } |
| |
| RefPtr<RTCSessionDescription> RTCPeerConnection::currentLocalDescription() const |
| { |
| return m_backend->currentLocalDescription(); |
| } |
| |
| RefPtr<RTCSessionDescription> RTCPeerConnection::pendingLocalDescription() const |
| { |
| return m_backend->pendingLocalDescription(); |
| } |
| |
| void RTCPeerConnection::queuedSetRemoteDescription(RTCSessionDescription* description, PeerConnection::VoidPromise&& promise) |
| { |
| if (m_signalingState == SignalingState::Closed) { |
| promise.reject(DOMError::create("InvalidStateError")); |
| return; |
| } |
| |
| ASSERT(description); |
| m_backend->setRemoteDescription(*description, WTFMove(promise)); |
| } |
| |
| RefPtr<RTCSessionDescription> RTCPeerConnection::remoteDescription() const |
| { |
| return m_backend->remoteDescription(); |
| } |
| |
| RefPtr<RTCSessionDescription> RTCPeerConnection::currentRemoteDescription() const |
| { |
| return m_backend->currentRemoteDescription(); |
| } |
| |
| RefPtr<RTCSessionDescription> RTCPeerConnection::pendingRemoteDescription() const |
| { |
| return m_backend->pendingRemoteDescription(); |
| } |
| |
| void RTCPeerConnection::queuedAddIceCandidate(RTCIceCandidate* rtcCandidate, VoidPromise&& promise) |
| { |
| if (m_signalingState == SignalingState::Closed) { |
| promise.reject(DOMError::create("InvalidStateError")); |
| return; |
| } |
| |
| ASSERT(rtcCandidate); |
| m_backend->addIceCandidate(*rtcCandidate, WTFMove(promise)); |
| } |
| |
| String RTCPeerConnection::signalingState() const |
| { |
| switch (m_signalingState) { |
| case SignalingState::Stable: |
| return ASCIILiteral("stable"); |
| case SignalingState::HaveLocalOffer: |
| return ASCIILiteral("have-local-offer"); |
| case SignalingState::HaveRemoteOffer: |
| return ASCIILiteral("have-remote-offer"); |
| case SignalingState::HaveLocalPrAnswer: |
| return ASCIILiteral("have-local-pranswer"); |
| case SignalingState::HaveRemotePrAnswer: |
| return ASCIILiteral("have-remote-pranswer"); |
| case SignalingState::Closed: |
| return ASCIILiteral("closed"); |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return String(); |
| } |
| |
| String RTCPeerConnection::iceGatheringState() const |
| { |
| switch (m_iceGatheringState) { |
| case IceGatheringState::New: |
| return ASCIILiteral("new"); |
| case IceGatheringState::Gathering: |
| return ASCIILiteral("gathering"); |
| case IceGatheringState::Complete: |
| return ASCIILiteral("complete"); |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return String(); |
| } |
| |
| String RTCPeerConnection::iceConnectionState() const |
| { |
| switch (m_iceConnectionState) { |
| case IceConnectionState::New: |
| return ASCIILiteral("new"); |
| case IceConnectionState::Checking: |
| return ASCIILiteral("checking"); |
| case IceConnectionState::Connected: |
| return ASCIILiteral("connected"); |
| case IceConnectionState::Completed: |
| return ASCIILiteral("completed"); |
| case IceConnectionState::Failed: |
| return ASCIILiteral("failed"); |
| case IceConnectionState::Disconnected: |
| return ASCIILiteral("disconnected"); |
| case IceConnectionState::Closed: |
| return ASCIILiteral("closed"); |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return String(); |
| } |
| |
| RTCConfiguration* RTCPeerConnection::getConfiguration() const |
| { |
| return m_configuration.get(); |
| } |
| |
| void RTCPeerConnection::setConfiguration(const Dictionary& configuration, ExceptionCode& ec) |
| { |
| if (configuration.isUndefinedOrNull()) { |
| ec = TypeError; |
| return; |
| } |
| |
| if (m_signalingState == SignalingState::Closed) { |
| ec = INVALID_STATE_ERR; |
| return; |
| } |
| |
| RefPtr<RTCConfiguration> newConfiguration = RTCConfiguration::create(configuration, ec); |
| if (ec) |
| return; |
| |
| m_configuration = WTFMove(newConfiguration); |
| m_backend->setConfiguration(*m_configuration); |
| } |
| |
| void RTCPeerConnection::privateGetStats(MediaStreamTrack* selector, PeerConnection::StatsPromise&& promise) |
| { |
| m_backend->getStats(selector, WTFMove(promise)); |
| } |
| |
| void RTCPeerConnection::privateGetStats(PeerConnection::StatsPromise&& promise) |
| { |
| privateGetStats(nullptr, WTFMove(promise)); |
| } |
| |
| RefPtr<RTCDataChannel> RTCPeerConnection::createDataChannel(String, const Dictionary&, ExceptionCode& ec) |
| { |
| if (m_signalingState == SignalingState::Closed) { |
| ec = INVALID_STATE_ERR; |
| return nullptr; |
| } |
| |
| return nullptr; |
| } |
| |
| void RTCPeerConnection::close() |
| { |
| if (m_signalingState == SignalingState::Closed) |
| return; |
| |
| m_signalingState = SignalingState::Closed; |
| } |
| |
| void RTCPeerConnection::stop() |
| { |
| if (m_stopped) |
| return; |
| |
| m_stopped = true; |
| m_iceConnectionState = IceConnectionState::Closed; |
| m_signalingState = SignalingState::Closed; |
| } |
| |
| const char* RTCPeerConnection::activeDOMObjectName() const |
| { |
| return "RTCPeerConnection"; |
| } |
| |
| bool RTCPeerConnection::canSuspendForDocumentSuspension() const |
| { |
| // FIXME: We should try and do better here. |
| return false; |
| } |
| |
| void RTCPeerConnection::setSignalingState(SignalingState newState) |
| { |
| m_signalingState = newState; |
| } |
| |
| void RTCPeerConnection::updateIceGatheringState(IceGatheringState newState) |
| { |
| scriptExecutionContext()->postTask([=](ScriptExecutionContext&) { |
| m_iceGatheringState = newState; |
| |
| dispatchEvent(Event::create(eventNames().icegatheringstatechangeEvent, false, false)); |
| }); |
| } |
| |
| void RTCPeerConnection::updateIceConnectionState(IceConnectionState newState) |
| { |
| scriptExecutionContext()->postTask([=](ScriptExecutionContext&) { |
| m_iceConnectionState = newState; |
| |
| dispatchEvent(Event::create(eventNames().iceconnectionstatechangeEvent, false, false)); |
| }); |
| } |
| |
| void RTCPeerConnection::scheduleNegotiationNeededEvent() |
| { |
| scriptExecutionContext()->postTask([=](ScriptExecutionContext&) { |
| if (m_backend->isNegotiationNeeded()) { |
| dispatchEvent(Event::create(eventNames().negotiationneededEvent, false, false)); |
| m_backend->clearNegotiationNeededState(); |
| } |
| }); |
| } |
| |
| void RTCPeerConnection::fireEvent(Event& event) |
| { |
| dispatchEvent(event); |
| } |
| |
| void RTCPeerConnection::replaceTrack(RTCRtpSender& sender, MediaStreamTrack& withTrack, PeerConnection::VoidPromise&& promise) |
| { |
| m_backend->replaceTrack(sender, withTrack, WTFMove(promise)); |
| } |
| |
| } // namespace WebCore |
| |
| #endif // ENABLE(MEDIA_STREAM) |