/* * Copyright (C) 2015, 2016 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 "MediaEndpointOwr.h" #include "MediaEndpointSessionConfiguration.h" #include "MediaPayload.h" #include "NotImplemented.h" #include "OpenWebRTCUtilities.h" #include "PeerConnectionStates.h" #include "RTCDataChannelHandler.h" #include "RealtimeAudioSourceOwr.h" #include "RealtimeVideoSourceOwr.h" #include #include #include #include #include #include #include namespace WebCore { static void gotCandidate(OwrSession*, OwrCandidate*, MediaEndpointOwr*); static void candidateGatheringDone(OwrSession*, MediaEndpointOwr*); static void iceConnectionStateChange(OwrSession*, GParamSpec*, MediaEndpointOwr*); static void gotIncomingSource(OwrMediaSession*, OwrMediaSource*, MediaEndpointOwr*); static const Vector candidateTypes = { "host", "srflx", "prflx", "relay" }; static const Vector candidateTcpTypes = { "", "active", "passive", "so" }; static const Vector codecTypes = { "NONE", "PCMU", "PCMA", "OPUS", "H264", "VP8" }; static const char* helperServerRegEx = "(turns|turn|stun):([\\w\\.\\-]+|\\[[\\w\\:]+\\])(:\\d+)?(\\?.+)?"; static const unsigned short helperServerDefaultPort = 3478; static const unsigned short candidateDefaultPort = 9; static std::unique_ptr createMediaEndpointOwr(MediaEndpointClient& client) { return std::unique_ptr(new MediaEndpointOwr(client)); } CreateMediaEndpoint MediaEndpoint::create = createMediaEndpointOwr; MediaEndpointOwr::MediaEndpointOwr(MediaEndpointClient& client) : m_transportAgent(nullptr) , m_client(client) , m_numberOfReceivePreparedSessions(0) , m_numberOfSendPreparedSessions(0) { initializeOpenWebRTC(); GRegexCompileFlags compileFlags = G_REGEX_JAVASCRIPT_COMPAT; GRegexMatchFlags matchFlags = static_cast(0); m_helperServerRegEx = g_regex_new(helperServerRegEx, compileFlags, matchFlags, nullptr); } MediaEndpointOwr::~MediaEndpointOwr() { stop(); g_regex_unref(m_helperServerRegEx); } void MediaEndpointOwr::setConfiguration(MediaEndpointConfiguration&& configuration) { m_configuration = WTFMove(configuration); } std::unique_ptr MediaEndpointOwr::createDataChannelHandler(const String&, const RTCDataChannelInit&) { // FIXME: Implement data channel. ASSERT_NOT_REACHED(); return nullptr; } static void cryptoDataCallback(gchar* privateKey, gchar* certificate, gchar* fingerprint, gchar* fingerprintFunction, gpointer data) { MediaEndpointOwr* mediaEndpoint = (MediaEndpointOwr*) data; mediaEndpoint->dispatchDtlsFingerprint(g_strdup(privateKey), g_strdup(certificate), String(fingerprint), String(fingerprintFunction)); } void MediaEndpointOwr::generateDtlsInfo() { owr_crypto_create_crypto_data(cryptoDataCallback, this); } MediaPayloadVector MediaEndpointOwr::getDefaultAudioPayloads() { MediaPayloadVector payloads; // FIXME: This list should be based on what is available in the platform (bug: http://webkit.org/b/163723) MediaPayload payload1; payload1.type = 111; payload1.encodingName = "OPUS"; payload1.clockRate = 48000; payload1.channels = 2; payloads.append(WTFMove(payload1)); MediaPayload payload2; payload2.type = 8; payload2.encodingName = "PCMA"; payload2.clockRate = 8000; payload2.channels = 1; payloads.append(WTFMove(payload2)); MediaPayload payload3; payload3.type = 0; payload3.encodingName = "PCMU"; payload3.clockRate = 8000; payload3.channels = 1; payloads.append(WTFMove(payload3)); return payloads; } MediaPayloadVector MediaEndpointOwr::getDefaultVideoPayloads() { MediaPayloadVector payloads; // FIXME: This list should be based on what is available in the platform (bug: http://webkit.org/b/163723) MediaPayload payload1; payload1.type = 103; payload1.encodingName = "H264"; payload1.clockRate = 90000; payload1.ccmfir = true; payload1.nackpli = true; payload1.addParameter("packetizationMode", 1); payloads.append(WTFMove(payload1)); MediaPayload payload2; payload2.type = 100; payload2.encodingName = "VP8"; payload2.clockRate = 90000; payload2.ccmfir = true; payload2.nackpli = true; payload2.nack = true; payloads.append(WTFMove(payload2)); MediaPayload payload3; payload3.type = 120; payload3.encodingName = "RTX"; payload3.clockRate = 90000; payload3.addParameter("apt", 100); payload3.addParameter("rtxTime", 200); payloads.append(WTFMove(payload3)); return payloads; } static bool payloadsContainType(const Vector& payloads, unsigned payloadType) { for (auto payload : payloads) { ASSERT(payload); if (payload->type == payloadType) return true; } return false; } MediaPayloadVector MediaEndpointOwr::filterPayloads(const MediaPayloadVector& remotePayloads, const MediaPayloadVector& defaultPayloads) { Vector filteredPayloads; for (auto& remotePayload : remotePayloads) { const MediaPayload* defaultPayload = nullptr; for (auto& p : defaultPayloads) { if (p.encodingName == remotePayload.encodingName.convertToASCIIUppercase()) { defaultPayload = &p; break; } } if (!defaultPayload) continue; if (defaultPayload->parameters.contains("packetizationMode") && remotePayload.parameters.contains("packetizationMode") && (defaultPayload->parameters.get("packetizationMode") != defaultPayload->parameters.get("packetizationMode"))) continue; filteredPayloads.append(&remotePayload); } MediaPayloadVector filteredAptPayloads; for (auto filteredPayload : filteredPayloads) { if (filteredPayload->parameters.contains("apt") && (!payloadsContainType(filteredPayloads, filteredPayload->parameters.get("apt")))) continue; filteredAptPayloads.append(*filteredPayload); } return filteredAptPayloads; } MediaEndpoint::UpdateResult MediaEndpointOwr::updateReceiveConfiguration(MediaEndpointSessionConfiguration* configuration, bool isInitiator) { Vector transceiverConfigs; for (unsigned i = m_transceivers.size(); i < configuration->mediaDescriptions().size(); ++i) { TransceiverConfig config; config.type = SessionTypeMedia; config.isDtlsClient = configuration->mediaDescriptions()[i].dtlsSetup == "active"; config.mid = configuration->mediaDescriptions()[i].mid; transceiverConfigs.append(WTFMove(config)); } ensureTransportAgentAndTransceivers(isInitiator, transceiverConfigs); // Prepare the new sessions. for (unsigned i = m_numberOfReceivePreparedSessions; i < m_transceivers.size(); ++i) { OwrSession* session = m_transceivers[i]->session(); prepareMediaSession(OWR_MEDIA_SESSION(session), &configuration->mediaDescriptions()[i], isInitiator); owr_transport_agent_add_session(m_transportAgent, session); } owr_transport_agent_start(m_transportAgent); m_numberOfReceivePreparedSessions = m_transceivers.size(); return UpdateResult::Success; } static const MediaPayload* findRtxPayload(const MediaPayloadVector& payloads, unsigned apt) { for (auto& payload : payloads) { if (payload.encodingName.convertToASCIIUppercase() == "RTX" && payload.parameters.contains("apt") && (payload.parameters.get("apt") == apt)) return &payload; } return nullptr; } MediaEndpoint::UpdateResult MediaEndpointOwr::updateSendConfiguration(MediaEndpointSessionConfiguration* configuration, const RealtimeMediaSourceMap& sendSourceMap, bool isInitiator) { Vector transceiverConfigs; for (unsigned i = m_transceivers.size(); i < configuration->mediaDescriptions().size(); ++i) { TransceiverConfig config; config.type = SessionTypeMedia; config.isDtlsClient = configuration->mediaDescriptions()[i].dtlsSetup != "active"; config.mid = configuration->mediaDescriptions()[i].mid; transceiverConfigs.append(WTFMove(config)); } ensureTransportAgentAndTransceivers(isInitiator, transceiverConfigs); for (unsigned i = 0; i < m_transceivers.size(); ++i) { auto* session = m_transceivers[i]->session(); auto& mdesc = configuration->mediaDescriptions()[i]; if (mdesc.type == "audio" || mdesc.type == "video") g_object_set(session, "rtcp-mux", mdesc.rtcpMux, nullptr); if (mdesc.iceCandidates.size()) { for (auto& candidate : mdesc.iceCandidates) internalAddRemoteCandidate(session, candidate, mdesc.iceUfrag, mdesc.icePassword); } if (i < m_numberOfSendPreparedSessions) continue; if (!sendSourceMap.contains(mdesc.mid)) continue; const MediaPayload* payload = nullptr; for (auto& p : mdesc.payloads) { if (p.encodingName.convertToASCIIUppercase() != "RTX") { payload = &p; break; } } if (!payload) return UpdateResult::Failed; auto* rtxPayload = findRtxPayload(mdesc.payloads, payload->type); auto* source = static_cast(sendSourceMap.get(mdesc.mid)); ASSERT(codecTypes.find(payload->encodingName.convertToASCIIUppercase()) != notFound); auto codecType = static_cast(codecTypes.find(payload->encodingName.convertToASCIIUppercase())); OwrPayload* sendPayload; if (mdesc.type == "audio") sendPayload = owr_audio_payload_new(codecType, payload->type, payload->clockRate, payload->channels); else { sendPayload = owr_video_payload_new(codecType, payload->type, payload->clockRate, payload->ccmfir, payload->nackpli); g_object_set(sendPayload, "rtx-payload-type", rtxPayload ? rtxPayload->type : -1, "rtx-time", rtxPayload && rtxPayload->parameters.contains("rtxTime") ? rtxPayload->parameters.get("rtxTime") : 0, nullptr); } owr_media_session_set_send_payload(OWR_MEDIA_SESSION(session), sendPayload); owr_media_session_set_send_source(OWR_MEDIA_SESSION(session), source->mediaSource()); // FIXME: Support for group-ssrc SDP line is missing. const Vector receiveSsrcs = mdesc.ssrcs; if (receiveSsrcs.size()) { g_object_set(session, "receive-ssrc", receiveSsrcs[0], nullptr); if (receiveSsrcs.size() == 2) g_object_set(session, "receive-rtx-ssrc", receiveSsrcs[1], nullptr); } m_numberOfSendPreparedSessions = i + 1; } return UpdateResult::Success; } void MediaEndpointOwr::addRemoteCandidate(const IceCandidate& candidate, const String& mid, const String& ufrag, const String& password) { for (auto& transceiver : m_transceivers) { if (transceiver->mid() == mid) { internalAddRemoteCandidate(transceiver->session(), candidate, ufrag, password); break; } } } void MediaEndpointOwr::replaceMutedRemoteSourceMid(const String& oldMid, const String& newMid) { RefPtr remoteSource = m_mutedRemoteSources.take(oldMid); m_mutedRemoteSources.set(newMid, remoteSource); } Ref MediaEndpointOwr::createMutedRemoteSource(const String& mid, RealtimeMediaSource::Type type) { String name; String id("not used"); RefPtr source; switch (type) { case RealtimeMediaSource::Audio: name = "remote audio"; source = adoptRef(new RealtimeAudioSourceOwr(nullptr, id, type, name)); break; case RealtimeMediaSource::Video: name = "remote video"; source = adoptRef(new RealtimeVideoSourceOwr(nullptr, id, type, name)); break; case RealtimeMediaSource::None: ASSERT_NOT_REACHED(); } m_mutedRemoteSources.set(mid, source); return *source; } void MediaEndpointOwr::replaceSendSource(RealtimeMediaSource& newSource, const String& mid) { UNUSED_PARAM(newSource); UNUSED_PARAM(mid); // FIXME: We want to use owr_media_session_set_send_source here, but it doesn't work as intended. // Issue tracked by OpenWebRTC bug: https://github.com/EricssonResearch/openwebrtc/issues/533 notImplemented(); } void MediaEndpointOwr::stop() { if (!m_transportAgent) return; for (auto& transceiver : m_transceivers) owr_media_session_set_send_source(OWR_MEDIA_SESSION(transceiver->session()), nullptr); g_object_unref(m_transportAgent); m_transportAgent = nullptr; } size_t MediaEndpointOwr::transceiverIndexForSession(OwrSession* session) const { for (unsigned i = 0; i < m_transceivers.size(); ++i) { if (m_transceivers[i]->session() == session) return i; } ASSERT_NOT_REACHED(); return notFound; } const String& MediaEndpointOwr::sessionMid(OwrSession* session) const { size_t index = transceiverIndexForSession(session); return m_transceivers[index]->mid(); } OwrTransceiver* MediaEndpointOwr::matchTransceiverByMid(const String& mid) const { for (auto& transceiver : m_transceivers) { if (transceiver->mid() == mid) return transceiver.get(); } return nullptr; } void MediaEndpointOwr::dispatchNewIceCandidate(const String& mid, IceCandidate&& iceCandidate) { m_client.gotIceCandidate(mid, WTFMove(iceCandidate)); } void MediaEndpointOwr::dispatchGatheringDone(const String& mid) { m_client.doneGatheringCandidates(mid); } void MediaEndpointOwr::processIceTransportStateChange(OwrSession* session) { OwrIceState owrIceState; g_object_get(session, "ice-connection-state", &owrIceState, nullptr); OwrTransceiver& transceiver = *m_transceivers[transceiverIndexForSession(session)]; if (owrIceState < transceiver.owrIceState()) return; transceiver.setOwrIceState(owrIceState); // We cannot go to Completed if there may be more remote candidates. if (owrIceState == OWR_ICE_STATE_READY && !transceiver.gotEndOfRemoteCandidates()) return; MediaEndpoint::IceTransportState transportState; switch (owrIceState) { case OWR_ICE_STATE_CONNECTING: transportState = MediaEndpoint::IceTransportState::Checking; break; case OWR_ICE_STATE_CONNECTED: transportState = MediaEndpoint::IceTransportState::Connected; break; case OWR_ICE_STATE_READY: transportState = MediaEndpoint::IceTransportState::Completed; break; case OWR_ICE_STATE_FAILED: transportState = MediaEndpoint::IceTransportState::Failed; break; default: return; } m_client.iceTransportStateChanged(transceiver.mid(), transportState); } void MediaEndpointOwr::dispatchDtlsFingerprint(gchar* privateKey, gchar* certificate, const String& fingerprint, const String& fingerprintFunction) { m_dtlsPrivateKey = String(privateKey); m_dtlsCertificate = String(certificate); g_free(privateKey); g_free(certificate); m_client.gotDtlsFingerprint(fingerprint, fingerprintFunction); } void MediaEndpointOwr::unmuteRemoteSource(const String& mid, OwrMediaSource* realSource) { RefPtr remoteSource = m_mutedRemoteSources.take(mid); if (!remoteSource) { LOG_ERROR("Unable to find muted remote source."); return; } if (!remoteSource->stopped()) remoteSource->swapOutShallowSource(*realSource); } void MediaEndpointOwr::prepareSession(OwrSession* session, PeerMediaDescription* mediaDescription) { g_object_set_data_full(G_OBJECT(session), "ice-ufrag", g_strdup(mediaDescription->iceUfrag.ascii().data()), g_free); g_object_set_data_full(G_OBJECT(session), "ice-password", g_strdup(mediaDescription->icePassword.ascii().data()), g_free); g_signal_connect(session, "on-new-candidate", G_CALLBACK(gotCandidate), this); g_signal_connect(session, "on-candidate-gathering-done", G_CALLBACK(candidateGatheringDone), this); g_signal_connect(session, "notify::ice-connection-state", G_CALLBACK(iceConnectionStateChange), this); } void MediaEndpointOwr::prepareMediaSession(OwrMediaSession* mediaSession, PeerMediaDescription* mediaDescription, bool isInitiator) { prepareSession(OWR_SESSION(mediaSession), mediaDescription); bool useRtcpMux = !isInitiator && mediaDescription->rtcpMux; g_object_set(mediaSession, "rtcp-mux", useRtcpMux, nullptr); if (!mediaDescription->cname.isEmpty() && mediaDescription->ssrcs.size()) { g_object_set(mediaSession, "cname", mediaDescription->cname.ascii().data(), "send-ssrc", mediaDescription->ssrcs[0], nullptr); } g_signal_connect(mediaSession, "on-incoming-source", G_CALLBACK(gotIncomingSource), this); for (auto& payload : mediaDescription->payloads) { if (payload.encodingName.convertToASCIIUppercase() == "RTX") continue; auto* rtxPayload = findRtxPayload(mediaDescription->payloads, payload.type); ASSERT(codecTypes.find(payload.encodingName) != notFound); OwrCodecType codecType = static_cast(codecTypes.find(payload.encodingName.convertToASCIIUppercase())); OwrPayload* receivePayload; if (mediaDescription->type == "audio") receivePayload = owr_audio_payload_new(codecType, payload.type, payload.clockRate, payload.channels); else { receivePayload = owr_video_payload_new(codecType, payload.type, payload.clockRate, payload.ccmfir, payload.nackpli); g_object_set(receivePayload, "rtx-payload-type", rtxPayload ? rtxPayload->type : -1, "rtx-time", rtxPayload && rtxPayload->parameters.contains("rtxTime") ? rtxPayload->parameters.get("rtxTime") : 0, nullptr); } owr_media_session_add_receive_payload(mediaSession, receivePayload); } } struct HelperServerUrl { String protocol; String host; unsigned short port; String query; }; static void parseHelperServerUrl(GRegex& regex, const URL& url, HelperServerUrl& outUrl) { GMatchInfo* matchInfo; if (g_regex_match(®ex, url.string().ascii().data(), static_cast(0), &matchInfo)) { gchar** matches = g_match_info_fetch_all(matchInfo); gint matchCount = g_strv_length(matches); outUrl.protocol = matches[1]; outUrl.host = matches[2][0] == '[' ? String(matches[2] + 1, strlen(matches[2]) - 2) // IPv6 : matches[2]; outUrl.port = 0; if (matchCount >= 4) { String portString = String(matches[3] + 1); // Skip port colon outUrl.port = portString.toUIntStrict(); } if (matchCount == 5) outUrl.query = String(matches[4] + 1); // Skip question mark g_strfreev(matches); } g_match_info_free(matchInfo); } void MediaEndpointOwr::ensureTransportAgentAndTransceivers(bool isInitiator, const Vector& transceiverConfigs) { ASSERT(m_dtlsPrivateKey); ASSERT(m_dtlsCertificate); if (!m_transportAgent) { // FIXME: Handle SDP BUNDLE line from the remote source instead of falling back to balanced. OwrBundlePolicyType bundlePolicy = OWR_BUNDLE_POLICY_TYPE_BALANCED; switch (m_configuration->bundlePolicy) { case PeerConnectionStates::BundlePolicy::Balanced: bundlePolicy = OWR_BUNDLE_POLICY_TYPE_BALANCED; break; case PeerConnectionStates::BundlePolicy::MaxCompat: bundlePolicy = OWR_BUNDLE_POLICY_TYPE_MAX_COMPAT; break; case PeerConnectionStates::BundlePolicy::MaxBundle: bundlePolicy = OWR_BUNDLE_POLICY_TYPE_MAX_BUNDLE; break; default: ASSERT_NOT_REACHED(); }; m_transportAgent = owr_transport_agent_new(false, bundlePolicy); ASSERT(m_configuration); for (auto& server : m_configuration->iceServers) { for (auto& webkitUrl : server.urls) { HelperServerUrl url; // WebKit's URL class can't handle ICE helper server urls properly parseHelperServerUrl(*m_helperServerRegEx, webkitUrl, url); unsigned short port = url.port ? url.port : helperServerDefaultPort; if (url.protocol == "stun") { owr_transport_agent_add_helper_server(m_transportAgent, OWR_HELPER_SERVER_TYPE_STUN, url.host.ascii().data(), port, nullptr, nullptr); } else if (url.protocol == "turn") { OwrHelperServerType serverType = url.query == "transport=tcp" ? OWR_HELPER_SERVER_TYPE_TURN_TCP : OWR_HELPER_SERVER_TYPE_TURN_UDP; owr_transport_agent_add_helper_server(m_transportAgent, serverType, url.host.ascii().data(), port, server.username.ascii().data(), server.credential.ascii().data()); } else if (url.protocol == "turns") { owr_transport_agent_add_helper_server(m_transportAgent, OWR_HELPER_SERVER_TYPE_TURN_TLS, url.host.ascii().data(), port, server.username.ascii().data(), server.credential.ascii().data()); } else ASSERT_NOT_REACHED(); } } } g_object_set(m_transportAgent, "ice-controlling-mode", isInitiator, nullptr); for (auto& config : transceiverConfigs) { OwrSession* session = OWR_SESSION(owr_media_session_new(config.isDtlsClient)); g_object_set(session, "dtls-certificate", m_dtlsCertificate.utf8().data(), "dtls-key", m_dtlsPrivateKey.utf8().data(), nullptr); m_transceivers.append(OwrTransceiver::create(config.mid, session)); } } void MediaEndpointOwr::internalAddRemoteCandidate(OwrSession* session, const IceCandidate& candidate, const String& ufrag, const String& password) { gboolean rtcpMux; g_object_get(session, "rtcp-mux", &rtcpMux, nullptr); if (rtcpMux && candidate.componentId == OWR_COMPONENT_TYPE_RTCP) return; ASSERT(candidateTypes.find(candidate.type) != notFound); OwrCandidateType candidateType = static_cast(candidateTypes.find(candidate.type)); OwrComponentType componentId = static_cast(candidate.componentId); OwrTransportType transportType; if (candidate.transport.convertToASCIIUppercase() == "UDP") transportType = OWR_TRANSPORT_TYPE_UDP; else { ASSERT(candidateTcpTypes.find(candidate.tcpType) != notFound); transportType = static_cast(candidateTcpTypes.find(candidate.tcpType)); } OwrCandidate* owrCandidate = owr_candidate_new(candidateType, componentId); g_object_set(owrCandidate, "transport-type", transportType, "address", candidate.address.ascii().data(), "port", candidate.port, "base-address", candidate.relatedAddress.ascii().data(), "base-port", candidate.relatedPort, "priority", candidate.priority, "foundation", candidate.foundation.ascii().data(), "ufrag", ufrag.ascii().data(), "password", password.ascii().data(), nullptr); owr_session_add_remote_candidate(session, owrCandidate); } static void gotCandidate(OwrSession* session, OwrCandidate* candidate, MediaEndpointOwr* mediaEndpoint) { OwrCandidateType candidateType; gchar* foundation; OwrComponentType componentId; OwrTransportType transportType; gint priority; gchar* address; guint port; gchar* relatedAddress; guint relatedPort; g_object_get(candidate, "type", &candidateType, "foundation", &foundation, "component-type", &componentId, "transport-type", &transportType, "priority", &priority, "address", &address, "port", &port, "base-address", &relatedAddress, "base-port", &relatedPort, nullptr); ASSERT(candidateType >= 0 && candidateType < candidateTypes.size()); ASSERT(transportType >= 0 && transportType < candidateTcpTypes.size()); IceCandidate iceCandidate; iceCandidate.type = candidateTypes[candidateType]; iceCandidate.foundation = foundation; iceCandidate.componentId = componentId; iceCandidate.priority = priority; iceCandidate.address = address; iceCandidate.port = port ? port : candidateDefaultPort; if (transportType == OWR_TRANSPORT_TYPE_UDP) iceCandidate.transport = "UDP"; else { iceCandidate.transport = "TCP"; iceCandidate.tcpType = candidateTcpTypes[transportType]; } if (candidateType != OWR_CANDIDATE_TYPE_HOST) { iceCandidate.relatedAddress = relatedAddress; iceCandidate.relatedPort = relatedPort ? relatedPort : candidateDefaultPort; } g_object_set(G_OBJECT(candidate), "ufrag", g_object_get_data(G_OBJECT(session), "ice-ufrag"), "password", g_object_get_data(G_OBJECT(session), "ice-password"), nullptr); mediaEndpoint->dispatchNewIceCandidate(mediaEndpoint->sessionMid(session), WTFMove(iceCandidate)); g_free(foundation); g_free(address); g_free(relatedAddress); } static void candidateGatheringDone(OwrSession* session, MediaEndpointOwr* mediaEndpoint) { mediaEndpoint->dispatchGatheringDone(mediaEndpoint->sessionMid(session)); } static void iceConnectionStateChange(OwrSession* session, GParamSpec*, MediaEndpointOwr* mediaEndpoint) { mediaEndpoint->processIceTransportStateChange(session); } static void gotIncomingSource(OwrMediaSession* mediaSession, OwrMediaSource* source, MediaEndpointOwr* mediaEndpoint) { mediaEndpoint->unmuteRemoteSource(mediaEndpoint->sessionMid(OWR_SESSION(mediaSession)), source); } } // namespace WebCore #endif // ENABLE(WEB_RTC)