media_player_impl_unittest.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "fuchsia_web/webengine/browser/media_player_impl.h"
  5. #include "base/run_loop.h"
  6. #include "base/strings/utf_string_conversions.h"
  7. #include "base/test/bind.h"
  8. #include "base/test/task_environment.h"
  9. #include "base/time/time.h"
  10. #include "content/public/browser/media_session.h"
  11. #include "mojo/public/cpp/bindings/pending_remote.h"
  12. #include "mojo/public/cpp/bindings/remote.h"
  13. #include "testing/gmock/include/gmock/gmock.h"
  14. #include "testing/gtest/include/gtest/gtest.h"
  15. namespace {
  16. class FakeMediaSession : public content::MediaSession {
  17. public:
  18. // content::MediaSession APIs mocked to observe if/when they are called.
  19. void SetDuckingVolumeMultiplier(double multiplier) override { ADD_FAILURE(); }
  20. void SetAudioFocusGroupId(const base::UnguessableToken& group_id) override {
  21. ADD_FAILURE();
  22. }
  23. MOCK_METHOD1(Suspend, void(SuspendType));
  24. MOCK_METHOD1(Resume, void(SuspendType));
  25. MOCK_METHOD0(StartDucking, void());
  26. MOCK_METHOD0(StopDucking, void());
  27. MOCK_METHOD0(PreviousTrack, void());
  28. MOCK_METHOD0(NextTrack, void());
  29. MOCK_METHOD0(SkipAd, void());
  30. MOCK_METHOD1(Seek, void(base::TimeDelta));
  31. MOCK_METHOD1(Stop, void(SuspendType));
  32. MOCK_METHOD1(SeekTo, void(base::TimeDelta));
  33. MOCK_METHOD1(ScrubTo, void(base::TimeDelta));
  34. MOCK_METHOD0(EnterPictureInPicture, void());
  35. MOCK_METHOD0(ExitPictureInPicture, void());
  36. MOCK_METHOD1(SetAudioSinkId, void(const absl::optional<std::string>& id));
  37. MOCK_METHOD0(ToggleMicrophone, void());
  38. MOCK_METHOD0(ToggleCamera, void());
  39. MOCK_METHOD0(HangUp, void());
  40. MOCK_METHOD0(Raise, void());
  41. MOCK_METHOD1(SetMute, void(bool));
  42. // content::MediaSession APIs faked to implement testing behaviour.
  43. MOCK_METHOD1(DidReceiveAction,
  44. void(media_session::mojom::MediaSessionAction));
  45. MOCK_METHOD1(GetMediaSessionInfo, void(GetMediaSessionInfoCallback));
  46. MOCK_METHOD1(GetDebugInfo, void(GetDebugInfoCallback));
  47. MOCK_METHOD4(GetMediaImageBitmap,
  48. void(const media_session::MediaImage& image,
  49. int minimum_size_px,
  50. int desired_size_px,
  51. GetMediaImageBitmapCallback callback));
  52. // content::MediaSession APIs faked to implement testing behaviour.
  53. void AddObserver(
  54. mojo::PendingRemote<media_session::mojom::MediaSessionObserver> observer)
  55. override {
  56. if (observer_.is_bound()) {
  57. ADD_FAILURE();
  58. } else {
  59. observer_.Bind(std::move(observer));
  60. }
  61. }
  62. media_session::mojom::MediaSessionObserver* observer() const {
  63. return observer_.is_bound() ? observer_.get() : nullptr;
  64. }
  65. protected:
  66. mojo::Remote<media_session::mojom::MediaSessionObserver> observer_;
  67. };
  68. bool HasFlag(const fuchsia::media::sessions2::PlayerCapabilityFlags bits,
  69. const fuchsia::media::sessions2::PlayerCapabilityFlags flag) {
  70. return (bits & flag) == flag;
  71. }
  72. } // namespace
  73. class MediaPlayerImplTest : public testing::Test {
  74. public:
  75. MediaPlayerImplTest()
  76. : task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {}
  77. MediaPlayerImplTest(const MediaPlayerImplTest&) = delete;
  78. MediaPlayerImplTest& operator=(const MediaPlayerImplTest&) = delete;
  79. ~MediaPlayerImplTest() override = default;
  80. void OnPlayerDisconnected() {}
  81. protected:
  82. base::test::SingleThreadTaskEnvironment task_environment_;
  83. testing::StrictMock<FakeMediaSession> fake_session_;
  84. fuchsia::media::sessions2::PlayerPtr player_;
  85. std::unique_ptr<MediaPlayerImpl> player_impl_;
  86. };
  87. // Verify that the |on_disconnect| closure is invoked if the client disconnects.
  88. TEST_F(MediaPlayerImplTest, OnDisconnectCalledOnDisconnect) {
  89. base::RunLoop run_loop;
  90. player_impl_ = std::make_unique<MediaPlayerImpl>(
  91. &fake_session_, player_.NewRequest(), run_loop.QuitClosure());
  92. player_.Unbind();
  93. run_loop.Run();
  94. }
  95. // Verify that the |on_disconnect| closure is invoked if the client calls the
  96. // WatchInfoChange() API incorrectly.
  97. TEST_F(MediaPlayerImplTest, ClientDisconnectedOnBadApiUsage) {
  98. base::RunLoop on_disconnected_loop;
  99. base::RunLoop player_error_loop;
  100. player_impl_ = std::make_unique<MediaPlayerImpl>(
  101. &fake_session_, player_.NewRequest(), on_disconnected_loop.QuitClosure());
  102. player_.set_error_handler([&player_error_loop](zx_status_t status) {
  103. EXPECT_EQ(status, ZX_ERR_BAD_STATE);
  104. player_error_loop.Quit();
  105. });
  106. // Call WatchInfoChange() three times in succession. The first call may
  107. // immediately invoke the callback, with initial state, but since there will
  108. // be no state-change between that and the second, it will hold the callback,
  109. // and the third call will therefore be a protocol violation.
  110. player_->WatchInfoChange([](fuchsia::media::sessions2::PlayerInfoDelta) {});
  111. player_->WatchInfoChange(
  112. [](fuchsia::media::sessions2::PlayerInfoDelta) { ADD_FAILURE(); });
  113. player_->WatchInfoChange(
  114. [](fuchsia::media::sessions2::PlayerInfoDelta) { ADD_FAILURE(); });
  115. // Wait for both on-disconnected and player error handler to be invoked.
  116. on_disconnected_loop.Run();
  117. player_error_loop.Run();
  118. }
  119. // Verify that the first WatchInfoChange() registers the observer.
  120. TEST_F(MediaPlayerImplTest, WatchInfoChangeRegistersObserver) {
  121. player_impl_ =
  122. std::make_unique<MediaPlayerImpl>(&fake_session_, player_.NewRequest(),
  123. MakeExpectedNotRunClosure(FROM_HERE));
  124. player_->WatchInfoChange([](fuchsia::media::sessions2::PlayerInfoDelta) {});
  125. ASSERT_FALSE(fake_session_.observer());
  126. // Pump the message loop to process the WatchInfoChange() call.
  127. base::RunLoop().RunUntilIdle();
  128. EXPECT_TRUE(fake_session_.observer());
  129. }
  130. // Verify that the initial session state is returned via WatchInfoChange(),
  131. // potentially via several calls to it.
  132. TEST_F(MediaPlayerImplTest, WatchInfoChangeReturnsInitialState) {
  133. player_impl_ =
  134. std::make_unique<MediaPlayerImpl>(&fake_session_, player_.NewRequest(),
  135. MakeExpectedNotRunClosure(FROM_HERE));
  136. base::RunLoop return_info_loop;
  137. fuchsia::media::sessions2::PlayerInfoDelta initial_info;
  138. std::function<void(fuchsia::media::sessions2::PlayerInfoDelta)> watch_info =
  139. [this, &initial_info, &watch_info,
  140. &return_info_loop](fuchsia::media::sessions2::PlayerInfoDelta delta) {
  141. if (delta.has_player_status())
  142. initial_info.set_player_status(
  143. std::move(*delta.mutable_player_status()));
  144. if (delta.has_metadata())
  145. initial_info.set_metadata(delta.metadata());
  146. if (delta.has_player_capabilities())
  147. initial_info.set_player_capabilities(
  148. std::move(*delta.mutable_player_capabilities()));
  149. // Only quit the loop once all of the expected fields are present.
  150. if (initial_info.has_player_status() && initial_info.has_metadata() &&
  151. initial_info.has_player_capabilities()) {
  152. return_info_loop.Quit();
  153. } else {
  154. player_->WatchInfoChange(watch_info);
  155. }
  156. };
  157. player_->WatchInfoChange(watch_info);
  158. // Pump the message loop to process the WatchInfoChange() call.
  159. base::RunLoop().RunUntilIdle();
  160. ASSERT_TRUE(fake_session_.observer());
  161. media_session::mojom::MediaSessionInfoPtr info(
  162. media_session::mojom::MediaSessionInfo::New());
  163. info->state =
  164. media_session::mojom::MediaSessionInfo::SessionState::kSuspended;
  165. fake_session_.observer()->MediaSessionInfoChanged(std::move(info));
  166. media_session::MediaMetadata metadata;
  167. constexpr char kExpectedTitle[] = "Love Like A Sunset, Pt.1";
  168. constexpr char16_t kExpectedTitle16[] = u"Love Like A Sunset, Pt.1";
  169. metadata.title = kExpectedTitle16;
  170. constexpr char kExpectedArtist[] = "Phoenix";
  171. constexpr char16_t kExpectedArtist16[] = u"Phoenix";
  172. metadata.artist = kExpectedArtist16;
  173. constexpr char kExpectedAlbum[] = "Wolfgang Amadeus Phoenix";
  174. constexpr char16_t kExpectedAlbum16[] = u"Wolfgang Amadeus Phoenix";
  175. metadata.album = kExpectedAlbum16;
  176. constexpr char kExpectedSourceTitle[] = "Unknown";
  177. constexpr char16_t kExpectedSourceTitle16[] = u"Unknown";
  178. metadata.source_title = kExpectedSourceTitle16;
  179. fake_session_.observer()->MediaSessionMetadataChanged(metadata);
  180. std::vector<media_session::mojom::MediaSessionAction> actions = {
  181. media_session::mojom::MediaSessionAction::kPlay,
  182. media_session::mojom::MediaSessionAction::kNextTrack,
  183. media_session::mojom::MediaSessionAction::kScrubTo};
  184. fake_session_.observer()->MediaSessionActionsChanged(actions);
  185. // These are sent by MediaSessionImpl, but currently ignored.
  186. fake_session_.observer()->MediaSessionImagesChanged({});
  187. fake_session_.observer()->MediaSessionPositionChanged({});
  188. return_info_loop.Run();
  189. // Verify that all of the expected fields are present, and correct.
  190. ASSERT_TRUE(initial_info.has_player_status());
  191. ASSERT_TRUE(initial_info.player_status().has_player_state());
  192. EXPECT_EQ(initial_info.player_status().player_state(),
  193. fuchsia::media::sessions2::PlayerState::PAUSED);
  194. ASSERT_TRUE(initial_info.has_metadata());
  195. std::map<std::string, std::string> received_metadata;
  196. for (auto& property : initial_info.metadata().properties)
  197. received_metadata[property.label] = property.value;
  198. EXPECT_EQ(received_metadata[fuchsia::media::METADATA_LABEL_TITLE],
  199. kExpectedTitle);
  200. EXPECT_EQ(received_metadata[fuchsia::media::METADATA_LABEL_ARTIST],
  201. kExpectedArtist);
  202. EXPECT_EQ(received_metadata[fuchsia::media::METADATA_LABEL_ALBUM],
  203. kExpectedAlbum);
  204. EXPECT_EQ(received_metadata[fuchsia::media::METADATA_SOURCE_TITLE],
  205. kExpectedSourceTitle);
  206. ASSERT_TRUE(initial_info.has_player_capabilities());
  207. ASSERT_TRUE(initial_info.player_capabilities().has_flags());
  208. const fuchsia::media::sessions2::PlayerCapabilityFlags received_flags =
  209. initial_info.player_capabilities().flags();
  210. EXPECT_TRUE(HasFlag(received_flags,
  211. fuchsia::media::sessions2::PlayerCapabilityFlags::PLAY));
  212. EXPECT_TRUE(HasFlag(
  213. received_flags,
  214. fuchsia::media::sessions2::PlayerCapabilityFlags::CHANGE_TO_NEXT_ITEM));
  215. EXPECT_FALSE(HasFlag(received_flags,
  216. fuchsia::media::sessions2::PlayerCapabilityFlags::SEEK));
  217. EXPECT_FALSE(HasFlag(
  218. received_flags, fuchsia::media::sessions2::PlayerCapabilityFlags::PAUSE));
  219. }
  220. // Verify that WatchInfoChange() waits for the next change to the session state
  221. // before returning.
  222. TEST_F(MediaPlayerImplTest, WatchInfoChangeWaitsForNextChange) {
  223. player_impl_ =
  224. std::make_unique<MediaPlayerImpl>(&fake_session_, player_.NewRequest(),
  225. MakeExpectedNotRunClosure(FROM_HERE));
  226. // Start watching, which will connect the observer, and send some initial
  227. // state so that WatchInfoChange() will return.
  228. base::RunLoop player_state_loop;
  229. std::function<void(fuchsia::media::sessions2::PlayerInfoDelta)>
  230. watch_for_player_state =
  231. [this, &watch_for_player_state, &player_state_loop](
  232. fuchsia::media::sessions2::PlayerInfoDelta delta) {
  233. if (!delta.has_player_status() ||
  234. !delta.player_status().has_player_state()) {
  235. player_->WatchInfoChange(watch_for_player_state);
  236. return;
  237. }
  238. EXPECT_EQ(delta.player_status().player_state(),
  239. fuchsia::media::sessions2::PlayerState::PAUSED);
  240. player_state_loop.Quit();
  241. };
  242. player_->WatchInfoChange(watch_for_player_state);
  243. // Pump the message loop to process the first WatchInfoChange() call.
  244. base::RunLoop().RunUntilIdle();
  245. ASSERT_TRUE(fake_session_.observer());
  246. // Set an initial player state for the session, and wait for it.
  247. media_session::mojom::MediaSessionInfoPtr info(
  248. media_session::mojom::MediaSessionInfo::New());
  249. info->state =
  250. media_session::mojom::MediaSessionInfo::SessionState::kSuspended;
  251. fake_session_.observer()->MediaSessionInfoChanged(std::move(info));
  252. player_state_loop.Run();
  253. // Calling WatchInfoChange() now should succeed, but not immediately return
  254. // any new data.
  255. base::RunLoop change_loop;
  256. absl::optional<fuchsia::media::sessions2::PlayerState> state_after_change;
  257. player_->WatchInfoChange(
  258. [&change_loop,
  259. &state_after_change](fuchsia::media::sessions2::PlayerInfoDelta delta) {
  260. ASSERT_TRUE(delta.has_player_status());
  261. ASSERT_TRUE(delta.player_status().has_player_state());
  262. state_after_change.emplace(delta.player_status().player_state());
  263. change_loop.Quit();
  264. });
  265. base::RunLoop().RunUntilIdle();
  266. EXPECT_FALSE(state_after_change.has_value());
  267. // Generate a player status change, which should cause WatchInfoChange() to
  268. // return.
  269. info = media_session::mojom::MediaSessionInfo::New();
  270. info->state = media_session::mojom::MediaSessionInfo::SessionState::kActive;
  271. fake_session_.observer()->MediaSessionInfoChanged(std::move(info));
  272. change_loop.Run();
  273. ASSERT_TRUE(state_after_change.has_value());
  274. EXPECT_EQ(*state_after_change,
  275. fuchsia::media::sessions2::PlayerState::PLAYING);
  276. }
  277. // Verify that each of the fire-and-forget playback controls are routed to the
  278. // expected MediaSession APIs.
  279. TEST_F(MediaPlayerImplTest, PlaybackControls) {
  280. testing::InSequence sequence;
  281. EXPECT_CALL(fake_session_, Resume(content::MediaSession::SuspendType::kUI));
  282. EXPECT_CALL(fake_session_, Suspend(content::MediaSession::SuspendType::kUI));
  283. EXPECT_CALL(fake_session_, Suspend(content::MediaSession::SuspendType::kUI));
  284. EXPECT_CALL(fake_session_, SeekTo(base::TimeDelta()));
  285. base::TimeDelta skip_forward_delta_;
  286. EXPECT_CALL(fake_session_, Seek(testing::_))
  287. .WillOnce(testing::SaveArg<0>(&skip_forward_delta_));
  288. base::TimeDelta skip_reverse_delta_;
  289. EXPECT_CALL(fake_session_, Seek(testing::_))
  290. .WillOnce(testing::SaveArg<0>(&skip_reverse_delta_));
  291. EXPECT_CALL(fake_session_, NextTrack());
  292. EXPECT_CALL(fake_session_, PreviousTrack());
  293. player_impl_ =
  294. std::make_unique<MediaPlayerImpl>(&fake_session_, player_.NewRequest(),
  295. MakeExpectedNotRunClosure(FROM_HERE));
  296. player_->Play();
  297. player_->Pause();
  298. player_->Stop();
  299. player_->Seek(0);
  300. player_->SkipForward();
  301. player_->SkipReverse();
  302. player_->NextItem();
  303. player_->PrevItem();
  304. // Pump the message loop to process each of the calls.
  305. base::RunLoop().RunUntilIdle();
  306. EXPECT_EQ(skip_forward_delta_, -skip_reverse_delta_);
  307. }