summaryrefslogtreecommitdiffstats
path: root/dom/media/hls
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/hls')
-rw-r--r--dom/media/hls/HLSDecoder.cpp310
-rw-r--r--dom/media/hls/HLSDecoder.h79
-rw-r--r--dom/media/hls/HLSDemuxer.cpp628
-rw-r--r--dom/media/hls/HLSDemuxer.h137
-rw-r--r--dom/media/hls/HLSUtils.cpp12
-rw-r--r--dom/media/hls/HLSUtils.h21
-rw-r--r--dom/media/hls/moz.build24
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")