dom/media/imagecapture/CaptureTask.cpp
author Sylvestre Ledru <sledru@mozilla.com>
Mon, 19 Nov 2018 13:25:37 +0000
changeset 446960 0ceae9db9ec0be18daa1a279511ad305723185d4
parent 440857 bb32faa290f0b8871c7c72798dc1876b01e48a31
child 447847 ed8ffdb9c33abedb857904f146b5e5995232ea37
permissions -rw-r--r--
Bug 1204606 - Reformat of dom/media r=jya # skip-blame Differential Revision: https://phabricator.services.mozilla.com/D12251

/* -*- 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 "CaptureTask.h"
#include "mozilla/dom/ImageCapture.h"
#include "mozilla/dom/ImageCaptureError.h"
#include "mozilla/dom/ImageEncoder.h"
#include "mozilla/dom/MediaStreamTrack.h"
#include "mozilla/dom/VideoStreamTrack.h"
#include "gfxUtils.h"
#include "nsThreadUtils.h"

namespace mozilla {

class CaptureTask::MediaStreamEventListener : public MediaStreamTrackListener {
 public:
  explicit MediaStreamEventListener(CaptureTask* aCaptureTask)
      : mCaptureTask(aCaptureTask){};

  // MediaStreamListener methods.
  void NotifyEnded() override {
    if (!mCaptureTask->mImageGrabbedOrTrackEnd) {
      mCaptureTask->PostTrackEndEvent();
    }
  }

 private:
  CaptureTask* mCaptureTask;
};

CaptureTask::CaptureTask(dom::ImageCapture* aImageCapture)
    : mImageCapture(aImageCapture),
      mEventListener(new MediaStreamEventListener(this)),
      mImageGrabbedOrTrackEnd(false),
      mPrincipalChanged(false) {}

nsresult CaptureTask::TaskComplete(already_AddRefed<dom::Blob> aBlob,
                                   nsresult aRv) {
  MOZ_ASSERT(NS_IsMainThread());

  DetachTrack();

  nsresult rv;
  RefPtr<dom::Blob> blob(aBlob);

  // We have to set the parent because the blob has been generated with a valid
  // one.
  if (blob) {
    blob = dom::Blob::Create(mImageCapture->GetParentObject(), blob->Impl());
  }

  if (mPrincipalChanged) {
    aRv = NS_ERROR_DOM_SECURITY_ERR;
    IC_LOG("MediaStream principal should not change during TakePhoto().");
  }

  if (NS_SUCCEEDED(aRv)) {
    rv = mImageCapture->PostBlobEvent(blob);
  } else {
    rv =
        mImageCapture->PostErrorEvent(dom::ImageCaptureError::PHOTO_ERROR, aRv);
  }

  // Ensure ImageCapture dereference on main thread here because the TakePhoto()
  // sequences stopped here.
  mImageCapture = nullptr;

  return rv;
}

void CaptureTask::AttachTrack() {
  MOZ_ASSERT(NS_IsMainThread());

  dom::MediaStreamTrack* track = mImageCapture->GetVideoStreamTrack();
  track->AddPrincipalChangeObserver(this);
  track->AddListener(mEventListener.get());
  track->AddDirectListener(this);
}

void CaptureTask::DetachTrack() {
  MOZ_ASSERT(NS_IsMainThread());

  dom::MediaStreamTrack* track = mImageCapture->GetVideoStreamTrack();
  track->RemovePrincipalChangeObserver(this);
  track->RemoveListener(mEventListener.get());
  track->RemoveDirectListener(this);
}

void CaptureTask::PrincipalChanged(dom::MediaStreamTrack* aMediaStreamTrack) {
  MOZ_ASSERT(NS_IsMainThread());
  mPrincipalChanged = true;
}

void CaptureTask::SetCurrentFrames(const VideoSegment& aSegment) {
  if (mImageGrabbedOrTrackEnd) {
    return;
  }

  // Callback for encoding complete, it calls on main thread.
  class EncodeComplete : public dom::EncodeCompleteCallback {
   public:
    explicit EncodeComplete(CaptureTask* aTask) : mTask(aTask) {}

    nsresult ReceiveBlob(already_AddRefed<dom::Blob> aBlob) override {
      RefPtr<dom::Blob> blob(aBlob);
      mTask->TaskComplete(blob.forget(), NS_OK);
      mTask = nullptr;
      return NS_OK;
    }

   protected:
    RefPtr<CaptureTask> mTask;
  };

  for (VideoSegment::ConstChunkIterator iter(aSegment); !iter.IsEnded();
       iter.Next()) {
    VideoChunk chunk = *iter;

    // Extract the first valid video frame.
    VideoFrame frame;
    if (!chunk.IsNull()) {
      RefPtr<layers::Image> image;
      if (chunk.mFrame.GetForceBlack()) {
        // Create a black image.
        image = VideoFrame::CreateBlackImage(chunk.mFrame.GetIntrinsicSize());
      } else {
        image = chunk.mFrame.GetImage();
      }
      if (!image) {
        MOZ_ASSERT(image);
        continue;
      }
      mImageGrabbedOrTrackEnd = true;

      // Encode image.
      nsresult rv;
      nsAutoString type(NS_LITERAL_STRING("image/jpeg"));
      nsAutoString options;
      rv = dom::ImageEncoder::ExtractDataFromLayersImageAsync(
          type, options, false, image, false, new EncodeComplete(this));
      if (NS_FAILED(rv)) {
        PostTrackEndEvent();
      }
      return;
    }
  }
}

void CaptureTask::PostTrackEndEvent() {
  mImageGrabbedOrTrackEnd = true;

  // Got track end or finish event, stop the task.
  class TrackEndRunnable : public Runnable {
   public:
    explicit TrackEndRunnable(CaptureTask* aTask)
        : mozilla::Runnable("TrackEndRunnable"), mTask(aTask) {}

    NS_IMETHOD Run() override {
      mTask->TaskComplete(nullptr, NS_ERROR_FAILURE);
      mTask = nullptr;
      return NS_OK;
    }

   protected:
    RefPtr<CaptureTask> mTask;
  };

  IC_LOG("Got MediaStream track removed or finished event.");
  nsCOMPtr<nsIRunnable> event = new TrackEndRunnable(this);
  SystemGroup::Dispatch(TaskCategory::Other, event.forget());
}

}  // namespace mozilla