// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/layers/video_frame_provider_client_impl.h"

#include "base/trace_event/trace_event.h"
#include "cc/base/math_util.h"
#include "cc/layers/video_layer_impl.h"
#include "media/base/video_frame.h"

namespace cc {

// static
scoped_refptr<VideoFrameProviderClientImpl>
VideoFrameProviderClientImpl::Create(VideoFrameProvider* provider,
                                     VideoFrameControllerClient* client) {
  return base::WrapRefCounted(
      new VideoFrameProviderClientImpl(provider, client));
}

VideoFrameProviderClientImpl::VideoFrameProviderClientImpl(
    VideoFrameProvider* provider,
    VideoFrameControllerClient* client)
    : provider_(provider),
      client_(client),
      active_video_layer_(nullptr),
      stopped_(false),
      rendering_(false),
      needs_put_current_frame_(false) {
  // |provider_| may be null if destructed before the layer.
  if (provider_) {
    // This only happens during a commit on the compositor thread while the main
    // thread is blocked. That makes this a thread-safe call to set the video
    // frame provider client that does not require a lock. The same is true of
    // the call to Stop().
    provider_->SetVideoFrameProviderClient(this);
  }
}

VideoFrameProviderClientImpl::~VideoFrameProviderClientImpl() {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(stopped_);
}

VideoLayerImpl* VideoFrameProviderClientImpl::ActiveVideoLayer() const {
  DCHECK(thread_checker_.CalledOnValidThread());
  return active_video_layer_;
}

void VideoFrameProviderClientImpl::SetActiveVideoLayer(
    VideoLayerImpl* video_layer) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(video_layer);
  active_video_layer_ = video_layer;
}

void VideoFrameProviderClientImpl::Stop() {
  DCHECK(thread_checker_.CalledOnValidThread());
  active_video_layer_ = nullptr;
  // It's called when the main thread is blocked, so lock isn't needed.
  if (provider_) {
    provider_->SetVideoFrameProviderClient(nullptr);
    provider_ = nullptr;
  }
  if (rendering_)
    StopRendering();
  stopped_ = true;
}

bool VideoFrameProviderClientImpl::Stopped() const {
  DCHECK(thread_checker_.CalledOnValidThread());
  return stopped_;
}

scoped_refptr<media::VideoFrame>
VideoFrameProviderClientImpl::AcquireLockAndCurrentFrame() {
  DCHECK(thread_checker_.CalledOnValidThread());
  provider_lock_.Acquire();  // Balanced by call to ReleaseLock().
  if (!provider_)
    return nullptr;

  return provider_->GetCurrentFrame();
}

void VideoFrameProviderClientImpl::PutCurrentFrame() {
  DCHECK(thread_checker_.CalledOnValidThread());
  provider_lock_.AssertAcquired();
  provider_->PutCurrentFrame();
  needs_put_current_frame_ = false;
}

void VideoFrameProviderClientImpl::ReleaseLock() {
  DCHECK(thread_checker_.CalledOnValidThread());
  provider_lock_.AssertAcquired();
  provider_lock_.Release();
}

bool VideoFrameProviderClientImpl::HasCurrentFrame() {
  base::AutoLock locker(provider_lock_);
  return provider_ && provider_->HasCurrentFrame();
}

void VideoFrameProviderClientImpl::StopUsingProvider() {
  {
    // Block the provider from shutting down until this client is done
    // using the frame.
    base::AutoLock locker(provider_lock_);
    provider_ = nullptr;
  }
  if (rendering_)
    StopRendering();
}

void VideoFrameProviderClientImpl::StartRendering() {
  DCHECK(thread_checker_.CalledOnValidThread());
  TRACE_EVENT0("cc", "VideoFrameProviderClientImpl::StartRendering");
  DCHECK(!rendering_);
  DCHECK(!stopped_);
  rendering_ = true;
  client_->AddVideoFrameController(this);
}

void VideoFrameProviderClientImpl::StopRendering() {
  DCHECK(thread_checker_.CalledOnValidThread());
  TRACE_EVENT0("cc", "VideoFrameProviderClientImpl::StopRendering");
  DCHECK(rendering_);
  DCHECK(!stopped_);
  client_->RemoveVideoFrameController(this);
  rendering_ = false;
  if (active_video_layer_)
    active_video_layer_->SetNeedsRedraw();
}

void VideoFrameProviderClientImpl::DidReceiveFrame() {
  TRACE_EVENT1("cc",
               "VideoFrameProviderClientImpl::DidReceiveFrame",
               "active_video_layer",
               !!active_video_layer_);
  DCHECK(thread_checker_.CalledOnValidThread());
  needs_put_current_frame_ = true;
  if (active_video_layer_)
    active_video_layer_->SetNeedsRedraw();
}

void VideoFrameProviderClientImpl::OnBeginFrame(
    const viz::BeginFrameArgs& args) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(rendering_);
  DCHECK(!stopped_);

  TRACE_EVENT0("cc", "VideoFrameProviderClientImpl::OnBeginFrame");
  {
    base::AutoLock locker(provider_lock_);

    // We use frame_time + interval here because that is the estimated time at
    // which a frame returned during this phase will end up being displayed.
    if (!provider_ ||
        !provider_->UpdateCurrentFrame(args.frame_time + args.interval,
                                       args.frame_time + 2 * args.interval)) {
      return;
    }
  }

  // Warning: Do not hold |provider_lock_| while calling this function, it may
  // lead to a reentrant call to HasCurrentFrame() above.
  DidReceiveFrame();
}

void VideoFrameProviderClientImpl::DidDrawFrame() {
  DCHECK(thread_checker_.CalledOnValidThread());
  {
    base::AutoLock locker(provider_lock_);
    if (provider_ && needs_put_current_frame_)
      provider_->PutCurrentFrame();
  }
  needs_put_current_frame_ = false;
}

bool VideoFrameProviderClientImpl::IsDrivingFrameUpdates() const {
  // We drive frame updates any time we're rendering, even if we're off-screen.
  return rendering_;
}

}  // namespace cc
