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