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 | |
47 | namespace WebCore { |
48 | |
49 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaStream); |
50 | |
51 | Ref<MediaStream> MediaStream::create(Document& document) |
52 | { |
53 | return MediaStream::create(document, MediaStreamPrivate::create(document.logger(), { })); |
54 | } |
55 | |
56 | Ref<MediaStream> MediaStream::create(Document& document, MediaStream& stream) |
57 | { |
58 | return adoptRef(*new MediaStream(document, stream.getTracks())); |
59 | } |
60 | |
61 | Ref<MediaStream> MediaStream::create(Document& document, const MediaStreamTrackVector& tracks) |
62 | { |
63 | return adoptRef(*new MediaStream(document, tracks)); |
64 | } |
65 | |
66 | Ref<MediaStream> MediaStream::create(Document& document, Ref<MediaStreamPrivate>&& streamPrivate) |
67 | { |
68 | return adoptRef(*new MediaStream(document, WTFMove(streamPrivate))); |
69 | } |
70 | |
71 | static 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 | |
80 | MediaStream::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 | |
99 | MediaStream::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 | |
118 | MediaStream::~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 | |
133 | RefPtr<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 | |
146 | void 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 | |
157 | void 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 | |
168 | MediaStreamTrack* 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 | |
177 | MediaStreamTrackVector MediaStream::getAudioTracks() const |
178 | { |
179 | return trackVectorForType(RealtimeMediaSource::Type::Audio); |
180 | } |
181 | |
182 | MediaStreamTrackVector MediaStream::getVideoTracks() const |
183 | { |
184 | return trackVectorForType(RealtimeMediaSource::Type::Video); |
185 | } |
186 | |
187 | MediaStreamTrackVector MediaStream::getTracks() const |
188 | { |
189 | return copyToVector(m_trackSet.values()); |
190 | } |
191 | |
192 | void MediaStream::trackDidEnd() |
193 | { |
194 | m_private->updateActiveState(MediaStreamPrivate::NotifyClientOption::Notify); |
195 | } |
196 | |
197 | void MediaStream::activeStatusChanged() |
198 | { |
199 | updateActiveState(); |
200 | } |
201 | |
202 | void 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 | |
212 | void MediaStream::didRemoveTrack(MediaStreamTrackPrivate& trackPrivate) |
213 | { |
214 | internalRemoveTrack(trackPrivate.id(), StreamModifier::Platform); |
215 | } |
216 | |
217 | void 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 | |
226 | bool 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 | |
246 | bool 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 | |
264 | void 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 | |
275 | void 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 | |
287 | void 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 | |
314 | void 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 | |
328 | void 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 | |
338 | MediaProducer::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 | |
351 | void 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 | |
362 | void MediaStream::characteristicsChanged() |
363 | { |
364 | auto state = mediaState(); |
365 | if (m_state != state) { |
366 | m_state = state; |
367 | statusDidChange(); |
368 | } |
369 | } |
370 | |
371 | void 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 | |
387 | URLRegistry& MediaStream::registry() const |
388 | { |
389 | return MediaStreamRegistry::shared(); |
390 | } |
391 | |
392 | MediaStreamTrackVector 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 | |
403 | void MediaStream::addObserver(MediaStream::Observer* observer) |
404 | { |
405 | if (m_observers.find(observer) == notFound) |
406 | m_observers.append(observer); |
407 | } |
408 | |
409 | void 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 | |
416 | Document* MediaStream::document() const |
417 | { |
418 | return downcast<Document>(scriptExecutionContext()); |
419 | } |
420 | |
421 | PlatformMediaSession::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 | |
431 | PlatformMediaSession::MediaType MediaStream::presentationType() const |
432 | { |
433 | return mediaType(); |
434 | } |
435 | |
436 | PlatformMediaSession::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 | |
452 | void 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 | |
457 | void MediaStream::suspendPlayback() |
458 | { |
459 | // FIXME: should a media stream pay attention to this directly, or only when attached to a media element? |
460 | } |
461 | |
462 | String 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 | |
473 | bool MediaStream::canProduceAudio() const |
474 | { |
475 | return !muted() && active() && m_private->hasAudio() && m_isProducingData; |
476 | } |
477 | |
478 | bool MediaStream::processingUserGestureForMedia() const |
479 | { |
480 | return document() ? document()->processingUserGestureForMedia() : false; |
481 | } |
482 | |
483 | void MediaStream::stop() |
484 | { |
485 | m_isActive = false; |
486 | endCaptureTracks(); |
487 | } |
488 | |
489 | const char* MediaStream::activeDOMObjectName() const |
490 | { |
491 | return "MediaStream" ; |
492 | } |
493 | |
494 | bool MediaStream::canSuspendForDocumentSuspension() const |
495 | { |
496 | return !hasPendingActivity(); |
497 | } |
498 | |
499 | bool MediaStream::hasPendingActivity() const |
500 | { |
501 | return m_isActive; |
502 | } |
503 | |
504 | #if !RELEASE_LOG_DISABLED |
505 | WTFLogChannel& MediaStream::logChannel() const |
506 | { |
507 | return LogWebRTC; |
508 | } |
509 | #endif |
510 | |
511 | } // namespace WebCore |
512 | |
513 | #endif // ENABLE(MEDIA_STREAM) |
514 | |