// Copyright 2014 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.

#ifndef MEDIA_RENDERERS_RENDERER_IMPL_H_
#define MEDIA_RENDERERS_RENDERER_IMPL_H_

#include <list>
#include <memory>
#include <vector>

#include "base/cancelable_callback.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/synchronization/lock.h"
#include "base/time/clock.h"
#include "base/time/default_tick_clock.h"
#include "base/time/time.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/buffering_state.h"
#include "media/base/decryptor.h"
#include "media/base/demuxer_stream.h"
#include "media/base/media_export.h"
#include "media/base/pipeline_status.h"
#include "media/base/renderer.h"
#include "media/base/video_decoder_config.h"
#include "ui/gfx/geometry/size.h"

namespace base {
class SingleThreadTaskRunner;
}

namespace media {

class AudioRenderer;
class MediaResource;
class TimeSource;
class VideoRenderer;
class WallClockTimeSource;

class MEDIA_EXPORT RendererImpl : public Renderer {
 public:
  // Renders audio/video streams using |audio_renderer| and |video_renderer|
  // provided. All methods except for GetMediaTime() run on the |task_runner|.
  // GetMediaTime() runs on the render main thread because it's part of JS sync
  // API.
  RendererImpl(const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
               std::unique_ptr<AudioRenderer> audio_renderer,
               std::unique_ptr<VideoRenderer> video_renderer);

  ~RendererImpl() final;

  // Renderer implementation.
  void Initialize(MediaResource* media_resource,
                  RendererClient* client,
                  const PipelineStatusCB& init_cb) final;
  void SetCdm(CdmContext* cdm_context,
              const CdmAttachedCB& cdm_attached_cb) final;
  void Flush(const base::Closure& flush_cb) final;
  void StartPlayingFrom(base::TimeDelta time) final;
  void SetPlaybackRate(double playback_rate) final;
  void SetVolume(float volume) final;
  base::TimeDelta GetMediaTime() final;
  void OnSelectedVideoTracksChanged(
      const std::vector<DemuxerStream*>& enabled_tracks,
      base::OnceClosure change_completed_cb) override;
  void OnEnabledAudioTracksChanged(
      const std::vector<DemuxerStream*>& enabled_tracks,
      base::OnceClosure change_completed_cb) override;

  // Helper functions for testing purposes. Must be called before Initialize().
  void DisableUnderflowForTesting();
  void EnableClocklessVideoPlaybackForTesting();
  void set_time_source_for_testing(TimeSource* time_source) {
    time_source_ = time_source;
  }
  void set_video_underflow_threshold_for_testing(base::TimeDelta threshold) {
    video_underflow_threshold_ = threshold;
  }

 private:
  class RendererClientInternal;

  enum State {
    STATE_UNINITIALIZED,
    STATE_INIT_PENDING_CDM,  // Initialization is waiting for the CDM to be set.
    STATE_INITIALIZING,      // Initializing audio/video renderers.
    STATE_FLUSHING,          // Flushing is in progress.
    STATE_FLUSHED,           // After initialization or after flush completed.
    STATE_PLAYING,           // After StartPlayingFrom has been called.
    STATE_ERROR
  };

  bool GetWallClockTimes(const std::vector<base::TimeDelta>& media_timestamps,
                         std::vector<base::TimeTicks>* wall_clock_times);

  bool HasEncryptedStream();

  void FinishInitialization(PipelineStatus status);

  // Helper functions and callbacks for Initialize().
  void InitializeAudioRenderer();
  void OnAudioRendererInitializeDone(PipelineStatus status);
  void InitializeVideoRenderer();
  void OnVideoRendererInitializeDone(PipelineStatus status);

  // Helper functions and callbacks for Flush().
  void FlushInternal();
  void FlushAudioRenderer();
  void OnAudioRendererFlushDone();
  void FlushVideoRenderer();
  void OnVideoRendererFlushDone();

  // Reinitialize audio/video renderer during a demuxer stream switching. The
  // renderer must be flushed first, and when the re-init is completed the
  // corresponding callback will be invoked to restart playback.
  // The |stream| parameter specifies the new demuxer stream, and the |time|
  // parameter specifies the time on media timeline where the switch occured.
  void ReinitializeAudioRenderer(DemuxerStream* stream,
                                 base::TimeDelta time,
                                 base::OnceClosure reinitialize_completed_cb);
  void OnAudioRendererReinitialized(DemuxerStream* stream,
                                    base::TimeDelta time,
                                    base::OnceClosure reinitialize_completed_cb,
                                    PipelineStatus status);
  void ReinitializeVideoRenderer(DemuxerStream* stream,
                                 base::TimeDelta time,
                                 base::OnceClosure restart_completed_cb);
  void OnVideoRendererReinitialized(DemuxerStream* stream,
                                    base::TimeDelta time,
                                    base::OnceClosure restart_completed_cb,
                                    PipelineStatus status);

