summaryrefslogtreecommitdiffstats
path: root/dom/media/VideoFrameContainer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/VideoFrameContainer.cpp')
-rw-r--r--dom/media/VideoFrameContainer.cpp359
1 files changed, 359 insertions, 0 deletions
diff --git a/dom/media/VideoFrameContainer.cpp b/dom/media/VideoFrameContainer.cpp
new file mode 100644
index 000000000..2b1965766
--- /dev/null
+++ b/dom/media/VideoFrameContainer.cpp
@@ -0,0 +1,359 @@
+/* -*- 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 "VideoFrameContainer.h"
+
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "nsIFrame.h"
+#include "nsDisplayList.h"
+#include "nsSVGEffects.h"
+
+using namespace mozilla::layers;
+
+namespace mozilla {
+static LazyLogModule gVideoFrameContainerLog("VideoFrameContainer");
+#define CONTAINER_LOG(type, msg) MOZ_LOG(gVideoFrameContainerLog, type, msg)
+
+VideoFrameContainer::VideoFrameContainer(dom::HTMLMediaElement* aElement,
+ already_AddRefed<ImageContainer> aContainer)
+ : mElement(aElement),
+ mImageContainer(aContainer), mMutex("nsVideoFrameContainer"),
+ mBlackImage(nullptr),
+ mFrameID(0),
+ mIntrinsicSizeChanged(false), mImageSizeChanged(false),
+ mPendingPrincipalHandle(PRINCIPAL_HANDLE_NONE), mFrameIDForPendingPrincipalHandle(0)
+{
+ NS_ASSERTION(aElement, "aElement must not be null");
+ NS_ASSERTION(mImageContainer, "aContainer must not be null");
+}
+
+VideoFrameContainer::~VideoFrameContainer()
+{}
+
+PrincipalHandle VideoFrameContainer::GetLastPrincipalHandle()
+{
+ MutexAutoLock lock(mMutex);
+ return GetLastPrincipalHandleLocked();
+}
+
+PrincipalHandle VideoFrameContainer::GetLastPrincipalHandleLocked()
+{
+ return mLastPrincipalHandle;
+}
+
+void VideoFrameContainer::UpdatePrincipalHandleForFrameID(const PrincipalHandle& aPrincipalHandle,
+ const ImageContainer::FrameID& aFrameID)
+{
+ MutexAutoLock lock(mMutex);
+ UpdatePrincipalHandleForFrameIDLocked(aPrincipalHandle, aFrameID);
+}
+
+void VideoFrameContainer::UpdatePrincipalHandleForFrameIDLocked(const PrincipalHandle& aPrincipalHandle,
+ const ImageContainer::FrameID& aFrameID)
+{
+ if (mPendingPrincipalHandle == aPrincipalHandle) {
+ return;
+ }
+ mPendingPrincipalHandle = aPrincipalHandle;
+ mFrameIDForPendingPrincipalHandle = aFrameID;
+}
+
+static void
+SetImageToBlackPixel(PlanarYCbCrImage* aImage)
+{
+ uint8_t blackPixel[] = { 0x10, 0x80, 0x80 };
+
+ PlanarYCbCrData data;
+ data.mYChannel = blackPixel;
+ data.mCbChannel = blackPixel + 1;
+ data.mCrChannel = blackPixel + 2;
+ data.mYStride = data.mCbCrStride = 1;
+ data.mPicSize = data.mYSize = data.mCbCrSize = gfx::IntSize(1, 1);
+ aImage->CopyData(data);
+}
+
+class VideoFrameContainerInvalidateRunnable : public Runnable {
+public:
+ explicit VideoFrameContainerInvalidateRunnable(VideoFrameContainer* aVideoFrameContainer)
+ : mVideoFrameContainer(aVideoFrameContainer)
+ {}
+ NS_IMETHOD Run()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mVideoFrameContainer->Invalidate();
+
+ return NS_OK;
+ }
+private:
+ RefPtr<VideoFrameContainer> mVideoFrameContainer;
+};
+
+void VideoFrameContainer::SetCurrentFrames(const VideoSegment& aSegment)
+{
+ if (aSegment.IsEmpty()) {
+ return;
+ }
+
+ MutexAutoLock lock(mMutex);
+
+ // Collect any new frames produced in this iteration.
+ AutoTArray<ImageContainer::NonOwningImage,4> newImages;
+ PrincipalHandle lastPrincipalHandle = PRINCIPAL_HANDLE_NONE;
+
+ VideoSegment::ConstChunkIterator iter(aSegment);
+ while (!iter.IsEnded()) {
+ VideoChunk chunk = *iter;
+
+ const VideoFrame* frame = &chunk.mFrame;
+ if (*frame == mLastPlayedVideoFrame) {
+ iter.Next();
+ continue;
+ }
+
+ Image* image = frame->GetImage();
+ CONTAINER_LOG(LogLevel::Verbose,
+ ("VideoFrameContainer %p writing video frame %p (%d x %d)",
+ this, image, frame->GetIntrinsicSize().width,
+ frame->GetIntrinsicSize().height));
+
+ if (frame->GetForceBlack()) {
+ if (!mBlackImage) {
+ mBlackImage = GetImageContainer()->CreatePlanarYCbCrImage();
+ if (mBlackImage) {
+ // Sets the image to a single black pixel, which will be scaled to
+ // fill the rendered size.
+ SetImageToBlackPixel(mBlackImage->AsPlanarYCbCrImage());
+ }
+ }
+ if (mBlackImage) {
+ image = mBlackImage;
+ }
+ }
+ // Don't append null image to the newImages.
+ if (!image) {
+ iter.Next();
+ continue;
+ }
+ newImages.AppendElement(ImageContainer::NonOwningImage(image, chunk.mTimeStamp));
+
+ lastPrincipalHandle = chunk.GetPrincipalHandle();
+
+ mLastPlayedVideoFrame = *frame;
+ iter.Next();
+ }
+
+ // Don't update if there are no changes.
+ if (newImages.IsEmpty()) {
+ return;
+ }
+
+ AutoTArray<ImageContainer::NonOwningImage,4> images;
+
+ bool principalHandleChanged =
+ lastPrincipalHandle != PRINCIPAL_HANDLE_NONE &&
+ lastPrincipalHandle != GetLastPrincipalHandleLocked();
+
+ // Add the frames from this iteration.
+ for (auto& image : newImages) {
+ image.mFrameID = NewFrameID();
+ images.AppendElement(image);
+ }
+
+ if (principalHandleChanged) {
+ UpdatePrincipalHandleForFrameIDLocked(lastPrincipalHandle,
+ newImages.LastElement().mFrameID);
+ }
+
+ SetCurrentFramesLocked(mLastPlayedVideoFrame.GetIntrinsicSize(), images);
+ nsCOMPtr<nsIRunnable> event =
+ new VideoFrameContainerInvalidateRunnable(this);
+ NS_DispatchToMainThread(event.forget());
+
+ images.ClearAndRetainStorage();
+}
+
+void VideoFrameContainer::ClearFrames()
+{
+ ClearFutureFrames();
+}
+
+void VideoFrameContainer::SetCurrentFrame(const gfx::IntSize& aIntrinsicSize,
+ Image* aImage,
+ const TimeStamp& aTargetTime)
+{
+ if (aImage) {
+ MutexAutoLock lock(mMutex);
+ AutoTArray<ImageContainer::NonOwningImage,1> imageList;
+ imageList.AppendElement(
+ ImageContainer::NonOwningImage(aImage, aTargetTime, ++mFrameID));
+ SetCurrentFramesLocked(aIntrinsicSize, imageList);
+ } else {
+ ClearCurrentFrame(aIntrinsicSize);
+ }
+}
+
+void VideoFrameContainer::SetCurrentFrames(const gfx::IntSize& aIntrinsicSize,
+ const nsTArray<ImageContainer::NonOwningImage>& aImages)
+{
+ MutexAutoLock lock(mMutex);
+ SetCurrentFramesLocked(aIntrinsicSize, aImages);
+}
+
+void VideoFrameContainer::SetCurrentFramesLocked(const gfx::IntSize& aIntrinsicSize,
+ const nsTArray<ImageContainer::NonOwningImage>& aImages)
+{
+ mMutex.AssertCurrentThreadOwns();
+
+ if (aIntrinsicSize != mIntrinsicSize) {
+ mIntrinsicSize = aIntrinsicSize;
+ mIntrinsicSizeChanged = true;
+ }
+
+ gfx::IntSize oldFrameSize = mImageContainer->GetCurrentSize();
+
+ // When using the OMX decoder, destruction of the current image can indirectly
+ // block on main thread I/O. If we let this happen while holding onto
+ // |mImageContainer|'s lock, then when the main thread then tries to
+ // composite it can then block on |mImageContainer|'s lock, causing a
+ // deadlock. We use this hack to defer the destruction of the current image
+ // until it is safe.
+ nsTArray<ImageContainer::OwningImage> oldImages;
+ mImageContainer->GetCurrentImages(&oldImages);
+
+ ImageContainer::FrameID lastFrameIDForOldPrincipalHandle =
+ mFrameIDForPendingPrincipalHandle - 1;
+ if (mPendingPrincipalHandle != PRINCIPAL_HANDLE_NONE &&
+ ((!oldImages.IsEmpty() &&
+ oldImages.LastElement().mFrameID >= lastFrameIDForOldPrincipalHandle) ||
+ (!aImages.IsEmpty() &&
+ aImages[0].mFrameID > lastFrameIDForOldPrincipalHandle))) {
+ // We are releasing the last FrameID prior to `lastFrameIDForOldPrincipalHandle`
+ // OR
+ // there are no FrameIDs prior to `lastFrameIDForOldPrincipalHandle` in the new
+ // set of images.
+ // This means that the old principal handle has been flushed out and we can
+ // notify our video element about this change.
+ RefPtr<VideoFrameContainer> self = this;
+ PrincipalHandle principalHandle = mPendingPrincipalHandle;
+ mLastPrincipalHandle = mPendingPrincipalHandle;
+ mPendingPrincipalHandle = PRINCIPAL_HANDLE_NONE;
+ mFrameIDForPendingPrincipalHandle = 0;
+ NS_DispatchToMainThread(NS_NewRunnableFunction([self, principalHandle]() {
+ if (self->mElement) {
+ self->mElement->PrincipalHandleChangedForVideoFrameContainer(self, principalHandle);
+ }
+ }));
+ }
+
+ if (aImages.IsEmpty()) {
+ mImageContainer->ClearAllImages();
+ } else {
+ mImageContainer->SetCurrentImages(aImages);
+ }
+ gfx::IntSize newFrameSize = mImageContainer->GetCurrentSize();
+ if (oldFrameSize != newFrameSize) {
+ mImageSizeChanged = true;
+ }
+}
+
+void VideoFrameContainer::ClearCurrentFrame()
+{
+ MutexAutoLock lock(mMutex);
+
+ // See comment in SetCurrentFrame for the reasoning behind
+ // using a kungFuDeathGrip here.
+ nsTArray<ImageContainer::OwningImage> kungFuDeathGrip;
+ mImageContainer->GetCurrentImages(&kungFuDeathGrip);
+
+ mImageContainer->ClearAllImages();
+ mImageContainer->ClearCachedResources();
+}
+
+void VideoFrameContainer::ClearFutureFrames()
+{
+ MutexAutoLock lock(mMutex);
+
+ // See comment in SetCurrentFrame for the reasoning behind
+ // using a kungFuDeathGrip here.
+ nsTArray<ImageContainer::OwningImage> kungFuDeathGrip;
+ mImageContainer->GetCurrentImages(&kungFuDeathGrip);
+
+ if (!kungFuDeathGrip.IsEmpty()) {
+ nsTArray<ImageContainer::NonOwningImage> currentFrame;
+ const ImageContainer::OwningImage& img = kungFuDeathGrip[0];
+ currentFrame.AppendElement(ImageContainer::NonOwningImage(img.mImage,
+ img.mTimeStamp, img.mFrameID, img.mProducerID));
+ mImageContainer->SetCurrentImages(currentFrame);
+ }
+}
+
+void
+VideoFrameContainer::ClearCachedResources()
+{
+ mImageContainer->ClearCachedResources();
+}
+
+ImageContainer* VideoFrameContainer::GetImageContainer() {
+ return mImageContainer;
+}
+
+
+double VideoFrameContainer::GetFrameDelay()
+{
+ return mImageContainer->GetPaintDelay().ToSeconds();
+}
+
+void VideoFrameContainer::InvalidateWithFlags(uint32_t aFlags)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Must call on main thread");
+
+ if (!mElement) {
+ // Element has been destroyed
+ return;
+ }
+
+ nsIFrame* frame = mElement->GetPrimaryFrame();
+ bool invalidateFrame = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ // Get mImageContainerSizeChanged while holding the lock.
+ invalidateFrame = mImageSizeChanged;
+ mImageSizeChanged = false;
+
+ if (mIntrinsicSizeChanged) {
+ mElement->UpdateMediaSize(mIntrinsicSize);
+ mIntrinsicSizeChanged = false;
+
+ if (frame) {
+ nsPresContext* presContext = frame->PresContext();
+ nsIPresShell *presShell = presContext->PresShell();
+ presShell->FrameNeedsReflow(frame,
+ nsIPresShell::eStyleChange,
+ NS_FRAME_IS_DIRTY);
+ }
+ }
+ }
+
+ bool asyncInvalidate = mImageContainer &&
+ mImageContainer->IsAsync() &&
+ !(aFlags & INVALIDATE_FORCE);
+
+ if (frame) {
+ if (invalidateFrame) {
+ frame->InvalidateFrame();
+ } else {
+ frame->InvalidateLayer(nsDisplayItem::TYPE_VIDEO, nullptr, nullptr,
+ asyncInvalidate ? nsIFrame::UPDATE_IS_ASYNC : 0);
+ }
+ }
+
+ nsSVGEffects::InvalidateDirectRenderingObservers(mElement);
+}
+
+} // namespace mozilla