diff options
Diffstat (limited to 'dom/media/hls')
-rw-r--r-- | dom/media/hls/HLSDecoder.cpp | 310 | ||||
-rw-r--r-- | dom/media/hls/HLSDecoder.h | 79 | ||||
-rw-r--r-- | dom/media/hls/HLSDemuxer.cpp | 628 | ||||
-rw-r--r-- | dom/media/hls/HLSDemuxer.h | 137 | ||||
-rw-r--r-- | dom/media/hls/HLSUtils.cpp | 12 | ||||
-rw-r--r-- | dom/media/hls/HLSUtils.h | 21 | ||||
-rw-r--r-- | dom/media/hls/moz.build | 24 |
7 files changed, 1211 insertions, 0 deletions
diff --git a/dom/media/hls/HLSDecoder.cpp b/dom/media/hls/HLSDecoder.cpp new file mode 100644 index 0000000000..99bf4c0ff6 --- /dev/null +++ b/dom/media/hls/HLSDecoder.cpp @@ -0,0 +1,310 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "HLSDecoder.h" +#include "AndroidBridge.h" +#include "base/process_util.h" +#include "DecoderTraits.h" +#include "HLSDemuxer.h" +#include "HLSUtils.h" +#include "JavaBuiltins.h" +#include "MediaContainerType.h" +#include "MediaDecoderStateMachine.h" +#include "MediaFormatReader.h" +#include "MediaShutdownManager.h" +#include "mozilla/java/GeckoHLSResourceWrapperNatives.h" +#include "nsContentUtils.h" +#include "nsIChannel.h" +#include "nsNetUtil.h" +#include "nsThreadUtils.h" +#include "mozilla/dom/HTMLMediaElement.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/StaticPrefs_media.h" + +namespace mozilla { + +class HLSResourceCallbacksSupport + : public java::GeckoHLSResourceWrapper::Callbacks::Natives< + HLSResourceCallbacksSupport> { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HLSResourceCallbacksSupport) + public: + typedef java::GeckoHLSResourceWrapper::Callbacks::Natives< + HLSResourceCallbacksSupport> + NativeCallbacks; + using NativeCallbacks::AttachNative; + using NativeCallbacks::DisposeNative; + + explicit HLSResourceCallbacksSupport(HLSDecoder* aResource); + void Detach(); + void OnLoad(jni::String::Param aUrl); + void OnDataArrived(); + void OnError(int aErrorCode); + + private: + ~HLSResourceCallbacksSupport() {} + Mutex mMutex MOZ_UNANNOTATED; + HLSDecoder* mDecoder; +}; + +HLSResourceCallbacksSupport::HLSResourceCallbacksSupport(HLSDecoder* aDecoder) + : mMutex("HLSResourceCallbacksSupport"), mDecoder(aDecoder) { + MOZ_ASSERT(mDecoder); +} + +void HLSResourceCallbacksSupport::Detach() { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mMutex); + mDecoder = nullptr; +} + +void HLSResourceCallbacksSupport::OnLoad(jni::String::Param aUrl) { + MutexAutoLock lock(mMutex); + if (!mDecoder) { + return; + } + RefPtr<HLSResourceCallbacksSupport> self = this; + jni::String::GlobalRef url = std::move(aUrl); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "HLSResourceCallbacksSupport::OnLoad", [self, url]() -> void { + if (self->mDecoder) { + self->mDecoder->NotifyLoad(url->ToCString()); + } + })); +} + +void HLSResourceCallbacksSupport::OnDataArrived() { + HLS_DEBUG("HLSResourceCallbacksSupport", "OnDataArrived."); + MutexAutoLock lock(mMutex); + if (!mDecoder) { + return; + } + RefPtr<HLSResourceCallbacksSupport> self = this; + NS_DispatchToMainThread(NS_NewRunnableFunction( + "HLSResourceCallbacksSupport::OnDataArrived", [self]() -> void { + if (self->mDecoder) { + self->mDecoder->NotifyDataArrived(); + } + })); +} + +void HLSResourceCallbacksSupport::OnError(int aErrorCode) { + HLS_DEBUG("HLSResourceCallbacksSupport", "onError(%d)", aErrorCode); + MutexAutoLock lock(mMutex); + if (!mDecoder) { + return; + } + RefPtr<HLSResourceCallbacksSupport> self = this; + NS_DispatchToMainThread(NS_NewRunnableFunction( + "HLSResourceCallbacksSupport::OnError", [self]() -> void { + if (self->mDecoder) { + // Since HLS source should be from the Internet, we treat all resource + // errors from GeckoHlsPlayer as network errors. + self->mDecoder->NetworkError( + MediaResult(NS_ERROR_FAILURE, "HLS error")); + } + })); +} + +size_t HLSDecoder::sAllocatedInstances = 0; + +// static +RefPtr<HLSDecoder> HLSDecoder::Create(MediaDecoderInit& aInit) { + MOZ_ASSERT(NS_IsMainThread()); + + return sAllocatedInstances < StaticPrefs::media_hls_max_allocations() + ? new HLSDecoder(aInit) + : nullptr; +} + +HLSDecoder::HLSDecoder(MediaDecoderInit& aInit) : MediaDecoder(aInit) { + MOZ_ASSERT(NS_IsMainThread()); + sAllocatedInstances++; + HLS_DEBUG("HLSDecoder", "HLSDecoder(): allocated=%zu", sAllocatedInstances); +} + +HLSDecoder::~HLSDecoder() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(sAllocatedInstances > 0); + sAllocatedInstances--; + HLS_DEBUG("HLSDecoder", "~HLSDecoder(): allocated=%zu", sAllocatedInstances); +} + +MediaDecoderStateMachineBase* HLSDecoder::CreateStateMachine( + bool aDisableExternalEngine) { + MOZ_ASSERT(NS_IsMainThread()); + + MediaFormatReaderInit init; + init.mVideoFrameContainer = GetVideoFrameContainer(); + init.mKnowsCompositor = GetCompositor(); + init.mCrashHelper = GetOwner()->CreateGMPCrashHelper(); + init.mFrameStats = mFrameStats; + init.mMediaDecoderOwnerID = mOwner; + static Atomic<uint32_t> sTrackingIdCounter(0); + init.mTrackingId = + Some(TrackingId(TrackingId::Source::HLSDecoder, sTrackingIdCounter++, + TrackingId::TrackAcrossProcesses::Yes)); + mReader = new MediaFormatReader( + init, new HLSDemuxer(mHLSResourceWrapper->GetPlayerId())); + + return new MediaDecoderStateMachine(this, mReader); +} + +bool HLSDecoder::IsEnabled() { return StaticPrefs::media_hls_enabled(); } + +bool HLSDecoder::IsSupportedType(const MediaContainerType& aContainerType) { + return IsEnabled() && DecoderTraits::IsHttpLiveStreamingType(aContainerType); +} + +nsresult HLSDecoder::Load(nsIChannel* aChannel) { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(mURI)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mChannel = aChannel; + nsCString spec; + Unused << mURI->GetSpec(spec); + ; + HLSResourceCallbacksSupport::Init(); + mJavaCallbacks = java::GeckoHLSResourceWrapper::Callbacks::New(); + mCallbackSupport = new HLSResourceCallbacksSupport(this); + HLSResourceCallbacksSupport::AttachNative(mJavaCallbacks, mCallbackSupport); + mHLSResourceWrapper = java::GeckoHLSResourceWrapper::Create( + NS_ConvertUTF8toUTF16(spec), mJavaCallbacks); + MOZ_ASSERT(mHLSResourceWrapper); + + rv = MediaShutdownManager::Instance().Register(this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return CreateAndInitStateMachine(false); +} + +void HLSDecoder::AddSizeOfResources(ResourceSizes* aSizes) { + MOZ_ASSERT(NS_IsMainThread()); + // TODO: track JAVA wrappers. +} + +already_AddRefed<nsIPrincipal> HLSDecoder::GetCurrentPrincipal() { + MOZ_ASSERT(NS_IsMainThread()); + return do_AddRef(mContentPrincipal); +} + +bool HLSDecoder::HadCrossOriginRedirects() { + MOZ_ASSERT(NS_IsMainThread()); + // Bug 1478843 + return false; +} + +void HLSDecoder::Play() { + MOZ_ASSERT(NS_IsMainThread()); + HLS_DEBUG("HLSDecoder", "MediaElement called Play"); + mHLSResourceWrapper->Play(); + return MediaDecoder::Play(); +} + +void HLSDecoder::Pause() { + MOZ_ASSERT(NS_IsMainThread()); + HLS_DEBUG("HLSDecoder", "MediaElement called Pause"); + mHLSResourceWrapper->Pause(); + return MediaDecoder::Pause(); +} + +void HLSDecoder::Suspend() { + MOZ_ASSERT(NS_IsMainThread()); + HLS_DEBUG("HLSDecoder", "Should suspend the resource fetching."); + mHLSResourceWrapper->Suspend(); +} + +void HLSDecoder::Resume() { + MOZ_ASSERT(NS_IsMainThread()); + HLS_DEBUG("HLSDecoder", "Should resume the resource fetching."); + mHLSResourceWrapper->Resume(); +} + +void HLSDecoder::Shutdown() { + HLS_DEBUG("HLSDecoder", "Shutdown"); + if (mCallbackSupport) { + mCallbackSupport->Detach(); + } + if (mHLSResourceWrapper) { + mHLSResourceWrapper->Destroy(); + mHLSResourceWrapper = nullptr; + } + if (mJavaCallbacks) { + HLSResourceCallbacksSupport::DisposeNative(mJavaCallbacks); + mJavaCallbacks = nullptr; + } + MediaDecoder::Shutdown(); +} + +void HLSDecoder::NotifyDataArrived() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(!IsShutdown()); + NotifyReaderDataArrived(); + GetOwner()->DownloadProgressed(); +} + +void HLSDecoder::NotifyLoad(nsCString aMediaUrl) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(!IsShutdown()); + UpdateCurrentPrincipal(aMediaUrl); +} + +// Should be called when the decoder loads media from a URL to ensure the +// principal of the media element is appropriately set for CORS. +void HLSDecoder::UpdateCurrentPrincipal(nsCString aMediaUrl) { + nsCOMPtr<nsIPrincipal> principal = GetContentPrincipal(aMediaUrl); + MOZ_DIAGNOSTIC_ASSERT(principal); + + // Check the subsumption of old and new principals. Should be either + // equal or disjoint. + if (!mContentPrincipal) { + mContentPrincipal = principal; + } else if (principal->Equals(mContentPrincipal)) { + return; + } else if (!principal->Subsumes(mContentPrincipal) && + !mContentPrincipal->Subsumes(principal)) { + // Principals are disjoint -- no access. + mContentPrincipal = NullPrincipal::Create(OriginAttributes()); + } else { + MOZ_DIAGNOSTIC_ASSERT(false, "non-equal principals should be disjoint"); + mContentPrincipal = nullptr; + } + MediaDecoder::NotifyPrincipalChanged(); +} + +already_AddRefed<nsIPrincipal> HLSDecoder::GetContentPrincipal( + nsCString aMediaUrl) { + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aMediaUrl.Data()); + NS_ENSURE_SUCCESS(rv, nullptr); + RefPtr<dom::HTMLMediaElement> element = GetOwner()->GetMediaElement(); + NS_ENSURE_SUCCESS(rv, nullptr); + nsSecurityFlags securityFlags = + element->ShouldCheckAllowOrigin() + ? nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT + : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT; + if (element->GetCORSMode() == CORS_USE_CREDENTIALS) { + securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE; + } + nsCOMPtr<nsIChannel> channel; + rv = NS_NewChannel(getter_AddRefs(channel), uri, + static_cast<dom::Element*>(element), securityFlags, + nsIContentPolicy::TYPE_INTERNAL_VIDEO); + NS_ENSURE_SUCCESS(rv, nullptr); + nsCOMPtr<nsIPrincipal> principal; + nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); + if (!secMan) { + return nullptr; + } + secMan->GetChannelResultPrincipal(channel, getter_AddRefs(principal)); + return principal.forget(); +} + +} // namespace mozilla diff --git a/dom/media/hls/HLSDecoder.h b/dom/media/hls/HLSDecoder.h new file mode 100644 index 0000000000..0f65457765 --- /dev/null +++ b/dom/media/hls/HLSDecoder.h @@ -0,0 +1,79 @@ +/* -*- 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/. */ + +#ifndef HLSDecoder_h_ +#define HLSDecoder_h_ + +#include "MediaDecoder.h" +#include "mozilla/java/GeckoHLSResourceWrapperWrappers.h" + +namespace mozilla { + +class HLSResourceCallbacksSupport; + +class HLSDecoder final : public MediaDecoder { + public: + static RefPtr<HLSDecoder> Create(MediaDecoderInit& aInit); + + // Returns true if the HLS backend is pref'ed on. + static bool IsEnabled(); + + // Returns true if aContainerType is an HLS type that we think we can render + // with the a platform decoder backend. + // If provided, codecs are checked for support. + static bool IsSupportedType(const MediaContainerType& aContainerType); + + nsresult Load(nsIChannel* aChannel); + + // MediaDecoder interface. + void Play() override; + + void Pause() override; + + void AddSizeOfResources(ResourceSizes* aSizes) override; + already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override; + bool HadCrossOriginRedirects() override; + bool IsTransportSeekable() override { return true; } + void Suspend() override; + void Resume() override; + void Shutdown() override; + + // Called as data arrives on the underlying HLS player. Main thread only. + void NotifyDataArrived(); + + // Called when Exoplayer start to load media. Main thread only. + void NotifyLoad(nsCString aMediaUrl); + + private: + friend class HLSResourceCallbacksSupport; + + explicit HLSDecoder(MediaDecoderInit& aInit); + ~HLSDecoder(); + MediaDecoderStateMachineBase* CreateStateMachine( + bool aDisableExternalEngine) override; + + bool CanPlayThroughImpl() final { + // TODO: We don't know how to estimate 'canplaythrough' for this decoder. + // For now we just return true for 'autoplay' can work. + return true; + } + + void UpdateCurrentPrincipal(nsCString aMediaUrl); + already_AddRefed<nsIPrincipal> GetContentPrincipal(nsCString aMediaUrl); + + static size_t sAllocatedInstances; // Access only in the main thread. + + nsCOMPtr<nsIChannel> mChannel; + nsCOMPtr<nsIURI> mURI; + java::GeckoHLSResourceWrapper::GlobalRef mHLSResourceWrapper; + java::GeckoHLSResourceWrapper::Callbacks::GlobalRef mJavaCallbacks; + RefPtr<HLSResourceCallbacksSupport> mCallbackSupport; + nsCOMPtr<nsIPrincipal> mContentPrincipal; +}; + +} // namespace mozilla + +#endif /* HLSDecoder_h_ */ diff --git a/dom/media/hls/HLSDemuxer.cpp b/dom/media/hls/HLSDemuxer.cpp new file mode 100644 index 0000000000..8c0b533c8e --- /dev/null +++ b/dom/media/hls/HLSDemuxer.cpp @@ -0,0 +1,628 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "HLSDemuxer.h" + +#include <algorithm> +#include <limits> +#include <stdint.h> + +#include "HLSUtils.h" +#include "MediaCodec.h" +#include "mozilla/java/GeckoAudioInfoWrappers.h" +#include "mozilla/java/GeckoHLSDemuxerWrapperNatives.h" +#include "mozilla/java/GeckoVideoInfoWrappers.h" +#include "mozilla/Unused.h" +#include "nsPrintfCString.h" + +namespace mozilla { + +static Atomic<uint32_t> sStreamSourceID(0u); + +typedef TrackInfo::TrackType TrackType; +using media::TimeInterval; +using media::TimeIntervals; +using media::TimeUnit; + +static VideoRotation getVideoInfoRotation(int aRotation) { + switch (aRotation) { + case 0: + return VideoRotation::kDegree_0; + case 90: + return VideoRotation::kDegree_90; + case 180: + return VideoRotation::kDegree_180; + case 270: + return VideoRotation::kDegree_270; + default: + return VideoRotation::kDegree_0; + } +} + +static mozilla::StereoMode getStereoMode(int aMode) { + switch (aMode) { + case 0: + return mozilla::StereoMode::MONO; + case 1: + return mozilla::StereoMode::TOP_BOTTOM; + case 2: + return mozilla::StereoMode::LEFT_RIGHT; + default: + return mozilla::StereoMode::MONO; + } +} + +// HLSDemuxerCallbacksSupport is a native implemented callback class for +// Callbacks in GeckoHLSDemuxerWrapper.java. +// The callback functions will be invoked by JAVA-side thread. +// Should dispatch the task to the demuxer's task queue. +// We ensure the callback will never be invoked after +// HLSDemuxerCallbacksSupport::DisposeNative has been called in ~HLSDemuxer. +class HLSDemuxer::HLSDemuxerCallbacksSupport + : public java::GeckoHLSDemuxerWrapper::Callbacks::Natives< + HLSDemuxerCallbacksSupport> { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HLSDemuxerCallbacksSupport) + public: + typedef java::GeckoHLSDemuxerWrapper::Callbacks::Natives< + HLSDemuxerCallbacksSupport> + NativeCallbacks; + using NativeCallbacks::AttachNative; + using NativeCallbacks::DisposeNative; + + explicit HLSDemuxerCallbacksSupport(HLSDemuxer* aDemuxer) + : mMutex("HLSDemuxerCallbacksSupport"), mDemuxer(aDemuxer) { + MOZ_ASSERT(mDemuxer); + } + + void OnInitialized(bool aHasAudio, bool aHasVideo) { + HLS_DEBUG("HLSDemuxerCallbacksSupport", "OnInitialized"); + MutexAutoLock lock(mMutex); + if (!mDemuxer) { + return; + } + RefPtr<HLSDemuxerCallbacksSupport> self = this; + nsresult rv = mDemuxer->GetTaskQueue()->Dispatch(NS_NewRunnableFunction( + "HLSDemuxer::HLSDemuxerCallbacksSupport::OnInitialized", [=]() { + MutexAutoLock lock(self->mMutex); + if (self->mDemuxer) { + self->mDemuxer->OnInitialized(aHasAudio, aHasVideo); + } + })); + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + Unused << rv; + } + + void OnError(int aErrorCode) { + HLS_DEBUG("HLSDemuxerCallbacksSupport", "Got error(%d) from java side", + aErrorCode); + MutexAutoLock lock(mMutex); + if (!mDemuxer) { + return; + } + RefPtr<HLSDemuxerCallbacksSupport> self = this; + nsresult rv = mDemuxer->GetTaskQueue()->Dispatch(NS_NewRunnableFunction( + "HLSDemuxer::HLSDemuxerCallbacksSupport::OnError", [=]() { + MutexAutoLock lock(self->mMutex); + if (self->mDemuxer) { + self->mDemuxer->OnError(aErrorCode); + } + })); + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + Unused << rv; + } + + void Detach() { + MutexAutoLock lock(mMutex); + mDemuxer = nullptr; + } + + Mutex mMutex MOZ_UNANNOTATED; + + private: + ~HLSDemuxerCallbacksSupport() {} + HLSDemuxer* mDemuxer; +}; + +HLSDemuxer::HLSDemuxer(int aPlayerId) + : mTaskQueue(TaskQueue::Create( + GetMediaThreadPool(MediaThreadType::SUPERVISOR), "HLSDemuxer", + /* aSupportsTailDispatch = */ false)) { + MOZ_ASSERT(NS_IsMainThread()); + HLSDemuxerCallbacksSupport::Init(); + mJavaCallbacks = java::GeckoHLSDemuxerWrapper::Callbacks::New(); + MOZ_ASSERT(mJavaCallbacks); + + mCallbackSupport = new HLSDemuxerCallbacksSupport(this); + HLSDemuxerCallbacksSupport::AttachNative(mJavaCallbacks, mCallbackSupport); + + mHLSDemuxerWrapper = + java::GeckoHLSDemuxerWrapper::Create(aPlayerId, mJavaCallbacks); + MOZ_ASSERT(mHLSDemuxerWrapper); +} + +void HLSDemuxer::OnInitialized(bool aHasAudio, bool aHasVideo) { + MOZ_ASSERT(OnTaskQueue()); + + if (aHasAudio) { + mAudioDemuxer = new HLSTrackDemuxer(this, TrackInfo::TrackType::kAudioTrack, + MakeUnique<AudioInfo>()); + } + if (aHasVideo) { + mVideoDemuxer = new HLSTrackDemuxer(this, TrackInfo::TrackType::kVideoTrack, + MakeUnique<VideoInfo>()); + } + + mInitPromise.ResolveIfExists(NS_OK, __func__); +} + +void HLSDemuxer::OnError(int aErrorCode) { + MOZ_ASSERT(OnTaskQueue()); + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); +} + +RefPtr<HLSDemuxer::InitPromise> HLSDemuxer::Init() { + RefPtr<HLSDemuxer> self = this; + return InvokeAsync(GetTaskQueue(), __func__, [self]() { + RefPtr<InitPromise> p = self->mInitPromise.Ensure(__func__); + return p; + }); +} + +void HLSDemuxer::NotifyDataArrived() { + HLS_DEBUG("HLSDemuxer", "NotifyDataArrived"); +} + +uint32_t HLSDemuxer::GetNumberTracks(TrackType aType) const { + switch (aType) { + case TrackType::kAudioTrack: + return mHLSDemuxerWrapper->GetNumberOfTracks(TrackType::kAudioTrack); + case TrackType::kVideoTrack: + return mHLSDemuxerWrapper->GetNumberOfTracks(TrackType::kVideoTrack); + default: + return 0; + } +} + +already_AddRefed<MediaTrackDemuxer> HLSDemuxer::GetTrackDemuxer( + TrackType aType, uint32_t aTrackNumber) { + RefPtr<HLSTrackDemuxer> e = nullptr; + if (aType == TrackInfo::TrackType::kAudioTrack) { + e = mAudioDemuxer; + } else { + e = mVideoDemuxer; + } + return e.forget(); +} + +bool HLSDemuxer::IsSeekable() const { + return !mHLSDemuxerWrapper->IsLiveStream(); +} + +UniquePtr<EncryptionInfo> HLSDemuxer::GetCrypto() { + // TODO: Currently, our HLS implementation doesn't support encrypted content. + // Return null at this stage. + return nullptr; +} + +TimeUnit HLSDemuxer::GetNextKeyFrameTime() { + MOZ_ASSERT(mHLSDemuxerWrapper); + return TimeUnit::FromMicroseconds(mHLSDemuxerWrapper->GetNextKeyFrameTime()); +} + +bool HLSDemuxer::OnTaskQueue() const { return mTaskQueue->IsCurrentThreadIn(); } + +HLSDemuxer::~HLSDemuxer() { + HLS_DEBUG("HLSDemuxer", "~HLSDemuxer()"); + mCallbackSupport->Detach(); + if (mHLSDemuxerWrapper) { + mHLSDemuxerWrapper->Destroy(); + mHLSDemuxerWrapper = nullptr; + } + if (mJavaCallbacks) { + HLSDemuxerCallbacksSupport::DisposeNative(mJavaCallbacks); + mJavaCallbacks = nullptr; + } + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); +} + +HLSTrackDemuxer::HLSTrackDemuxer(HLSDemuxer* aParent, + TrackInfo::TrackType aType, + UniquePtr<TrackInfo> aTrackInfo) + : mParent(aParent), + mType(aType), + mMutex("HLSTrackDemuxer"), + mTrackInfo(std::move(aTrackInfo)) { + // Only support audio and video track currently. + MOZ_ASSERT(mType == TrackInfo::kVideoTrack || + mType == TrackInfo::kAudioTrack); + UpdateMediaInfo(0); +} + +UniquePtr<TrackInfo> HLSTrackDemuxer::GetInfo() const { + MutexAutoLock lock(mMutex); + return mTrackInfo->Clone(); +} + +RefPtr<HLSTrackDemuxer::SeekPromise> HLSTrackDemuxer::Seek( + const TimeUnit& aTime) { + MOZ_ASSERT(mParent, "Called after BreackCycle()"); + return InvokeAsync<TimeUnit&&>(mParent->GetTaskQueue(), this, __func__, + &HLSTrackDemuxer::DoSeek, aTime); +} + +RefPtr<HLSTrackDemuxer::SeekPromise> HLSTrackDemuxer::DoSeek( + const TimeUnit& aTime) { + MOZ_ASSERT(mParent, "Called after BreackCycle()"); + MOZ_ASSERT(mParent->OnTaskQueue()); + mQueuedSample = nullptr; + int64_t seekTimeUs = aTime.ToMicroseconds(); + bool result = mParent->mHLSDemuxerWrapper->Seek(seekTimeUs); + if (!result) { + return SeekPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, + __func__); + } + TimeUnit seekTime = TimeUnit::FromMicroseconds(seekTimeUs); + return SeekPromise::CreateAndResolve(seekTime, __func__); +} + +RefPtr<HLSTrackDemuxer::SamplesPromise> HLSTrackDemuxer::GetSamples( + int32_t aNumSamples) { + MOZ_ASSERT(mParent, "Called after BreackCycle()"); + return InvokeAsync(mParent->GetTaskQueue(), this, __func__, + &HLSTrackDemuxer::DoGetSamples, aNumSamples); +} + +RefPtr<HLSTrackDemuxer::SamplesPromise> HLSTrackDemuxer::DoGetSamples( + int32_t aNumSamples) { + MOZ_ASSERT(mParent, "Called after BreackCycle()"); + MOZ_ASSERT(mParent->OnTaskQueue()); + if (!aNumSamples) { + return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, + __func__); + } + RefPtr<SamplesHolder> samples = new SamplesHolder; + if (mQueuedSample) { + if (mQueuedSample->mEOS) { + return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, + __func__); + } + MOZ_ASSERT(mQueuedSample->mKeyframe, "mQueuedSample must be a keyframe"); + samples->AppendSample(mQueuedSample); + mQueuedSample = nullptr; + aNumSamples--; + } + if (aNumSamples == 0) { + // Return the queued sample. + return SamplesPromise::CreateAndResolve(samples, __func__); + } + mozilla::jni::ObjectArray::LocalRef demuxedSamples = + (mType == TrackInfo::kAudioTrack) + ? mParent->mHLSDemuxerWrapper->GetSamples(TrackInfo::kAudioTrack, + aNumSamples) + : mParent->mHLSDemuxerWrapper->GetSamples(TrackInfo::kVideoTrack, + aNumSamples); + nsTArray<jni::Object::LocalRef> sampleObjectArray( + demuxedSamples->GetElements()); + + if (sampleObjectArray.IsEmpty()) { + return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, + __func__); + } + + for (auto&& demuxedSample : sampleObjectArray) { + java::GeckoHLSSample::LocalRef sample(std::move(demuxedSample)); + if (sample->IsEOS()) { + HLS_DEBUG("HLSTrackDemuxer", "Met BUFFER_FLAG_END_OF_STREAM."); + if (samples->GetSamples().IsEmpty()) { + return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, + __func__); + } + mQueuedSample = new MediaRawData(); + mQueuedSample->mEOS = true; + break; + } + RefPtr<MediaRawData> mrd = ConvertToMediaRawData(sample); + if (!mrd) { + return SamplesPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__); + } + if (!mrd->HasValidTime()) { + return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, + __func__); + } + samples->AppendSample(mrd); + } + if (mType == TrackInfo::kVideoTrack && + (mNextKeyframeTime.isNothing() || + samples->GetSamples().LastElement()->mTime >= + mNextKeyframeTime.value())) { + // Only need to find NextKeyFrame for Video + UpdateNextKeyFrameTime(); + } + + return SamplesPromise::CreateAndResolve(samples, __func__); +} + +void HLSTrackDemuxer::UpdateMediaInfo(int index) { + MOZ_ASSERT(mParent->OnTaskQueue()); + MOZ_ASSERT(mParent->mHLSDemuxerWrapper); + MutexAutoLock lock(mMutex); + jni::Object::LocalRef infoObj = nullptr; + if (mType == TrackType::kAudioTrack) { + infoObj = mParent->mHLSDemuxerWrapper->GetAudioInfo(index); + if (!infoObj) { + NS_WARNING("Failed to get audio info from Java wrapper"); + return; + } + auto* audioInfo = mTrackInfo->GetAsAudioInfo(); + MOZ_ASSERT(audioInfo != nullptr); + HLS_DEBUG("HLSTrackDemuxer", "Update audio info (%d)", index); + java::GeckoAudioInfo::LocalRef audioInfoObj(std::move(infoObj)); + audioInfo->mRate = audioInfoObj->Rate(); + audioInfo->mChannels = audioInfoObj->Channels(); + audioInfo->mProfile = audioInfoObj->Profile(); + audioInfo->mBitDepth = audioInfoObj->BitDepth(); + audioInfo->mMimeType = + NS_ConvertUTF16toUTF8(audioInfoObj->MimeType()->ToString()); + audioInfo->mDuration = TimeUnit::FromMicroseconds(audioInfoObj->Duration()); + jni::ByteArray::LocalRef csdBytes = audioInfoObj->CodecSpecificData(); + if (csdBytes) { + auto&& csd = csdBytes->GetElements(); + AudioCodecSpecificBinaryBlob blob; + blob.mBinaryBlob->AppendElements(reinterpret_cast<uint8_t*>(&csd[0]), + csd.Length()); + audioInfo->mCodecSpecificConfig = + AudioCodecSpecificVariant{std::move(blob)}; + } + } else { + infoObj = mParent->mHLSDemuxerWrapper->GetVideoInfo(index); + if (!infoObj) { + NS_WARNING("Failed to get video info from Java wrapper"); + return; + } + auto* videoInfo = mTrackInfo->GetAsVideoInfo(); + MOZ_ASSERT(videoInfo != nullptr); + java::GeckoVideoInfo::LocalRef videoInfoObj(std::move(infoObj)); + videoInfo->mStereoMode = getStereoMode(videoInfoObj->StereoMode()); + videoInfo->mRotation = getVideoInfoRotation(videoInfoObj->Rotation()); + videoInfo->mImage.width = videoInfoObj->DisplayWidth(); + videoInfo->mImage.height = videoInfoObj->DisplayHeight(); + videoInfo->mDisplay.width = videoInfoObj->PictureWidth(); + videoInfo->mDisplay.height = videoInfoObj->PictureHeight(); + videoInfo->mMimeType = + NS_ConvertUTF16toUTF8(videoInfoObj->MimeType()->ToString()); + videoInfo->mDuration = TimeUnit::FromMicroseconds(videoInfoObj->Duration()); + HLS_DEBUG("HLSTrackDemuxer", "Update video info (%d) / I(%dx%d) / D(%dx%d)", + index, videoInfo->mImage.width, videoInfo->mImage.height, + videoInfo->mDisplay.width, videoInfo->mDisplay.height); + } +} + +CryptoSample HLSTrackDemuxer::ExtractCryptoSample( + size_t aSampleSize, + java::sdk::MediaCodec::CryptoInfo::LocalRef aCryptoInfo) { + if (!aCryptoInfo) { + return CryptoSample{}; + } + // Extract Crypto information + CryptoSample crypto; + char const* msg = ""; + do { + HLS_DEBUG("HLSTrackDemuxer", "Sample has Crypto Info"); + + int32_t mode = 0; + if (NS_FAILED(aCryptoInfo->Mode(&mode))) { + msg = "Error when extracting encryption mode."; + break; + } + // We currently only handle ctr mode. + if (mode != java::sdk::MediaCodec::CRYPTO_MODE_AES_CTR) { + msg = "Error: unexpected encryption mode."; + break; + } + + crypto.mCryptoScheme = CryptoScheme::Cenc; + + mozilla::jni::ByteArray::LocalRef ivData; + if (NS_FAILED(aCryptoInfo->Iv(&ivData))) { + msg = "Error when extracting encryption IV."; + break; + } + // Data in mIV is uint8_t and jbyte is signed char + auto&& ivArr = ivData->GetElements(); + crypto.mIV.AppendElements(reinterpret_cast<uint8_t*>(&ivArr[0]), + ivArr.Length()); + crypto.mIVSize = ivArr.Length(); + mozilla::jni::ByteArray::LocalRef keyData; + if (NS_FAILED(aCryptoInfo->Key(&keyData))) { + msg = "Error when extracting encryption key."; + break; + } + auto&& keyArr = keyData->GetElements(); + // Data in mKeyId is uint8_t and jbyte is signed char + crypto.mKeyId.AppendElements(reinterpret_cast<uint8_t*>(&keyArr[0]), + keyArr.Length()); + + mozilla::jni::IntArray::LocalRef clearData; + if (NS_FAILED(aCryptoInfo->NumBytesOfClearData(&clearData))) { + msg = "Error when extracting clear data."; + break; + } + auto&& clearArr = clearData->GetElements(); + // Data in mPlainSizes is uint32_t, NumBytesOfClearData is int32_t + crypto.mPlainSizes.AppendElements(reinterpret_cast<uint32_t*>(&clearArr[0]), + clearArr.Length()); + + mozilla::jni::IntArray::LocalRef encryptedData; + if (NS_FAILED(aCryptoInfo->NumBytesOfEncryptedData(&encryptedData))) { + msg = "Error when extracting encrypted data."; + break; + } + auto&& encryptedArr = encryptedData->GetElements(); + // Data in mEncryptedSizes is uint32_t, NumBytesOfEncryptedData is int32_t + crypto.mEncryptedSizes.AppendElements( + reinterpret_cast<uint32_t*>(&encryptedArr[0]), encryptedArr.Length()); + int subSamplesNum = 0; + if (NS_FAILED(aCryptoInfo->NumSubSamples(&subSamplesNum))) { + msg = "Error when extracting subsamples."; + break; + } + crypto.mPlainSizes[0] -= (aSampleSize - subSamplesNum); + + return crypto; + } while (false); + + HLS_DEBUG("HLSTrackDemuxer", "%s", msg); + return CryptoSample{}; +} + +RefPtr<MediaRawData> HLSTrackDemuxer::ConvertToMediaRawData( + java::GeckoHLSSample::LocalRef aSample) { + java::sdk::MediaCodec::BufferInfo::LocalRef info = aSample->Info(); + // Currently extract PTS, Size and Data without Crypto information. + // Transform java Sample into MediaRawData + RefPtr<MediaRawData> mrd = new MediaRawData(); + int64_t presentationTimeUs = 0; + bool ok = NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs)); + mrd->mTime = TimeUnit::FromMicroseconds(presentationTimeUs); + mrd->mTimecode = TimeUnit::FromMicroseconds(presentationTimeUs); + mrd->mKeyframe = aSample->IsKeyFrame(); + mrd->mDuration = (mType == TrackInfo::kVideoTrack) + ? TimeUnit::FromMicroseconds(aSample->Duration()) + : TimeUnit::Zero(); + + int32_t size = 0; + ok &= NS_SUCCEEDED(info->Size(&size)); + if (!ok) { + HLS_DEBUG("HLSTrackDemuxer", + "Error occurred during extraction from Sample java object."); + return nullptr; + } + + // Update A/V stream souce ID & Audio/VideoInfo for MFR. + auto sampleFormatIndex = aSample->FormatIndex(); + if (mLastFormatIndex != sampleFormatIndex) { + mLastFormatIndex = sampleFormatIndex; + UpdateMediaInfo(mLastFormatIndex); + MutexAutoLock lock(mMutex); + mrd->mTrackInfo = new TrackInfoSharedPtr(*mTrackInfo, ++sStreamSourceID); + } + + // Write payload into MediaRawData + UniquePtr<MediaRawDataWriter> writer(mrd->CreateWriter()); + if (!writer->SetSize(size)) { + HLS_DEBUG("HLSTrackDemuxer", "Exit failed to allocate media buffer"); + return nullptr; + } + jni::ByteBuffer::LocalRef dest = + jni::ByteBuffer::New(writer->Data(), writer->Size()); + aSample->WriteToByteBuffer(dest); + + writer->mCrypto = ExtractCryptoSample(writer->Size(), aSample->CryptoInfo()); + return mrd; +} + +void HLSTrackDemuxer::Reset() { + MOZ_ASSERT(mParent, "Called after BreackCycle()"); + mQueuedSample = nullptr; +} + +void HLSTrackDemuxer::UpdateNextKeyFrameTime() { + MOZ_ASSERT(mParent, "Called after BreackCycle()"); + TimeUnit nextKeyFrameTime = mParent->GetNextKeyFrameTime(); + if (nextKeyFrameTime != mNextKeyframeTime.refOr(TimeUnit::FromInfinity())) { + HLS_DEBUG("HLSTrackDemuxer", "Update mNextKeyframeTime to %" PRId64, + nextKeyFrameTime.ToMicroseconds()); + mNextKeyframeTime = Some(nextKeyFrameTime); + } +} + +nsresult HLSTrackDemuxer::GetNextRandomAccessPoint(TimeUnit* aTime) { + if (mNextKeyframeTime.isNothing()) { + // There's no next key frame. + *aTime = TimeUnit::FromInfinity(); + } else { + *aTime = mNextKeyframeTime.value(); + } + return NS_OK; +} + +RefPtr<HLSTrackDemuxer::SkipAccessPointPromise> +HLSTrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold) { + return InvokeAsync(mParent->GetTaskQueue(), this, __func__, + &HLSTrackDemuxer::DoSkipToNextRandomAccessPoint, + aTimeThreshold); +} + +RefPtr<HLSTrackDemuxer::SkipAccessPointPromise> +HLSTrackDemuxer::DoSkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold) { + MOZ_ASSERT(mParent, "Called after BreackCycle()"); + MOZ_ASSERT(mParent->OnTaskQueue()); + mQueuedSample = nullptr; + uint32_t parsed = 0; + bool found = false; + MediaResult result = NS_ERROR_DOM_MEDIA_END_OF_STREAM; + do { + mozilla::jni::ObjectArray::LocalRef demuxedSamples = + mParent->mHLSDemuxerWrapper->GetSamples(mType, 1); + nsTArray<jni::Object::LocalRef> sampleObjectArray( + demuxedSamples->GetElements()); + if (sampleObjectArray.IsEmpty()) { + result = NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA; + break; + } + parsed++; + java::GeckoHLSSample::LocalRef sample(std::move(sampleObjectArray[0])); + if (sample->IsEOS()) { + result = NS_ERROR_DOM_MEDIA_END_OF_STREAM; + break; + } + if (sample->IsKeyFrame()) { + java::sdk::MediaCodec::BufferInfo::LocalRef info = sample->Info(); + int64_t presentationTimeUs = 0; + if (NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs)) && + TimeUnit::FromMicroseconds(presentationTimeUs) >= aTimeThreshold) { + RefPtr<MediaRawData> rawData = ConvertToMediaRawData(sample); + if (!rawData) { + result = NS_ERROR_OUT_OF_MEMORY; + break; + } + if (!rawData->HasValidTime()) { + result = NS_ERROR_DOM_MEDIA_DEMUXER_ERR; + break; + } + found = true; + mQueuedSample = rawData; + break; + } + } + } while (true); + + if (!found) { + return SkipAccessPointPromise::CreateAndReject( + SkipFailureHolder(result, parsed), __func__); + } + return SkipAccessPointPromise::CreateAndResolve(parsed, __func__); +} + +TimeIntervals HLSTrackDemuxer::GetBuffered() { + MOZ_ASSERT(mParent, "Called after BreackCycle()"); + int64_t bufferedTime = mParent->mHLSDemuxerWrapper->GetBuffered(); // us + return TimeIntervals( + TimeInterval(TimeUnit(), TimeUnit::FromMicroseconds(bufferedTime))); +} + +void HLSTrackDemuxer::BreakCycles() { + RefPtr<HLSTrackDemuxer> self = this; + nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction( + "HLSTrackDemuxer::BreakCycles", [self]() { self->mParent = nullptr; }); + nsresult rv = mParent->GetTaskQueue()->Dispatch(task.forget()); + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + Unused << rv; +} + +HLSTrackDemuxer::~HLSTrackDemuxer() {} + +} // namespace mozilla diff --git a/dom/media/hls/HLSDemuxer.h b/dom/media/hls/HLSDemuxer.h new file mode 100644 index 0000000000..679faa759f --- /dev/null +++ b/dom/media/hls/HLSDemuxer.h @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#if !defined(HLSDemuxer_h_) +# define HLSDemuxer_h_ + +# include "MediaCodec.h" +# include "MediaDataDemuxer.h" +# include "MediaDecoder.h" +# include "mozilla/Atomics.h" +# include "mozilla/java/GeckoHLSDemuxerWrapperWrappers.h" +# include "mozilla/java/GeckoHLSSampleWrappers.h" +# include "mozilla/Maybe.h" +# include "mozilla/Mutex.h" +# include "mozilla/TaskQueue.h" + +# include "VideoUtils.h" + +namespace mozilla { + +class AbstractThread; +class MediaResult; +class HLSTrackDemuxer; + +DDLoggedTypeDeclNameAndBase(HLSDemuxer, MediaDataDemuxer); +DDLoggedTypeNameAndBase(HLSTrackDemuxer, MediaTrackDemuxer); + +class HLSDemuxer final : public MediaDataDemuxer, + public DecoderDoctorLifeLogger<HLSDemuxer> { + class HLSDemuxerCallbacksSupport; + + public: + explicit HLSDemuxer(int aPlayerId); + + RefPtr<InitPromise> Init() override; + + uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override; + + already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer( + TrackInfo::TrackType aType, uint32_t aTrackNumber) override; + + bool IsSeekable() const override; + + UniquePtr<EncryptionInfo> GetCrypto() override; + + bool ShouldComputeStartTime() const override { return true; } + + void NotifyDataArrived() override; + + TaskQueue* GetTaskQueue() const { return mTaskQueue; } + void OnInitialized(bool aHasAudio, bool aHasVideo); + void OnError(int aErrorCode); + + private: + media::TimeUnit GetNextKeyFrameTime(); + + bool OnTaskQueue() const; + ~HLSDemuxer(); + friend class HLSTrackDemuxer; + + const RefPtr<TaskQueue> mTaskQueue; + RefPtr<HLSTrackDemuxer> mAudioDemuxer; + RefPtr<HLSTrackDemuxer> mVideoDemuxer; + + MozPromiseHolder<InitPromise> mInitPromise; + RefPtr<HLSDemuxerCallbacksSupport> mCallbackSupport; + + java::GeckoHLSDemuxerWrapper::Callbacks::GlobalRef mJavaCallbacks; + java::GeckoHLSDemuxerWrapper::GlobalRef mHLSDemuxerWrapper; +}; + +class HLSTrackDemuxer : public MediaTrackDemuxer, + public DecoderDoctorLifeLogger<HLSTrackDemuxer> { + public: + HLSTrackDemuxer(HLSDemuxer* aParent, TrackInfo::TrackType aType, + UniquePtr<TrackInfo> aTrackInfo); + ~HLSTrackDemuxer(); + UniquePtr<TrackInfo> GetInfo() const override; + + RefPtr<SeekPromise> Seek(const media::TimeUnit& aTime) override; + + RefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override; + + void UpdateMediaInfo(int index); + + void Reset() override; + + nsresult GetNextRandomAccessPoint(media::TimeUnit* aTime) override; + + RefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint( + const media::TimeUnit& aTimeThreshold) override; + + media::TimeIntervals GetBuffered() override; + + void BreakCycles() override; + + bool GetSamplesMayBlock() const override { return false; } + + bool IsTrackValid() const { + MutexAutoLock lock(mMutex); + return mTrackInfo->IsValid(); + } + + private: + // Update the timestamp of the next keyframe if there's one. + void UpdateNextKeyFrameTime(); + + // Runs on HLSDemuxer's task queue. + RefPtr<SeekPromise> DoSeek(const media::TimeUnit& aTime); + RefPtr<SamplesPromise> DoGetSamples(int32_t aNumSamples); + RefPtr<SkipAccessPointPromise> DoSkipToNextRandomAccessPoint( + const media::TimeUnit& aTimeThreshold); + + CryptoSample ExtractCryptoSample( + size_t aSampleSize, + java::sdk::MediaCodec::CryptoInfo::LocalRef aCryptoInfo); + RefPtr<MediaRawData> ConvertToMediaRawData( + java::GeckoHLSSample::LocalRef aSample); + + RefPtr<HLSDemuxer> mParent; + TrackInfo::TrackType mType; + Maybe<media::TimeUnit> mNextKeyframeTime; + int32_t mLastFormatIndex = -1; + // Queued samples extracted by the demuxer, but not yet returned. + RefPtr<MediaRawData> mQueuedSample; + + // Mutex to protect members below across multiple threads. + mutable Mutex mMutex MOZ_UNANNOTATED; + UniquePtr<TrackInfo> mTrackInfo; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/hls/HLSUtils.cpp b/dom/media/hls/HLSUtils.cpp new file mode 100644 index 0000000000..872de1ced6 --- /dev/null +++ b/dom/media/hls/HLSUtils.cpp @@ -0,0 +1,12 @@ +/* -*- 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 "HLSUtils.h" + +mozilla::LogModule* GetHLSLog() { + static mozilla::LazyLogModule sLogModule("HLS"); + return sLogModule; +} diff --git a/dom/media/hls/HLSUtils.h b/dom/media/hls/HLSUtils.h new file mode 100644 index 0000000000..d5505fbd1d --- /dev/null +++ b/dom/media/hls/HLSUtils.h @@ -0,0 +1,21 @@ +/* -*- 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/. */ + +#ifndef HLSUtils_h_ +#define HLSUtils_h_ + +#include "mozilla/Logging.h" +// Logger +mozilla::LogModule* GetHLSLog(); + +#define HLS_DEBUG(TAG, format, ...) \ + MOZ_LOG(GetHLSLog(), mozilla::LogLevel::Debug, \ + (TAG "(%p)::%s: " format, this, __func__, ##__VA_ARGS__)) +#define HLS_DEBUG_NON_MEMBER(TAG, format, ...) \ + MOZ_LOG(GetHLSLog(), mozilla::LogLevel::Debug, \ + (TAG " %s: " format, __func__, ##__VA_ARGS__)) + +#endif // HLSUtils_h_ diff --git a/dom/media/hls/moz.build b/dom/media/hls/moz.build new file mode 100644 index 0000000000..8a6c176974 --- /dev/null +++ b/dom/media/hls/moz.build @@ -0,0 +1,24 @@ +# vim: set filetype=python: +# 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/. + + +EXPORTS += [ + "HLSDecoder.h", + "HLSDemuxer.h", + "HLSUtils.h", +] + +UNIFIED_SOURCES += [ + "HLSDecoder.cpp", + "HLSDemuxer.cpp", + "HLSUtils.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") |