| /* |
| * 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 "RealtimeMediaSourceOwr.h" |
| #include <owr/owr.h> |
| #include <owr/owr_audio_payload.h> |
| #include <owr/owr_crypto_utils.h> |
| #include <owr/owr_media_session.h> |
| #include <owr/owr_transport_agent.h> |
| #include <owr/owr_video_payload.h> |
| #include <wtf/text/CString.h> |
| |
| 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<String> candidateTypes = { "host", "srflx", "prflx", "relay" }; |
| static const Vector<String> candidateTcpTypes = { "", "active", "passive", "so" }; |
| static const Vector<String> 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<MediaEndpoint> createMediaEndpointOwr(MediaEndpointClient& client) |
| { |
| return std::unique_ptr<MediaEndpoint>(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<GRegexMatchFlags>(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<RTCDataChannelHandler> 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<const MediaPayload*>& 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<const MediaPayload*> 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<TransceiverConfig> 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<TransceiverConfig> 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<RealtimeMediaSourceOwr*>(sendSourceMap.get(mdesc.mid)); |
| |
| ASSERT(codecTypes.find(payload->encodingName.convertToASCIIUppercase()) != notFound); |
| auto codecType = static_cast<OwrCodecType>(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<unsigned> 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<RealtimeMediaSourceOwr> remoteSource = m_mutedRemoteSources.take(oldMid); |
| m_mutedRemoteSources.set(newMid, remoteSource); |
| } |
| |
| Ref<RealtimeMediaSource> MediaEndpointOwr::createMutedRemoteSource(const String& mid, RealtimeMediaSource::Type type) |
| { |
| String name; |
| String id("not used"); |
| |
| switch (type) { |
| case RealtimeMediaSource::Audio: name = "remote audio"; break; |
| case RealtimeMediaSource::Video: name = "remote video"; break; |
| case RealtimeMediaSource::None: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| RefPtr<RealtimeMediaSourceOwr> source = adoptRef(new RealtimeMediaSourceOwr(nullptr, id, type, name)); |
| 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<RealtimeMediaSourceOwr> 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<OwrCodecType>(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<GRegexMatchFlags>(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<TransceiverConfig>& 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<OwrCandidateType>(candidateTypes.find(candidate.type)); |
| OwrComponentType componentId = static_cast<OwrComponentType>(candidate.componentId); |
| OwrTransportType transportType; |
| |
| if (candidate.transport.convertToASCIIUppercase() == "UDP") |
| transportType = OWR_TRANSPORT_TYPE_UDP; |
| else { |
| ASSERT(candidateTcpTypes.find(candidate.tcpType) != notFound); |
| transportType = static_cast<OwrTransportType>(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) |