Bug 1350246 - [Part3] Implement a HLSDemuxer and its trackdemuxers for AV. draft
authorJames Cheng <jacheng@mozilla.com>
Fri, 05 May 2017 16:44:52 +0800
changeset 592555 2e4304f84cdf4257cac60921b0a3b679b9e832b7
parent 592554 c08f418676e12be06be7736594d8cb5cf6186df3
child 592556 3b8ce8f1996ac4195c400ea02d671fa482b39180
push id63431
push userbmo:jacheng@mozilla.com
push dateMon, 12 Jun 2017 12:38:14 +0000
bugs1350246
milestone55.0a1
Bug 1350246 - [Part3] Implement a HLSDemuxer and its trackdemuxers for AV. MozReview-Commit-ID: EndIUfiSEMu
dom/media/hls/HLSDemuxer.cpp
dom/media/hls/HLSDemuxer.h
new file mode 100644
--- /dev/null
+++ b/dom/media/hls/HLSDemuxer.cpp
@@ -0,0 +1,655 @@
+/* -*- 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 "HLSResource.h"
+#include "HLSUtils.h"
+#include "MediaCodec.h"
+#include "mozilla/Unused.h"
+#include "nsPrintfCString.h"
+
+using namespace mozilla::java;
+
+namespace mozilla {
+
+static Atomic<uint32_t> sStreamSourceID(0u);
+
+typedef TrackInfo::TrackType TrackType;
+using media::TimeUnit;
+using media::TimeIntervals;
+using media::TimeInterval;
+
+static
+VideoInfo::Rotation getVideoInfoRotation(int aRotation)
+{
+  switch (aRotation) {
+    case 0:
+      return VideoInfo::Rotation::kDegree_0;
+    case 90:
+      return VideoInfo::Rotation::kDegree_90;
+    case 180:
+      return VideoInfo::Rotation::kDegree_180;
+    case 270:
+      return VideoInfo::Rotation::kDegree_270;
+    default:
+      return VideoInfo::Rotation::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
+// HlsDemuxerCallbacks 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 GeckoHlsDemuxerWrapper::HlsDemuxerCallbacks::Natives<HlsDemuxerCallbacksSupport>
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HlsDemuxerCallbacksSupport)
+public:
+  typedef GeckoHlsDemuxerWrapper::HlsDemuxerCallbacks::Natives<HlsDemuxerCallbacksSupport> NativeCallbacks;
+  using NativeCallbacks::DisposeNative;
+  using NativeCallbacks::AttachNative;
+
+  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;
+    mDemuxer->GetTaskQueue()->Dispatch(NS_NewRunnableFunction(
+     [=] () {
+       MutexAutoLock lock(self->mMutex);
+       if (self->mDemuxer) {
+         self->mDemuxer->OnInitialized(aHasAudio, aHasVideo);
+       }
+     }));
+  }
+
+  // TODO: Handle the unexpected error signal from the java implementation
+  // in bug 1368904.
+  void OnError(int aErrorCode)
+  {
+    HLS_DEBUG("HlsDemuxerCallbacksSupport",
+              "Got error(%d) from java side",
+              aErrorCode);
+  }
+  void Detach()
+  {
+    MutexAutoLock lock(mMutex);
+    mDemuxer = nullptr;
+  }
+
+  Mutex mMutex;
+private:
+  ~HlsDemuxerCallbacksSupport() { }
+  HLSDemuxer* mDemuxer;
+
+};
+
+HLSDemuxer::HLSDemuxer(MediaResource* aResource)
+  : mResource(aResource)
+  , mTaskQueue(new AutoTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
+                                 /* aSupportsTailDispatch = */ false))
+  , mMutex("HLSDemuxer")
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aResource);
+  HlsDemuxerCallbacksSupport::Init();
+  mJavaCallbacks = GeckoHlsDemuxerWrapper::HlsDemuxerCallbacks::New();
+  MOZ_ASSERT(mJavaCallbacks);
+
+  mCallbackSupport = new HlsDemuxerCallbacksSupport(this);
+  HlsDemuxerCallbacksSupport::AttachNative(mJavaCallbacks,
+                                           mCallbackSupport);
+
+  auto resourceWrapper = static_cast<HLSResource*>(aResource)->GetResourceWrapper();
+  mHlsDemuxerWrapper = GeckoHlsDemuxerWrapper::Create(resourceWrapper->GetPlayer(), mJavaCallbacks);
+  MOZ_ASSERT(mHlsDemuxerWrapper);
+}
+
+void
+HLSDemuxer::OnInitialized(bool aHasAudio, bool aHasVideo)
+{
+  MOZ_ASSERT(OnTaskQueue());
+
+  if (aHasAudio) {
+    UpdateAudioInfo(0);
+  }
+  if (aHasVideo) {
+    UpdateVideoInfo(0);
+  }
+
+  mInitPromise.ResolveIfExists(NS_OK, __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");
+}
+
+bool
+HLSDemuxer::HasTrackType(TrackType aType) const
+{
+  MutexAutoLock lock(mMutex);
+  HLS_DEBUG("HLSDemuxer", "HasTrackType(%d)", aType);
+  switch (aType) {
+    case TrackType::kAudioTrack:
+      return mInfo.HasAudio();
+    case TrackType::kVideoTrack:
+      return mInfo.HasVideo();
+    default:
+      return false;
+  }
+}
+
+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 = new HLSTrackDemuxer(this, aType);
+  mDemuxers.AppendElement(e);
+  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;
+}
+
+TrackInfo*
+HLSDemuxer::GetTrackInfo(TrackType aTrack)
+{
+  MutexAutoLock lock(mMutex);
+  switch (aTrack) {
+    case TrackType::kAudioTrack: {
+      return &mInfo.mAudio;
+    }
+    case TrackType::kVideoTrack: {
+      return &mInfo.mVideo;
+    }
+    default:
+      return nullptr;
+  }
+}
+
+TimeUnit
+HLSDemuxer::GetNextKeyFrameTime()
+{
+  MOZ_ASSERT(mHlsDemuxerWrapper);
+  return TimeUnit::FromMicroseconds(mHlsDemuxerWrapper->GetNextKeyFrameTime());
+}
+
+void
+HLSDemuxer::UpdateAudioInfo(int index)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  MOZ_ASSERT(mHlsDemuxerWrapper);
+  HLS_DEBUG("HLSDemuxer", "UpdateAudioInfo (%d)", index);
+  MutexAutoLock lock(mMutex);
+  jni::Object::LocalRef infoObj = mHlsDemuxerWrapper->GetAudioInfo(index);
+  if (infoObj) {
+    java::GeckoAudioInfo::LocalRef audioInfo(Move(infoObj));
+    mInfo.mAudio.mRate = audioInfo->Rate();
+    mInfo.mAudio.mChannels = audioInfo->Channels();
+    mInfo.mAudio.mProfile = audioInfo->Profile();
+    mInfo.mAudio.mBitDepth = audioInfo->BitDepth();
+    mInfo.mAudio.mMimeType = NS_ConvertUTF16toUTF8(audioInfo->MimeType()->ToString());
+    mInfo.mAudio.mDuration = TimeUnit::FromMicroseconds(audioInfo->Duration());
+    auto&& csd = audioInfo->CodecSpecificData()->GetElements();
+    mInfo.mAudio.mCodecSpecificConfig->Clear();
+    mInfo.mAudio.mCodecSpecificConfig->AppendElements(reinterpret_cast<uint8_t*>(&csd[0]),
+                                                      csd.Length());
+  }
+}
+
+void
+HLSDemuxer::UpdateVideoInfo(int index)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  MOZ_ASSERT(mHlsDemuxerWrapper);
+  MutexAutoLock lock(mMutex);
+  jni::Object::LocalRef infoObj = mHlsDemuxerWrapper->GetVideoInfo(index);
+  if (infoObj) {
+    java::GeckoVideoInfo::LocalRef videoInfo(Move(infoObj));
+    mInfo.mVideo.mStereoMode = getStereoMode(videoInfo->StereoMode());
+    mInfo.mVideo.mRotation = getVideoInfoRotation(videoInfo->Rotation());
+    mInfo.mVideo.mImage.width = videoInfo->DisplayWidth();
+    mInfo.mVideo.mImage.height = videoInfo->DisplayHeight();
+    mInfo.mVideo.mDisplay.width = videoInfo->PictureWidth();
+    mInfo.mVideo.mDisplay.height = videoInfo->PictureHeight();
+    mInfo.mVideo.mMimeType = NS_ConvertUTF16toUTF8(videoInfo->MimeType()->ToString());
+    mInfo.mVideo.mDuration = TimeUnit::FromMicroseconds(videoInfo->Duration());
+    HLS_DEBUG("HLSDemuxer", "UpdateVideoInfo (%d) / I(%dx%d) / D(%dx%d)",
+     index, mInfo.mVideo.mImage.width, mInfo.mVideo.mImage.height,
+     mInfo.mVideo.mDisplay.width, mInfo.mVideo.mDisplay.height);
+  }
+}
+
+bool
+HLSDemuxer::OnTaskQueue() const
+{
+  return mTaskQueue->IsCurrentThreadIn();
+}
+
+HLSDemuxer::~HLSDemuxer()
+{
+  HLS_DEBUG("HLSDemuxer", "~HLSDemuxer()");
+  mCallbackSupport->Detach();
+  if (mJavaCallbacks) {
+    HlsDemuxerCallbacksSupport::DisposeNative(mJavaCallbacks);
+    mJavaCallbacks = nullptr;
+  }
+  if (mHlsDemuxerWrapper) {
+    mHlsDemuxerWrapper->Destroy();
+    mHlsDemuxerWrapper = nullptr;
+  }
+  mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+}
+
+HLSTrackDemuxer::HLSTrackDemuxer(HLSDemuxer* aParent, TrackInfo::TrackType aType)
+  : mParent(aParent)
+  , mType(aType)
+{
+}
+
+UniquePtr<TrackInfo>
+HLSTrackDemuxer::GetInfo() const
+{
+  return mParent->GetTrackInfo(mType)->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->mSamples.AppendElement(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(Move(demuxedSample));
+    if (sample->IsEOS()) {
+      HLS_DEBUG("HLSTrackDemuxer", "Met BUFFER_FLAG_END_OF_STREAM.");
+      if (samples->mSamples.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__);
+    }
+    samples->mSamples.AppendElement(mrd);
+  }
+  if (mType == TrackInfo::kVideoTrack &&
+      (mNextKeyframeTime.isNothing() ||
+       samples->mSamples.LastElement()->mTime >= mNextKeyframeTime.value())) {
+    // Only need to find NextKeyFrame for Video
+    UpdateNextKeyFrameTime();
+  }
+
+  return SamplesPromise::CreateAndResolve(samples, __func__);
+}
+
+
+CryptoSample
+HLSTrackDemuxer::ExtractCryptoSample(size_t aSampleSize,
+                                     java::sdk::CryptoInfo::LocalRef aCryptoInfo)
+{
+  if (!aCryptoInfo) {
+    return CryptoSample{};
+  }
+  // Extract Crypto information
+  CryptoSample crypto;
+  char const* msg = "";
+  do {
+    HLS_DEBUG("HLSTrackDemuxer", "Sample has Crypto Info");
+    crypto.mValid = true;
+    int32_t mode = 0;
+    if (NS_FAILED(aCryptoInfo->Mode(&mode))) {
+      msg = "Error when extracting encryption mode.";
+      break;
+    }
+    crypto.mMode = mode;
+    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;
+    }
+    // Data in mPlainSizes is uint16_t, NumBytesOfClearData is int32_t
+    // , so need a for loop to copy
+    for (const auto& b : clearData->GetElements()) {
+      crypto.mPlainSizes.AppendElement(b);
+    }
+
+    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::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 streamSouceID & videoInfo for MFR.
+  if (mType == TrackInfo::kVideoTrack) {
+    auto sampleFormatIndex = aSample->FormatIndex();
+    if (mLastFormatIndex != sampleFormatIndex) {
+      mLastFormatIndex = sampleFormatIndex;
+      mParent->UpdateVideoInfo(mLastFormatIndex);
+      MutexAutoLock lock(mParent->mMutex);
+      mrd->mTrackInfo = new TrackInfoSharedPtr(mParent->mInfo.mVideo, ++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(Move(sampleObjectArray[0]));
+    if (sample->IsEOS()) {
+      result = NS_ERROR_DOM_MEDIA_END_OF_STREAM;
+      break;
+    }
+    if (sample->IsKeyFrame()) {
+      java::sdk::BufferInfo::LocalRef info = sample->Info();
+      int64_t presentationTimeUs = 0;
+      bool ok = NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs));
+      if (ok && TimeUnit::FromMicroseconds(presentationTimeUs) >= aTimeThreshold) {
+        found = true;
+        mQueuedSample = ConvertToMediaRawData(sample);
+        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([self]() {
+      self->mParent = nullptr;
+    } );
+  mParent->GetTaskQueue()->Dispatch(task.forget());
+}
+
+HLSTrackDemuxer::~HLSTrackDemuxer()
+{
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/hls/HLSDemuxer.h
@@ -0,0 +1,130 @@
+/* -*- 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 "AutoTaskQueue.h"
+#include "GeneratedJNINatives.h"
+#include "GeneratedJNIWrappers.h"
+#include "MediaCodec.h"
+#include "MediaDataDemuxer.h"
+#include "MediaDecoder.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Mutex.h"
+
+#include "VideoUtils.h"
+
+namespace mozilla {
+
+class AbstractThread;
+class MediaResult;
+class HLSTrackDemuxer;
+
+class HLSDemuxer final : public MediaDataDemuxer
+{
+  class HlsDemuxerCallbacksSupport;
+public:
+  explicit HLSDemuxer(MediaResource* aResource);
+
+  RefPtr<InitPromise> Init() override;
+
+  bool HasTrackType(TrackInfo::TrackType aType) const 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;
+
+  AutoTaskQueue* GetTaskQueue() const { return mTaskQueue; }
+  void OnInitialized(bool aHasAudio, bool aHasVideo);
+
+private:
+  media::TimeUnit GetNextKeyFrameTime();
+  void UpdateVideoInfo(int index);
+  void UpdateAudioInfo(int index);
+  bool OnTaskQueue() const;
+  TrackInfo* GetTrackInfo(TrackInfo::TrackType);
+  ~HLSDemuxer();
+  RefPtr<MediaResource> mResource;
+  friend class HLSTrackDemuxer;
+
+  const RefPtr<AutoTaskQueue> mTaskQueue;
+  nsTArray<RefPtr<HLSTrackDemuxer>> mDemuxers;
+
+  MozPromiseHolder<InitPromise> mInitPromise;
+  RefPtr<HlsDemuxerCallbacksSupport> mCallbackSupport;
+
+  // Mutex to protect members below across multiple threads.
+  mutable Mutex mMutex;
+  MediaInfo mInfo;
+
+  java::GeckoHlsDemuxerWrapper::HlsDemuxerCallbacks::GlobalRef mJavaCallbacks;
+  java::GeckoHlsDemuxerWrapper::GlobalRef mHlsDemuxerWrapper;
+};
+
+class HLSTrackDemuxer : public MediaTrackDemuxer
+{
+public:
+  HLSTrackDemuxer(HLSDemuxer* aParent,
+                  TrackInfo::TrackType aType);
+  ~HLSTrackDemuxer();
+  UniquePtr<TrackInfo> GetInfo() const override;
+
+  RefPtr<SeekPromise> Seek(const media::TimeUnit& aTime) override;
+
+  RefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override;
+
+  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;
+  }
+
+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::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;
+};
+
+} // namespace mozilla
+
+#endif