MediaSourceDecoder.cpp (14824B)
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 <algorithm> 9 10 #include "ExternalEngineStateMachine.h" 11 #include "MediaDecoder.h" 12 #include "MediaDecoderStateMachine.h" 13 #include "MediaShutdownManager.h" 14 #include "MediaSource.h" 15 #include "MediaSourceDemuxer.h" 16 #include "MediaSourceUtils.h" 17 #include "SourceBuffer.h" 18 #include "SourceBufferList.h" 19 #include "VideoUtils.h" 20 #include "base/process_util.h" 21 #include "mozilla/Logging.h" 22 23 extern mozilla::LogModule* GetMediaSourceLog(); 24 25 #define MSE_DEBUG(arg, ...) \ 26 DDMOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, "::%s: " arg, \ 27 __func__, ##__VA_ARGS__) 28 #define MSE_DEBUGV(arg, ...) \ 29 DDMOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, "::%s: " arg, \ 30 __func__, ##__VA_ARGS__) 31 32 using namespace mozilla::media; 33 34 namespace mozilla { 35 36 MediaSourceDecoder::MediaSourceDecoder(MediaDecoderInit& aInit) 37 : MediaDecoder(aInit), mMediaSource(nullptr), mEnded(false) { 38 mExplicitDuration.emplace(UnspecifiedNaN<double>()); 39 } 40 41 MediaDecoderStateMachineBase* MediaSourceDecoder::CreateStateMachine( 42 bool aDisableExternalEngine) { 43 MOZ_ASSERT(NS_IsMainThread()); 44 // if `mDemuxer` already exists, that means we're in the process of recreating 45 // the state machine. The track buffers are tied to the demuxer so we would 46 // need to reuse it. 47 if (!mDemuxer) { 48 mDemuxer = new MediaSourceDemuxer(AbstractMainThread()); 49 } 50 MediaFormatReaderInit init; 51 init.mVideoFrameContainer = GetVideoFrameContainer(); 52 init.mKnowsCompositor = GetCompositor(); 53 init.mCrashHelper = GetOwner()->CreateGMPCrashHelper(); 54 init.mFrameStats = mFrameStats; 55 init.mMediaDecoderOwnerID = mOwner; 56 static Atomic<uint32_t> sTrackingIdCounter(0); 57 init.mTrackingId.emplace(TrackingId::Source::MSEDecoder, sTrackingIdCounter++, 58 TrackingId::TrackAcrossProcesses::Yes); 59 mReader = new MediaFormatReader(init, mDemuxer); 60 #ifdef MOZ_WMF_CDM 61 // ExternalEngineStateMachine is primarily used for encrypted playback when 62 // the key system is supported via the WMF-based CDM. However, we cannot 63 // currently determine the purpose of the playback, so we will always start 64 // with ExternalEngineStateMachine. If this is not the case, we will switch 65 // back to MediaDecoderStateMachine. The following outlines different 66 // scenarios: 67 // 1) Playback is non-encrypted or media format is not supported 68 // An internal error NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR 69 // will be received, resulting in a switch to another state machine. 70 // 2) Playback is encrypted but the media key is not yet set 71 // 2-1) If the CDMProxy is not WMF-based CDM when setting the media key, 72 // An internal error NS_ERROR_DOM_MEDIA_CDM_PROXY_NOT_SUPPORTED_ERR 73 // will be received, causing a switch to another state machine. 74 // 2-2) If the CDMProxy is WMF-based CDM when setting the media key, 75 // There will be no error, and ExternalEngineStateMachine will operate. 76 // 3) Playback is encrypted and the media key is already set 77 // 3-1) If the CDMProxy is not WMF-based CDM, 78 // An internal error NS_ERROR_DOM_MEDIA_CDM_PROXY_NOT_SUPPORTED_ERR 79 // will be received, resulting in a switch to another state machine. 80 // 3-2) If the CDMProxy is WMF-based CDM, 81 // There will be no error, and ExternalEngineStateMachine will operate. 82 // Additionally, for testing purposes, non-encrypted playback can be performed 83 // via ExternalEngineStateMachine as well by modifying the preference value. 84 bool isCDMNotSupported = 85 !!mOwner->GetCDMProxy() && !mOwner->GetCDMProxy()->AsWMFCDMProxy(); 86 if (StaticPrefs::media_wmf_media_engine_enabled() && !isCDMNotSupported && 87 !aDisableExternalEngine) { 88 return new ExternalEngineStateMachine(this, mReader); 89 } 90 #endif 91 return new MediaDecoderStateMachine(this, mReader); 92 } 93 94 nsresult MediaSourceDecoder::Load(nsIPrincipal* aPrincipal) { 95 MOZ_ASSERT(NS_IsMainThread()); 96 MOZ_ASSERT(!GetStateMachine()); 97 98 mPrincipal = aPrincipal; 99 100 nsresult rv = MediaShutdownManager::Instance().Register(this); 101 if (NS_WARN_IF(NS_FAILED(rv))) { 102 return rv; 103 } 104 return CreateAndInitStateMachine(!mEnded); 105 } 106 107 template <typename IntervalType> 108 IntervalType MediaSourceDecoder::GetSeekableImpl() { 109 MOZ_ASSERT(NS_IsMainThread()); 110 if (!mMediaSource) { 111 NS_WARNING("MediaSource element isn't attached"); 112 return IntervalType(); 113 } 114 115 TimeIntervals seekable; 116 double duration = mMediaSource->Duration(); 117 if (std::isnan(duration)) { 118 // Return empty range. 119 } else if (duration > 0 && std::isinf(duration)) { 120 media::TimeIntervals buffered = GetBuffered(); 121 122 // 1. If live seekable range is not empty: 123 if (mMediaSource->HasLiveSeekableRange()) { 124 // 1. Let union ranges be the union of live seekable range and the 125 // HTMLMediaElement.buffered attribute. 126 TimeRanges unionRanges = 127 media::TimeRanges(buffered) + mMediaSource->LiveSeekableRange(); 128 // 2. Return a single range with a start time equal to the earliest start 129 // time in union ranges and an end time equal to the highest end time in 130 // union ranges and abort these steps. 131 if constexpr (std::is_same<IntervalType, TimeRanges>::value) { 132 TimeRanges seekableRange = media::TimeRanges( 133 TimeRange(unionRanges.GetStart(), unionRanges.GetEnd())); 134 return seekableRange; 135 } else { 136 MOZ_RELEASE_ASSERT(false); 137 } 138 } 139 140 if (!buffered.IsEmpty()) { 141 seekable += media::TimeInterval(TimeUnit::Zero(), buffered.GetEnd()); 142 } 143 } else { 144 if constexpr (std::is_same<IntervalType, TimeRanges>::value) { 145 // Common case: seekable in entire range of the media. 146 return TimeRanges(TimeRange(0, duration)); 147 } else if constexpr (std::is_same<IntervalType, TimeIntervals>::value) { 148 seekable += media::TimeInterval(TimeUnit::Zero(), 149 mDuration.match(DurationToTimeUnit())); 150 } else { 151 MOZ_RELEASE_ASSERT(false); 152 } 153 } 154 MSE_DEBUG("ranges=%s", DumpTimeRanges(seekable).get()); 155 return IntervalType(seekable); 156 } 157 158 media::TimeIntervals MediaSourceDecoder::GetSeekable() { 159 return GetSeekableImpl<media::TimeIntervals>(); 160 } 161 162 media::TimeRanges MediaSourceDecoder::GetSeekableTimeRanges() { 163 return GetSeekableImpl<media::TimeRanges>(); 164 } 165 166 media::TimeIntervals MediaSourceDecoder::GetBuffered() { 167 MOZ_ASSERT(NS_IsMainThread()); 168 169 if (!mMediaSource) { 170 NS_WARNING("MediaSource element isn't attached"); 171 return media::TimeIntervals::Invalid(); 172 } 173 dom::SourceBufferList* sourceBuffers = mMediaSource->ActiveSourceBuffers(); 174 if (!sourceBuffers) { 175 // Media source object is shutting down. 176 return TimeIntervals(); 177 } 178 TimeUnit highestEndTime; 179 nsTArray<media::TimeIntervals> activeRanges; 180 media::TimeIntervals buffered; 181 182 for (uint32_t i = 0; i < sourceBuffers->Length(); i++) { 183 bool found; 184 dom::SourceBuffer* sb = sourceBuffers->IndexedGetter(i, found); 185 MOZ_ASSERT(found); 186 187 activeRanges.AppendElement(sb->GetTimeIntervals()); 188 highestEndTime = 189 std::max(highestEndTime, activeRanges.LastElement().GetEnd()); 190 } 191 192 buffered += media::TimeInterval(TimeUnit::Zero(), highestEndTime); 193 194 for (auto& range : activeRanges) { 195 if (mEnded && !range.IsEmpty()) { 196 // Set the end time on the last range to highestEndTime by adding a 197 // new range spanning the current end time to highestEndTime, which 198 // Normalize() will then merge with the old last range. 199 range += media::TimeInterval(range.GetEnd(), highestEndTime); 200 } 201 buffered.Intersection(range); 202 } 203 204 MSE_DEBUG("ranges=%s", DumpTimeRanges(buffered).get()); 205 return buffered; 206 } 207 208 void MediaSourceDecoder::Shutdown() { 209 MOZ_ASSERT(NS_IsMainThread()); 210 MSE_DEBUG("Shutdown"); 211 // Detach first so that TrackBuffers are unused on the main thread when 212 // shut down on the decode task queue. 213 if (mMediaSource) { 214 mMediaSource->Detach(); 215 } 216 mDemuxer = nullptr; 217 218 MediaDecoder::Shutdown(); 219 } 220 221 void MediaSourceDecoder::AttachMediaSource(dom::MediaSource* aMediaSource) { 222 MOZ_ASSERT(!mMediaSource && !GetStateMachine() && NS_IsMainThread()); 223 mMediaSource = aMediaSource; 224 DDLINKCHILD("mediasource", aMediaSource); 225 } 226 227 void MediaSourceDecoder::DetachMediaSource() { 228 MOZ_ASSERT(mMediaSource && NS_IsMainThread()); 229 DDUNLINKCHILD(mMediaSource); 230 mMediaSource = nullptr; 231 } 232 233 void MediaSourceDecoder::Ended(bool aEnded) { 234 MOZ_ASSERT(NS_IsMainThread()); 235 if (aEnded) { 236 // We want the MediaSourceReader to refresh its buffered range as it may 237 // have been modified (end lined up). 238 NotifyDataArrived(); 239 } 240 mEnded = aEnded; 241 GetStateMachine()->DispatchIsLiveStream(!mEnded); 242 } 243 244 void MediaSourceDecoder::AddSizeOfResources(ResourceSizes* aSizes) { 245 MOZ_ASSERT(NS_IsMainThread()); 246 if (GetDemuxer()) { 247 GetDemuxer()->AddSizeOfResources(aSizes); 248 } 249 } 250 251 void MediaSourceDecoder::SetInitialDuration(const TimeUnit& aDuration) { 252 MOZ_ASSERT(NS_IsMainThread()); 253 // Only use the decoded duration if one wasn't already 254 // set. 255 if (!mMediaSource || !std::isnan(ExplicitDuration())) { 256 return; 257 } 258 SetMediaSourceDuration(aDuration); 259 } 260 261 void MediaSourceDecoder::SetMediaSourceDuration(const TimeUnit& aDuration) { 262 MOZ_ASSERT(NS_IsMainThread()); 263 MOZ_ASSERT(!IsShutdown()); 264 if (aDuration.IsPositiveOrZero()) { 265 // Truncate to microsecond resolution for consistency with the 266 // SourceBuffer.buffered getter. 267 SetExplicitDuration(aDuration.ToBase(USECS_PER_S).ToSeconds()); 268 } else { 269 SetExplicitDuration(PositiveInfinity<double>()); 270 } 271 } 272 273 void MediaSourceDecoder::SetMediaSourceDuration(double aDuration) { 274 MOZ_ASSERT(NS_IsMainThread()); 275 MOZ_ASSERT(!IsShutdown()); 276 if (aDuration >= 0) { 277 SetExplicitDuration(aDuration); 278 } else { 279 SetExplicitDuration(PositiveInfinity<double>()); 280 } 281 } 282 283 RefPtr<GenericPromise> MediaSourceDecoder::RequestDebugInfo( 284 dom::MediaSourceDecoderDebugInfo& aInfo) { 285 // This should be safe to call off main thead, but there's no such usage at 286 // time of writing. Can be carefully relaxed if needed. 287 MOZ_ASSERT(NS_IsMainThread(), "Expects to be called on main thread."); 288 nsTArray<RefPtr<GenericPromise>> promises; 289 if (mReader) { 290 promises.AppendElement(mReader->RequestDebugInfo(aInfo.mReader)); 291 } 292 if (mDemuxer) { 293 promises.AppendElement(mDemuxer->GetDebugInfo(aInfo.mDemuxer)); 294 } 295 return GenericPromise::All(GetCurrentSerialEventTarget(), promises) 296 ->Then( 297 GetCurrentSerialEventTarget(), __func__, 298 []() { return GenericPromise::CreateAndResolve(true, __func__); }, 299 [] { 300 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); 301 }); 302 } 303 304 double MediaSourceDecoder::GetDuration() { 305 MOZ_ASSERT(NS_IsMainThread()); 306 return ExplicitDuration(); 307 } 308 309 MediaDecoderOwner::NextFrameStatus 310 MediaSourceDecoder::NextFrameBufferedStatus() { 311 MOZ_ASSERT(NS_IsMainThread()); 312 313 if (!mMediaSource || 314 mMediaSource->ReadyState() == dom::MediaSourceReadyState::Closed) { 315 return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE; 316 } 317 318 // Next frame hasn't been decoded yet. 319 // Use the buffered range to consider if we have the next frame available. 320 auto currentPosition = CurrentPosition(); 321 TimeIntervals buffered = GetBuffered(); 322 buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2); 323 TimeInterval interval( 324 currentPosition, currentPosition + DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED); 325 return buffered.ContainsWithStrictEnd(ClampIntervalToEnd(interval)) 326 ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE 327 : MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE; 328 } 329 330 bool MediaSourceDecoder::CanPlayThroughImpl() { 331 MOZ_ASSERT(NS_IsMainThread()); 332 333 if (NextFrameBufferedStatus() == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE) { 334 return false; 335 } 336 337 if (std::isnan(mMediaSource->Duration())) { 338 // Don't have any data yet. 339 return false; 340 } 341 TimeUnit duration = TimeUnit::FromSeconds(mMediaSource->Duration()); 342 auto currentPosition = CurrentPosition(); 343 if (duration <= currentPosition) { 344 return true; 345 } 346 // If we have data up to the mediasource's duration or 3s ahead, we can 347 // assume that we can play without interruption. 348 TimeIntervals buffered = GetBuffered(); 349 buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2); 350 TimeUnit timeAhead = 351 std::min(duration, currentPosition + TimeUnit::FromSeconds(3)); 352 TimeInterval interval(currentPosition, timeAhead); 353 return buffered.ToMicrosecondResolution().ContainsWithStrictEnd( 354 ClampIntervalToEnd(interval)); 355 } 356 357 TimeInterval MediaSourceDecoder::ClampIntervalToEnd( 358 const TimeInterval& aInterval) { 359 MOZ_ASSERT(NS_IsMainThread()); 360 361 if (!mEnded) { 362 return aInterval; 363 } 364 TimeUnit duration = mDuration.match(DurationToTimeUnit()); 365 if (duration < aInterval.mStart) { 366 return aInterval; 367 } 368 return TimeInterval(aInterval.mStart, std::min(aInterval.mEnd, duration), 369 aInterval.mFuzz); 370 } 371 372 void MediaSourceDecoder::NotifyInitDataArrived() { 373 MOZ_ASSERT(NS_IsMainThread()); 374 if (mDemuxer) { 375 mDemuxer->NotifyInitDataArrived(); 376 } 377 } 378 379 void MediaSourceDecoder::NotifyDataArrived() { 380 MOZ_ASSERT(NS_IsMainThread()); 381 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown()); 382 NotifyReaderDataArrived(); 383 GetOwner()->DownloadProgressed(); 384 } 385 386 already_AddRefed<nsIPrincipal> MediaSourceDecoder::GetCurrentPrincipal() { 387 MOZ_ASSERT(NS_IsMainThread()); 388 return do_AddRef(mPrincipal); 389 } 390 391 bool MediaSourceDecoder::HadCrossOriginRedirects() { 392 MOZ_ASSERT(NS_IsMainThread()); 393 return false; 394 } 395 396 #ifdef MOZ_WMF_MEDIA_ENGINE 397 void MediaSourceDecoder::MetadataLoaded( 398 UniquePtr<MediaInfo> aInfo, UniquePtr<MetadataTags> aTags, 399 MediaDecoderEventVisibility aEventVisibility) { 400 // If the metadata has been loaded before, we don't want to notify throughout 401 // that again when switching from media engine playback to normal playback. 402 if (mPendingStatusUpdateForNewlyCreatedStateMachine && mFiredMetadataLoaded) { 403 MSE_DEBUG( 404 "Metadata already loaded and being informed by previous state machine"); 405 SetStatusUpdateForNewlyCreatedStateMachineIfNeeded(); 406 return; 407 } 408 MediaDecoder::MetadataLoaded(std::move(aInfo), std::move(aTags), 409 aEventVisibility); 410 } 411 #endif 412 413 #undef MSE_DEBUG 414 #undef MSE_DEBUGV 415 416 } // namespace mozilla