mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-30 13:45:27 +00:00
306 lines
7.4 KiB
C++
306 lines
7.4 KiB
C++
/* 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 "base/basictypes.h"
|
|
#include "Layers.h"
|
|
#include "VideoUtils.h"
|
|
#include "DOMCameraPreview.h"
|
|
#include "CameraCommon.h"
|
|
#include "nsGlobalWindow.h"
|
|
#include "nsIDocument.h"
|
|
#include "nsPIDOMWindow.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::layers;
|
|
|
|
/**
|
|
* 'PreviewControl' is a helper class that dispatches preview control
|
|
* events from the main thread.
|
|
*
|
|
* NS_NewRunnableMethod() can't be used because it AddRef()s the method's
|
|
* object, which can't be done off the main thread for cycle collection
|
|
* participants.
|
|
*
|
|
* Before using this class, 'aDOMPreview' must be appropriately AddRef()ed.
|
|
*/
|
|
class PreviewControl : public nsRunnable
|
|
{
|
|
public:
|
|
enum {
|
|
START,
|
|
STOP,
|
|
STARTED,
|
|
STOPPED
|
|
};
|
|
PreviewControl(DOMCameraPreview* aDOMPreview, uint32_t aControl)
|
|
: mDOMPreview(aDOMPreview)
|
|
, mControl(aControl)
|
|
{ }
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "PreviewControl not run on main thread");
|
|
|
|
switch (mControl) {
|
|
case START:
|
|
mDOMPreview->Start();
|
|
break;
|
|
|
|
case STOP:
|
|
mDOMPreview->StopPreview();
|
|
break;
|
|
|
|
case STARTED:
|
|
mDOMPreview->SetStateStarted();
|
|
break;
|
|
|
|
case STOPPED:
|
|
mDOMPreview->SetStateStopped();
|
|
break;
|
|
|
|
default:
|
|
DOM_CAMERA_LOGE("PreviewControl: invalid control %d\n", mControl);
|
|
break;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
protected:
|
|
/**
|
|
* This must be a raw pointer because this class is not created on the
|
|
* main thread, and DOMCameraPreview is not threadsafe. Prior to
|
|
* issuing a preview control event, the caller must ensure that
|
|
* mDOMPreview will not disappear.
|
|
*/
|
|
DOMCameraPreview* mDOMPreview;
|
|
uint32_t mControl;
|
|
};
|
|
|
|
class DOMCameraPreviewListener : public MediaStreamListener
|
|
{
|
|
public:
|
|
DOMCameraPreviewListener(DOMCameraPreview* aDOMPreview) :
|
|
mDOMPreview(aDOMPreview)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
}
|
|
|
|
~DOMCameraPreviewListener()
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
}
|
|
|
|
void NotifyConsumptionChanged(MediaStreamGraph* aGraph, Consumption aConsuming)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
|
|
#ifdef PR_LOGGING
|
|
const char* state;
|
|
|
|
switch (aConsuming) {
|
|
case NOT_CONSUMED:
|
|
state = "not consuming";
|
|
break;
|
|
|
|
case CONSUMED:
|
|
state = "consuming";
|
|
break;
|
|
|
|
default:
|
|
state = "unknown";
|
|
break;
|
|
}
|
|
|
|
DOM_CAMERA_LOGA("camera viewfinder is %s\n", state);
|
|
#endif
|
|
nsCOMPtr<nsIRunnable> previewControl;
|
|
|
|
switch (aConsuming) {
|
|
case NOT_CONSUMED:
|
|
previewControl = new PreviewControl(mDOMPreview, PreviewControl::STOP);
|
|
break;
|
|
|
|
case CONSUMED:
|
|
previewControl = new PreviewControl(mDOMPreview, PreviewControl::START);
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
nsresult rv = NS_DispatchToMainThread(previewControl);
|
|
if (NS_FAILED(rv)) {
|
|
DOM_CAMERA_LOGE("Failed to dispatch preview control (%d)!\n", rv);
|
|
}
|
|
}
|
|
|
|
protected:
|
|
// Raw pointer; if we exist, 'mDOMPreview' exists as well
|
|
DOMCameraPreview* mDOMPreview;
|
|
};
|
|
|
|
DOMCameraPreview::DOMCameraPreview(nsGlobalWindow* aWindow,
|
|
ICameraControl* aCameraControl,
|
|
uint32_t aWidth, uint32_t aHeight,
|
|
uint32_t aFrameRate)
|
|
: DOMMediaStream()
|
|
, mState(STOPPED)
|
|
, mWidth(aWidth)
|
|
, mHeight(aHeight)
|
|
, mFramesPerSecond(aFrameRate)
|
|
, mFrameCount(0)
|
|
, mCameraControl(aCameraControl)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p : mWidth=%d, mHeight=%d, mFramesPerSecond=%d\n", __func__, __LINE__, this, mWidth, mHeight, mFramesPerSecond);
|
|
|
|
mImageContainer = LayerManager::CreateImageContainer();
|
|
mWindow = aWindow;
|
|
mInput = new CameraPreviewMediaStream(this);
|
|
mStream = mInput;
|
|
|
|
mListener = new DOMCameraPreviewListener(this);
|
|
mInput->AddListener(mListener);
|
|
|
|
if (aWindow->GetExtantDoc()) {
|
|
CombineWithPrincipal(aWindow->GetExtantDoc()->NodePrincipal());
|
|
}
|
|
}
|
|
|
|
DOMCameraPreview::~DOMCameraPreview()
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p, mListener=%p\n", __func__, __LINE__, this, mListener);
|
|
mInput->RemoveListener(mListener);
|
|
}
|
|
|
|
bool
|
|
DOMCameraPreview::HaveEnoughBuffered()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
DOMCameraPreview::ReceiveFrame(void* aBuffer, ImageFormat aFormat, FrameBuilder aBuilder)
|
|
{
|
|
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
|
if (!aBuffer || !aBuilder) {
|
|
return false;
|
|
}
|
|
if (mState != STARTED) {
|
|
return false;
|
|
}
|
|
|
|
ImageFormat format = aFormat;
|
|
nsRefPtr<Image> image = mImageContainer->CreateImage(&format, 1);
|
|
aBuilder(image, aBuffer, mWidth, mHeight);
|
|
|
|
mInput->SetCurrentFrame(gfxIntSize(mWidth, mHeight), image);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
DOMCameraPreview::Start()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Start() not called from main thread");
|
|
if (mState != STOPPED) {
|
|
return;
|
|
}
|
|
|
|
DOM_CAMERA_LOGI("Starting preview stream\n");
|
|
|
|
/**
|
|
* Add a reference to ourselves to make sure we stay alive while
|
|
* the preview is running, as the CameraControlImpl object holds a
|
|
* weak reference to us.
|
|
*
|
|
* This reference is removed in SetStateStopped().
|
|
*/
|
|
NS_ADDREF_THIS();
|
|
DOM_CAMERA_SETSTATE(STARTING);
|
|
mCameraControl->StartPreview(this);
|
|
}
|
|
|
|
void
|
|
DOMCameraPreview::SetStateStarted()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "SetStateStarted() not called from main thread");
|
|
|
|
DOM_CAMERA_SETSTATE(STARTED);
|
|
DOM_CAMERA_LOGI("Preview stream started\n");
|
|
}
|
|
|
|
void
|
|
DOMCameraPreview::Started()
|
|
{
|
|
if (mState != STARTING) {
|
|
return;
|
|
}
|
|
|
|
DOM_CAMERA_LOGI("Dispatching preview stream started\n");
|
|
nsCOMPtr<nsIRunnable> started = new PreviewControl(this, PreviewControl::STARTED);
|
|
nsresult rv = NS_DispatchToMainThread(started);
|
|
if (NS_FAILED(rv)) {
|
|
DOM_CAMERA_LOGE("failed to set statrted state (%d), POTENTIAL MEMORY LEAK!\n", rv);
|
|
}
|
|
}
|
|
|
|
void
|
|
DOMCameraPreview::StopPreview()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "StopPreview() not called from main thread");
|
|
if (mState != STARTED) {
|
|
return;
|
|
}
|
|
|
|
DOM_CAMERA_LOGI("Stopping preview stream\n");
|
|
DOM_CAMERA_SETSTATE(STOPPING);
|
|
mCameraControl->StopPreview();
|
|
//mInput->EndTrack(TRACK_VIDEO);
|
|
//mInput->Finish();
|
|
}
|
|
|
|
void
|
|
DOMCameraPreview::SetStateStopped()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "SetStateStopped() not called from main thread");
|
|
|
|
// see bug 809259 and bug 817367.
|
|
if (mState != STOPPING) {
|
|
//mInput->EndTrack(TRACK_VIDEO);
|
|
//mInput->Finish();
|
|
}
|
|
DOM_CAMERA_SETSTATE(STOPPED);
|
|
DOM_CAMERA_LOGI("Preview stream stopped\n");
|
|
|
|
/**
|
|
* Only remove the reference added in Start() once the preview
|
|
* has stopped completely.
|
|
*/
|
|
NS_RELEASE_THIS();
|
|
}
|
|
|
|
void
|
|
DOMCameraPreview::Stopped(bool aForced)
|
|
{
|
|
if (mState != STOPPING && !aForced) {
|
|
return;
|
|
}
|
|
|
|
mInput->ClearCurrentFrame();
|
|
|
|
DOM_CAMERA_LOGI("Dispatching preview stream stopped\n");
|
|
nsCOMPtr<nsIRunnable> stopped = new PreviewControl(this, PreviewControl::STOPPED);
|
|
nsresult rv = NS_DispatchToMainThread(stopped);
|
|
if (NS_FAILED(rv)) {
|
|
DOM_CAMERA_LOGE("failed to decrement reference count (%d), MEMORY LEAK!\n", rv);
|
|
}
|
|
}
|
|
|
|
void
|
|
DOMCameraPreview::Error()
|
|
{
|
|
DOM_CAMERA_LOGE("Error occurred changing preview state!\n");
|
|
Stopped(true);
|
|
}
|