1/* 2 * Copyright (C) 2012 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * 3. Neither the name of Google Inc. nor the names of its contributors 15 * may be used to endorse or promote products derived from this 16 * software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31#include "config.h" 32 33#if ENABLE(MEDIA_STREAM) 34 35#include "RTCPeerConnection.h" 36 37#include "ArrayValue.h" 38#include "Document.h" 39#include "Event.h" 40#include "ExceptionCode.h" 41#include "Frame.h" 42#include "FrameLoader.h" 43#include "FrameLoaderClient.h" 44#include "MediaConstraintsImpl.h" 45#include "MediaStreamEvent.h" 46#include "RTCConfiguration.h" 47#include "RTCDTMFSender.h" 48#include "RTCDataChannel.h" 49#include "RTCDataChannelEvent.h" 50#include "RTCDataChannelHandler.h" 51#include "RTCErrorCallback.h" 52#include "RTCIceCandidate.h" 53#include "RTCIceCandidateDescriptor.h" 54#include "RTCIceCandidateEvent.h" 55#include "RTCSessionDescription.h" 56#include "RTCSessionDescriptionCallback.h" 57#include "RTCSessionDescriptionDescriptor.h" 58#include "RTCSessionDescriptionRequestImpl.h" 59#include "RTCStatsCallback.h" 60#include "RTCStatsRequestImpl.h" 61#include "RTCVoidRequestImpl.h" 62#include "ScriptExecutionContext.h" 63#include "VoidCallback.h" 64 65namespace WebCore { 66 67PassRefPtr<RTCConfiguration> RTCPeerConnection::parseConfiguration(const Dictionary& configuration, ExceptionCode& ec) 68{ 69 if (configuration.isUndefinedOrNull()) 70 return 0; 71 72 ArrayValue iceServers; 73 bool ok = configuration.get("iceServers", iceServers); 74 if (!ok || iceServers.isUndefinedOrNull()) { 75 ec = TYPE_MISMATCH_ERR; 76 return 0; 77 } 78 79 size_t numberOfServers; 80 ok = iceServers.length(numberOfServers); 81 if (!ok) { 82 ec = TYPE_MISMATCH_ERR; 83 return 0; 84 } 85 86 RefPtr<RTCConfiguration> rtcConfiguration = RTCConfiguration::create(); 87 88 for (size_t i = 0; i < numberOfServers; ++i) { 89 Dictionary iceServer; 90 ok = iceServers.get(i, iceServer); 91 if (!ok) { 92 ec = TYPE_MISMATCH_ERR; 93 return 0; 94 } 95 96 String urlString, credential; 97 ok = iceServer.get("url", urlString); 98 if (!ok) { 99 ec = TYPE_MISMATCH_ERR; 100 return 0; 101 } 102 KURL url(KURL(), urlString); 103 if (!url.isValid() || !(url.protocolIs("turn") || url.protocolIs("stun"))) { 104 ec = TYPE_MISMATCH_ERR; 105 return 0; 106 } 107 108 iceServer.get("credential", credential); 109 110 rtcConfiguration->appendServer(RTCIceServer::create(url, credential)); 111 } 112 113 return rtcConfiguration.release(); 114} 115 116PassRefPtr<RTCPeerConnection> RTCPeerConnection::create(ScriptExecutionContext* context, const Dictionary& rtcConfiguration, const Dictionary& mediaConstraints, ExceptionCode& ec) 117{ 118 RefPtr<RTCConfiguration> configuration = parseConfiguration(rtcConfiguration, ec); 119 if (ec) 120 return 0; 121 122 RefPtr<MediaConstraints> constraints = MediaConstraintsImpl::create(mediaConstraints, ec); 123 if (ec) 124 return 0; 125 126 RefPtr<RTCPeerConnection> peerConnection = adoptRef(new RTCPeerConnection(context, configuration.release(), constraints.release(), ec)); 127 peerConnection->suspendIfNeeded(); 128 if (ec) 129 return 0; 130 131 return peerConnection.release(); 132} 133 134RTCPeerConnection::RTCPeerConnection(ScriptExecutionContext* context, PassRefPtr<RTCConfiguration> configuration, PassRefPtr<MediaConstraints> constraints, ExceptionCode& ec) 135 : ActiveDOMObject(context) 136 , m_signalingState(SignalingStateStable) 137 , m_iceGatheringState(IceGatheringStateNew) 138 , m_iceConnectionState(IceConnectionStateNew) 139 , m_scheduledEventTimer(this, &RTCPeerConnection::scheduledEventTimerFired) 140 , m_stopped(false) 141{ 142 Document* document = toDocument(m_scriptExecutionContext); 143 144 if (!document->frame()) { 145 ec = NOT_SUPPORTED_ERR; 146 return; 147 } 148 149 m_peerHandler = RTCPeerConnectionHandler::create(this); 150 if (!m_peerHandler) { 151 ec = NOT_SUPPORTED_ERR; 152 return; 153 } 154 155 document->frame()->loader()->client()->dispatchWillStartUsingPeerConnectionHandler(m_peerHandler.get()); 156 157 if (!m_peerHandler->initialize(configuration, constraints)) { 158 ec = NOT_SUPPORTED_ERR; 159 return; 160 } 161} 162 163RTCPeerConnection::~RTCPeerConnection() 164{ 165 stop(); 166} 167 168void RTCPeerConnection::createOffer(PassRefPtr<RTCSessionDescriptionCallback> successCallback, PassRefPtr<RTCErrorCallback> errorCallback, const Dictionary& mediaConstraints, ExceptionCode& ec) 169{ 170 if (m_signalingState == SignalingStateClosed) { 171 ec = INVALID_STATE_ERR; 172 return; 173 } 174 175 if (!successCallback) { 176 ec = TYPE_MISMATCH_ERR; 177 return; 178 } 179 180 RefPtr<MediaConstraints> constraints = MediaConstraintsImpl::create(mediaConstraints, ec); 181 if (ec) 182 return; 183 184 RefPtr<RTCSessionDescriptionRequestImpl> request = RTCSessionDescriptionRequestImpl::create(scriptExecutionContext(), successCallback, errorCallback); 185 m_peerHandler->createOffer(request.release(), constraints); 186} 187 188void RTCPeerConnection::createAnswer(PassRefPtr<RTCSessionDescriptionCallback> successCallback, PassRefPtr<RTCErrorCallback> errorCallback, const Dictionary& mediaConstraints, ExceptionCode& ec) 189{ 190 if (m_signalingState == SignalingStateClosed) { 191 ec = INVALID_STATE_ERR; 192 return; 193 } 194 195 if (!successCallback) { 196 ec = TYPE_MISMATCH_ERR; 197 return; 198 } 199 200 RefPtr<MediaConstraints> constraints = MediaConstraintsImpl::create(mediaConstraints, ec); 201 if (ec) 202 return; 203 204 RefPtr<RTCSessionDescriptionRequestImpl> request = RTCSessionDescriptionRequestImpl::create(scriptExecutionContext(), successCallback, errorCallback); 205 m_peerHandler->createAnswer(request.release(), constraints.release()); 206} 207 208void RTCPeerConnection::setLocalDescription(PassRefPtr<RTCSessionDescription> prpSessionDescription, PassRefPtr<VoidCallback> successCallback, PassRefPtr<RTCErrorCallback> errorCallback, ExceptionCode& ec) 209{ 210 if (m_signalingState == SignalingStateClosed) { 211 ec = INVALID_STATE_ERR; 212 return; 213 } 214 215 RefPtr<RTCSessionDescription> sessionDescription = prpSessionDescription; 216 if (!sessionDescription) { 217 ec = TYPE_MISMATCH_ERR; 218 return; 219 } 220 221 RefPtr<RTCVoidRequestImpl> request = RTCVoidRequestImpl::create(scriptExecutionContext(), successCallback, errorCallback); 222 m_peerHandler->setLocalDescription(request.release(), sessionDescription->descriptor()); 223} 224 225PassRefPtr<RTCSessionDescription> RTCPeerConnection::localDescription(ExceptionCode& ec) 226{ 227 RefPtr<RTCSessionDescriptionDescriptor> descriptor = m_peerHandler->localDescription(); 228 if (!descriptor) 229 return 0; 230 231 RefPtr<RTCSessionDescription> sessionDescription = RTCSessionDescription::create(descriptor.release()); 232 return sessionDescription.release(); 233} 234 235void RTCPeerConnection::setRemoteDescription(PassRefPtr<RTCSessionDescription> prpSessionDescription, PassRefPtr<VoidCallback> successCallback, PassRefPtr<RTCErrorCallback> errorCallback, ExceptionCode& ec) 236{ 237 if (m_signalingState == SignalingStateClosed) { 238 ec = INVALID_STATE_ERR; 239 return; 240 } 241 242 RefPtr<RTCSessionDescription> sessionDescription = prpSessionDescription; 243 if (!sessionDescription) { 244 ec = TYPE_MISMATCH_ERR; 245 return; 246 } 247 248 RefPtr<RTCVoidRequestImpl> request = RTCVoidRequestImpl::create(scriptExecutionContext(), successCallback, errorCallback); 249 m_peerHandler->setRemoteDescription(request.release(), sessionDescription->descriptor()); 250} 251 252PassRefPtr<RTCSessionDescription> RTCPeerConnection::remoteDescription(ExceptionCode& ec) 253{ 254 RefPtr<RTCSessionDescriptionDescriptor> descriptor = m_peerHandler->remoteDescription(); 255 if (!descriptor) 256 return 0; 257 258 RefPtr<RTCSessionDescription> desc = RTCSessionDescription::create(descriptor.release()); 259 return desc.release(); 260} 261 262void RTCPeerConnection::updateIce(const Dictionary& rtcConfiguration, const Dictionary& mediaConstraints, ExceptionCode& ec) 263{ 264 if (m_signalingState == SignalingStateClosed) { 265 ec = INVALID_STATE_ERR; 266 return; 267 } 268 269 RefPtr<RTCConfiguration> configuration = parseConfiguration(rtcConfiguration, ec); 270 if (ec) 271 return; 272 273 RefPtr<MediaConstraints> constraints = MediaConstraintsImpl::create(mediaConstraints, ec); 274 if (ec) 275 return; 276 277 bool valid = m_peerHandler->updateIce(configuration, constraints); 278 if (!valid) 279 ec = SYNTAX_ERR; 280} 281 282void RTCPeerConnection::addIceCandidate(RTCIceCandidate* iceCandidate, ExceptionCode& ec) 283{ 284 if (m_signalingState == SignalingStateClosed) { 285 ec = INVALID_STATE_ERR; 286 return; 287 } 288 289 if (!iceCandidate) { 290 ec = TYPE_MISMATCH_ERR; 291 return; 292 } 293 294 bool valid = m_peerHandler->addIceCandidate(iceCandidate->descriptor()); 295 if (!valid) 296 ec = SYNTAX_ERR; 297} 298 299String RTCPeerConnection::signalingState() const 300{ 301 switch (m_signalingState) { 302 case SignalingStateStable: 303 return ASCIILiteral("stable"); 304 case SignalingStateHaveLocalOffer: 305 return ASCIILiteral("have-local-offer"); 306 case SignalingStateHaveRemoteOffer: 307 return ASCIILiteral("have-remote-offer"); 308 case SignalingStateHaveLocalPrAnswer: 309 return ASCIILiteral("have-local-pranswer"); 310 case SignalingStateHaveRemotePrAnswer: 311 return ASCIILiteral("have-remote-pranswer"); 312 case SignalingStateClosed: 313 return ASCIILiteral("closed"); 314 } 315 316 ASSERT_NOT_REACHED(); 317 return String(); 318} 319 320String RTCPeerConnection::iceGatheringState() const 321{ 322 switch (m_iceGatheringState) { 323 case IceGatheringStateNew: 324 return ASCIILiteral("new"); 325 case IceGatheringStateGathering: 326 return ASCIILiteral("gathering"); 327 case IceGatheringStateComplete: 328 return ASCIILiteral("complete"); 329 } 330 331 ASSERT_NOT_REACHED(); 332 return String(); 333} 334 335String RTCPeerConnection::iceConnectionState() const 336{ 337 switch (m_iceConnectionState) { 338 case IceConnectionStateNew: 339 return ASCIILiteral("new"); 340 case IceConnectionStateChecking: 341 return ASCIILiteral("checking"); 342 case IceConnectionStateConnected: 343 return ASCIILiteral("connected"); 344 case IceConnectionStateCompleted: 345 return ASCIILiteral("completed"); 346 case IceConnectionStateFailed: 347 return ASCIILiteral("failed"); 348 case IceConnectionStateDisconnected: 349 return ASCIILiteral("disconnected"); 350 case IceConnectionStateClosed: 351 return ASCIILiteral("closed"); 352 } 353 354 ASSERT_NOT_REACHED(); 355 return String(); 356} 357 358void RTCPeerConnection::addStream(PassRefPtr<MediaStream> prpStream, const Dictionary& mediaConstraints, ExceptionCode& ec) 359{ 360 if (m_signalingState == SignalingStateClosed) { 361 ec = INVALID_STATE_ERR; 362 return; 363 } 364 365 RefPtr<MediaStream> stream = prpStream; 366 if (!stream) { 367 ec = TYPE_MISMATCH_ERR; 368 return; 369 } 370 371 if (m_localStreams.contains(stream)) 372 return; 373 374 RefPtr<MediaConstraints> constraints = MediaConstraintsImpl::create(mediaConstraints, ec); 375 if (ec) 376 return; 377 378 m_localStreams.append(stream); 379 380 bool valid = m_peerHandler->addStream(stream->descriptor(), constraints); 381 if (!valid) 382 ec = SYNTAX_ERR; 383} 384 385void RTCPeerConnection::removeStream(PassRefPtr<MediaStream> prpStream, ExceptionCode& ec) 386{ 387 if (m_signalingState == SignalingStateClosed) { 388 ec = INVALID_STATE_ERR; 389 return; 390 } 391 392 if (!prpStream) { 393 ec = TYPE_MISMATCH_ERR; 394 return; 395 } 396 397 RefPtr<MediaStream> stream = prpStream; 398 399 size_t pos = m_localStreams.find(stream); 400 if (pos == notFound) 401 return; 402 403 m_localStreams.remove(pos); 404 405 m_peerHandler->removeStream(stream->descriptor()); 406} 407 408MediaStreamVector RTCPeerConnection::getLocalStreams() const 409{ 410 return m_localStreams; 411} 412 413MediaStreamVector RTCPeerConnection::getRemoteStreams() const 414{ 415 return m_remoteStreams; 416} 417 418MediaStream* RTCPeerConnection::getStreamById(const String& streamId) 419{ 420 for (MediaStreamVector::iterator iter = m_localStreams.begin(); iter != m_localStreams.end(); ++iter) { 421 if ((*iter)->id() == streamId) 422 return iter->get(); 423 } 424 425 for (MediaStreamVector::iterator iter = m_remoteStreams.begin(); iter != m_remoteStreams.end(); ++iter) { 426 if ((*iter)->id() == streamId) 427 return iter->get(); 428 } 429 430 return 0; 431} 432 433void RTCPeerConnection::getStats(PassRefPtr<RTCStatsCallback> successCallback, PassRefPtr<MediaStreamTrack> selector) 434{ 435 RefPtr<RTCStatsRequestImpl> statsRequest = RTCStatsRequestImpl::create(scriptExecutionContext(), successCallback, selector); 436 // FIXME: Add passing selector as part of the statsRequest. 437 m_peerHandler->getStats(statsRequest.release()); 438} 439 440PassRefPtr<RTCDataChannel> RTCPeerConnection::createDataChannel(String label, const Dictionary& options, ExceptionCode& ec) 441{ 442 if (m_signalingState == SignalingStateClosed) { 443 ec = INVALID_STATE_ERR; 444 return 0; 445 } 446 447 bool reliable = true; 448 options.get("reliable", reliable); 449 RefPtr<RTCDataChannel> channel = RTCDataChannel::create(scriptExecutionContext(), m_peerHandler.get(), label, reliable, ec); 450 if (ec) 451 return 0; 452 m_dataChannels.append(channel); 453 return channel.release(); 454} 455 456bool RTCPeerConnection::hasLocalStreamWithTrackId(const String& trackId) 457{ 458 for (MediaStreamVector::iterator iter = m_localStreams.begin(); iter != m_localStreams.end(); ++iter) { 459 if ((*iter)->getTrackById(trackId)) 460 return true; 461 } 462 return false; 463} 464 465PassRefPtr<RTCDTMFSender> RTCPeerConnection::createDTMFSender(PassRefPtr<MediaStreamTrack> prpTrack, ExceptionCode& ec) 466{ 467 if (m_signalingState == SignalingStateClosed) { 468 ec = INVALID_STATE_ERR; 469 return 0; 470 } 471 472 if (!prpTrack) { 473 ec = TypeError; 474 return 0; 475 } 476 477 RefPtr<MediaStreamTrack> track = prpTrack; 478 479 if (!hasLocalStreamWithTrackId(track->id())) { 480 ec = SYNTAX_ERR; 481 return 0; 482 } 483 484 RefPtr<RTCDTMFSender> dtmfSender = RTCDTMFSender::create(scriptExecutionContext(), m_peerHandler.get(), track.release(), ec); 485 if (ec) 486 return 0; 487 return dtmfSender.release(); 488} 489 490void RTCPeerConnection::close(ExceptionCode& ec) 491{ 492 if (m_signalingState == SignalingStateClosed) { 493 ec = INVALID_STATE_ERR; 494 return; 495 } 496 497 m_peerHandler->stop(); 498 499 changeIceConnectionState(IceConnectionStateClosed); 500 changeIceGatheringState(IceGatheringStateComplete); 501 changeSignalingState(SignalingStateClosed); 502} 503 504void RTCPeerConnection::negotiationNeeded() 505{ 506 scheduleDispatchEvent(Event::create(eventNames().negotiationneededEvent, false, false)); 507} 508 509void RTCPeerConnection::didGenerateIceCandidate(PassRefPtr<RTCIceCandidateDescriptor> iceCandidateDescriptor) 510{ 511 ASSERT(scriptExecutionContext()->isContextThread()); 512 if (!iceCandidateDescriptor) 513 scheduleDispatchEvent(RTCIceCandidateEvent::create(false, false, 0)); 514 else { 515 RefPtr<RTCIceCandidate> iceCandidate = RTCIceCandidate::create(iceCandidateDescriptor); 516 scheduleDispatchEvent(RTCIceCandidateEvent::create(false, false, iceCandidate.release())); 517 } 518} 519 520void RTCPeerConnection::didChangeSignalingState(SignalingState newState) 521{ 522 ASSERT(scriptExecutionContext()->isContextThread()); 523 changeSignalingState(newState); 524} 525 526void RTCPeerConnection::didChangeIceGatheringState(IceGatheringState newState) 527{ 528 ASSERT(scriptExecutionContext()->isContextThread()); 529 changeIceGatheringState(newState); 530} 531 532void RTCPeerConnection::didChangeIceConnectionState(IceConnectionState newState) 533{ 534 ASSERT(scriptExecutionContext()->isContextThread()); 535 changeIceConnectionState(newState); 536} 537 538void RTCPeerConnection::didAddRemoteStream(PassRefPtr<MediaStreamDescriptor> streamDescriptor) 539{ 540 ASSERT(scriptExecutionContext()->isContextThread()); 541 542 if (m_signalingState == SignalingStateClosed) 543 return; 544 545 RefPtr<MediaStream> stream = MediaStream::create(scriptExecutionContext(), streamDescriptor); 546 m_remoteStreams.append(stream); 547 548 scheduleDispatchEvent(MediaStreamEvent::create(eventNames().addstreamEvent, false, false, stream.release())); 549} 550 551void RTCPeerConnection::didRemoveRemoteStream(MediaStreamDescriptor* streamDescriptor) 552{ 553 ASSERT(scriptExecutionContext()->isContextThread()); 554 ASSERT(streamDescriptor->client()); 555 556 RefPtr<MediaStream> stream = static_cast<MediaStream*>(streamDescriptor->client()); 557 stream->streamEnded(); 558 559 if (m_signalingState == SignalingStateClosed) 560 return; 561 562 size_t pos = m_remoteStreams.find(stream); 563 ASSERT(pos != notFound); 564 m_remoteStreams.remove(pos); 565 566 scheduleDispatchEvent(MediaStreamEvent::create(eventNames().removestreamEvent, false, false, stream.release())); 567} 568 569void RTCPeerConnection::didAddRemoteDataChannel(PassOwnPtr<RTCDataChannelHandler> handler) 570{ 571 ASSERT(scriptExecutionContext()->isContextThread()); 572 573 if (m_signalingState == SignalingStateClosed) 574 return; 575 576 RefPtr<RTCDataChannel> channel = RTCDataChannel::create(scriptExecutionContext(), handler); 577 m_dataChannels.append(channel); 578 579 scheduleDispatchEvent(RTCDataChannelEvent::create(eventNames().datachannelEvent, false, false, channel.release())); 580} 581 582const AtomicString& RTCPeerConnection::interfaceName() const 583{ 584 return eventNames().interfaceForRTCPeerConnection; 585} 586 587ScriptExecutionContext* RTCPeerConnection::scriptExecutionContext() const 588{ 589 return ActiveDOMObject::scriptExecutionContext(); 590} 591 592void RTCPeerConnection::stop() 593{ 594 if (m_stopped) 595 return; 596 597 m_stopped = true; 598 m_iceConnectionState = IceConnectionStateClosed; 599 m_signalingState = SignalingStateClosed; 600 601 Vector<RefPtr<RTCDataChannel> >::iterator i = m_dataChannels.begin(); 602 for (; i != m_dataChannels.end(); ++i) 603 (*i)->stop(); 604} 605 606EventTargetData* RTCPeerConnection::eventTargetData() 607{ 608 return &m_eventTargetData; 609} 610 611EventTargetData* RTCPeerConnection::ensureEventTargetData() 612{ 613 return &m_eventTargetData; 614} 615 616void RTCPeerConnection::changeSignalingState(SignalingState signalingState) 617{ 618 if (m_signalingState != SignalingStateClosed && m_signalingState != signalingState) { 619 m_signalingState = signalingState; 620 scheduleDispatchEvent(Event::create(eventNames().signalingstatechangeEvent, false, false)); 621 } 622} 623 624void RTCPeerConnection::changeIceGatheringState(IceGatheringState iceGatheringState) 625{ 626 m_iceGatheringState = iceGatheringState; 627} 628 629void RTCPeerConnection::changeIceConnectionState(IceConnectionState iceConnectionState) 630{ 631 if (m_iceConnectionState != IceConnectionStateClosed && m_iceConnectionState != iceConnectionState) { 632 m_iceConnectionState = iceConnectionState; 633 scheduleDispatchEvent(Event::create(eventNames().iceconnectionstatechangeEvent, false, false)); 634 } 635} 636 637void RTCPeerConnection::scheduleDispatchEvent(PassRefPtr<Event> event) 638{ 639 m_scheduledEvents.append(event); 640 641 if (!m_scheduledEventTimer.isActive()) 642 m_scheduledEventTimer.startOneShot(0); 643} 644 645void RTCPeerConnection::scheduledEventTimerFired(Timer<RTCPeerConnection>*) 646{ 647 if (m_stopped) 648 return; 649 650 Vector<RefPtr<Event> > events; 651 events.swap(m_scheduledEvents); 652 653 Vector<RefPtr<Event> >::iterator it = events.begin(); 654 for (; it != events.end(); ++it) 655 dispatchEvent((*it).release()); 656 657 events.clear(); 658} 659 660} // namespace WebCore 661 662#endif // ENABLE(MEDIA_STREAM) 663