/* -*- mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MediaSourceDecoder.h" #include "base/process_util.h" #include "mozilla/Logging.h" #include "ExternalEngineStateMachine.h" #include "MediaDecoder.h" #include "MediaDecoderStateMachine.h" #include "MediaShutdownManager.h" #include "MediaSource.h" #include "MediaSourceDemuxer.h" #include "MediaSourceUtils.h" #include "SourceBuffer.h" #include "SourceBufferList.h" #include "VideoUtils.h" #include extern mozilla::LogModule* GetMediaSourceLog(); #define MSE_DEBUG(arg, ...) \ DDMOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, "::%s: " arg, \ __func__, ##__VA_ARGS__) #define MSE_DEBUGV(arg, ...) \ DDMOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, "::%s: " arg, \ __func__, ##__VA_ARGS__) using namespace mozilla::media; namespace mozilla { MediaSourceDecoder::MediaSourceDecoder(MediaDecoderInit& aInit) : MediaDecoder(aInit), mMediaSource(nullptr), mEnded(false) { mExplicitDuration.emplace(UnspecifiedNaN()); } MediaDecoderStateMachineBase* MediaSourceDecoder::CreateStateMachine( bool aDisableExternalEngine) { MOZ_ASSERT(NS_IsMainThread()); // if `mDemuxer` already exists, that means we're in the process of recreating // the state machine. The track buffers are tied to the demuxer so we would // need to reuse it. if (!mDemuxer) { mDemuxer = new MediaSourceDemuxer(AbstractMainThread()); } MediaFormatReaderInit init; init.mVideoFrameContainer = GetVideoFrameContainer(); init.mKnowsCompositor = GetCompositor(); init.mCrashHelper = GetOwner()->CreateGMPCrashHelper(); init.mFrameStats = mFrameStats; init.mMediaDecoderOwnerID = mOwner; static Atomic sTrackingIdCounter(0); init.mTrackingId.emplace(TrackingId::Source::MSEDecoder, sTrackingIdCounter++, TrackingId::TrackAcrossProcesses::Yes); mReader = new MediaFormatReader(init, mDemuxer); #ifdef MOZ_WMF_MEDIA_ENGINE // TODO : Only for testing development for now. In the future this should be // used for encrypted content only. if (StaticPrefs::media_wmf_media_engine_enabled() && !aDisableExternalEngine) { return new ExternalEngineStateMachine(this, mReader); } #endif return new MediaDecoderStateMachine(this, mReader); } nsresult MediaSourceDecoder::Load(nsIPrincipal* aPrincipal) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!GetStateMachine()); mPrincipal = aPrincipal; nsresult rv = MediaShutdownManager::Instance().Register(this); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return CreateAndInitStateMachine(!mEnded); } template IntervalType MediaSourceDecoder::GetSeekableImpl() { MOZ_ASSERT(NS_IsMainThread()); if (!mMediaSource) { NS_WARNING("MediaSource element isn't attached"); return IntervalType(); } TimeIntervals seekable; double duration = mMediaSource->Duration(); if (std::isnan(duration)) { // Return empty range. } else if (duration > 0 && std::isinf(duration)) { media::TimeIntervals buffered = GetBuffered(); // 1. If live seekable range is not empty: if (mMediaSource->HasLiveSeekableRange()) { // 1. Let union ranges be the union of live seekable range and the // HTMLMediaElement.buffered attribute. TimeRanges unionRanges = media::TimeRanges(buffered) + mMediaSource->LiveSeekableRange(); // 2. Return a single range with a start time equal to the earliest start // time in union ranges and an end time equal to the highest end time in // union ranges and abort these steps. if constexpr (std::is_same::value) { TimeRanges seekableRange = media::TimeRanges( TimeRange(unionRanges.GetStart(), unionRanges.GetEnd())); return seekableRange; } else { MOZ_RELEASE_ASSERT(false); } } if (!buffered.IsEmpty()) { seekable += media::TimeInterval(TimeUnit::Zero(), buffered.GetEnd()); } } else { if constexpr (std::is_same::value) { // Common case: seekable in entire range of the media. return TimeRanges(TimeRange(0, duration)); } else if constexpr (std::is_same::value) { seekable += media::TimeInterval(TimeUnit::Zero(), mDuration.match(DurationToTimeUnit())); } else { MOZ_RELEASE_ASSERT(false); } } MSE_DEBUG("ranges=%s", DumpTimeRanges(seekable).get()); return IntervalType(seekable); } media::TimeIntervals MediaSourceDecoder::GetSeekable() { return GetSeekableImpl(); } media::TimeRanges MediaSourceDecoder::GetSeekableTimeRanges() { return GetSeekableImpl(); } media::TimeIntervals MediaSourceDecoder::GetBuffered() { MOZ_ASSERT(NS_IsMainThread()); if (!mMediaSource) { NS_WARNING("MediaSource element isn't attached"); return media::TimeIntervals::Invalid(); } dom::SourceBufferList* sourceBuffers = mMediaSource->ActiveSourceBuffers(); if (!sourceBuffers) { // Media source object is shutting down. return TimeIntervals(); } TimeUnit highestEndTime; nsTArray activeRanges; media::TimeIntervals buffered; for (uint32_t i = 0; i < sourceBuffers->Length(); i++) { bool found; dom::SourceBuffer* sb = sourceBuffers->IndexedGetter(i, found); MOZ_ASSERT(found); activeRanges.AppendElement(sb->GetTimeIntervals()); highestEndTime = std::max(highestEndTime, activeRanges.LastElement().GetEnd()); } buffered += media::TimeInterval(TimeUnit::Zero(), highestEndTime); for (auto& range : activeRanges) { if (mEnded && !range.IsEmpty()) { // Set the end time on the last range to highestEndTime by adding a // new range spanning the current end time to highestEndTime, which // Normalize() will then merge with the old last range. range += media::TimeInterval(range.GetEnd(), highestEndTime); } buffered.Intersection(range); } MSE_DEBUG("ranges=%s", DumpTimeRanges(buffered).get()); return buffered; } void MediaSourceDecoder::Shutdown() { MOZ_ASSERT(NS_IsMainThread()); MSE_DEBUG("Shutdown"); // Detach first so that TrackBuffers are unused on the main thread when // shut down on the decode task queue. if (mMediaSource) { mMediaSource->Detach(); } mDemuxer = nullptr; MediaDecoder::Shutdown(); } void MediaSourceDecoder::AttachMediaSource(dom::MediaSource* aMediaSource) { MOZ_ASSERT(!mMediaSource && !GetStateMachine() && NS_IsMainThread()); mMediaSource = aMediaSource; DDLINKCHILD("mediasource", aMediaSource); } void MediaSourceDecoder::DetachMediaSource() { MOZ_ASSERT(mMediaSource && NS_IsMainThread()); DDUNLINKCHILD(mMediaSource); mMediaSource = nullptr; } void MediaSourceDecoder::Ended(bool aEnded) { MOZ_ASSERT(NS_IsMainThread()); if (aEnded) { // We want the MediaSourceReader to refresh its buffered range as it may // have been modified (end lined up). NotifyDataArrived(); } mEnded = aEnded; GetStateMachine()->DispatchIsLiveStream(!mEnded); } void MediaSourceDecoder::AddSizeOfResources(ResourceSizes* aSizes) { MOZ_ASSERT(NS_IsMainThread()); if (GetDemuxer()) { GetDemuxer()->AddSizeOfResources(aSizes); } } void MediaSourceDecoder::SetInitialDuration(const TimeUnit& aDuration) { MOZ_ASSERT(NS_IsMainThread()); // Only use the decoded duration if one wasn't already // set. if (!mMediaSource || !std::isnan(ExplicitDuration())) { return; } SetMediaSourceDuration(aDuration); } void MediaSourceDecoder::SetMediaSourceDuration(const TimeUnit& aDuration) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!IsShutdown()); if (aDuration.IsPositiveOrZero()) { SetExplicitDuration(ToMicrosecondResolution(aDuration.ToSeconds())); } else { SetExplicitDuration(PositiveInfinity()); } } void MediaSourceDecoder::SetMediaSourceDuration(double aDuration) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!IsShutdown()); if (aDuration >= 0) { SetExplicitDuration(aDuration); } else { SetExplicitDuration(PositiveInfinity()); } } RefPtr MediaSourceDecoder::RequestDebugInfo( dom::MediaSourceDecoderDebugInfo& aInfo) { // This should be safe to call off main thead, but there's no such usage at // time of writing. Can be carefully relaxed if needed. MOZ_ASSERT(NS_IsMainThread(), "Expects to be called on main thread."); nsTArray> promises; if (mReader) { promises.AppendElement(mReader->RequestDebugInfo(aInfo.mReader)); } if (mDemuxer) { promises.AppendElement(mDemuxer->GetDebugInfo(aInfo.mDemuxer)); } return GenericPromise::All(GetCurrentSerialEventTarget(), promises) ->Then( GetCurrentSerialEventTarget(), __func__, []() { return GenericPromise::CreateAndResolve(true, __func__); }, [] { return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); }); } double MediaSourceDecoder::GetDuration() { MOZ_ASSERT(NS_IsMainThread()); return ExplicitDuration(); } MediaDecoderOwner::NextFrameStatus MediaSourceDecoder::NextFrameBufferedStatus() { MOZ_ASSERT(NS_IsMainThread()); if (!mMediaSource || mMediaSource->ReadyState() == dom::MediaSourceReadyState::Closed) { return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE; } // Next frame hasn't been decoded yet. // Use the buffered range to consider if we have the next frame available. auto currentPosition = CurrentPosition(); TimeIntervals buffered = GetBuffered(); buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2); TimeInterval interval( currentPosition, currentPosition + DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED); return buffered.ContainsWithStrictEnd(ClampIntervalToEnd(interval)) ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE : MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE; } bool MediaSourceDecoder::CanPlayThroughImpl() { MOZ_ASSERT(NS_IsMainThread()); if (NextFrameBufferedStatus() == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE) { return false; } if (std::isnan(mMediaSource->Duration())) { // Don't have any data yet. return false; } TimeUnit duration = TimeUnit::FromSeconds(mMediaSource->Duration()); auto currentPosition = CurrentPosition(); if (duration <= currentPosition) { return true; } // If we have data up to the mediasource's duration or 3s ahead, we can // assume that we can play without interruption. dom::SourceBufferList* sourceBuffers = mMediaSource->ActiveSourceBuffers(); TimeUnit bufferedEnd = sourceBuffers->GetHighestBufferedEndTime(); TimeUnit timeAhead = std::min(duration, currentPosition + TimeUnit::FromSeconds(3)); TimeInterval interval(currentPosition, timeAhead); return bufferedEnd >= timeAhead; } TimeInterval MediaSourceDecoder::ClampIntervalToEnd( const TimeInterval& aInterval) { MOZ_ASSERT(NS_IsMainThread()); if (!mEnded) { return aInterval; } TimeUnit duration = mDuration.match(DurationToTimeUnit()); if (duration < aInterval.mStart) { return aInterval; } return TimeInterval(aInterval.mStart, std::min(aInterval.mEnd, duration), aInterval.mFuzz); } void MediaSourceDecoder::NotifyInitDataArrived() { MOZ_ASSERT(NS_IsMainThread()); if (mDemuxer) { mDemuxer->NotifyInitDataArrived(); } } void MediaSourceDecoder::NotifyDataArrived() { MOZ_ASSERT(NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(!IsShutdown()); NotifyReaderDataArrived(); GetOwner()->DownloadProgressed(); } already_AddRefed MediaSourceDecoder::GetCurrentPrincipal() { MOZ_ASSERT(NS_IsMainThread()); return do_AddRef(mPrincipal); } bool MediaSourceDecoder::HadCrossOriginRedirects() { MOZ_ASSERT(NS_IsMainThread()); return false; } #undef MSE_DEBUG #undef MSE_DEBUGV } // namespace mozilla