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 #include "MediaSourceDecoder.h"
7
8 #include "mozilla/Logging.h"
9 #include "MediaDecoderStateMachine.h"
10 #include "MediaShutdownManager.h"
11 #include "MediaSource.h"
12 #include "MediaSourceDemuxer.h"
13 #include "MediaSourceUtils.h"
14 #include "SourceBuffer.h"
15 #include "SourceBufferList.h"
16 #include "VideoUtils.h"
17 #include <algorithm>
18
19 extern mozilla::LogModule* GetMediaSourceLog();
20
21 #define MSE_DEBUG(arg, ...) \
22 DDMOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, "::%s: " arg, \
23 __func__, ##__VA_ARGS__)
24 #define MSE_DEBUGV(arg, ...) \
25 DDMOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, "::%s: " arg, \
26 __func__, ##__VA_ARGS__)
27
28 using namespace mozilla::media;
29
30 namespace mozilla {
31
MediaSourceDecoder(MediaDecoderInit & aInit)32 MediaSourceDecoder::MediaSourceDecoder(MediaDecoderInit& aInit)
33 : MediaDecoder(aInit), mMediaSource(nullptr), mEnded(false) {
34 mExplicitDuration.emplace(UnspecifiedNaN<double>());
35 }
36
CreateStateMachine()37 MediaDecoderStateMachine* MediaSourceDecoder::CreateStateMachine() {
38 MOZ_ASSERT(NS_IsMainThread());
39 mDemuxer = new MediaSourceDemuxer(AbstractMainThread());
40 MediaFormatReaderInit init;
41 init.mVideoFrameContainer = GetVideoFrameContainer();
42 init.mKnowsCompositor = GetCompositor();
43 init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
44 init.mFrameStats = mFrameStats;
45 init.mMediaDecoderOwnerID = mOwner;
46 mReader = new MediaFormatReader(init, mDemuxer);
47 return new MediaDecoderStateMachine(this, mReader);
48 }
49
Load(nsIPrincipal * aPrincipal)50 nsresult MediaSourceDecoder::Load(nsIPrincipal* aPrincipal) {
51 MOZ_ASSERT(NS_IsMainThread());
52 MOZ_ASSERT(!GetStateMachine());
53
54 mPrincipal = aPrincipal;
55
56 nsresult rv = MediaShutdownManager::Instance().Register(this);
57 if (NS_WARN_IF(NS_FAILED(rv))) {
58 return rv;
59 }
60
61 SetStateMachine(CreateStateMachine());
62 if (!GetStateMachine()) {
63 NS_WARNING("Failed to create state machine!");
64 return NS_ERROR_FAILURE;
65 }
66
67 rv = GetStateMachine()->Init(this);
68 NS_ENSURE_SUCCESS(rv, rv);
69
70 GetStateMachine()->DispatchIsLiveStream(!mEnded);
71 SetStateMachineParameters();
72 return NS_OK;
73 }
74
GetSeekable()75 media::TimeIntervals MediaSourceDecoder::GetSeekable() {
76 MOZ_ASSERT(NS_IsMainThread());
77 if (!mMediaSource) {
78 NS_WARNING("MediaSource element isn't attached");
79 return media::TimeIntervals::Invalid();
80 }
81
82 media::TimeIntervals seekable;
83 double duration = mMediaSource->Duration();
84 if (IsNaN(duration)) {
85 // Return empty range.
86 } else if (duration > 0 && mozilla::IsInfinite(duration)) {
87 media::TimeIntervals buffered = GetBuffered();
88
89 // 1. If live seekable range is not empty:
90 if (mMediaSource->HasLiveSeekableRange()) {
91 // 1. Let union ranges be the union of live seekable range and the
92 // HTMLMediaElement.buffered attribute.
93 media::TimeIntervals unionRanges =
94 buffered + mMediaSource->LiveSeekableRange();
95 // 2. Return a single range with a start time equal to the earliest start
96 // time in union ranges and an end time equal to the highest end time in
97 // union ranges and abort these steps.
98 seekable +=
99 media::TimeInterval(unionRanges.GetStart(), unionRanges.GetEnd());
100 return seekable;
101 }
102
103 if (!buffered.IsEmpty()) {
104 seekable += media::TimeInterval(TimeUnit::Zero(), buffered.GetEnd());
105 }
106 } else {
107 seekable +=
108 media::TimeInterval(TimeUnit::Zero(), TimeUnit::FromSeconds(duration));
109 }
110 MSE_DEBUG("ranges=%s", DumpTimeRanges(seekable).get());
111 return seekable;
112 }
113
GetBuffered()114 media::TimeIntervals MediaSourceDecoder::GetBuffered() {
115 MOZ_ASSERT(NS_IsMainThread());
116
117 if (!mMediaSource) {
118 NS_WARNING("MediaSource element isn't attached");
119 return media::TimeIntervals::Invalid();
120 }
121 dom::SourceBufferList* sourceBuffers = mMediaSource->ActiveSourceBuffers();
122 if (!sourceBuffers) {
123 // Media source object is shutting down.
124 return TimeIntervals();
125 }
126 TimeUnit highestEndTime;
127 nsTArray<media::TimeIntervals> activeRanges;
128 media::TimeIntervals buffered;
129
130 for (uint32_t i = 0; i < sourceBuffers->Length(); i++) {
131 bool found;
132 dom::SourceBuffer* sb = sourceBuffers->IndexedGetter(i, found);
133 MOZ_ASSERT(found);
134
135 activeRanges.AppendElement(sb->GetTimeIntervals());
136 highestEndTime =
137 std::max(highestEndTime, activeRanges.LastElement().GetEnd());
138 }
139
140 buffered += media::TimeInterval(TimeUnit::Zero(), highestEndTime);
141
142 for (auto& range : activeRanges) {
143 if (mEnded && !range.IsEmpty()) {
144 // Set the end time on the last range to highestEndTime by adding a
145 // new range spanning the current end time to highestEndTime, which
146 // Normalize() will then merge with the old last range.
147 range += media::TimeInterval(range.GetEnd(), highestEndTime);
148 }
149 buffered.Intersection(range);
150 }
151
152 MSE_DEBUG("ranges=%s", DumpTimeRanges(buffered).get());
153 return buffered;
154 }
155
Shutdown()156 void MediaSourceDecoder::Shutdown() {
157 MOZ_ASSERT(NS_IsMainThread());
158 MSE_DEBUG("Shutdown");
159 // Detach first so that TrackBuffers are unused on the main thread when
160 // shut down on the decode task queue.
161 if (mMediaSource) {
162 mMediaSource->Detach();
163 }
164 mDemuxer = nullptr;
165
166 MediaDecoder::Shutdown();
167 }
168
AttachMediaSource(dom::MediaSource * aMediaSource)169 void MediaSourceDecoder::AttachMediaSource(dom::MediaSource* aMediaSource) {
170 MOZ_ASSERT(!mMediaSource && !GetStateMachine() && NS_IsMainThread());
171 mMediaSource = aMediaSource;
172 DDLINKCHILD("mediasource", aMediaSource);
173 }
174
DetachMediaSource()175 void MediaSourceDecoder::DetachMediaSource() {
176 MOZ_ASSERT(mMediaSource && NS_IsMainThread());
177 DDUNLINKCHILD(mMediaSource);
178 mMediaSource = nullptr;
179 }
180
Ended(bool aEnded)181 void MediaSourceDecoder::Ended(bool aEnded) {
182 MOZ_ASSERT(NS_IsMainThread());
183 if (aEnded) {
184 // We want the MediaSourceReader to refresh its buffered range as it may
185 // have been modified (end lined up).
186 NotifyDataArrived();
187 }
188 mEnded = aEnded;
189 GetStateMachine()->DispatchIsLiveStream(!mEnded);
190 }
191
AddSizeOfResources(ResourceSizes * aSizes)192 void MediaSourceDecoder::AddSizeOfResources(ResourceSizes* aSizes) {
193 MOZ_ASSERT(NS_IsMainThread());
194 if (GetDemuxer()) {
195 GetDemuxer()->AddSizeOfResources(aSizes);
196 }
197 }
198
SetInitialDuration(int64_t aDuration)199 void MediaSourceDecoder::SetInitialDuration(int64_t aDuration) {
200 MOZ_ASSERT(NS_IsMainThread());
201 // Only use the decoded duration if one wasn't already
202 // set.
203 if (!mMediaSource || !IsNaN(ExplicitDuration())) {
204 return;
205 }
206 double duration = aDuration;
207 // A duration of -1 is +Infinity.
208 if (aDuration >= 0) {
209 duration /= USECS_PER_S;
210 }
211 SetMediaSourceDuration(duration);
212 }
213
SetMediaSourceDuration(double aDuration)214 void MediaSourceDecoder::SetMediaSourceDuration(double aDuration) {
215 MOZ_ASSERT(NS_IsMainThread());
216 MOZ_ASSERT(!IsShutdown());
217 if (aDuration >= 0) {
218 int64_t checkedDuration;
219 if (NS_FAILED(SecondsToUsecs(aDuration, checkedDuration))) {
220 // INT64_MAX is used as infinity by the state machine.
221 // We want a very bigger number, but not infinity.
222 checkedDuration = INT64_MAX - 1;
223 }
224 SetExplicitDuration(aDuration);
225 } else {
226 SetExplicitDuration(PositiveInfinity<double>());
227 }
228 }
229
GetDebugInfo(dom::MediaSourceDecoderDebugInfo & aInfo)230 void MediaSourceDecoder::GetDebugInfo(dom::MediaSourceDecoderDebugInfo& aInfo) {
231 if (mReader && mDemuxer) {
232 mReader->GetDebugInfo(aInfo.mReader);
233 mDemuxer->GetDebugInfo(aInfo.mDemuxer);
234 }
235 }
236
GetDuration()237 double MediaSourceDecoder::GetDuration() {
238 MOZ_ASSERT(NS_IsMainThread());
239 return ExplicitDuration();
240 }
241
242 MediaDecoderOwner::NextFrameStatus
NextFrameBufferedStatus()243 MediaSourceDecoder::NextFrameBufferedStatus() {
244 MOZ_ASSERT(NS_IsMainThread());
245
246 if (!mMediaSource ||
247 mMediaSource->ReadyState() == dom::MediaSourceReadyState::Closed) {
248 return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
249 }
250
251 // Next frame hasn't been decoded yet.
252 // Use the buffered range to consider if we have the next frame available.
253 auto currentPosition = CurrentPosition();
254 TimeIntervals buffered = GetBuffered();
255 buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
256 TimeInterval interval(
257 currentPosition, currentPosition + DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED);
258 return buffered.ContainsWithStrictEnd(ClampIntervalToEnd(interval))
259 ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
260 : MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
261 }
262
CanPlayThroughImpl()263 bool MediaSourceDecoder::CanPlayThroughImpl() {
264 MOZ_ASSERT(NS_IsMainThread());
265
266 if (NextFrameBufferedStatus() == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE) {
267 return false;
268 }
269
270 if (IsNaN(mMediaSource->Duration())) {
271 // Don't have any data yet.
272 return false;
273 }
274 TimeUnit duration = TimeUnit::FromSeconds(mMediaSource->Duration());
275 auto currentPosition = CurrentPosition();
276 if (duration <= currentPosition) {
277 return true;
278 }
279 // If we have data up to the mediasource's duration or 3s ahead, we can
280 // assume that we can play without interruption.
281 TimeIntervals buffered = GetBuffered();
282 buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
283 TimeUnit timeAhead =
284 std::min(duration, currentPosition + TimeUnit::FromSeconds(3));
285 TimeInterval interval(currentPosition, timeAhead);
286 return buffered.ContainsWithStrictEnd(ClampIntervalToEnd(interval));
287 }
288
ClampIntervalToEnd(const TimeInterval & aInterval)289 TimeInterval MediaSourceDecoder::ClampIntervalToEnd(
290 const TimeInterval& aInterval) {
291 MOZ_ASSERT(NS_IsMainThread());
292
293 if (!mEnded) {
294 return aInterval;
295 }
296 TimeUnit duration = TimeUnit::FromSeconds(GetDuration());
297 if (duration < aInterval.mStart) {
298 return aInterval;
299 }
300 return TimeInterval(aInterval.mStart, std::min(aInterval.mEnd, duration),
301 aInterval.mFuzz);
302 }
303
NotifyInitDataArrived()304 void MediaSourceDecoder::NotifyInitDataArrived() {
305 MOZ_ASSERT(NS_IsMainThread());
306
307 if (mDemuxer) {
308 mDemuxer->NotifyInitDataArrived();
309 }
310 }
311
NotifyDataArrived()312 void MediaSourceDecoder::NotifyDataArrived() {
313 MOZ_ASSERT(NS_IsMainThread());
314 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
315 NotifyReaderDataArrived();
316 GetOwner()->DownloadProgressed();
317 }
318
GetCurrentPrincipal()319 already_AddRefed<nsIPrincipal> MediaSourceDecoder::GetCurrentPrincipal() {
320 MOZ_ASSERT(NS_IsMainThread());
321 return do_AddRef(mPrincipal);
322 }
323
HadCrossOriginRedirects()324 bool MediaSourceDecoder::HadCrossOriginRedirects() {
325 MOZ_ASSERT(NS_IsMainThread());
326 return false;
327 }
328
329 #undef MSE_DEBUG
330 #undef MSE_DEBUGV
331
332 } // namespace mozilla
333