|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "DOMMediaStream.h" |
|
7 #include "nsContentUtils.h" |
|
8 #include "mozilla/dom/MediaStreamBinding.h" |
|
9 #include "mozilla/dom/LocalMediaStreamBinding.h" |
|
10 #include "mozilla/dom/AudioNode.h" |
|
11 #include "MediaStreamGraph.h" |
|
12 #include "AudioStreamTrack.h" |
|
13 #include "VideoStreamTrack.h" |
|
14 |
|
15 using namespace mozilla; |
|
16 using namespace mozilla::dom; |
|
17 |
|
18 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMMediaStream) |
|
19 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
|
20 NS_INTERFACE_MAP_ENTRY(nsISupports) |
|
21 NS_INTERFACE_MAP_ENTRY(nsIDOMMediaStream) |
|
22 NS_INTERFACE_MAP_END |
|
23 |
|
24 NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMMediaStream) |
|
25 NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMMediaStream) |
|
26 |
|
27 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaStream) |
|
28 |
|
29 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMMediaStream) |
|
30 tmp->Destroy(); |
|
31 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) |
|
32 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTracks) |
|
33 NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumersToKeepAlive) |
|
34 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER |
|
35 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
36 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMMediaStream) |
|
37 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) |
|
38 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTracks) |
|
39 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumersToKeepAlive) |
|
40 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS |
|
41 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
42 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(DOMMediaStream) |
|
43 |
|
44 NS_IMPL_ISUPPORTS_INHERITED(DOMLocalMediaStream, DOMMediaStream, |
|
45 nsIDOMLocalMediaStream) |
|
46 |
|
47 NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream, |
|
48 mStreamNode) |
|
49 |
|
50 NS_IMPL_ADDREF_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream) |
|
51 NS_IMPL_RELEASE_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream) |
|
52 |
|
53 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMAudioNodeMediaStream) |
|
54 NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream) |
|
55 |
|
56 class DOMMediaStream::StreamListener : public MediaStreamListener { |
|
57 public: |
|
58 StreamListener(DOMMediaStream* aStream) |
|
59 : mStream(aStream) |
|
60 {} |
|
61 |
|
62 // Main thread only |
|
63 void Forget() { mStream = nullptr; } |
|
64 DOMMediaStream* GetStream() { return mStream; } |
|
65 |
|
66 class TrackChange : public nsRunnable { |
|
67 public: |
|
68 TrackChange(StreamListener* aListener, |
|
69 TrackID aID, TrackTicks aTrackOffset, |
|
70 uint32_t aEvents, MediaSegment::Type aType) |
|
71 : mListener(aListener), mID(aID), mEvents(aEvents), mType(aType) |
|
72 { |
|
73 } |
|
74 |
|
75 NS_IMETHOD Run() |
|
76 { |
|
77 NS_ASSERTION(NS_IsMainThread(), "main thread only"); |
|
78 |
|
79 DOMMediaStream* stream = mListener->GetStream(); |
|
80 if (!stream) { |
|
81 return NS_OK; |
|
82 } |
|
83 |
|
84 nsRefPtr<MediaStreamTrack> track; |
|
85 if (mEvents & MediaStreamListener::TRACK_EVENT_CREATED) { |
|
86 track = stream->CreateDOMTrack(mID, mType); |
|
87 } else { |
|
88 track = stream->GetDOMTrackFor(mID); |
|
89 } |
|
90 if (mEvents & MediaStreamListener::TRACK_EVENT_ENDED) { |
|
91 track->NotifyEnded(); |
|
92 } |
|
93 return NS_OK; |
|
94 } |
|
95 |
|
96 StreamTime mEndTime; |
|
97 nsRefPtr<StreamListener> mListener; |
|
98 TrackID mID; |
|
99 uint32_t mEvents; |
|
100 MediaSegment::Type mType; |
|
101 }; |
|
102 |
|
103 /** |
|
104 * Notify that changes to one of the stream tracks have been queued. |
|
105 * aTrackEvents can be any combination of TRACK_EVENT_CREATED and |
|
106 * TRACK_EVENT_ENDED. aQueuedMedia is the data being added to the track |
|
107 * at aTrackOffset (relative to the start of the stream). |
|
108 * aQueuedMedia can be null if there is no output. |
|
109 */ |
|
110 virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, |
|
111 TrackRate aTrackRate, |
|
112 TrackTicks aTrackOffset, |
|
113 uint32_t aTrackEvents, |
|
114 const MediaSegment& aQueuedMedia) MOZ_OVERRIDE |
|
115 { |
|
116 if (aTrackEvents & (TRACK_EVENT_CREATED | TRACK_EVENT_ENDED)) { |
|
117 nsRefPtr<TrackChange> runnable = |
|
118 new TrackChange(this, aID, aTrackOffset, aTrackEvents, |
|
119 aQueuedMedia.GetType()); |
|
120 NS_DispatchToMainThread(runnable); |
|
121 } |
|
122 } |
|
123 |
|
124 private: |
|
125 // These fields may only be accessed on the main thread |
|
126 DOMMediaStream* mStream; |
|
127 }; |
|
128 |
|
129 DOMMediaStream::DOMMediaStream() |
|
130 : mLogicalStreamStartTime(0), |
|
131 mStream(nullptr), mHintContents(0), mTrackTypesAvailable(0), |
|
132 mNotifiedOfMediaStreamGraphShutdown(false) |
|
133 { |
|
134 SetIsDOMBinding(); |
|
135 } |
|
136 |
|
137 DOMMediaStream::~DOMMediaStream() |
|
138 { |
|
139 Destroy(); |
|
140 } |
|
141 |
|
142 void |
|
143 DOMMediaStream::Destroy() |
|
144 { |
|
145 if (mListener) { |
|
146 mListener->Forget(); |
|
147 mListener = nullptr; |
|
148 } |
|
149 if (mStream) { |
|
150 mStream->Destroy(); |
|
151 mStream = nullptr; |
|
152 } |
|
153 } |
|
154 |
|
155 JSObject* |
|
156 DOMMediaStream::WrapObject(JSContext* aCx) |
|
157 { |
|
158 return dom::MediaStreamBinding::Wrap(aCx, this); |
|
159 } |
|
160 |
|
161 double |
|
162 DOMMediaStream::CurrentTime() |
|
163 { |
|
164 if (!mStream) { |
|
165 return 0.0; |
|
166 } |
|
167 return MediaTimeToSeconds(mStream->GetCurrentTime() - mLogicalStreamStartTime); |
|
168 } |
|
169 |
|
170 void |
|
171 DOMMediaStream::GetAudioTracks(nsTArray<nsRefPtr<AudioStreamTrack> >& aTracks) |
|
172 { |
|
173 for (uint32_t i = 0; i < mTracks.Length(); ++i) { |
|
174 AudioStreamTrack* t = mTracks[i]->AsAudioStreamTrack(); |
|
175 if (t) { |
|
176 aTracks.AppendElement(t); |
|
177 } |
|
178 } |
|
179 } |
|
180 |
|
181 void |
|
182 DOMMediaStream::GetVideoTracks(nsTArray<nsRefPtr<VideoStreamTrack> >& aTracks) |
|
183 { |
|
184 for (uint32_t i = 0; i < mTracks.Length(); ++i) { |
|
185 VideoStreamTrack* t = mTracks[i]->AsVideoStreamTrack(); |
|
186 if (t) { |
|
187 aTracks.AppendElement(t); |
|
188 } |
|
189 } |
|
190 } |
|
191 |
|
192 bool |
|
193 DOMMediaStream::IsFinished() |
|
194 { |
|
195 return !mStream || mStream->IsFinished(); |
|
196 } |
|
197 |
|
198 void |
|
199 DOMMediaStream::InitSourceStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents) |
|
200 { |
|
201 mWindow = aWindow; |
|
202 SetHintContents(aHintContents); |
|
203 MediaStreamGraph* gm = MediaStreamGraph::GetInstance(); |
|
204 InitStreamCommon(gm->CreateSourceStream(this)); |
|
205 } |
|
206 |
|
207 void |
|
208 DOMMediaStream::InitTrackUnionStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents) |
|
209 { |
|
210 mWindow = aWindow; |
|
211 SetHintContents(aHintContents); |
|
212 MediaStreamGraph* gm = MediaStreamGraph::GetInstance(); |
|
213 InitStreamCommon(gm->CreateTrackUnionStream(this)); |
|
214 } |
|
215 |
|
216 void |
|
217 DOMMediaStream::InitStreamCommon(MediaStream* aStream) |
|
218 { |
|
219 mStream = aStream; |
|
220 |
|
221 // Setup track listener |
|
222 mListener = new StreamListener(this); |
|
223 aStream->AddListener(mListener); |
|
224 } |
|
225 |
|
226 already_AddRefed<DOMMediaStream> |
|
227 DOMMediaStream::CreateSourceStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents) |
|
228 { |
|
229 nsRefPtr<DOMMediaStream> stream = new DOMMediaStream(); |
|
230 stream->InitSourceStream(aWindow, aHintContents); |
|
231 return stream.forget(); |
|
232 } |
|
233 |
|
234 already_AddRefed<DOMMediaStream> |
|
235 DOMMediaStream::CreateTrackUnionStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents) |
|
236 { |
|
237 nsRefPtr<DOMMediaStream> stream = new DOMMediaStream(); |
|
238 stream->InitTrackUnionStream(aWindow, aHintContents); |
|
239 return stream.forget(); |
|
240 } |
|
241 |
|
242 void |
|
243 DOMMediaStream::SetTrackEnabled(TrackID aTrackID, bool aEnabled) |
|
244 { |
|
245 if (mStream) { |
|
246 mStream->SetTrackEnabled(aTrackID, aEnabled); |
|
247 } |
|
248 } |
|
249 |
|
250 bool |
|
251 DOMMediaStream::CombineWithPrincipal(nsIPrincipal* aPrincipal) |
|
252 { |
|
253 return nsContentUtils::CombineResourcePrincipals(&mPrincipal, aPrincipal); |
|
254 } |
|
255 |
|
256 MediaStreamTrack* |
|
257 DOMMediaStream::CreateDOMTrack(TrackID aTrackID, MediaSegment::Type aType) |
|
258 { |
|
259 MediaStreamTrack* track; |
|
260 switch (aType) { |
|
261 case MediaSegment::AUDIO: |
|
262 track = new AudioStreamTrack(this, aTrackID); |
|
263 mTrackTypesAvailable |= HINT_CONTENTS_AUDIO; |
|
264 break; |
|
265 case MediaSegment::VIDEO: |
|
266 track = new VideoStreamTrack(this, aTrackID); |
|
267 mTrackTypesAvailable |= HINT_CONTENTS_VIDEO; |
|
268 break; |
|
269 default: |
|
270 MOZ_CRASH("Unhandled track type"); |
|
271 } |
|
272 mTracks.AppendElement(track); |
|
273 |
|
274 CheckTracksAvailable(); |
|
275 |
|
276 return track; |
|
277 } |
|
278 |
|
279 MediaStreamTrack* |
|
280 DOMMediaStream::GetDOMTrackFor(TrackID aTrackID) |
|
281 { |
|
282 for (uint32_t i = 0; i < mTracks.Length(); ++i) { |
|
283 MediaStreamTrack* t = mTracks[i]; |
|
284 // We may add streams to our track list that are actually owned by |
|
285 // a different DOMMediaStream. Ignore those. |
|
286 if (t->GetTrackID() == aTrackID && t->GetStream() == this) { |
|
287 return t; |
|
288 } |
|
289 } |
|
290 return nullptr; |
|
291 } |
|
292 |
|
293 void |
|
294 DOMMediaStream::NotifyMediaStreamGraphShutdown() |
|
295 { |
|
296 // No more tracks will ever be added, so just clear these callbacks now |
|
297 // to prevent leaks. |
|
298 mNotifiedOfMediaStreamGraphShutdown = true; |
|
299 mRunOnTracksAvailable.Clear(); |
|
300 |
|
301 mConsumersToKeepAlive.Clear(); |
|
302 } |
|
303 |
|
304 void |
|
305 DOMMediaStream::NotifyStreamStateChanged() |
|
306 { |
|
307 if (IsFinished()) { |
|
308 mConsumersToKeepAlive.Clear(); |
|
309 } |
|
310 } |
|
311 |
|
312 void |
|
313 DOMMediaStream::OnTracksAvailable(OnTracksAvailableCallback* aRunnable) |
|
314 { |
|
315 if (mNotifiedOfMediaStreamGraphShutdown) { |
|
316 // No more tracks will ever be added, so just delete the callback now. |
|
317 delete aRunnable; |
|
318 return; |
|
319 } |
|
320 mRunOnTracksAvailable.AppendElement(aRunnable); |
|
321 CheckTracksAvailable(); |
|
322 } |
|
323 |
|
324 void |
|
325 DOMMediaStream::CheckTracksAvailable() |
|
326 { |
|
327 if (mTrackTypesAvailable == 0) { |
|
328 return; |
|
329 } |
|
330 nsTArray<nsAutoPtr<OnTracksAvailableCallback> > callbacks; |
|
331 callbacks.SwapElements(mRunOnTracksAvailable); |
|
332 |
|
333 for (uint32_t i = 0; i < callbacks.Length(); ++i) { |
|
334 OnTracksAvailableCallback* cb = callbacks[i]; |
|
335 if (~mTrackTypesAvailable & cb->GetExpectedTracks()) { |
|
336 // Some expected tracks not available yet. Try this callback again later. |
|
337 *mRunOnTracksAvailable.AppendElement() = callbacks[i].forget(); |
|
338 continue; |
|
339 } |
|
340 cb->NotifyTracksAvailable(this); |
|
341 } |
|
342 } |
|
343 |
|
344 DOMLocalMediaStream::~DOMLocalMediaStream() |
|
345 { |
|
346 if (mStream) { |
|
347 // Make sure Listeners of this stream know it's going away |
|
348 Stop(); |
|
349 } |
|
350 } |
|
351 |
|
352 JSObject* |
|
353 DOMLocalMediaStream::WrapObject(JSContext* aCx) |
|
354 { |
|
355 return dom::LocalMediaStreamBinding::Wrap(aCx, this); |
|
356 } |
|
357 |
|
358 void |
|
359 DOMLocalMediaStream::Stop() |
|
360 { |
|
361 if (mStream && mStream->AsSourceStream()) { |
|
362 mStream->AsSourceStream()->EndAllTrackAndFinish(); |
|
363 } |
|
364 } |
|
365 |
|
366 already_AddRefed<DOMLocalMediaStream> |
|
367 DOMLocalMediaStream::CreateSourceStream(nsIDOMWindow* aWindow, |
|
368 TrackTypeHints aHintContents) |
|
369 { |
|
370 nsRefPtr<DOMLocalMediaStream> stream = new DOMLocalMediaStream(); |
|
371 stream->InitSourceStream(aWindow, aHintContents); |
|
372 return stream.forget(); |
|
373 } |
|
374 |
|
375 already_AddRefed<DOMLocalMediaStream> |
|
376 DOMLocalMediaStream::CreateTrackUnionStream(nsIDOMWindow* aWindow, |
|
377 TrackTypeHints aHintContents) |
|
378 { |
|
379 nsRefPtr<DOMLocalMediaStream> stream = new DOMLocalMediaStream(); |
|
380 stream->InitTrackUnionStream(aWindow, aHintContents); |
|
381 return stream.forget(); |
|
382 } |
|
383 |
|
384 DOMAudioNodeMediaStream::DOMAudioNodeMediaStream(AudioNode* aNode) |
|
385 : mStreamNode(aNode) |
|
386 { |
|
387 } |
|
388 |
|
389 already_AddRefed<DOMAudioNodeMediaStream> |
|
390 DOMAudioNodeMediaStream::CreateTrackUnionStream(nsIDOMWindow* aWindow, |
|
391 AudioNode* aNode, |
|
392 TrackTypeHints aHintContents) |
|
393 { |
|
394 nsRefPtr<DOMAudioNodeMediaStream> stream = new DOMAudioNodeMediaStream(aNode); |
|
395 stream->InitTrackUnionStream(aWindow, aHintContents); |
|
396 return stream.forget(); |
|
397 } |