1/*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 * Copyright (C) 2011, 2012, 2015 Ericsson AB. All rights reserved.
4 * Copyright (C) 2013-2019 Apple Inc. All rights reserved.
5 * Copyright (C) 2013 Nokia Corporation and/or its subsidiary(-ies).
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29#include "MediaStream.h"
30
31#if ENABLE(MEDIA_STREAM)
32
33#include "Document.h"
34#include "Event.h"
35#include "EventNames.h"
36#include "Frame.h"
37#include "FrameLoader.h"
38#include "Logging.h"
39#include "MediaStreamRegistry.h"
40#include "MediaStreamTrackEvent.h"
41#include "NetworkingContext.h"
42#include "Page.h"
43#include "RealtimeMediaSource.h"
44#include <wtf/IsoMallocInlines.h>
45#include <wtf/URL.h>
46
47namespace WebCore {
48
49WTF_MAKE_ISO_ALLOCATED_IMPL(MediaStream);
50
51Ref<MediaStream> MediaStream::create(Document& document)
52{
53 return MediaStream::create(document, MediaStreamPrivate::create(document.logger(), { }));
54}
55
56Ref<MediaStream> MediaStream::create(Document& document, MediaStream& stream)
57{
58 return adoptRef(*new MediaStream(document, stream.getTracks()));
59}
60
61Ref<MediaStream> MediaStream::create(Document& document, const MediaStreamTrackVector& tracks)
62{
63 return adoptRef(*new MediaStream(document, tracks));
64}
65
66Ref<MediaStream> MediaStream::create(Document& document, Ref<MediaStreamPrivate>&& streamPrivate)
67{
68 return adoptRef(*new MediaStream(document, WTFMove(streamPrivate)));
69}
70
71static inline MediaStreamTrackPrivateVector createTrackPrivateVector(const MediaStreamTrackVector& tracks)
72{
73 MediaStreamTrackPrivateVector trackPrivates;
74 trackPrivates.reserveCapacity(tracks.size());
75 for (auto& track : tracks)
76 trackPrivates.append(&track->privateTrack());
77 return trackPrivates;
78}
79
80MediaStream::MediaStream(Document& document, const MediaStreamTrackVector& tracks)
81 : ActiveDOMObject(document)
82 , m_private(MediaStreamPrivate::create(document.logger(), createTrackPrivateVector(tracks)))
83 , m_mediaSession(PlatformMediaSession::create(*this))
84{
85 // This constructor preserves MediaStreamTrack instances and must be used by calls originating
86 // from the JavaScript MediaStream constructor.
87
88 for (auto& track : tracks) {
89 track->addObserver(*this);
90 m_trackSet.add(track->id(), track);
91 }
92
93 setIsActive(m_private->active());
94 m_private->addObserver(*this);
95 MediaStreamRegistry::shared().registerStream(*this);
96 suspendIfNeeded();
97}
98
99MediaStream::MediaStream(Document& document, Ref<MediaStreamPrivate>&& streamPrivate)
100 : ActiveDOMObject(document)
101 , m_private(WTFMove(streamPrivate))
102 , m_mediaSession(PlatformMediaSession::create(*this))
103{
104 ALWAYS_LOG(LOGIDENTIFIER);
105
106 setIsActive(m_private->active());
107 m_private->addObserver(*this);
108 MediaStreamRegistry::shared().registerStream(*this);
109
110 for (auto& trackPrivate : m_private->tracks()) {
111 auto track = MediaStreamTrack::create(document, *trackPrivate);
112 track->addObserver(*this);
113 m_trackSet.add(track->id(), WTFMove(track));
114 }
115 suspendIfNeeded();
116}
117
118MediaStream::~MediaStream()
119{
120 // Set isActive to false immediately so any callbacks triggered by shutting down, e.g.
121 // mediaState(), are short circuited.
122 m_isActive = false;
123 MediaStreamRegistry::shared().unregisterStream(*this);
124 m_private->removeObserver(*this);
125 for (auto& track : m_trackSet.values())
126 track->removeObserver(*this);
127 if (Document* document = this->document()) {
128 if (m_isWaitingUntilMediaCanStart)
129 document->removeMediaCanStartListener(*this);
130 }
131}
132
133RefPtr<MediaStream> MediaStream::clone()
134{
135 ALWAYS_LOG(LOGIDENTIFIER);
136
137 MediaStreamTrackVector clonedTracks;
138 clonedTracks.reserveInitialCapacity(m_trackSet.size());
139
140 for (auto& track : m_trackSet.values())
141 clonedTracks.uncheckedAppend(track->clone());
142
143 return MediaStream::create(*document(), clonedTracks);
144}
145
146void MediaStream::addTrack(MediaStreamTrack& track)
147{
148 ALWAYS_LOG(LOGIDENTIFIER, track.logIdentifier());
149
150 if (!internalAddTrack(track, StreamModifier::DomAPI))
151 return;
152
153 for (auto& observer : m_observers)
154 observer->didAddOrRemoveTrack();
155}
156
157void MediaStream::removeTrack(MediaStreamTrack& track)
158{
159 ALWAYS_LOG(LOGIDENTIFIER, track.logIdentifier());
160
161 if (!internalRemoveTrack(track.id(), StreamModifier::DomAPI))
162 return;
163
164 for (auto& observer : m_observers)
165 observer->didAddOrRemoveTrack();
166}
167
168MediaStreamTrack* MediaStream::getTrackById(String id)
169{
170 auto it = m_trackSet.find(id);
171 if (it != m_trackSet.end())
172 return it->value.get();
173
174 return nullptr;
175}
176
177MediaStreamTrackVector MediaStream::getAudioTracks() const
178{
179 return trackVectorForType(RealtimeMediaSource::Type::Audio);
180}
181
182MediaStreamTrackVector MediaStream::getVideoTracks() const
183{
184 return trackVectorForType(RealtimeMediaSource::Type::Video);
185}
186
187MediaStreamTrackVector MediaStream::getTracks() const
188{
189 return copyToVector(m_trackSet.values());
190}
191
192void MediaStream::trackDidEnd()
193{
194 m_private->updateActiveState(MediaStreamPrivate::NotifyClientOption::Notify);
195}
196
197void MediaStream::activeStatusChanged()
198{
199 updateActiveState();
200}
201
202void MediaStream::didAddTrack(MediaStreamTrackPrivate& trackPrivate)
203{
204 ScriptExecutionContext* context = scriptExecutionContext();
205 if (!context)
206 return;
207
208 if (!getTrackById(trackPrivate.id()))
209 internalAddTrack(MediaStreamTrack::create(*context, trackPrivate), StreamModifier::Platform);
210}
211
212void MediaStream::didRemoveTrack(MediaStreamTrackPrivate& trackPrivate)
213{
214 internalRemoveTrack(trackPrivate.id(), StreamModifier::Platform);
215}
216
217void MediaStream::addTrackFromPlatform(Ref<MediaStreamTrack>&& track)
218{
219 ALWAYS_LOG(LOGIDENTIFIER, track->logIdentifier());
220
221 auto* privateTrack = &track->privateTrack();
222 internalAddTrack(WTFMove(track), StreamModifier::Platform);
223 m_private->addTrack(privateTrack, MediaStreamPrivate::NotifyClientOption::Notify);
224}
225
226bool MediaStream::internalAddTrack(Ref<MediaStreamTrack>&& trackToAdd, StreamModifier streamModifier)
227{
228 auto result = m_trackSet.add(trackToAdd->id(), WTFMove(trackToAdd));
229 if (!result.isNewEntry)
230 return false;
231
232 ASSERT(result.iterator->value);
233 auto& track = *result.iterator->value;
234 track.addObserver(*this);
235
236 updateActiveState();
237
238 if (streamModifier == StreamModifier::DomAPI)
239 m_private->addTrack(&track.privateTrack(), MediaStreamPrivate::NotifyClientOption::DontNotify);
240 else
241 dispatchEvent(MediaStreamTrackEvent::create(eventNames().addtrackEvent, Event::CanBubble::No, Event::IsCancelable::No, &track));
242
243 return true;
244}
245
246bool MediaStream::internalRemoveTrack(const String& trackId, StreamModifier streamModifier)
247{
248 auto track = m_trackSet.take(trackId);
249 if (!track)
250 return false;
251
252 track->removeObserver(*this);
253
254 updateActiveState();
255
256 if (streamModifier == StreamModifier::DomAPI)
257 m_private->removeTrack(track->privateTrack(), MediaStreamPrivate::NotifyClientOption::DontNotify);
258 else
259 dispatchEvent(MediaStreamTrackEvent::create(eventNames().removetrackEvent, Event::CanBubble::No, Event::IsCancelable::No, WTFMove(track)));
260
261 return true;
262}
263
264void MediaStream::setIsActive(bool active)
265{
266 if (m_isActive == active)
267 return;
268
269 ALWAYS_LOG(LOGIDENTIFIER, active);
270
271 m_isActive = active;
272 statusDidChange();
273}
274
275void MediaStream::mediaCanStart(Document& document)
276{
277 ALWAYS_LOG(LOGIDENTIFIER);
278
279 ASSERT_UNUSED(document, &document == this->document());
280 ASSERT(m_isWaitingUntilMediaCanStart);
281 if (m_isWaitingUntilMediaCanStart) {
282 m_isWaitingUntilMediaCanStart = false;
283 startProducingData();
284 }
285}
286
287void MediaStream::startProducingData()
288{
289 Document* document = this->document();
290 if (!document || !document->page())
291 return;
292
293 ALWAYS_LOG(LOGIDENTIFIER);
294
295 // If we can't start a load right away, start it later.
296 if (!document->page()->canStartMedia()) {
297 ALWAYS_LOG(LOGIDENTIFIER, "not allowed to start in background, waiting");
298 if (m_isWaitingUntilMediaCanStart)
299 return;
300
301 m_isWaitingUntilMediaCanStart = true;
302 document->addMediaCanStartListener(*this);
303 return;
304 }
305
306 if (m_isProducingData)
307 return;
308 m_isProducingData = true;
309
310 m_mediaSession->canProduceAudioChanged();
311 m_private->startProducingData();
312}
313
314void MediaStream::stopProducingData()
315{
316 if (!m_isProducingData)
317 return;
318
319 ALWAYS_LOG(LOGIDENTIFIER);
320
321 m_isProducingData = false;
322
323 m_mediaSession->canProduceAudioChanged();
324
325 m_private->stopProducingData();
326}
327
328void MediaStream::endCaptureTracks()
329{
330 ALWAYS_LOG(LOGIDENTIFIER);
331
332 for (auto& track : m_trackSet.values()) {
333 if (track->isCaptureTrack())
334 track->stopTrack(MediaStreamTrack::StopMode::PostEvent);
335 }
336}
337
338MediaProducer::MediaStateFlags MediaStream::mediaState() const
339{
340 MediaProducer::MediaStateFlags state = MediaProducer::IsNotPlaying;
341
342 if (!m_isActive || !document() || !document()->page())
343 return state;
344
345 for (const auto& track : m_trackSet.values())
346 state |= track->mediaState();
347
348 return state;
349}
350
351void MediaStream::statusDidChange()
352{
353 m_mediaSession->canProduceAudioChanged();
354
355 if (Document* document = this->document()) {
356 if (!m_isActive)
357 return;
358 document->updateIsPlayingMedia();
359 }
360}
361
362void MediaStream::characteristicsChanged()
363{
364 auto state = mediaState();
365 if (m_state != state) {
366 m_state = state;
367 statusDidChange();
368 }
369}
370
371void MediaStream::updateActiveState()
372{
373 bool active = false;
374 for (auto& track : m_trackSet.values()) {
375 if (!track->ended()) {
376 active = true;
377 break;
378 }
379 }
380
381 if (m_isActive == active)
382 return;
383
384 setIsActive(active);
385}
386
387URLRegistry& MediaStream::registry() const
388{
389 return MediaStreamRegistry::shared();
390}
391
392MediaStreamTrackVector MediaStream::trackVectorForType(RealtimeMediaSource::Type filterType) const
393{
394 MediaStreamTrackVector tracks;
395 for (auto& track : m_trackSet.values()) {
396 if (track->source().type() == filterType)
397 tracks.append(track);
398 }
399
400 return tracks;
401}
402
403void MediaStream::addObserver(MediaStream::Observer* observer)
404{
405 if (m_observers.find(observer) == notFound)
406 m_observers.append(observer);
407}
408
409void MediaStream::removeObserver(MediaStream::Observer* observer)
410{
411 size_t pos = m_observers.find(observer);
412 if (pos != notFound)
413 m_observers.remove(pos);
414}
415
416Document* MediaStream::document() const
417{
418 return downcast<Document>(scriptExecutionContext());
419}
420
421PlatformMediaSession::MediaType MediaStream::mediaType() const
422{
423 // We only need to override the type when capturing audio, HTMLMediaElement and/or WebAudio
424 // will do the right thing when a stream is attached to a media element or an audio context.
425 if (m_private->hasAudio() && m_isProducingData && m_private->hasCaptureAudioSource())
426 return PlatformMediaSession::MediaStreamCapturingAudio;
427
428 return PlatformMediaSession::None;
429}
430
431PlatformMediaSession::MediaType MediaStream::presentationType() const
432{
433 return mediaType();
434}
435
436PlatformMediaSession::CharacteristicsFlags MediaStream::characteristics() const
437{
438 PlatformMediaSession::CharacteristicsFlags state = PlatformMediaSession::HasNothing;
439
440 if (!m_isProducingData)
441 return state;
442
443 if (m_private->hasAudio())
444 state |= PlatformMediaSession::HasAudio;
445
446 if (m_private->hasVideo())
447 state |= PlatformMediaSession::HasVideo;
448
449 return state;
450}
451
452void MediaStream::mayResumePlayback(bool)
453{
454 // FIXME: should a media stream pay attention to this directly, or only when attached to a media element?
455}
456
457void MediaStream::suspendPlayback()
458{
459 // FIXME: should a media stream pay attention to this directly, or only when attached to a media element?
460}
461
462String MediaStream::sourceApplicationIdentifier() const
463{
464 Document* document = this->document();
465 if (document && document->frame()) {
466 if (NetworkingContext* networkingContext = document->frame()->loader().networkingContext())
467 return networkingContext->sourceApplicationIdentifier();
468 }
469
470 return emptyString();
471}
472
473bool MediaStream::canProduceAudio() const
474{
475 return !muted() && active() && m_private->hasAudio() && m_isProducingData;
476}
477
478bool MediaStream::processingUserGestureForMedia() const
479{
480 return document() ? document()->processingUserGestureForMedia() : false;
481}
482
483void MediaStream::stop()
484{
485 m_isActive = false;
486 endCaptureTracks();
487}
488
489const char* MediaStream::activeDOMObjectName() const
490{
491 return "MediaStream";
492}
493
494bool MediaStream::canSuspendForDocumentSuspension() const
495{
496 return !hasPendingActivity();
497}
498
499bool MediaStream::hasPendingActivity() const
500{
501 return m_isActive;
502}
503
504#if !RELEASE_LOG_DISABLED
505WTFLogChannel& MediaStream::logChannel() const
506{
507 return LogWebRTC;
508}
509#endif
510
511} // namespace WebCore
512
513#endif // ENABLE(MEDIA_STREAM)
514