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(ScriptExecutionContext& context)
52{
53 return MediaStream::create(context, MediaStreamPrivate::create({ }));
54}
55
56Ref<MediaStream> MediaStream::create(ScriptExecutionContext& context, MediaStream& stream)
57{
58 return adoptRef(*new MediaStream(context, stream.getTracks()));
59}
60
61Ref<MediaStream> MediaStream::create(ScriptExecutionContext& context, const MediaStreamTrackVector& tracks)
62{
63 return adoptRef(*new MediaStream(context, tracks));
64}
65
66Ref<MediaStream> MediaStream::create(ScriptExecutionContext& context, Ref<MediaStreamPrivate>&& streamPrivate)
67{
68 return adoptRef(*new MediaStream(context, 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(ScriptExecutionContext& context, const MediaStreamTrackVector& tracks)
81 : ActiveDOMObject(&context)
82 , m_private(MediaStreamPrivate::create(createTrackPrivateVector(tracks)))
83 , m_mediaSession(PlatformMediaSession::create(*this))
84#if !RELEASE_LOG_DISABLED
85 , m_logger(document()->logger())
86 , m_logIdentifier(uniqueLogIdentifier())
87#endif
88{
89 // This constructor preserves MediaStreamTrack instances and must be used by calls originating
90 // from the JavaScript MediaStream constructor.
91
92#if !RELEASE_LOG_DISABLED
93 ALWAYS_LOG(LOGIDENTIFIER);
94 m_private->setLogger(logger(), logIdentifier());
95#endif
96
97 for (auto& track : tracks) {
98 track->addObserver(*this);
99 m_trackSet.add(track->id(), track);
100 }
101
102 setIsActive(m_private->active());
103 m_private->addObserver(*this);
104 MediaStreamRegistry::shared().registerStream(*this);
105 suspendIfNeeded();
106}
107
108MediaStream::MediaStream(ScriptExecutionContext& context, Ref<MediaStreamPrivate>&& streamPrivate)
109 : ActiveDOMObject(&context)
110 , m_private(WTFMove(streamPrivate))
111 , m_mediaSession(PlatformMediaSession::create(*this))
112#if !RELEASE_LOG_DISABLED
113 , m_logger(document()->logger())
114 , m_logIdentifier(uniqueLogIdentifier())
115#endif
116{
117#if !RELEASE_LOG_DISABLED
118 ALWAYS_LOG(LOGIDENTIFIER);
119 m_private->setLogger(logger(), logIdentifier());
120#endif
121 setIsActive(m_private->active());
122 m_private->addObserver(*this);
123 MediaStreamRegistry::shared().registerStream(*this);
124
125 for (auto& trackPrivate : m_private->tracks()) {
126 auto track = MediaStreamTrack::create(context, *trackPrivate);
127 track->addObserver(*this);
128 m_trackSet.add(track->id(), WTFMove(track));
129 }
130 suspendIfNeeded();
131}
132
133MediaStream::~MediaStream()
134{
135 // Set isActive to false immediately so any callbacks triggered by shutting down, e.g.
136 // mediaState(), are short circuited.
137 m_isActive = false;
138 MediaStreamRegistry::shared().unregisterStream(*this);
139 m_private->removeObserver(*this);
140 for (auto& track : m_trackSet.values())
141 track->removeObserver(*this);
142 if (Document* document = this->document()) {
143 if (m_isWaitingUntilMediaCanStart)
144 document->removeMediaCanStartListener(*this);
145 }
146}
147
148RefPtr<MediaStream> MediaStream::clone()
149{
150 ALWAYS_LOG(LOGIDENTIFIER);
151
152 MediaStreamTrackVector clonedTracks;
153 clonedTracks.reserveInitialCapacity(m_trackSet.size());
154
155 for (auto& track : m_trackSet.values())
156 clonedTracks.uncheckedAppend(track->clone());
157
158 return MediaStream::create(*scriptExecutionContext(), clonedTracks);
159}
160
161void MediaStream::addTrack(MediaStreamTrack& track)
162{
163 ALWAYS_LOG(LOGIDENTIFIER, track.logIdentifier());
164
165 if (!internalAddTrack(track, StreamModifier::DomAPI))
166 return;
167
168 for (auto& observer : m_observers)
169 observer->didAddOrRemoveTrack();
170}
171
172void MediaStream::removeTrack(MediaStreamTrack& track)
173{
174 ALWAYS_LOG(LOGIDENTIFIER, track.logIdentifier());
175
176 if (!internalRemoveTrack(track.id(), StreamModifier::DomAPI))
177 return;
178
179 for (auto& observer : m_observers)
180 observer->didAddOrRemoveTrack();
181}
182
183MediaStreamTrack* MediaStream::getTrackById(String id)
184{
185 auto it = m_trackSet.find(id);
186 if (it != m_trackSet.end())
187 return it->value.get();
188
189 return nullptr;
190}
191
192MediaStreamTrackVector MediaStream::getAudioTracks() const
193{
194 return trackVectorForType(RealtimeMediaSource::Type::Audio);
195}
196
197MediaStreamTrackVector MediaStream::getVideoTracks() const
198{
199 return trackVectorForType(RealtimeMediaSource::Type::Video);
200}
201
202MediaStreamTrackVector MediaStream::getTracks() const
203{
204 return copyToVector(m_trackSet.values());
205}
206
207void MediaStream::trackDidEnd()
208{
209 m_private->updateActiveState(MediaStreamPrivate::NotifyClientOption::Notify);
210}
211
212void MediaStream::activeStatusChanged()
213{
214 updateActiveState();
215}
216
217void MediaStream::didAddTrack(MediaStreamTrackPrivate& trackPrivate)
218{
219 ScriptExecutionContext* context = scriptExecutionContext();
220 if (!context)
221 return;
222
223 if (!getTrackById(trackPrivate.id()))
224 internalAddTrack(MediaStreamTrack::create(*context, trackPrivate), StreamModifier::Platform);
225}
226
227void MediaStream::didRemoveTrack(MediaStreamTrackPrivate& trackPrivate)
228{
229 internalRemoveTrack(trackPrivate.id(), StreamModifier::Platform);
230}
231
232void MediaStream::addTrackFromPlatform(Ref<MediaStreamTrack>&& track)
233{
234 ALWAYS_LOG(LOGIDENTIFIER, track->logIdentifier());
235
236 auto* privateTrack = &track->privateTrack();
237 internalAddTrack(WTFMove(track), StreamModifier::Platform);
238 m_private->addTrack(privateTrack, MediaStreamPrivate::NotifyClientOption::Notify);
239}
240
241bool MediaStream::internalAddTrack(Ref<MediaStreamTrack>&& trackToAdd, StreamModifier streamModifier)
242{
243 auto result = m_trackSet.add(trackToAdd->id(), WTFMove(trackToAdd));
244 if (!result.isNewEntry)
245 return false;
246
247 ASSERT(result.iterator->value);
248 auto& track = *result.iterator->value;
249 track.addObserver(*this);
250
251 updateActiveState();
252
253 if (streamModifier == StreamModifier::DomAPI)
254 m_private->addTrack(&track.privateTrack(), MediaStreamPrivate::NotifyClientOption::DontNotify);
255 else
256 dispatchEvent(MediaStreamTrackEvent::create(eventNames().addtrackEvent, Event::CanBubble::No, Event::IsCancelable::No, &track));
257
258 return true;
259}
260
261bool MediaStream::internalRemoveTrack(const String& trackId, StreamModifier streamModifier)
262{
263 auto track = m_trackSet.take(trackId);
264 if (!track)
265 return false;
266
267 track->removeObserver(*this);
268
269 updateActiveState();
270
271 if (streamModifier == StreamModifier::DomAPI)
272 m_private->removeTrack(track->privateTrack(), MediaStreamPrivate::NotifyClientOption::DontNotify);
273 else
274 dispatchEvent(MediaStreamTrackEvent::create(eventNames().removetrackEvent, Event::CanBubble::No, Event::IsCancelable::No, WTFMove(track)));
275
276 return true;
277}
278
279void MediaStream::setIsActive(bool active)
280{
281 if (m_isActive == active)
282 return;
283
284 ALWAYS_LOG(LOGIDENTIFIER, active);
285
286 m_isActive = active;
287 statusDidChange();
288}
289
290void MediaStream::mediaCanStart(Document& document)
291{
292 ALWAYS_LOG(LOGIDENTIFIER);
293
294 ASSERT_UNUSED(document, &document == this->document());
295 ASSERT(m_isWaitingUntilMediaCanStart);
296 if (m_isWaitingUntilMediaCanStart) {
297 m_isWaitingUntilMediaCanStart = false;
298 startProducingData();
299 }
300}
301
302void MediaStream::startProducingData()
303{
304 Document* document = this->document();
305 if (!document || !document->page())
306 return;
307
308 ALWAYS_LOG(LOGIDENTIFIER);
309
310 // If we can't start a load right away, start it later.
311 if (!document->page()->canStartMedia()) {
312 ALWAYS_LOG(LOGIDENTIFIER, "not allowed to start in background, waiting");
313 if (m_isWaitingUntilMediaCanStart)
314 return;
315
316 m_isWaitingUntilMediaCanStart = true;
317 document->addMediaCanStartListener(*this);
318 return;
319 }
320
321 if (m_isProducingData)
322 return;
323 m_isProducingData = true;
324
325 m_mediaSession->canProduceAudioChanged();
326 m_private->startProducingData();
327}
328
329void MediaStream::stopProducingData()
330{
331 if (!m_isProducingData)
332 return;
333
334 ALWAYS_LOG(LOGIDENTIFIER);
335
336 m_isProducingData = false;
337
338 m_mediaSession->canProduceAudioChanged();
339
340 m_private->stopProducingData();
341}
342
343void MediaStream::endCaptureTracks()
344{
345 ALWAYS_LOG(LOGIDENTIFIER);
346
347 for (auto& track : m_trackSet.values()) {
348 if (track->isCaptureTrack())
349 track->stopTrack(MediaStreamTrack::StopMode::PostEvent);
350 }
351}
352
353MediaProducer::MediaStateFlags MediaStream::mediaState() const
354{
355 MediaProducer::MediaStateFlags state = MediaProducer::IsNotPlaying;
356
357 if (!m_isActive || !document() || !document()->page())
358 return state;
359
360 for (const auto& track : m_trackSet.values())
361 state |= track->mediaState();
362
363 return state;
364}
365
366void MediaStream::statusDidChange()
367{
368 m_mediaSession->canProduceAudioChanged();
369
370 if (Document* document = this->document()) {
371 if (!m_isActive)
372 return;
373 document->updateIsPlayingMedia();
374 }
375}
376
377void MediaStream::characteristicsChanged()
378{
379 auto state = mediaState();
380 if (m_state != state) {
381 m_state = state;
382 statusDidChange();
383 }
384}
385
386void MediaStream::updateActiveState()
387{
388 bool active = false;
389 for (auto& track : m_trackSet.values()) {
390 if (!track->ended()) {
391 active = true;
392 break;
393 }
394 }
395
396 if (m_isActive == active)
397 return;
398
399 setIsActive(active);
400}
401
402URLRegistry& MediaStream::registry() const
403{
404 return MediaStreamRegistry::shared();
405}
406
407MediaStreamTrackVector MediaStream::trackVectorForType(RealtimeMediaSource::Type filterType) const
408{
409 MediaStreamTrackVector tracks;
410 for (auto& track : m_trackSet.values()) {
411 if (track->source().type() == filterType)
412 tracks.append(track);
413 }
414
415 return tracks;
416}
417
418void MediaStream::addObserver(MediaStream::Observer* observer)
419{
420 if (m_observers.find(observer) == notFound)
421 m_observers.append(observer);
422}
423
424void MediaStream::removeObserver(MediaStream::Observer* observer)
425{
426 size_t pos = m_observers.find(observer);
427 if (pos != notFound)
428 m_observers.remove(pos);
429}
430
431Document* MediaStream::document() const
432{
433 return downcast<Document>(scriptExecutionContext());
434}
435
436PlatformMediaSession::MediaType MediaStream::mediaType() const
437{
438 // We only need to override the type when capturing audio, HTMLMediaElement and/or WebAudio
439 // will do the right thing when a stream is attached to a media element or an audio context.
440 if (m_private->hasAudio() && m_isProducingData && m_private->hasCaptureAudioSource())
441 return PlatformMediaSession::MediaStreamCapturingAudio;
442
443 return PlatformMediaSession::None;
444}
445
446PlatformMediaSession::MediaType MediaStream::presentationType() const
447{
448 return mediaType();
449}
450
451PlatformMediaSession::CharacteristicsFlags MediaStream::characteristics() const
452{
453 PlatformMediaSession::CharacteristicsFlags state = PlatformMediaSession::HasNothing;
454
455 if (!m_isProducingData)
456 return state;
457
458 if (m_private->hasAudio())
459 state |= PlatformMediaSession::HasAudio;
460
461 if (m_private->hasVideo())
462 state |= PlatformMediaSession::HasVideo;
463
464 return state;
465}
466
467void MediaStream::mayResumePlayback(bool)
468{
469 // FIXME: should a media stream pay attention to this directly, or only when attached to a media element?
470}
471
472void MediaStream::suspendPlayback()
473{
474 // FIXME: should a media stream pay attention to this directly, or only when attached to a media element?
475}
476
477String MediaStream::sourceApplicationIdentifier() const
478{
479 Document* document = this->document();
480 if (document && document->frame()) {
481 if (NetworkingContext* networkingContext = document->frame()->loader().networkingContext())
482 return networkingContext->sourceApplicationIdentifier();
483 }
484
485 return emptyString();
486}
487
488bool MediaStream::canProduceAudio() const
489{
490 return !muted() && active() && m_private->hasAudio() && m_isProducingData;
491}
492
493bool MediaStream::processingUserGestureForMedia() const
494{
495 return document() ? document()->processingUserGestureForMedia() : false;
496}
497
498void MediaStream::stop()
499{
500 m_isActive = false;
501 endCaptureTracks();
502}
503
504const char* MediaStream::activeDOMObjectName() const
505{
506 return "MediaStream";
507}
508
509bool MediaStream::canSuspendForDocumentSuspension() const
510{
511 return !hasPendingActivity();
512}
513
514bool MediaStream::hasPendingActivity() const
515{
516 return m_isActive;
517}
518
519#if !RELEASE_LOG_DISABLED
520WTFLogChannel& MediaStream::logChannel() const
521{
522 return LogWebRTC;
523}
524#endif
525
526} // namespace WebCore
527
528#endif // ENABLE(MEDIA_STREAM)
529