MediaStreamAudioSourceNode.cpp (9259B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "MediaStreamAudioSourceNode.h" 8 9 #include "AudioNodeEngine.h" 10 #include "AudioNodeExternalInputTrack.h" 11 #include "AudioStreamTrack.h" 12 #include "Tracing.h" 13 #include "mozilla/dom/Document.h" 14 #include "mozilla/dom/MediaStreamAudioSourceNodeBinding.h" 15 #include "nsContentUtils.h" 16 #include "nsGlobalWindowInner.h" 17 #include "nsID.h" 18 #include "nsIScriptError.h" 19 20 namespace mozilla::dom { 21 22 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamAudioSourceNode) 23 24 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaStreamAudioSourceNode) 25 tmp->Destroy(); 26 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStream) 27 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputTrack) 28 NS_IMPL_CYCLE_COLLECTION_UNLINK(mListener) 29 NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(AudioNode) 30 31 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamAudioSourceNode, 32 AudioNode) 33 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStream) 34 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputTrack) 35 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListener) 36 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 37 38 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamAudioSourceNode) 39 NS_INTERFACE_MAP_END_INHERITING(AudioNode) 40 41 NS_IMPL_ADDREF_INHERITED(MediaStreamAudioSourceNode, AudioNode) 42 NS_IMPL_RELEASE_INHERITED(MediaStreamAudioSourceNode, AudioNode) 43 44 MediaStreamAudioSourceNode::MediaStreamAudioSourceNode( 45 AudioContext* aContext, TrackChangeBehavior aBehavior) 46 : AudioNode(aContext, 2, ChannelCountMode::Max, 47 ChannelInterpretation::Speakers), 48 mBehavior(aBehavior) {} 49 50 /* static */ 51 already_AddRefed<MediaStreamAudioSourceNode> MediaStreamAudioSourceNode::Create( 52 AudioContext& aAudioContext, const MediaStreamAudioSourceOptions& aOptions, 53 ErrorResult& aRv) { 54 // The spec has a pointless check here. See 55 // https://github.com/WebAudio/web-audio-api/issues/2149 56 MOZ_RELEASE_ASSERT(!aAudioContext.IsOffline(), "Bindings messed up?"); 57 58 RefPtr<MediaStreamAudioSourceNode> node = 59 new MediaStreamAudioSourceNode(&aAudioContext, LockOnTrackPicked); 60 61 // aOptions.mMediaStream is not nullable. 62 node->Init(*aOptions.mMediaStream, aRv); 63 if (aRv.Failed()) { 64 return nullptr; 65 } 66 67 return node.forget(); 68 } 69 70 void MediaStreamAudioSourceNode::Init(DOMMediaStream& aMediaStream, 71 ErrorResult& aRv) { 72 mListener = new TrackListener(this); 73 mInputStream = &aMediaStream; 74 AudioNodeEngine* engine = new MediaStreamAudioSourceNodeEngine(this); 75 mTrack = AudioNodeExternalInputTrack::Create(Context()->Graph(), engine); 76 mInputStream->AddConsumerToKeepAlive(ToSupports(this)); 77 78 mInputStream->RegisterTrackListener(mListener); 79 if (mInputStream->Audible()) { 80 NotifyAudible(); 81 } 82 AttachToRightTrack(mInputStream, aRv); 83 } 84 85 void MediaStreamAudioSourceNode::Destroy() { 86 if (mInputStream) { 87 mInputStream->UnregisterTrackListener(mListener); 88 mInputStream = nullptr; 89 mListener = nullptr; 90 } 91 DetachFromTrack(); 92 } 93 94 MediaStreamAudioSourceNode::~MediaStreamAudioSourceNode() { Destroy(); } 95 96 void MediaStreamAudioSourceNode::AttachToTrack(AudioStreamTrack* aTrack) { 97 MOZ_ASSERT(aTrack); 98 MOZ_ASSERT(!mInputTrack); 99 MOZ_DIAGNOSTIC_ASSERT(!aTrack->Ended()); 100 101 if (!mTrack) { 102 return; 103 } 104 105 mInputTrack = aTrack; 106 ProcessedMediaTrack* outputTrack = 107 static_cast<ProcessedMediaTrack*>(mTrack.get()); 108 mInputPort = aTrack->AddConsumerPort(outputTrack); 109 MOZ_DIAGNOSTIC_ASSERT(mInputPort); 110 111 PrincipalChanged(mInputTrack); // trigger enabling/disabling of the connector 112 mInputTrack->AddPrincipalChangeObserver(this); 113 MarkActive(); 114 } 115 116 void MediaStreamAudioSourceNode::DetachFromTrack() { 117 if (mInputTrack) { 118 mInputTrack->RemovePrincipalChangeObserver(this); 119 mInputTrack->RemoveConsumerPort(mInputPort); 120 mInputTrack = nullptr; 121 } 122 if (mInputPort) { 123 mInputPort->Destroy(); 124 mInputPort = nullptr; 125 } 126 } 127 128 static int AudioTrackCompare(const RefPtr<AudioStreamTrack>& aLhs, 129 const RefPtr<AudioStreamTrack>& aRhs) { 130 nsAutoStringN<NSID_LENGTH> IDLhs; 131 nsAutoStringN<NSID_LENGTH> IDRhs; 132 aLhs->GetId(IDLhs); 133 aRhs->GetId(IDRhs); 134 return Compare(NS_ConvertUTF16toUTF8(IDLhs), NS_ConvertUTF16toUTF8(IDRhs)); 135 } 136 137 void MediaStreamAudioSourceNode::AttachToRightTrack( 138 const RefPtr<DOMMediaStream>& aMediaStream, ErrorResult& aRv) { 139 nsTArray<RefPtr<AudioStreamTrack>> tracks; 140 aMediaStream->GetAudioTracks(tracks); 141 142 if (tracks.IsEmpty() && mBehavior == LockOnTrackPicked) { 143 aRv.ThrowInvalidStateError("No audio tracks in MediaStream"); 144 return; 145 } 146 147 // Sort the track to have a stable order, on their ID by lexicographic 148 // ordering on sequences of code unit values. 149 tracks.Sort(AudioTrackCompare); 150 151 for (const RefPtr<AudioStreamTrack>& track : tracks) { 152 if (mBehavior == FollowChanges) { 153 if (track->Ended()) { 154 continue; 155 } 156 } 157 158 if (!track->Ended()) { 159 AttachToTrack(track); 160 } 161 return; 162 } 163 164 // There was no track available. We'll allow the node to be garbage collected. 165 MarkInactive(); 166 } 167 168 void MediaStreamAudioSourceNode::NotifyTrackAdded( 169 const RefPtr<MediaStreamTrack>& aTrack) { 170 if (mBehavior != FollowChanges) { 171 return; 172 } 173 if (mInputTrack) { 174 return; 175 } 176 177 if (!aTrack->AsAudioStreamTrack()) { 178 return; 179 } 180 181 AttachToTrack(aTrack->AsAudioStreamTrack()); 182 } 183 184 void MediaStreamAudioSourceNode::NotifyTrackRemoved( 185 const RefPtr<MediaStreamTrack>& aTrack) { 186 if (mBehavior == FollowChanges) { 187 if (aTrack != mInputTrack) { 188 return; 189 } 190 191 DetachFromTrack(); 192 AttachToRightTrack(mInputStream, IgnoreErrors()); 193 } 194 } 195 196 void MediaStreamAudioSourceNode::NotifyAudible() { 197 MOZ_ASSERT(mInputStream); 198 Context()->StartBlockedAudioContextIfAllowed(); 199 } 200 201 /** 202 * Changes the principal. Note that this will be called on the main thread, but 203 * changes will be enacted on the MediaTrackGraph thread. If the principal 204 * change results in the document principal losing access to the stream, then 205 * there needs to be other measures in place to ensure that any media that is 206 * governed by the new stream principal is not available to the MediaTrackGraph 207 * before this change completes. Otherwise, a site could get access to 208 * media that they are not authorized to receive. 209 * 210 * One solution is to block the altered content, call this method, then dispatch 211 * another change request to the MediaTrackGraph thread that allows the content 212 * under the new principal to flow. This might be unnecessary if the principal 213 * change is changing to be the document principal. 214 */ 215 void MediaStreamAudioSourceNode::PrincipalChanged( 216 MediaStreamTrack* aMediaStreamTrack) { 217 MOZ_ASSERT(aMediaStreamTrack == mInputTrack); 218 219 bool subsumes = false; 220 Document* doc = nullptr; 221 if (nsGlobalWindowInner* parent = Context()->GetOwnerWindow()) { 222 doc = parent->GetExtantDoc(); 223 if (doc) { 224 nsIPrincipal* docPrincipal = doc->NodePrincipal(); 225 nsIPrincipal* trackPrincipal = aMediaStreamTrack->GetPrincipal(); 226 if (!trackPrincipal || 227 NS_FAILED(docPrincipal->Subsumes(trackPrincipal, &subsumes))) { 228 subsumes = false; 229 } 230 } 231 } 232 auto track = static_cast<AudioNodeExternalInputTrack*>(mTrack.get()); 233 bool enabled = subsumes; 234 track->SetInt32Parameter(MediaStreamAudioSourceNodeEngine::ENABLE, enabled); 235 236 if (!enabled && doc) { 237 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Web Audio"_ns, 238 doc, nsContentUtils::eDOM_PROPERTIES, 239 CrossOriginErrorString()); 240 } 241 } 242 243 size_t MediaStreamAudioSourceNode::SizeOfExcludingThis( 244 MallocSizeOf aMallocSizeOf) const { 245 // Future: 246 // - mInputStream 247 size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); 248 if (mInputPort) { 249 amount += mInputPort->SizeOfIncludingThis(aMallocSizeOf); 250 } 251 return amount; 252 } 253 254 size_t MediaStreamAudioSourceNode::SizeOfIncludingThis( 255 MallocSizeOf aMallocSizeOf) const { 256 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 257 } 258 259 void MediaStreamAudioSourceNode::DestroyMediaTrack() { 260 DetachFromTrack(); 261 AudioNode::DestroyMediaTrack(); 262 } 263 264 JSObject* MediaStreamAudioSourceNode::WrapObject( 265 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { 266 return MediaStreamAudioSourceNode_Binding::Wrap(aCx, this, aGivenProto); 267 } 268 269 NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaStreamAudioSourceNode::TrackListener, 270 DOMMediaStream::TrackListener, mNode) 271 NS_IMPL_ADDREF_INHERITED(MediaStreamAudioSourceNode::TrackListener, 272 DOMMediaStream::TrackListener) 273 NS_IMPL_RELEASE_INHERITED(MediaStreamAudioSourceNode::TrackListener, 274 DOMMediaStream::TrackListener) 275 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( 276 MediaStreamAudioSourceNode::TrackListener) 277 NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream::TrackListener) 278 279 } // namespace mozilla::dom