diff --git a/dom/media/mediasource/SourceBuffer.cpp b/dom/media/mediasource/SourceBuffer.cpp index 96cfc0d39f3b..71103bfd3111 100644 --- a/dom/media/mediasource/SourceBuffer.cpp +++ b/dom/media/mediasource/SourceBuffer.cpp @@ -301,6 +301,47 @@ void SourceBuffer::ChangeType(const nsAString& aType, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); + + DecoderDoctorDiagnostics diagnostics; + nsresult rv = MediaSource::IsTypeSupported(aType, &diagnostics); + diagnostics.StoreFormatDiagnostics(mMediaSource->GetOwner() + ? mMediaSource->GetOwner()->GetExtantDoc() + : nullptr, + aType, NS_SUCCEEDED(rv), __func__); + MSE_API("ChangeType(aType=%s)%s", + NS_ConvertUTF16toUTF8(aType).get(), + rv == NS_OK ? "" : " [not supported]"); + if (NS_FAILED(rv)) { + DDLOG(DDLogCategory::API, "ChangeType", rv); + aRv.Throw(rv); + return; + } + if (!mMediaSource->GetDecoder() || + mMediaSource->GetDecoder()->OwnerHasError()) { + MSE_DEBUG("HTMLMediaElement.error is not null"); + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + if (!IsAttached() || mUpdating) { + DDLOG(DDLogCategory::API, "ChangeType", NS_ERROR_DOM_INVALID_STATE_ERR); + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed); + if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) { + mMediaSource->SetReadyState(MediaSourceReadyState::Open); + } + if (mCurrentAttributes.GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT){ + DDLOG(DDLogCategory::API, "ChangeType", NS_ERROR_DOM_INVALID_STATE_ERR); + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + Maybe containerType = MakeMediaContainerType(aType); + MOZ_ASSERT(containerType); + mType = *containerType; + ResetParserState(); + mTrackBuffersManager->ChangeType(mType); } void diff --git a/dom/media/mediasource/SourceBufferTask.h b/dom/media/mediasource/SourceBufferTask.h index b73094fbd282..769ae3587a1d 100644 --- a/dom/media/mediasource/SourceBufferTask.h +++ b/dom/media/mediasource/SourceBufferTask.h @@ -24,7 +24,8 @@ public: Reset, RangeRemoval, EvictData, - Detach + Detach, + ChangeType }; typedef Pair AppendBufferResult; @@ -112,6 +113,20 @@ public: const char* GetTypeName() const override { return "Detach"; } }; +class ChangeTypeTask : public SourceBufferTask { +public: + explicit ChangeTypeTask(const MediaContainerType& aType) + : mType(aType) + { + } + + static const Type sType = Type::ChangeType; + Type GetType() const override { return Type::ChangeType; } + const char* GetTypeName() const override { return "ChangeType"; } + + const MediaContainerType mType; +}; + } // end mozilla namespace #endif diff --git a/dom/media/mediasource/TrackBuffersManager.cpp b/dom/media/mediasource/TrackBuffersManager.cpp index ef2c5e1a2242..3de834a1e29a 100644 --- a/dom/media/mediasource/TrackBuffersManager.cpp +++ b/dom/media/mediasource/TrackBuffersManager.cpp @@ -111,6 +111,7 @@ TrackBuffersManager::TrackBuffersManager(MediaSourceDecoder* aParentDecoder, : mInputBuffer(new MediaByteBuffer) , mBufferFull(false) , mFirstInitializationSegmentReceived(false) + , mChangeTypeReceived(false) , mNewMediaSegmentStarted(false) , mActiveTrack(false) , mType(aType) @@ -280,6 +281,14 @@ TrackBuffersManager::ProcessTasks() ShutdownDemuxers(); ResetTaskQueue(); return; + case Type::ChangeType: + MOZ_RELEASE_ASSERT(!mCurrentTask); + mType = task->As()->mType; + mChangeTypeReceived = true; + mInitData = nullptr; + CompleteResetParserState(); + CreateDemuxerforMIMEType(); + break; default: NS_WARNING("Invalid Task"); } @@ -385,6 +394,15 @@ TrackBuffersManager::EvictData(const TimeUnit& aPlaybackTime, int64_t aSize) return result; } +void +TrackBuffersManager::ChangeType(const MediaContainerType& aType) +{ + MOZ_ASSERT(NS_IsMainThread()); + + QueueTask(new ChangeTypeTask(aType)); +} + + TimeIntervals TrackBuffersManager::Buffered() const { @@ -475,10 +493,13 @@ TrackBuffersManager::CompleteResetParserState() } // We could be left with a demuxer in an unusable state. It needs to be - // recreated. We store in the InputBuffer an init segment which will be parsed - // during the next Segment Parser Loop and a new demuxer will be created and - // initialized. - if (mFirstInitializationSegmentReceived) { + // recreated. Unless we have a pending changeType operation, we store in the + // InputBuffer an init segment which will be parsed during the next Segment + // Parser Loop and a new demuxer will be created and initialized. + // If we are in the middle of a changeType operation, then we do not have an + // init segment yet. The next appendBuffer operation will need to provide such + // init segment. + if (mFirstInitializationSegmentReceived && !mChangeTypeReceived) { MOZ_ASSERT(mInitData && mInitData->Length(), "we must have an init segment"); // The aim here is really to destroy our current demuxer. CreateDemuxerforMIMEType(); @@ -486,8 +507,10 @@ TrackBuffersManager::CompleteResetParserState() // to mInputBuffer as it will get modified in the Segment Parser Loop. mInputBuffer = new MediaByteBuffer; mInputBuffer->AppendElements(*mInitData); + RecreateParser(true); + } else { + RecreateParser(false); } - RecreateParser(true); } int64_t @@ -721,7 +744,7 @@ TrackBuffersManager::SegmentParserLoop() MediaResult haveInitSegment = mParser->IsInitSegmentPresent(mInputBuffer); if (NS_SUCCEEDED(haveInitSegment)) { SetAppendState(AppendState::PARSING_INIT_SEGMENT); - if (mFirstInitializationSegmentReceived) { + if (mFirstInitializationSegmentReceived && !mChangeTypeReceived) { // This is a new initialization segment. Obsolete the old one. RecreateParser(false); } @@ -772,8 +795,12 @@ TrackBuffersManager::SegmentParserLoop() return; } if (mSourceBufferAttributes->GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT) { - // 1. If the first initialization segment received flag is false, then run the append error algorithm with the decode error parameter set to true and abort this algorithm. - if (!mFirstInitializationSegmentReceived) { + // 1. If the first initialization segment received flag is false, then run + // the append error algorithm with the decode error parameter set to + // true and abort this algorithm. + // Or we are in the process of changeType, in which case we must first + // get an init segment before getting a media segment. + if (!mFirstInitializationSegmentReceived || mChangeTypeReceived) { RejectAppend(NS_ERROR_FAILURE, __func__); return; } @@ -1108,13 +1135,17 @@ TrackBuffersManager::OnDemuxerInitDone(const MediaResult& aResult) if (mFirstInitializationSegmentReceived) { if (numVideos != mVideoTracks.mNumTracks || numAudios != mAudioTracks.mNumTracks || - (numVideos && info.mVideo.mMimeType != mVideoTracks.mInfo->mMimeType) || - (numAudios && info.mAudio.mMimeType != mAudioTracks.mInfo->mMimeType)) { + (!mChangeTypeReceived && + ((numVideos && + info.mVideo.mMimeType != mVideoTracks.mInfo->mMimeType) || + (numAudios && + info.mAudio.mMimeType != mAudioTracks.mInfo->mMimeType)))) { RejectAppend(NS_ERROR_FAILURE, __func__); return; } - // 1. If more than one track for a single type are present (ie 2 audio tracks), - // then the Track IDs match the ones in the first initialization segment. + // 1. If more than one track for a single type are present (ie 2 audio + // tracks), then the Track IDs match the ones in the first initialization + // segment. // TODO // 2. Add the appropriate track descriptions from this initialization // segment to each of the track buffers. @@ -1214,6 +1245,9 @@ TrackBuffersManager::OnDemuxerInitDone(const MediaResult& aResult) mVideoTracks.mLastInfo = new TrackInfoSharedPtr(info.mVideo, streamID); } + // We have now completed the changeType operation. + mChangeTypeReceived = false; + UniquePtr crypto = mInputDemuxer->GetCrypto(); if (crypto && crypto->IsEncrypted()) { // Try and dispatch 'encrypted'. Won't go if ready state still HAVE_NOTHING. diff --git a/dom/media/mediasource/TrackBuffersManager.h b/dom/media/mediasource/TrackBuffersManager.h index 47fd1df8e326..9c7d191fc55c 100644 --- a/dom/media/mediasource/TrackBuffersManager.h +++ b/dom/media/mediasource/TrackBuffersManager.h @@ -115,6 +115,9 @@ public: // and if still more space is needed remove from the end. EvictDataResult EvictData(const media::TimeUnit& aPlaybackTime, int64_t aSize); + // Queue a task to run ChangeType + void ChangeType(const MediaContainerType& aType); + // Returns the buffered range currently managed. // This may be called on any thread. // Buffered must conform to http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered @@ -205,10 +208,11 @@ private: // Accessed on both the main thread and the task queue. Atomic mBufferFull; bool mFirstInitializationSegmentReceived; + bool mChangeTypeReceived; // Set to true once a new segment is started. bool mNewMediaSegmentStarted; bool mActiveTrack; - const MediaContainerType mType; + MediaContainerType mType; // ContainerParser objects and methods. // Those are used to parse the incoming input buffer.