michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "SourceBuffer.h" michael@0: michael@0: #include "AsyncEventRunner.h" michael@0: #include "DecoderTraits.h" michael@0: #include "MediaDecoder.h" michael@0: #include "MediaSourceDecoder.h" michael@0: #include "SourceBufferResource.h" michael@0: #include "mozilla/ErrorResult.h" michael@0: #include "mozilla/FloatingPoint.h" michael@0: #include "mozilla/dom/MediaSourceBinding.h" michael@0: #include "mozilla/dom/TimeRanges.h" michael@0: #include "nsError.h" michael@0: #include "nsIEventTarget.h" michael@0: #include "nsIRunnable.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "prlog.h" michael@0: #include "SubBufferDecoder.h" michael@0: michael@0: struct JSContext; michael@0: class JSObject; michael@0: michael@0: #ifdef PR_LOGGING michael@0: extern PRLogModuleInfo* gMediaSourceLog; michael@0: #define MSE_DEBUG(...) PR_LOG(gMediaSourceLog, PR_LOG_DEBUG, (__VA_ARGS__)) michael@0: #else michael@0: #define MSE_DEBUG(...) michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: michael@0: class MediaResource; michael@0: class ReentrantMonitor; michael@0: michael@0: namespace layers { michael@0: michael@0: class ImageContainer; michael@0: michael@0: } // namespace layers michael@0: michael@0: ReentrantMonitor& michael@0: SubBufferDecoder::GetReentrantMonitor() michael@0: { michael@0: return mParentDecoder->GetReentrantMonitor(); michael@0: } michael@0: michael@0: bool michael@0: SubBufferDecoder::OnStateMachineThread() const michael@0: { michael@0: return mParentDecoder->OnStateMachineThread(); michael@0: } michael@0: michael@0: bool michael@0: SubBufferDecoder::OnDecodeThread() const michael@0: { michael@0: return mParentDecoder->OnDecodeThread(); michael@0: } michael@0: michael@0: SourceBufferResource* michael@0: SubBufferDecoder::GetResource() const michael@0: { michael@0: return static_cast(mResource.get()); michael@0: } michael@0: michael@0: void michael@0: SubBufferDecoder::SetMediaDuration(int64_t aDuration) michael@0: { michael@0: mMediaDuration = aDuration; michael@0: } michael@0: michael@0: void michael@0: SubBufferDecoder::UpdateEstimatedMediaDuration(int64_t aDuration) michael@0: { michael@0: //mParentDecoder->UpdateEstimatedMediaDuration(aDuration); michael@0: } michael@0: michael@0: void michael@0: SubBufferDecoder::SetMediaSeekable(bool aMediaSeekable) michael@0: { michael@0: //mParentDecoder->SetMediaSeekable(aMediaSeekable); michael@0: } michael@0: michael@0: void michael@0: SubBufferDecoder::SetTransportSeekable(bool aTransportSeekable) michael@0: { michael@0: //mParentDecoder->SetTransportSeekable(aTransportSeekable); michael@0: } michael@0: michael@0: layers::ImageContainer* michael@0: SubBufferDecoder::GetImageContainer() michael@0: { michael@0: return mParentDecoder->GetImageContainer(); michael@0: } michael@0: michael@0: MediaDecoderOwner* michael@0: SubBufferDecoder::GetOwner() michael@0: { michael@0: return mParentDecoder->GetOwner(); michael@0: } michael@0: michael@0: int64_t michael@0: SubBufferDecoder::ConvertToByteOffset(double aTime) michael@0: { michael@0: // Uses a conversion based on (aTime/duration) * length. For the michael@0: // purposes of eviction this should be adequate since we have the michael@0: // byte threshold as well to ensure data actually gets evicted and michael@0: // we ensure we don't evict before the current playable point. michael@0: if (mMediaDuration == -1) { michael@0: return -1; michael@0: } michael@0: int64_t length = GetResource()->GetLength(); michael@0: MOZ_ASSERT(length > 0); michael@0: int64_t offset = (aTime / (double(mMediaDuration) / USECS_PER_S)) * length; michael@0: return offset; michael@0: } michael@0: michael@0: class ContainerParser { michael@0: public: michael@0: virtual ~ContainerParser() {} michael@0: michael@0: virtual bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength) michael@0: { michael@0: return false; michael@0: } michael@0: }; michael@0: michael@0: class WebMContainerParser : public ContainerParser { michael@0: public: michael@0: bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength) michael@0: { michael@0: // XXX: This is overly primitive, needs to collect data as it's appended michael@0: // to the SB and handle, rather than assuming everything is present in a michael@0: // single aData segment. michael@0: // 0x1a45dfa3 // EBML michael@0: // ... michael@0: // DocType == "webm" michael@0: // ... michael@0: // 0x18538067 // Segment (must be "unknown" size) michael@0: // 0x1549a966 // -> Segment Info michael@0: // 0x1654ae6b // -> One or more Tracks michael@0: if (aLength >= 4 && michael@0: aData[0] == 0x1a && aData[1] == 0x45 && aData[2] == 0xdf && aData[3] == 0xa3) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: }; michael@0: michael@0: namespace dom { michael@0: michael@0: void michael@0: SourceBuffer::SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv) michael@0: { michael@0: if (!IsAttached() || mUpdating) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed); michael@0: if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) { michael@0: mMediaSource->SetReadyState(MediaSourceReadyState::Open); michael@0: } michael@0: // TODO: Test append state. michael@0: // TODO: If aMode is "sequence", set sequence start time. michael@0: mAppendMode = aMode; michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::SetTimestampOffset(double aTimestampOffset, ErrorResult& aRv) michael@0: { michael@0: if (!IsAttached() || mUpdating) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed); michael@0: if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) { michael@0: mMediaSource->SetReadyState(MediaSourceReadyState::Open); michael@0: } michael@0: // TODO: Test append state. michael@0: // TODO: If aMode is "sequence", set sequence start time. michael@0: mTimestampOffset = aTimestampOffset; michael@0: } michael@0: michael@0: already_AddRefed michael@0: SourceBuffer::GetBuffered(ErrorResult& aRv) michael@0: { michael@0: if (!IsAttached()) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return nullptr; michael@0: } michael@0: nsRefPtr ranges = new TimeRanges(); michael@0: for (uint32_t i = 0; i < mDecoders.Length(); ++i) { michael@0: nsRefPtr r = new TimeRanges(); michael@0: mDecoders[i]->GetBuffered(r); michael@0: if (r->Length() > 0) { michael@0: MSE_DEBUG("%p GetBuffered decoder=%u Length=%u Start=%f End=%f", this, i, r->Length(), michael@0: r->GetStartTime(), r->GetEndTime()); michael@0: ranges->Add(r->GetStartTime(), r->GetEndTime()); michael@0: } else { michael@0: MSE_DEBUG("%p GetBuffered decoder=%u Length=%u", this, i, r->Length()); michael@0: } michael@0: } michael@0: ranges->Normalize(); michael@0: MSE_DEBUG("%p GetBuffered Length=%u Start=%f End=%f", this, ranges->Length(), michael@0: ranges->GetStartTime(), ranges->GetEndTime()); michael@0: return ranges.forget(); michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::SetAppendWindowStart(double aAppendWindowStart, ErrorResult& aRv) michael@0: { michael@0: if (!IsAttached() || mUpdating) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: if (aAppendWindowStart < 0 || aAppendWindowStart >= mAppendWindowEnd) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); michael@0: return; michael@0: } michael@0: mAppendWindowStart = aAppendWindowStart; michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::SetAppendWindowEnd(double aAppendWindowEnd, ErrorResult& aRv) michael@0: { michael@0: if (!IsAttached() || mUpdating) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: if (IsNaN(aAppendWindowEnd) || michael@0: aAppendWindowEnd <= mAppendWindowStart) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); michael@0: return; michael@0: } michael@0: mAppendWindowEnd = aAppendWindowEnd; michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::AppendBuffer(const ArrayBuffer& aData, ErrorResult& aRv) michael@0: { michael@0: aData.ComputeLengthAndData(); michael@0: michael@0: AppendData(aData.Data(), aData.Length(), aRv); michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::AppendBuffer(const ArrayBufferView& aData, ErrorResult& aRv) michael@0: { michael@0: aData.ComputeLengthAndData(); michael@0: michael@0: AppendData(aData.Data(), aData.Length(), aRv); michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::Abort(ErrorResult& aRv) michael@0: { michael@0: MSE_DEBUG("%p Abort()", this); michael@0: if (!IsAttached()) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: if (mMediaSource->ReadyState() != MediaSourceReadyState::Open) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: if (mUpdating) { michael@0: // TODO: Abort segment parser loop, buffer append, and stream append loop algorithms. michael@0: AbortUpdating(); michael@0: } michael@0: // TODO: Run reset parser algorithm. michael@0: mAppendWindowStart = 0; michael@0: mAppendWindowEnd = PositiveInfinity(); michael@0: michael@0: MSE_DEBUG("%p Abort: Discarding decoders.", this); michael@0: if (mCurrentDecoder) { michael@0: mCurrentDecoder->GetResource()->Ended(); michael@0: mCurrentDecoder = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::Remove(double aStart, double aEnd, ErrorResult& aRv) michael@0: { michael@0: MSE_DEBUG("%p Remove(Start=%f End=%f)", this, aStart, aEnd); michael@0: if (!IsAttached() || mUpdating || michael@0: mMediaSource->ReadyState() != MediaSourceReadyState::Open) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: if (aStart < 0 || aStart > mMediaSource->Duration() || michael@0: aEnd <= aStart) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); michael@0: return; michael@0: } michael@0: StartUpdating(); michael@0: /// TODO: Run coded frame removal algorithm asynchronously (would call StopUpdating()). michael@0: StopUpdating(); michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::Detach() michael@0: { michael@0: Ended(); michael@0: mDecoders.Clear(); michael@0: mCurrentDecoder = nullptr; michael@0: mMediaSource = nullptr; michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::Ended() michael@0: { michael@0: for (uint32_t i = 0; i < mDecoders.Length(); ++i) { michael@0: mDecoders[i]->GetResource()->Ended(); michael@0: } michael@0: } michael@0: michael@0: SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType) michael@0: : DOMEventTargetHelper(aMediaSource->GetParentObject()) michael@0: , mMediaSource(aMediaSource) michael@0: , mType(aType) michael@0: , mAppendWindowStart(0) michael@0: , mAppendWindowEnd(PositiveInfinity()) michael@0: , mTimestampOffset(0) michael@0: , mAppendMode(SourceBufferAppendMode::Segments) michael@0: , mUpdating(false) michael@0: { michael@0: MOZ_ASSERT(aMediaSource); michael@0: if (mType.EqualsIgnoreCase("video/webm") || mType.EqualsIgnoreCase("audio/webm")) { michael@0: mParser = new WebMContainerParser(); michael@0: } else { michael@0: // XXX: Plug in parsers for MPEG4, etc. here. michael@0: mParser = new ContainerParser(); michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: SourceBuffer::Create(MediaSource* aMediaSource, const nsACString& aType) michael@0: { michael@0: nsRefPtr sourceBuffer = new SourceBuffer(aMediaSource, aType); michael@0: return sourceBuffer.forget(); michael@0: } michael@0: michael@0: SourceBuffer::~SourceBuffer() michael@0: { michael@0: for (uint32_t i = 0; i < mDecoders.Length(); ++i) { michael@0: mDecoders[i]->GetResource()->Ended(); michael@0: } michael@0: } michael@0: michael@0: MediaSource* michael@0: SourceBuffer::GetParentObject() const michael@0: { michael@0: return mMediaSource; michael@0: } michael@0: michael@0: JSObject* michael@0: SourceBuffer::WrapObject(JSContext* aCx) michael@0: { michael@0: return SourceBufferBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::DispatchSimpleEvent(const char* aName) michael@0: { michael@0: MSE_DEBUG("%p Dispatching event %s to SourceBuffer", this, aName); michael@0: DispatchTrustedEvent(NS_ConvertUTF8toUTF16(aName)); michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::QueueAsyncSimpleEvent(const char* aName) michael@0: { michael@0: MSE_DEBUG("%p Queuing event %s to SourceBuffer", this, aName); michael@0: nsCOMPtr event = new AsyncEventRunner(this, aName); michael@0: NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: bool michael@0: SourceBuffer::InitNewDecoder() michael@0: { michael@0: MediaSourceDecoder* parentDecoder = mMediaSource->GetDecoder(); michael@0: nsRefPtr decoder = parentDecoder->CreateSubDecoder(mType); michael@0: if (!decoder) { michael@0: return false; michael@0: } michael@0: mDecoders.AppendElement(decoder); michael@0: // XXX: At this point, we really want to push through any remaining michael@0: // processing for the old decoder and discard it, rather than hanging on michael@0: // to all of them in mDecoders. michael@0: mCurrentDecoder = decoder; michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::StartUpdating() michael@0: { michael@0: MOZ_ASSERT(!mUpdating); michael@0: mUpdating = true; michael@0: QueueAsyncSimpleEvent("updatestart"); michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::StopUpdating() michael@0: { michael@0: MOZ_ASSERT(mUpdating); michael@0: mUpdating = false; michael@0: QueueAsyncSimpleEvent("update"); michael@0: QueueAsyncSimpleEvent("updateend"); michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::AbortUpdating() michael@0: { michael@0: MOZ_ASSERT(mUpdating); michael@0: mUpdating = false; michael@0: QueueAsyncSimpleEvent("abort"); michael@0: QueueAsyncSimpleEvent("updateend"); michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv) michael@0: { michael@0: MSE_DEBUG("%p AppendBuffer(Data=%u bytes)", this, aLength); michael@0: if (!IsAttached() || mUpdating) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) { michael@0: mMediaSource->SetReadyState(MediaSourceReadyState::Open); michael@0: } michael@0: // TODO: Run coded frame eviction algorithm. michael@0: // TODO: Test buffer full flag. michael@0: StartUpdating(); michael@0: // TODO: Run buffer append algorithm asynchronously (would call StopUpdating()). michael@0: if (mParser->IsInitSegmentPresent(aData, aLength) || !mCurrentDecoder) { michael@0: MSE_DEBUG("%p AppendBuffer: New initialization segment, switching decoders.", this); michael@0: if (mCurrentDecoder) { michael@0: mCurrentDecoder->GetResource()->Ended(); michael@0: } michael@0: if (!InitNewDecoder()) { michael@0: aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling. michael@0: return; michael@0: } michael@0: } michael@0: // XXX: For future reference: NDA call must run on the main thread. michael@0: mCurrentDecoder->NotifyDataArrived(reinterpret_cast(aData), michael@0: aLength, michael@0: mCurrentDecoder->GetResource()->GetLength()); michael@0: mCurrentDecoder->GetResource()->AppendData(aData, aLength); michael@0: michael@0: // Eviction uses a byte threshold. If the buffer is greater than the michael@0: // number of bytes then data is evicted. The time range for this michael@0: // eviction is reported back to the media source. It will then michael@0: // evict data before that range across all SourceBuffer's it knows michael@0: // about. michael@0: const int evict_threshold = 1000000; michael@0: bool evicted = mCurrentDecoder->GetResource()->EvictData(evict_threshold); michael@0: if (evicted) { michael@0: double start = 0.0; michael@0: double end = 0.0; michael@0: GetBufferedStartEndTime(&start, &end); michael@0: michael@0: // We notify that we've evicted from the time range 0 through to michael@0: // the current start point. michael@0: mMediaSource->NotifyEvicted(0.0, start); michael@0: } michael@0: StopUpdating(); michael@0: michael@0: // Schedule the state machine thread to ensure playback starts michael@0: // if required when data is appended. michael@0: mMediaSource->GetDecoder()->ScheduleStateMachineThread(); michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::GetBufferedStartEndTime(double* aStart, double* aEnd) michael@0: { michael@0: ErrorResult dummy; michael@0: nsRefPtr ranges = GetBuffered(dummy); michael@0: if (!ranges || ranges->Length() == 0) { michael@0: *aStart = *aEnd = 0.0; michael@0: return; michael@0: } michael@0: *aStart = ranges->Start(0, dummy); michael@0: *aEnd = ranges->End(ranges->Length() - 1, dummy); michael@0: } michael@0: michael@0: void michael@0: SourceBuffer::Evict(double aStart, double aEnd) michael@0: { michael@0: for (uint32_t i = 0; i < mDecoders.Length(); ++i) { michael@0: // Need to map time to byte offset then evict michael@0: int64_t end = mDecoders[i]->ConvertToByteOffset(aEnd); michael@0: if (end <= 0) { michael@0: NS_WARNING("SourceBuffer::Evict failed"); michael@0: continue; michael@0: } michael@0: mDecoders[i]->GetResource()->EvictBefore(end); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: SourceBuffer::ContainsTime(double aTime) michael@0: { michael@0: ErrorResult dummy; michael@0: nsRefPtr ranges = GetBuffered(dummy); michael@0: if (!ranges || ranges->Length() == 0) { michael@0: return false; michael@0: } michael@0: for (uint32_t i = 0; i < ranges->Length(); ++i) { michael@0: if (aTime >= ranges->Start(i, dummy) && michael@0: aTime <= ranges->End(i, dummy)) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_INHERITED(SourceBuffer, DOMEventTargetHelper, michael@0: mMediaSource) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(SourceBuffer, DOMEventTargetHelper) michael@0: NS_IMPL_RELEASE_INHERITED(SourceBuffer, DOMEventTargetHelper) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SourceBuffer) michael@0: NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) michael@0: michael@0: } // namespace dom michael@0: michael@0: } // namespace mozilla