  // Restart audio/video renderer playback after a demuxer stream switch or
  // after a demuxer stream has been disabled and re-enabled. The |stream|
  // parameter specifies which stream needs to be restarted. The |time|
  // parameter specifies the position on the media timeline where the playback
  // needs to be restarted. It is necessary for demuxers with independent
  // streams (e.g. MSE / ChunkDemuxer) to synchronize data reading between those
  // streams.
  void RestartAudioRenderer(DemuxerStream* stream,
                            base::TimeDelta time,
                            base::OnceClosure restart_completed_cb);
  void RestartVideoRenderer(DemuxerStream* stream,
                            base::TimeDelta time,
                            base::OnceClosure restart_completed_cb);

  // Fix state booleans after the stream switching is finished.
  void CleanUpTrackChange(base::RepeatingClosure on_finished,
                          bool* pending_change,
                          bool* ended,
                          bool* playing);

  // Callback executed by filters to update statistics.
  void OnStatisticsUpdate(const PipelineStatistics& stats);

  // Collection of callback methods and helpers for tracking changes in
  // buffering state and transition from paused/underflow states and playing
  // states.
  //
  // While in the kPlaying state:
  //   - A waiting to non-waiting transition indicates preroll has completed
  //     and StartPlayback() should be called
  //   - A non-waiting to waiting transition indicates underflow has occurred
  //     and PausePlayback() should be called
  void OnBufferingStateChange(DemuxerStream::Type type,
                              BufferingState new_buffering_state);

  // Handles the buffering notifications that we might get while an audio or a
  // video stream is being restarted. In those cases we don't want to report
  // underflows immediately and instead give decoders a chance to catch up with
  // currently playing stream. Returns true if the buffering nofication has been
  // handled and no further processing is necessary, returns false to indicate
  // that we should fall back to the regular OnBufferingStateChange logic.
  bool HandleRestartedStreamBufferingChanges(
      DemuxerStream::Type type,
      BufferingState new_buffering_state);

  bool WaitingForEnoughData() const;
  void PausePlayback();
  void StartPlayback();

  // Callbacks executed when a renderer has ended.
  void OnRendererEnded(DemuxerStream::Type type);
  bool PlaybackHasEnded() const;
  void RunEndedCallbackIfNeeded();

  // Callback executed when a runtime error happens.
  void OnError(PipelineStatus error);

  void OnWaitingForDecryptionKey();
  void OnVideoNaturalSizeChange(const gfx::Size& size);
  void OnAudioConfigChange(const AudioDecoderConfig& config);
  void OnVideoConfigChange(const VideoDecoderConfig& config);
  void OnVideoOpacityChange(bool opaque);

  void OnStreamRestartCompleted();

  State state_;

  // Task runner used to execute pipeline tasks.
  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;

  MediaResource* media_resource_;
  RendererClient* client_;

  // Temporary callback used for Initialize() and Flush().
  PipelineStatusCB init_cb_;
  base::Closure flush_cb_;

  std::unique_ptr<RendererClientInternal> audio_renderer_client_;
  std::unique_ptr<RendererClientInternal> video_renderer_client_;
  std::unique_ptr<AudioRenderer> audio_renderer_;
  std::unique_ptr<VideoRenderer> video_renderer_;

  DemuxerStream* current_audio_stream_;
  DemuxerStream* current_video_stream_;

  // Renderer-provided time source used to control playback.
  TimeSource* time_source_;
  std::unique_ptr<WallClockTimeSource> wall_clock_time_source_;
  bool time_ticking_;
  double playback_rate_;

  // The time to start playback from after starting/seeking has completed.
  base::TimeDelta start_time_;

  BufferingState audio_buffering_state_;
  BufferingState video_buffering_state_;

  // Whether we've received the audio/video ended events.
  bool audio_ended_;
  bool video_ended_;
  bool audio_playing_;
  bool video_playing_;

  CdmContext* cdm_context_;

  bool underflow_disabled_for_testing_;
  bool clockless_video_playback_enabled_for_testing_;

  // Used to defer underflow for video when audio is present.
  base::CancelableClosure deferred_video_underflow_cb_;

  // Used to defer underflow for audio when restarting audio playback.
  base::CancelableClosure deferred_audio_restart_underflow_cb_;

  // The amount of time to wait before declaring underflow if the video renderer
  // runs out of data but the audio renderer still has enough.
  base::TimeDelta video_underflow_threshold_;

  // Lock used to protect access to the |restarting_audio_| flag and
  // |restarting_audio_time_|.
  // TODO(servolk): Get rid of the lock and replace restarting_audio_ with
  // std::atomic<bool> when atomics are unbanned in Chromium.
  base::Lock restarting_audio_lock_;
  bool pending_audio_track_change_ = false;
  base::TimeDelta restarting_audio_time_ = kNoTimestamp;

  bool pending_video_track_change_ = false;

  base::WeakPtr<RendererImpl> weak_this_;
  base::WeakPtrFactory<RendererImpl> weak_factory_;

  DISALLOW_COPY_AND_ASSIGN(RendererImpl);
};

}  // namespace media

#endif  // MEDIA_RENDERERS_RENDERER_IMPL_H_
