1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/media/webrtc/signaling/src/softphonewrapper/CC_SIPCCCall.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,589 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include "CSFLog.h" 1.9 +#include "timecard.h" 1.10 + 1.11 +#include "CC_Common.h" 1.12 + 1.13 +#include "CC_SIPCCCall.h" 1.14 +#include "CC_SIPCCCallInfo.h" 1.15 +#include "VcmSIPCCBinding.h" 1.16 +#include "CSFVideoTermination.h" 1.17 +#include "CSFAudioTermination.h" 1.18 +#include "CSFAudioControl.h" 1.19 + 1.20 +extern "C" 1.21 +{ 1.22 +#include "ccapi_call.h" 1.23 +#include "ccapi_call_listener.h" 1.24 +#include "config_api.h" 1.25 +} 1.26 + 1.27 +using namespace std; 1.28 +using namespace CSF; 1.29 + 1.30 +static const char* logTag = "CC_SIPCCCall"; 1.31 + 1.32 +CSF_IMPLEMENT_WRAP(CC_SIPCCCall, cc_call_handle_t); 1.33 + 1.34 +CC_SIPCCCall::CC_SIPCCCall (cc_call_handle_t aCallHandle) : 1.35 + callHandle(aCallHandle), 1.36 + pMediaData(new CC_SIPCCCallMediaData(nullptr, false, false, -1)), 1.37 + m_lock("CC_SIPCCCall") 1.38 +{ 1.39 + CSFLogInfo( logTag, "Creating CC_SIPCCCall %u", callHandle ); 1.40 + 1.41 + AudioControl * audioControl = VcmSIPCCBinding::getAudioControl(); 1.42 + 1.43 + if(audioControl) 1.44 + { 1.45 + pMediaData->volume = audioControl->getDefaultVolume(); 1.46 + } 1.47 +} 1.48 + 1.49 + 1.50 +/* 1.51 + CCAPI_CALL_EV_CAPABILITY -- From phone team: "...CCAPI_CALL_EV_CAPABILITY is generated for the capability changes but we decided to 1.52 + suppress it if it's due to state changes. We found it redundant as a state change implicitly implies a 1.53 + capability change. This event will still be generated if the capability changes without a state change. 1.54 + CCAPI_CALL_EV_CALLINFO -- From phone team: "...CCAPI_CALL_EV_CALLINFO is generated for any caller id related changes including 1.55 + called/calling/redirecting name/number etc..." 1.56 + CCAPI_CALL_EV_PLACED_CALLINFO -- From phone team: "CCAPI_CALL_EV_PLACED_CALLINFO was a trigger to update the placed call history and 1.57 + gives the actual number dialed out. I think this event can be deprecated." 1.58 +*/ 1.59 + 1.60 +/* 1.61 + CallState 1.62 + 1.63 + REORDER: You get this if you misdial a number. 1.64 + */ 1.65 + 1.66 +// This function sets the remote window parameters for the call. Note that it currently only tolerates a single 1.67 +// video stream on the call and would need to be updated to handle multiple remote video streams for conferencing. 1.68 +void CC_SIPCCCall::setRemoteWindow (VideoWindowHandle window) 1.69 +{ 1.70 + VideoTermination * pVideo = VcmSIPCCBinding::getVideoTermination(); 1.71 + pMediaData->remoteWindow = window; 1.72 + 1.73 + if (!pVideo) 1.74 + { 1.75 + CSFLogWarn( logTag, "setRemoteWindow: no video provider found"); 1.76 + return; 1.77 + } 1.78 + 1.79 + for (StreamMapType::iterator entry = pMediaData->streamMap.begin(); entry != pMediaData->streamMap.end(); entry++) 1.80 + { 1.81 + if (entry->second.isVideo) 1.82 + { 1.83 + // first video stream found 1.84 + int streamId = entry->first; 1.85 + pVideo->setRemoteWindow(streamId, pMediaData->remoteWindow); 1.86 + 1.87 + return; 1.88 + } 1.89 + } 1.90 + CSFLogInfo( logTag, "setRemoteWindow:no video stream found in call %u", callHandle ); 1.91 +} 1.92 + 1.93 +int CC_SIPCCCall::setExternalRenderer(VideoFormat vFormat, ExternalRendererHandle renderer) 1.94 +{ 1.95 + VideoTermination * pVideo = VcmSIPCCBinding::getVideoTermination(); 1.96 + pMediaData->extRenderer = renderer; 1.97 + pMediaData->videoFormat = vFormat; 1.98 + 1.99 + if (!pVideo) 1.100 + { 1.101 + CSFLogWarn( logTag, "setExternalRenderer: no video provider found"); 1.102 + return -1; 1.103 + } 1.104 + 1.105 + for (StreamMapType::iterator entry = pMediaData->streamMap.begin(); entry != pMediaData->streamMap.end(); entry++) 1.106 + { 1.107 + if (entry->second.isVideo) 1.108 + { 1.109 + // first video stream found 1.110 + int streamId = entry->first; 1.111 + return pVideo->setExternalRenderer(streamId, pMediaData->videoFormat, pMediaData->extRenderer); 1.112 + } 1.113 + } 1.114 + CSFLogInfo( logTag, "setExternalRenderer:no video stream found in call %u", callHandle ); 1.115 + return -1; 1.116 +} 1.117 + 1.118 +void CC_SIPCCCall::sendIFrame() 1.119 +{ 1.120 + VideoTermination * pVideo = VcmSIPCCBinding::getVideoTermination(); 1.121 + 1.122 + if (pVideo) 1.123 + { 1.124 + pVideo->sendIFrame(callHandle); 1.125 + } 1.126 +} 1.127 + 1.128 +CC_CallInfoPtr CC_SIPCCCall::getCallInfo () 1.129 +{ 1.130 + cc_callinfo_ref_t callInfo = CCAPI_Call_getCallInfo(callHandle); 1.131 + CC_SIPCCCallInfoPtr callInfoPtr = CC_SIPCCCallInfo::wrap(callInfo); 1.132 + callInfoPtr->setMediaData( pMediaData); 1.133 + return callInfoPtr.get(); 1.134 +} 1.135 + 1.136 + 1.137 +// 1.138 +// Operations - The following function are actions that can be taken the execute an operation on the Call, ie calls 1.139 +// down to pSIPCC to originate a call, end a call etc. 1.140 +// 1.141 + 1.142 +bool CC_SIPCCCall::originateCall (cc_sdp_direction_t video_pref, const string & digits) 1.143 +{ 1.144 + return (CCAPI_Call_originateCall(callHandle, video_pref, digits.c_str()) == CC_SUCCESS); 1.145 +} 1.146 + 1.147 +bool CC_SIPCCCall::answerCall (cc_sdp_direction_t video_pref) 1.148 +{ 1.149 + return (CCAPI_Call_answerCall(callHandle, video_pref) == CC_SUCCESS); 1.150 +} 1.151 + 1.152 +bool CC_SIPCCCall::hold (cc_hold_reason_t reason) 1.153 +{ 1.154 + return (CCAPI_Call_hold(callHandle, reason) == CC_SUCCESS); 1.155 +} 1.156 + 1.157 +bool CC_SIPCCCall::resume (cc_sdp_direction_t video_pref) 1.158 +{ 1.159 + return (CCAPI_Call_resume(callHandle, video_pref) == CC_SUCCESS); 1.160 +} 1.161 + 1.162 +bool CC_SIPCCCall::endCall() 1.163 +{ 1.164 + return (CCAPI_Call_endCall(callHandle) == CC_SUCCESS); 1.165 +} 1.166 + 1.167 +bool CC_SIPCCCall::sendDigit (cc_digit_t digit) 1.168 +{ 1.169 + AudioTermination * pAudio = VcmSIPCCBinding::getAudioTermination(); 1.170 + mozilla::MutexAutoLock lock(m_lock); 1.171 + 1.172 + // Convert public digit (as enum or char) to RFC2833 form. 1.173 + int digitId = -1; 1.174 + switch(digit) 1.175 + { 1.176 + case '0': 1.177 + digitId = 0; 1.178 + break; 1.179 + case '1': 1.180 + digitId = 1; 1.181 + break; 1.182 + case '2': 1.183 + digitId = 2; 1.184 + break; 1.185 + case '3': 1.186 + digitId = 3; 1.187 + break; 1.188 + case '4': 1.189 + digitId = 4; 1.190 + break; 1.191 + case '5': 1.192 + digitId = 5; 1.193 + break; 1.194 + case '6': 1.195 + digitId = 6; 1.196 + break; 1.197 + case '7': 1.198 + digitId = 7; 1.199 + break; 1.200 + case '8': 1.201 + digitId = 8; 1.202 + break; 1.203 + case '9': 1.204 + digitId = 9; 1.205 + break; 1.206 + case '*': 1.207 + digitId = 10; 1.208 + break; 1.209 + case '#': 1.210 + digitId = 11; 1.211 + break; 1.212 + case 'A': 1.213 + digitId = 12; 1.214 + break; 1.215 + case 'B': 1.216 + digitId = 13; 1.217 + break; 1.218 + case 'C': 1.219 + digitId = 14; 1.220 + break; 1.221 + case 'D': 1.222 + digitId = 15; 1.223 + break; 1.224 + case '+': 1.225 + digitId = 16; 1.226 + break; 1.227 + } 1.228 + 1.229 + for (StreamMapType::iterator entry = pMediaData->streamMap.begin(); entry != pMediaData->streamMap.end(); entry++) 1.230 + { 1.231 + if (entry->second.isVideo == false) 1.232 + { 1.233 + // first is the streamId 1.234 + if (pAudio->sendDtmf(entry->first, digitId) != 0) 1.235 + { 1.236 + // We have sent a digit, done. 1.237 + break; 1.238 + } 1.239 + else 1.240 + { 1.241 + CSFLogWarn( logTag, "sendDigit:sendDtmf returned fail"); 1.242 + } 1.243 + } 1.244 + } 1.245 + return (CCAPI_Call_sendDigit(callHandle, digit) == CC_SUCCESS); 1.246 +} 1.247 + 1.248 +bool CC_SIPCCCall::backspace() 1.249 +{ 1.250 + return (CCAPI_Call_backspace(callHandle) == CC_SUCCESS); 1.251 +} 1.252 + 1.253 +bool CC_SIPCCCall::redial (cc_sdp_direction_t video_pref) 1.254 +{ 1.255 + return (CCAPI_Call_redial(callHandle, video_pref) == CC_SUCCESS); 1.256 +} 1.257 + 1.258 +bool CC_SIPCCCall::initiateCallForwardAll() 1.259 +{ 1.260 + return (CCAPI_Call_initiateCallForwardAll(callHandle) == CC_SUCCESS); 1.261 +} 1.262 + 1.263 +bool CC_SIPCCCall::endConsultativeCall() 1.264 +{ 1.265 + return (CCAPI_Call_endConsultativeCall(callHandle) == CC_SUCCESS); 1.266 +} 1.267 + 1.268 +bool CC_SIPCCCall::conferenceStart (cc_sdp_direction_t video_pref) 1.269 +{ 1.270 + return (CCAPI_Call_conferenceStart(callHandle, video_pref) == CC_SUCCESS); 1.271 +} 1.272 + 1.273 +bool CC_SIPCCCall::conferenceComplete (CC_CallPtr otherLeg, cc_sdp_direction_t video_pref) 1.274 +{ 1.275 + return (CCAPI_Call_conferenceComplete(callHandle, ((CC_SIPCCCall*)otherLeg.get())->callHandle, video_pref) == CC_SUCCESS); 1.276 +} 1.277 + 1.278 +bool CC_SIPCCCall::transferStart (cc_sdp_direction_t video_pref) 1.279 +{ 1.280 + return (CCAPI_Call_transferStart(callHandle, video_pref) == CC_SUCCESS); 1.281 +} 1.282 + 1.283 +bool CC_SIPCCCall::transferComplete (CC_CallPtr otherLeg, 1.284 + cc_sdp_direction_t video_pref) 1.285 +{ 1.286 + return (CCAPI_Call_transferComplete(callHandle, ((CC_SIPCCCall*)otherLeg.get())->callHandle, video_pref) == CC_SUCCESS); 1.287 +} 1.288 + 1.289 +bool CC_SIPCCCall::cancelTransferOrConferenceFeature() 1.290 +{ 1.291 + return (CCAPI_Call_cancelTransferOrConferenceFeature(callHandle) == CC_SUCCESS); 1.292 +} 1.293 + 1.294 +bool CC_SIPCCCall::directTransfer (CC_CallPtr target) 1.295 +{ 1.296 + return (CCAPI_Call_directTransfer(callHandle, ((CC_SIPCCCall*)target.get())->callHandle) == CC_SUCCESS); 1.297 +} 1.298 + 1.299 +bool CC_SIPCCCall::joinAcrossLine (CC_CallPtr target) 1.300 +{ 1.301 + return (CCAPI_Call_joinAcrossLine(callHandle, ((CC_SIPCCCall*)target.get())->callHandle) == CC_SUCCESS); 1.302 +} 1.303 + 1.304 +bool CC_SIPCCCall::blfCallPickup (cc_sdp_direction_t video_pref, const string & speed) 1.305 +{ 1.306 + return (CCAPI_Call_blfCallPickup(callHandle, video_pref, speed.c_str()) == CC_SUCCESS); 1.307 +} 1.308 + 1.309 +bool CC_SIPCCCall::select() 1.310 +{ 1.311 + return (CCAPI_Call_select(callHandle) == CC_SUCCESS); 1.312 +} 1.313 + 1.314 +bool CC_SIPCCCall::updateVideoMediaCap (cc_sdp_direction_t video_pref) 1.315 +{ 1.316 + return (CCAPI_Call_updateVideoMediaCap(callHandle, video_pref) == CC_SUCCESS); 1.317 +} 1.318 + 1.319 +bool CC_SIPCCCall::sendInfo (const string & infopackage, const string & infotype, const string & infobody) 1.320 +{ 1.321 + return (CCAPI_Call_sendInfo(callHandle, infopackage.c_str(), infotype.c_str(), infobody.c_str()) == CC_SUCCESS); 1.322 +} 1.323 + 1.324 +bool CC_SIPCCCall::muteAudio(void) 1.325 +{ 1.326 + return setAudioMute(true); 1.327 +} 1.328 + 1.329 +bool CC_SIPCCCall::unmuteAudio() 1.330 +{ 1.331 + return setAudioMute(false); 1.332 +} 1.333 + 1.334 +bool CC_SIPCCCall::muteVideo() 1.335 +{ 1.336 + return setVideoMute(true); 1.337 +} 1.338 + 1.339 +bool CC_SIPCCCall::unmuteVideo() 1.340 +{ 1.341 + return setVideoMute(false); 1.342 +} 1.343 + 1.344 +bool CC_SIPCCCall::setAudioMute(bool mute) 1.345 +{ 1.346 + bool returnCode = false; 1.347 + AudioTermination * pAudio = VcmSIPCCBinding::getAudioTermination(); 1.348 + pMediaData->audioMuteState = mute; 1.349 + // we need to set the mute status of all audio streams in the map 1.350 + { 1.351 + mozilla::MutexAutoLock lock(m_lock); 1.352 + for (StreamMapType::iterator entry = pMediaData->streamMap.begin(); entry != pMediaData->streamMap.end(); entry++) 1.353 + { 1.354 + if (entry->second.isVideo == false) 1.355 + { 1.356 + // first is the streamId 1.357 + if (pAudio->mute(entry->first, mute)) 1.358 + { 1.359 + // We have muted at least one stream 1.360 + returnCode = true; 1.361 + } 1.362 + else 1.363 + { 1.364 + CSFLogWarn( logTag, "setAudioMute:audio mute returned fail"); 1.365 + } 1.366 + } 1.367 + } 1.368 + } 1.369 + 1.370 + if (CCAPI_Call_setAudioMute(callHandle, mute) != CC_SUCCESS) 1.371 + { 1.372 + returnCode = false; 1.373 + } 1.374 + 1.375 + return returnCode; 1.376 +} 1.377 + 1.378 +bool CC_SIPCCCall::setVideoMute(bool mute) 1.379 +{ 1.380 + bool returnCode = false; 1.381 + VideoTermination * pVideo = VcmSIPCCBinding::getVideoTermination(); 1.382 + pMediaData->videoMuteState = mute; 1.383 + // we need to set the mute status of all audio streams in the map 1.384 + { 1.385 + mozilla::MutexAutoLock lock(m_lock); 1.386 + for (StreamMapType::iterator entry = pMediaData->streamMap.begin(); entry != pMediaData->streamMap.end(); entry++) 1.387 + { 1.388 + if (entry->second.isVideo == true) 1.389 + { 1.390 + // first is the streamId 1.391 + if (pVideo->mute(entry->first, mute)) 1.392 + { 1.393 + // We have muted at least one stream 1.394 + returnCode = true; 1.395 + } 1.396 + else 1.397 + { 1.398 + CSFLogWarn( logTag, "setVideoMute:video mute returned fail"); 1.399 + } 1.400 + } 1.401 + } 1.402 + } 1.403 + 1.404 + if (CCAPI_Call_setVideoMute(callHandle, mute) != CC_SUCCESS) 1.405 + { 1.406 + returnCode = false; 1.407 + } 1.408 + 1.409 + return returnCode; 1.410 +} 1.411 + 1.412 +void CC_SIPCCCall::addStream(int streamId, bool isVideo) 1.413 +{ 1.414 + 1.415 + CSFLogInfo( logTag, "addStream: %d video=%s callhandle=%u", 1.416 + streamId, isVideo ? "TRUE" : "FALSE", callHandle); 1.417 + { 1.418 + mozilla::MutexAutoLock lock(m_lock); 1.419 + pMediaData->streamMap[streamId].isVideo = isVideo; 1.420 + } 1.421 + // The new stream needs to be given any properties that the call has for it. 1.422 + // At the moment the only candidate is the muted state 1.423 + if (isVideo) 1.424 + { 1.425 +#ifndef NO_WEBRTC_VIDEO 1.426 + VideoTermination * pVideo = VcmSIPCCBinding::getVideoTermination(); 1.427 + 1.428 + // if there is a window for this call apply it to the stream 1.429 + if ( pMediaData->remoteWindow != nullptr) 1.430 + { 1.431 + pVideo->setRemoteWindow(streamId, pMediaData->remoteWindow); 1.432 + } 1.433 + else 1.434 + { 1.435 + CSFLogInfo( logTag, "addStream: remoteWindow is NULL"); 1.436 + } 1.437 + 1.438 + if(pMediaData->extRenderer != nullptr) 1.439 + { 1.440 + pVideo->setExternalRenderer(streamId, pMediaData->videoFormat, pMediaData->extRenderer); 1.441 + } 1.442 + else 1.443 + { 1.444 + CSFLogInfo( logTag, "addStream: externalRenderer is NULL"); 1.445 + 1.446 + } 1.447 + 1.448 + 1.449 + for (StreamMapType::iterator entry = pMediaData->streamMap.begin(); entry != pMediaData->streamMap.end(); entry++) 1.450 + { 1.451 + if (entry->second.isVideo == false) 1.452 + { 1.453 + // first is the streamId 1.454 + pVideo->setAudioStreamId(entry->first); 1.455 + } 1.456 + } 1.457 + if (!pVideo->mute(streamId, pMediaData->videoMuteState)) 1.458 + { 1.459 + CSFLogError( logTag, "setting video mute state failed for new stream: %d", streamId); 1.460 + } else 1.461 + { 1.462 + CSFLogError( logTag, "setting video mute state SUCCEEDED for new stream: %d", streamId); 1.463 + 1.464 + } 1.465 +#endif 1.466 + } 1.467 + else 1.468 + { 1.469 + AudioTermination * pAudio = VcmSIPCCBinding::getAudioTermination(); 1.470 + if (!pAudio->mute(streamId, pMediaData->audioMuteState)) 1.471 + { 1.472 + CSFLogError( logTag, "setting audio mute state failed for new stream: %d", streamId); 1.473 + } 1.474 + if (!pAudio->setVolume(streamId, pMediaData->volume)) 1.475 + { 1.476 + CSFLogError( logTag, "setting volume state failed for new stream: %d", streamId); 1.477 + } 1.478 + } 1.479 +} 1.480 + 1.481 +void CC_SIPCCCall::removeStream(int streamId) 1.482 +{ 1.483 + mozilla::MutexAutoLock lock(m_lock); 1.484 + 1.485 + if ( pMediaData->streamMap.erase(streamId) != 1) 1.486 + { 1.487 + CSFLogError( logTag, "removeStream stream that was never in the streamMap: %d", streamId); 1.488 + } 1.489 +} 1.490 + 1.491 +bool CC_SIPCCCall::setVolume(int volume) 1.492 +{ 1.493 + bool returnCode = false; 1.494 + 1.495 + AudioTermination * pAudio = VcmSIPCCBinding::getAudioTermination(); 1.496 + { 1.497 + mozilla::MutexAutoLock lock(m_lock); 1.498 + for (StreamMapType::iterator entry = pMediaData->streamMap.begin(); entry != pMediaData->streamMap.end(); entry++) 1.499 + { 1.500 + if (entry->second.isVideo == false) 1.501 + { 1.502 + // first is the streamId 1.503 + int streamId = entry->first; 1.504 + if (pAudio->setVolume(streamId, volume)) 1.505 + { 1.506 + //We have changed the volume on at least one stream, 1.507 + //so persist the new volume as the call volume, 1.508 + //and report success for the volume change, even if it 1.509 + //fails on other streams 1.510 + pMediaData->volume = volume; 1.511 + returnCode = true; 1.512 + } 1.513 + else 1.514 + { 1.515 + CSFLogWarn( logTag, "setVolume:set volume on stream %d returned fail", 1.516 + streamId); 1.517 + } 1.518 + } 1.519 + } 1.520 + } 1.521 + return returnCode; 1.522 +} 1.523 + 1.524 +CC_SIPCCCallMediaDataPtr CC_SIPCCCall::getMediaData() 1.525 +{ 1.526 + return pMediaData; 1.527 +} 1.528 + 1.529 +void CC_SIPCCCall::originateP2PCall (cc_sdp_direction_t video_pref, const std::string & digits, const std::string & ip) 1.530 +{ 1.531 + CCAPI_Config_set_server_address(ip.c_str()); 1.532 + CCAPI_Call_originateCall(callHandle, video_pref, digits.c_str()); 1.533 +} 1.534 + 1.535 +/* 1.536 + * This method works asynchronously, is an onCallEvent with the resulting SDP 1.537 + */ 1.538 +void CC_SIPCCCall::createOffer (cc_media_constraints_t *constraints, 1.539 + Timecard *tc) { 1.540 + CCAPI_CreateOffer(callHandle, constraints, tc); 1.541 +} 1.542 +/* 1.543 + * This method works asynchronously, there is onCallEvent with the resulting SDP 1.544 + */ 1.545 +void CC_SIPCCCall::createAnswer (cc_media_constraints_t *constraints, 1.546 + Timecard *tc) { 1.547 + CCAPI_CreateAnswer(callHandle, constraints, tc); 1.548 + 1.549 +} 1.550 + 1.551 +void CC_SIPCCCall::setLocalDescription(cc_jsep_action_t action, 1.552 + const std::string & sdp, 1.553 + Timecard *tc) { 1.554 + CCAPI_SetLocalDescription(callHandle, action, sdp.c_str(), tc); 1.555 +} 1.556 + 1.557 +void CC_SIPCCCall::setRemoteDescription(cc_jsep_action_t action, 1.558 + const std::string & sdp, 1.559 + Timecard *tc) { 1.560 + CCAPI_SetRemoteDescription(callHandle, action, sdp.c_str(), tc); 1.561 +} 1.562 + 1.563 +void CC_SIPCCCall::setPeerConnection(const std::string& handle) 1.564 +{ 1.565 + CSFLogDebug(logTag, "setPeerConnection"); 1.566 + 1.567 + peerconnection = handle; // Cache this here. we need it to make the CC_SIPCCCallInfo 1.568 + CCAPI_SetPeerConnection(callHandle, handle.c_str()); 1.569 +} 1.570 + 1.571 +const std::string& CC_SIPCCCall::getPeerConnection() const { 1.572 + return peerconnection; 1.573 +} 1.574 + 1.575 +void CC_SIPCCCall::addStream(cc_media_stream_id_t stream_id, 1.576 + cc_media_track_id_t track_id, 1.577 + cc_media_type_t media_type, 1.578 + cc_media_constraints_t *constraints) { 1.579 + CCAPI_AddStream(callHandle, stream_id, track_id, media_type, constraints); 1.580 +} 1.581 + 1.582 +void CC_SIPCCCall::removeStream(cc_media_stream_id_t stream_id, cc_media_track_id_t track_id, cc_media_type_t media_type) { 1.583 + CCAPI_RemoveStream(callHandle, stream_id, track_id, media_type); 1.584 +} 1.585 + 1.586 +void CC_SIPCCCall::addICECandidate(const std::string & candidate, 1.587 + const std::string & mid, 1.588 + unsigned short level, 1.589 + Timecard *tc) { 1.590 + CCAPI_AddICECandidate(callHandle, candidate.c_str(), mid.c_str(), 1.591 + (cc_level_t) level, tc); 1.592 +}