// Copyright 2017 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 "content/public/browser/media_session.h"

#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "media/base/media_switches.h"
#include "services/media_session/public/cpp/features.h"
#include "services/media_session/public/cpp/test/audio_focus_test_util.h"

namespace content {

namespace {

// Integration tests for content::MediaSession that do not take into
// consideration the implementation details contrary to
// MediaSessionImplBrowserTest.
class MediaSessionBrowserTest : public ContentBrowserTest {
 public:
  MediaSessionBrowserTest() = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitchASCII(
        switches::kAutoplayPolicy,
        switches::autoplay::kNoUserGestureRequiredPolicy);

    scoped_feature_list_.InitAndEnableFeature(media::kInternalMediaSession);
  }

  void DisableInternalMediaSession() {
    disabled_feature_list_.InitWithFeatures(
        {}, {media::kInternalMediaSession,
             media_session::features::kMediaSessionService});
  }

  void StartPlaybackAndWait(Shell* shell, const std::string& id) {
    shell->web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
        base::ASCIIToUTF16("document.querySelector('#" + id + "').play();"));
    WaitForStart(shell);
  }

  void StopPlaybackAndWait(Shell* shell, const std::string& id) {
    shell->web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
        base::ASCIIToUTF16("document.querySelector('#" + id + "').pause();"));
    WaitForStop(shell);
  }

  void WaitForStart(Shell* shell) {
    MediaStartStopObserver observer(shell->web_contents(),
                                    MediaStartStopObserver::Type::kStart);
    observer.Wait();
  }

  void WaitForStop(Shell* shell) {
    MediaStartStopObserver observer(shell->web_contents(),
                                    MediaStartStopObserver::Type::kStop);
    observer.Wait();
  }

  bool IsPlaying(Shell* shell, const std::string& id) {
    bool result;
    EXPECT_TRUE(
        ExecuteScriptAndExtractBool(shell->web_contents(),
                                    "window.domAutomationController.send("
                                    "!document.querySelector('#" +
                                        id + "').paused);",
                                    &result));
    return result;
  }

 private:
  class MediaStartStopObserver : public WebContentsObserver {
   public:
    enum class Type { kStart, kStop };

    MediaStartStopObserver(WebContents* web_contents, Type type)
        : WebContentsObserver(web_contents), type_(type) {}

    void MediaStartedPlaying(const MediaPlayerInfo& info,
                             const MediaPlayerId& id) override {
      if (type_ != Type::kStart)
        return;

      run_loop_.Quit();
    }

    void MediaStoppedPlaying(
        const MediaPlayerInfo& info,
        const MediaPlayerId& id,
        WebContentsObserver::MediaStoppedReason reason) override {
      if (type_ != Type::kStop)
        return;

      run_loop_.Quit();
    }

    void Wait() { run_loop_.Run(); }

   private:
    base::RunLoop run_loop_;
    Type type_;

    DISALLOW_COPY_AND_ASSIGN(MediaStartStopObserver);
  };

  base::test::ScopedFeatureList disabled_feature_list_;
  base::test::ScopedFeatureList scoped_feature_list_;

  DISALLOW_COPY_AND_ASSIGN(MediaSessionBrowserTest);
};

}  // anonymous namespace

IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, MediaSessionNoOpWhenDisabled) {
  DisableInternalMediaSession();

  NavigateToURL(shell(), GetTestUrl("media/session", "media-session.html"));

  MediaSession* media_session = MediaSession::Get(shell()->web_contents());
  ASSERT_NE(nullptr, media_session);

  StartPlaybackAndWait(shell(), "long-video");
  StartPlaybackAndWait(shell(), "long-audio");

  media_session->Suspend(MediaSession::SuspendType::kSystem);
  StopPlaybackAndWait(shell(), "long-audio");

  // At that point, only "long-audio" is paused.
  EXPECT_FALSE(IsPlaying(shell(), "long-audio"));
  EXPECT_TRUE(IsPlaying(shell(), "long-video"));
}

IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, SimplePlayPause) {
  NavigateToURL(shell(), GetTestUrl("media/session", "media-session.html"));

  MediaSession* media_session = MediaSession::Get(shell()->web_contents());
  ASSERT_NE(nullptr, media_session);

  StartPlaybackAndWait(shell(), "long-video");

  media_session->Suspend(MediaSession::SuspendType::kSystem);
  WaitForStop(shell());
  EXPECT_FALSE(IsPlaying(shell(), "long-video"));

  media_session->Resume(MediaSession::SuspendType::kSystem);
  WaitForStart(shell());
  EXPECT_TRUE(IsPlaying(shell(), "long-video"));
}

IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, MultiplePlayersPlayPause) {
  NavigateToURL(shell(), GetTestUrl("media/session", "media-session.html"));

  MediaSession* media_session = MediaSession::Get(shell()->web_contents());
  ASSERT_NE(nullptr, media_session);

  StartPlaybackAndWait(shell(), "long-video");
  StartPlaybackAndWait(shell(), "long-audio");

  media_session->Suspend(MediaSession::SuspendType::kSystem);
  WaitForStop(shell());
  EXPECT_FALSE(IsPlaying(shell(), "long-video"));
  EXPECT_FALSE(IsPlaying(shell(), "long-audio"));

  media_session->Resume(MediaSession::SuspendType::kSystem);
  WaitForStart(shell());
  EXPECT_TRUE(IsPlaying(shell(), "long-video"));
  EXPECT_TRUE(IsPlaying(shell(), "long-audio"));
}

IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, WebContents_Muted) {
  NavigateToURL(shell(), GetTestUrl("media/session", "media-session.html"));

  shell()->web_contents()->SetAudioMuted(true);
  MediaSession* media_session = MediaSession::Get(shell()->web_contents());
  ASSERT_NE(nullptr, media_session);

  StartPlaybackAndWait(shell(), "long-video");
  EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(media_session)
                   ->is_controllable);

  // Unmute the web contents and the player should be created.
  shell()->web_contents()->SetAudioMuted(false);
  EXPECT_TRUE(media_session::test::GetMediaSessionInfoSync(media_session)
                  ->is_controllable);

  // Now mute it again and the player should be removed.
  shell()->web_contents()->SetAudioMuted(true);
  EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(media_session)
                   ->is_controllable);
}

#if !defined(OS_ANDROID)
// On Android, System Audio Focus would break this test.
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, MultipleTabsPlayPause) {
  Shell* other_shell = CreateBrowser();

  NavigateToURL(shell(), GetTestUrl("media/session", "media-session.html"));
  NavigateToURL(other_shell, GetTestUrl("media/session", "media-session.html"));

  MediaSession* media_session = MediaSession::Get(shell()->web_contents());
  MediaSession* other_media_session =
      MediaSession::Get(other_shell->web_contents());
  ASSERT_NE(nullptr, media_session);
  ASSERT_NE(nullptr, other_media_session);

  StartPlaybackAndWait(shell(), "long-video");
  StartPlaybackAndWait(other_shell, "long-video");

  media_session->Suspend(MediaSession::SuspendType::kSystem);
  WaitForStop(shell());
  EXPECT_FALSE(IsPlaying(shell(), "long-video"));
  EXPECT_TRUE(IsPlaying(other_shell, "long-video"));

  other_media_session->Suspend(MediaSession::SuspendType::kSystem);
  WaitForStop(other_shell);
  EXPECT_FALSE(IsPlaying(shell(), "long-video"));
  EXPECT_FALSE(IsPlaying(other_shell, "long-video"));

  media_session->Resume(MediaSession::SuspendType::kSystem);
  WaitForStart(shell());
  EXPECT_TRUE(IsPlaying(shell(), "long-video"));
  EXPECT_FALSE(IsPlaying(other_shell, "long-video"));

  other_media_session->Resume(MediaSession::SuspendType::kSystem);
  WaitForStart(other_shell);
  EXPECT_TRUE(IsPlaying(shell(), "long-video"));
  EXPECT_TRUE(IsPlaying(other_shell, "long-video"));
}
#endif  // defined(OS_ANDROID)

}  // namespace content
