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(ScriptExecutionContext& context) |
52 | { |
53 | return MediaStream::create(context, MediaStreamPrivate::create({ })); |
54 | } |
55 | |
56 | Ref<MediaStream> MediaStream::create(ScriptExecutionContext& context, MediaStream& stream) |
57 | { |
58 | return adoptRef(*new MediaStream(context, stream.getTracks())); |
59 | } |
60 | |
61 | Ref<MediaStream> MediaStream::create(ScriptExecutionContext& context, const MediaStreamTrackVector& tracks) |
62 | { |
63 | return adoptRef(*new MediaStream(context, tracks)); |
64 | } |
65 | |
66 | Ref<MediaStream> MediaStream::create(ScriptExecutionContext& context, Ref<MediaStreamPrivate>&& streamPrivate) |
67 | { |
68 | return adoptRef(*new MediaStream(context, 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(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 | |
108 | MediaStream::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 | |
133 | MediaStream::~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 | |
148 | RefPtr<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 | |
161 | void 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 | |
172 | void 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 | |
183 | MediaStreamTrack* 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 | |
192 | MediaStreamTrackVector MediaStream::getAudioTracks() const |
193 | { |
194 | return trackVectorForType(RealtimeMediaSource::Type::Audio); |
195 | } |
196 | |
197 | MediaStreamTrackVector MediaStream::getVideoTracks() const |
198 | { |
199 | return trackVectorForType(RealtimeMediaSource::Type::Video); |
200 | } |
201 | |
202 | MediaStreamTrackVector MediaStream::getTracks() const |
203 | { |
204 | return copyToVector(m_trackSet.values()); |
205 | } |
206 | |
207 | void MediaStream::trackDidEnd() |
208 | { |
209 | m_private->updateActiveState(MediaStreamPrivate::NotifyClientOption::Notify); |
210 | } |
211 | |
212 | void MediaStream::activeStatusChanged() |
213 | { |
214 | updateActiveState(); |
215 | } |
216 | |
217 | void 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 | |
227 | void MediaStream::didRemoveTrack(MediaStreamTrackPrivate& trackPrivate) |
228 | { |
229 | internalRemoveTrack(trackPrivate.id(), StreamModifier::Platform); |
230 | } |
231 | |
232 | void 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 | |
241 | bool 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 | |
261 | bool 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 | |
279 | void 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 | |
290 | void 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 | |
302 | void 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 | |
329 | void 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 | |
343 | void 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 | |
353 | MediaProducer::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 | |
366 | void 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 | |
377 | void MediaStream::characteristicsChanged() |
378 | { |
379 | auto state = mediaState(); |
380 | if (m_state != state) { |
381 | m_state = state; |
382 | statusDidChange(); |
383 | } |
384 | } |
385 | |
386 | void 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 | |
402 | URLRegistry& MediaStream::registry() const |
403 | { |
404 | return MediaStreamRegistry::shared(); |
405 | } |
406 | |
407 | MediaStreamTrackVector 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 | |
418 | void MediaStream::addObserver(MediaStream::Observer* observer) |
419 | { |
420 | if (m_observers.find(observer) == notFound) |
421 | m_observers.append(observer); |
422 | } |
423 | |
424 | void 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 | |
431 | Document* MediaStream::document() const |
432 | { |
433 | return downcast<Document>(scriptExecutionContext()); |
434 | } |
435 | |
436 | PlatformMediaSession::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 | |
446 | PlatformMediaSession::MediaType MediaStream::presentationType() const |
447 | { |
448 | return mediaType(); |
449 | } |
450 | |
451 | PlatformMediaSession::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 | |
467 | void 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 | |
472 | void MediaStream::suspendPlayback() |
473 | { |
474 | // FIXME: should a media stream pay attention to this directly, or only when attached to a media element? |
475 | } |
476 | |
477 | String 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 | |
488 | bool MediaStream::canProduceAudio() const |
489 | { |
490 | return !muted() && active() && m_private->hasAudio() && m_isProducingData; |
491 | } |
492 | |
493 | bool MediaStream::processingUserGestureForMedia() const |
494 | { |
495 | return document() ? document()->processingUserGestureForMedia() : false; |
496 | } |
497 | |
498 | void MediaStream::stop() |
499 | { |
500 | m_isActive = false; |
501 | endCaptureTracks(); |
502 | } |
503 | |
504 | const char* MediaStream::activeDOMObjectName() const |
505 | { |
506 | return "MediaStream" ; |
507 | } |
508 | |
509 | bool MediaStream::canSuspendForDocumentSuspension() const |
510 | { |
511 | return !hasPendingActivity(); |
512 | } |
513 | |
514 | bool MediaStream::hasPendingActivity() const |
515 | { |
516 | return m_isActive; |
517 | } |
518 | |
519 | #if !RELEASE_LOG_DISABLED |
520 | WTFLogChannel& MediaStream::logChannel() const |
521 | { |
522 | return LogWebRTC; |
523 | } |
524 | #endif |
525 | |
526 | } // namespace WebCore |
527 | |
528 | #endif // ENABLE(MEDIA_STREAM) |
529 | |