/src/mozilla-central/dom/media/ReaderProxy.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
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 "mozilla/MozPromise.h" |
8 | | #include "MediaFormatReader.h" |
9 | | #include "ReaderProxy.h" |
10 | | #include "TimeUnits.h" |
11 | | |
12 | | namespace mozilla { |
13 | | |
14 | | ReaderProxy::ReaderProxy(AbstractThread* aOwnerThread, |
15 | | MediaFormatReader* aReader) |
16 | | : mOwnerThread(aOwnerThread) |
17 | | , mReader(aReader) |
18 | | , mWatchManager(this, aReader->OwnerThread()) |
19 | | , mDuration(aReader->OwnerThread(), |
20 | | media::NullableTimeUnit(), |
21 | | "ReaderProxy::mDuration (Mirror)") |
22 | | , mSeamlessLoopingBlocked(false) |
23 | | , mSeamlessLoopingEnabled(false) |
24 | 0 | { |
25 | 0 | // Must support either heuristic buffering or WaitForData(). |
26 | 0 | MOZ_ASSERT(mReader->UseBufferingHeuristics() || |
27 | 0 | mReader->IsWaitForDataSupported()); |
28 | 0 | } |
29 | | |
30 | | ReaderProxy::~ReaderProxy() |
31 | 0 | {} |
32 | | |
33 | | media::TimeUnit |
34 | | ReaderProxy::StartTime() const |
35 | 0 | { |
36 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
37 | 0 | return mStartTime.ref(); |
38 | 0 | } |
39 | | |
40 | | RefPtr<ReaderProxy::MetadataPromise> |
41 | | ReaderProxy::ReadMetadata() |
42 | 0 | { |
43 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
44 | 0 | MOZ_ASSERT(!mShutdown); |
45 | 0 | return InvokeAsync(mReader->OwnerThread(), |
46 | 0 | mReader.get(), |
47 | 0 | __func__, |
48 | 0 | &MediaFormatReader::AsyncReadMetadata) |
49 | 0 | ->Then(mOwnerThread, |
50 | 0 | __func__, |
51 | 0 | this, |
52 | 0 | &ReaderProxy::OnMetadataRead, |
53 | 0 | &ReaderProxy::OnMetadataNotRead); |
54 | 0 | } |
55 | | |
56 | | RefPtr<ReaderProxy::AudioDataPromise> |
57 | | ReaderProxy::OnAudioDataRequestCompleted(RefPtr<AudioData> aAudio) |
58 | 0 | { |
59 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
60 | 0 |
|
61 | 0 | // Subtract the start time and add the looping-offset time. |
62 | 0 | int64_t offset = |
63 | 0 | StartTime().ToMicroseconds() - mLoopingOffset.ToMicroseconds(); |
64 | 0 | aAudio->AdjustForStartTime(offset); |
65 | 0 | if (aAudio->mTime.IsValid()) { |
66 | 0 | mLastAudioEndTime = aAudio->mTime; |
67 | 0 | return AudioDataPromise::CreateAndResolve(aAudio.forget(), __func__); |
68 | 0 | } |
69 | 0 | return AudioDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, |
70 | 0 | __func__); |
71 | 0 | } |
72 | | |
73 | | RefPtr<ReaderProxy::AudioDataPromise> |
74 | | ReaderProxy::OnAudioDataRequestFailed(const MediaResult& aError) |
75 | 0 | { |
76 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
77 | 0 |
|
78 | 0 | if (mSeamlessLoopingBlocked || !mSeamlessLoopingEnabled || |
79 | 0 | aError.Code() != NS_ERROR_DOM_MEDIA_END_OF_STREAM) { |
80 | 0 | return AudioDataPromise::CreateAndReject(aError, __func__); |
81 | 0 | } |
82 | 0 | |
83 | 0 | // The data time in the audio queue is assumed to be increased linearly, |
84 | 0 | // so we need to add the last ending time as the offset to correct the |
85 | 0 | // audio data time in the next round when seamless looping is enabled. |
86 | 0 | mLoopingOffset = mLastAudioEndTime; |
87 | 0 |
|
88 | 0 | // Save the duration of the audio track if it hasn't been set. |
89 | 0 | if (!mAudioDuration.IsValid()) { |
90 | 0 | mAudioDuration = mLastAudioEndTime; |
91 | 0 | } |
92 | 0 |
|
93 | 0 | // For seamless looping, the demuxer is sought to the beginning and then |
94 | 0 | // keep requesting decoded data in advance, upon receiving EOS. |
95 | 0 | // The MDSM will not be aware of the EOS and keep receiving decoded data |
96 | 0 | // as usual while looping is on. |
97 | 0 | RefPtr<ReaderProxy> self = this; |
98 | 0 | RefPtr<MediaFormatReader> reader = mReader; |
99 | 0 | ResetDecode(TrackInfo::kAudioTrack); |
100 | 0 | return SeekInternal(SeekTarget(media::TimeUnit::Zero(), SeekTarget::Accurate)) |
101 | 0 | ->Then(mReader->OwnerThread(), |
102 | 0 | __func__, |
103 | 0 | [reader]() { return reader->RequestAudioData(); }, |
104 | 0 | [](const SeekRejectValue& aReject) { |
105 | 0 | return AudioDataPromise::CreateAndReject(aReject.mError, __func__); |
106 | 0 | }) |
107 | 0 | ->Then(mOwnerThread, |
108 | 0 | __func__, |
109 | 0 | [self](RefPtr<AudioData> aAudio) { |
110 | 0 | return self->OnAudioDataRequestCompleted(aAudio.forget()); |
111 | 0 | }, |
112 | 0 | [](const MediaResult& aError) { |
113 | 0 | return AudioDataPromise::CreateAndReject(aError, __func__); |
114 | 0 | }); |
115 | 0 | } |
116 | | |
117 | | RefPtr<ReaderProxy::AudioDataPromise> |
118 | | ReaderProxy::RequestAudioData() |
119 | 0 | { |
120 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
121 | 0 | MOZ_ASSERT(!mShutdown); |
122 | 0 |
|
123 | 0 | mSeamlessLoopingBlocked = false; |
124 | 0 | return InvokeAsync(mReader->OwnerThread(), |
125 | 0 | mReader.get(), |
126 | 0 | __func__, |
127 | 0 | &MediaFormatReader::RequestAudioData) |
128 | 0 | ->Then(mOwnerThread, |
129 | 0 | __func__, |
130 | 0 | this, |
131 | 0 | &ReaderProxy::OnAudioDataRequestCompleted, |
132 | 0 | &ReaderProxy::OnAudioDataRequestFailed); |
133 | 0 | } |
134 | | |
135 | | RefPtr<ReaderProxy::VideoDataPromise> |
136 | | ReaderProxy::RequestVideoData(const media::TimeUnit& aTimeThreshold) |
137 | 0 | { |
138 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
139 | 0 | MOZ_ASSERT(!mShutdown); |
140 | 0 |
|
141 | 0 | mSeamlessLoopingBlocked = false; |
142 | 0 | const auto threshold = aTimeThreshold > media::TimeUnit::Zero() |
143 | 0 | ? aTimeThreshold + StartTime() |
144 | 0 | : aTimeThreshold; |
145 | 0 |
|
146 | 0 | int64_t startTime = StartTime().ToMicroseconds(); |
147 | 0 | return InvokeAsync(mReader->OwnerThread(), |
148 | 0 | mReader.get(), |
149 | 0 | __func__, |
150 | 0 | &MediaFormatReader::RequestVideoData, |
151 | 0 | threshold) |
152 | 0 | ->Then(mOwnerThread, |
153 | 0 | __func__, |
154 | 0 | [startTime](RefPtr<VideoData> aVideo) { |
155 | 0 | aVideo->AdjustForStartTime(startTime); |
156 | 0 | return aVideo->mTime.IsValid() |
157 | 0 | ? VideoDataPromise::CreateAndResolve(aVideo.forget(), |
158 | 0 | __func__) |
159 | 0 | : VideoDataPromise::CreateAndReject( |
160 | 0 | NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, __func__); |
161 | 0 | }, |
162 | 0 | [](const MediaResult& aError) { |
163 | 0 | return VideoDataPromise::CreateAndReject(aError, __func__); |
164 | 0 | }); |
165 | 0 | } |
166 | | |
167 | | RefPtr<ReaderProxy::SeekPromise> |
168 | | ReaderProxy::Seek(const SeekTarget& aTarget) |
169 | 0 | { |
170 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
171 | 0 | mSeamlessLoopingBlocked = true; |
172 | 0 | // Reset the members for seamless looping if the seek is triggered outside. |
173 | 0 | mLoopingOffset = media::TimeUnit::Zero(); |
174 | 0 | mLastAudioEndTime = media::TimeUnit::Zero(); |
175 | 0 | mAudioDuration = media::TimeUnit::Invalid(); |
176 | 0 | return SeekInternal(aTarget); |
177 | 0 | } |
178 | | |
179 | | RefPtr<ReaderProxy::SeekPromise> |
180 | | ReaderProxy::SeekInternal(const SeekTarget& aTarget) |
181 | 0 | { |
182 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
183 | 0 | SeekTarget adjustedTarget = aTarget; |
184 | 0 | adjustedTarget.SetTime(adjustedTarget.GetTime() + StartTime()); |
185 | 0 | return InvokeAsync(mReader->OwnerThread(), |
186 | 0 | mReader.get(), |
187 | 0 | __func__, |
188 | 0 | &MediaFormatReader::Seek, |
189 | 0 | std::move(adjustedTarget)); |
190 | 0 | } |
191 | | |
192 | | RefPtr<ReaderProxy::WaitForDataPromise> |
193 | | ReaderProxy::WaitForData(MediaData::Type aType) |
194 | 0 | { |
195 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
196 | 0 | MOZ_ASSERT(mReader->IsWaitForDataSupported()); |
197 | 0 | return InvokeAsync(mReader->OwnerThread(), |
198 | 0 | mReader.get(), |
199 | 0 | __func__, |
200 | 0 | &MediaFormatReader::WaitForData, |
201 | 0 | aType); |
202 | 0 | } |
203 | | |
204 | | void |
205 | | ReaderProxy::ReleaseResources() |
206 | 0 | { |
207 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
208 | 0 | nsCOMPtr<nsIRunnable> r = |
209 | 0 | NewRunnableMethod("MediaFormatReader::ReleaseResources", |
210 | 0 | mReader, |
211 | 0 | &MediaFormatReader::ReleaseResources); |
212 | 0 | nsresult rv = mReader->OwnerThread()->Dispatch(r.forget()); |
213 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
214 | 0 | Unused << rv; |
215 | 0 | } |
216 | | |
217 | | void |
218 | | ReaderProxy::ResetDecode(TrackSet aTracks) |
219 | 0 | { |
220 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
221 | 0 | nsCOMPtr<nsIRunnable> r = |
222 | 0 | NewRunnableMethod<TrackSet>("MediaFormatReader::ResetDecode", |
223 | 0 | mReader, |
224 | 0 | &MediaFormatReader::ResetDecode, |
225 | 0 | aTracks); |
226 | 0 | nsresult rv = mReader->OwnerThread()->Dispatch(r.forget()); |
227 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
228 | 0 | Unused << rv; |
229 | 0 | } |
230 | | |
231 | | RefPtr<ShutdownPromise> |
232 | | ReaderProxy::Shutdown() |
233 | 0 | { |
234 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
235 | 0 | mShutdown = true; |
236 | 0 | RefPtr<ReaderProxy> self = this; |
237 | 0 | return InvokeAsync(mReader->OwnerThread(), __func__, [self]() { |
238 | 0 | self->mDuration.DisconnectIfConnected(); |
239 | 0 | self->mWatchManager.Shutdown(); |
240 | 0 | return self->mReader->Shutdown(); |
241 | 0 | }); |
242 | 0 | } |
243 | | |
244 | | RefPtr<ReaderProxy::MetadataPromise> |
245 | | ReaderProxy::OnMetadataRead(MetadataHolder&& aMetadata) |
246 | 0 | { |
247 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
248 | 0 | if (mShutdown) { |
249 | 0 | return MetadataPromise::CreateAndReject( |
250 | 0 | NS_ERROR_DOM_MEDIA_ABORT_ERR, __func__); |
251 | 0 | } |
252 | 0 | |
253 | 0 | if (mStartTime.isNothing()) { |
254 | 0 | mStartTime.emplace(aMetadata.mInfo->mStartTime); |
255 | 0 | } |
256 | 0 | return MetadataPromise::CreateAndResolve(std::move(aMetadata), __func__); |
257 | 0 | } |
258 | | |
259 | | RefPtr<ReaderProxy::MetadataPromise> |
260 | | ReaderProxy::OnMetadataNotRead(const MediaResult& aError) |
261 | 0 | { |
262 | 0 | return MetadataPromise::CreateAndReject(aError, __func__); |
263 | 0 | } |
264 | | |
265 | | void |
266 | | ReaderProxy::SetVideoBlankDecode(bool aIsBlankDecode) |
267 | 0 | { |
268 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
269 | 0 | nsCOMPtr<nsIRunnable> r = |
270 | 0 | NewRunnableMethod<bool>("MediaFormatReader::SetVideoNullDecode", |
271 | 0 | mReader, |
272 | 0 | &MediaFormatReader::SetVideoNullDecode, |
273 | 0 | aIsBlankDecode); |
274 | 0 | nsresult rv = mReader->OwnerThread()->Dispatch(r.forget()); |
275 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
276 | 0 | Unused << rv; |
277 | 0 | } |
278 | | |
279 | | void |
280 | | ReaderProxy::UpdateDuration() |
281 | 0 | { |
282 | 0 | MOZ_ASSERT(mReader->OwnerThread()->IsCurrentThreadIn()); |
283 | 0 | mReader->UpdateDuration(mDuration.Ref().ref()); |
284 | 0 | } |
285 | | |
286 | | void |
287 | | ReaderProxy::SetCanonicalDuration( |
288 | | AbstractCanonical<media::NullableTimeUnit>* aCanonical) |
289 | 0 | { |
290 | 0 | using DurationT = AbstractCanonical<media::NullableTimeUnit>; |
291 | 0 | RefPtr<ReaderProxy> self = this; |
292 | 0 | RefPtr<DurationT> canonical = aCanonical; |
293 | 0 | nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( |
294 | 0 | "ReaderProxy::SetCanonicalDuration", [this, self, canonical]() { |
295 | 0 | mDuration.Connect(canonical); |
296 | 0 | mWatchManager.Watch(mDuration, &ReaderProxy::UpdateDuration); |
297 | 0 | }); |
298 | 0 | nsresult rv = mReader->OwnerThread()->Dispatch(r.forget()); |
299 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
300 | 0 | Unused << rv; |
301 | 0 | } |
302 | | |
303 | | void |
304 | | ReaderProxy::SetSeamlessLoopingEnabled(bool aEnabled) |
305 | 0 | { |
306 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
307 | 0 | mSeamlessLoopingEnabled = aEnabled; |
308 | 0 | } |
309 | | |
310 | | void |
311 | | ReaderProxy::AdjustByLooping(media::TimeUnit& aTime) |
312 | 0 | { |
313 | 0 | MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); |
314 | 0 | MOZ_ASSERT(!mShutdown); |
315 | 0 | MOZ_ASSERT(!mSeamlessLoopingEnabled || !mSeamlessLoopingBlocked); |
316 | 0 | if (mAudioDuration.IsValid() && mAudioDuration.IsPositive()) { |
317 | 0 | aTime = aTime % mAudioDuration.ToMicroseconds(); |
318 | 0 | } |
319 | 0 | } |
320 | | |
321 | | } // namespace mozilla |