// Copyright 2016 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 "media/blink/webmediaplayer_impl.h" #include #include #include "base/bind.h" #include "base/callback_helpers.h" #include "base/command_line.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/strings/string_number_conversions.h" #include "base/task_runner_util.h" #include "base/test/mock_callback.h" #include "base/test/scoped_feature_list.h" #include "base/test/scoped_task_environment.h" #include "base/test/simple_test_tick_clock.h" #include "base/threading/thread.h" #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" #include "cc/blink/web_layer_impl.h" #include "components/viz/test/test_context_provider.h" #include "media/base/decoder_buffer.h" #include "media/base/gmock_callback_support.h" #include "media/base/media_log.h" #include "media/base/media_switches.h" #include "media/base/mock_audio_renderer_sink.h" #include "media/base/mock_media_log.h" #include "media/base/test_data_util.h" #include "media/base/test_helpers.h" #include "media/blink/mock_resource_fetch_context.h" #include "media/blink/mock_webassociatedurlloader.h" #include "media/blink/resource_multibuffer_data_provider.h" #include "media/blink/webmediaplayer_delegate.h" #include "media/blink/webmediaplayer_params.h" #include "media/mojo/services/media_metrics_provider.h" #include "media/mojo/services/video_decode_stats_recorder.h" #include "media/mojo/services/watch_time_recorder.h" #include "media/renderers/default_renderer_factory.h" #include "mojo/public/cpp/bindings/strong_binding.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h" #include "third_party/blink/public/platform/scheduler/web_main_thread_scheduler.h" #include "third_party/blink/public/platform/web_fullscreen_video_status.h" #include "third_party/blink/public/platform/web_media_player.h" #include "third_party/blink/public/platform/web_media_player_client.h" #include "third_party/blink/public/platform/web_media_player_source.h" #include "third_party/blink/public/platform/web_security_origin.h" #include "third_party/blink/public/platform/web_size.h" #include "third_party/blink/public/platform/web_surface_layer_bridge.h" #include "third_party/blink/public/platform/web_thread.h" #include "third_party/blink/public/platform/web_url_response.h" #include "third_party/blink/public/web/blink.h" #include "third_party/blink/public/web/web_frame_client.h" #include "third_party/blink/public/web/web_local_frame.h" #include "third_party/blink/public/web/web_scoped_user_gesture.h" #include "third_party/blink/public/web/web_view.h" #include "url/gurl.h" #if defined(OS_ANDROID) #include "media/blink/renderer_media_player_interface.h" #endif using ::testing::AnyNumber; using ::testing::Eq; using ::testing::InSequence; using ::testing::Invoke; using ::testing::NiceMock; using ::testing::NotNull; using ::testing::Return; using ::testing::ReturnRef; using ::testing::StrictMock; using ::testing::_; namespace media { constexpr char kAudioOnlyTestFile[] = "sfx-opus-441.webm"; // Specify different values for testing. const base::TimeDelta kMaxKeyframeDistanceToDisableBackgroundVideo = base::TimeDelta::FromSeconds(5); const base::TimeDelta kMaxKeyframeDistanceToDisableBackgroundVideoMSE = base::TimeDelta::FromSeconds(10); MATCHER(WmpiDestroyed, "") { return CONTAINS_STRING(arg, "WEBMEDIAPLAYER_DESTROYED {}"); } MATCHER_P2(PlaybackRateChanged, old_rate_string, new_rate_string, "") { return CONTAINS_STRING(arg, "Effective playback rate changed from " + std::string(old_rate_string) + " to " + std::string(new_rate_string)); } #if defined(OS_ANDROID) class MockRendererMediaPlayerManager : public RendererMediaPlayerManagerInterface { public: MOCK_METHOD7(Initialize, void(MediaPlayerHostMsg_Initialize_Type type, int player_id, const GURL& url, const GURL& site_for_cookies, const GURL& frame_url, bool allow_credentials, int delegate_id)); MOCK_METHOD1(Start, void(int player_id)); MOCK_METHOD2(Pause, void(int player_id, bool is_media_related_action)); MOCK_METHOD2(Seek, void(int player_id, base::TimeDelta time)); MOCK_METHOD2(SetVolume, void(int player_id, double volume)); MOCK_METHOD2(SetPoster, void(int player_id, const GURL& poster)); MOCK_METHOD1(SuspendAndReleaseResources, void(int player_id)); MOCK_METHOD1(DestroyPlayer, void(int player_id)); MOCK_METHOD1(RequestRemotePlayback, void(int player_id)); MOCK_METHOD1(RequestRemotePlaybackControl, void(int player_id)); MOCK_METHOD1(RequestRemotePlaybackStop, void(int player_id)); MOCK_METHOD1(RegisterMediaPlayer, int(RendererMediaPlayerInterface* player)); MOCK_METHOD1(UnregisterMediaPlayer, void(int player_id)); }; #endif class MockWebMediaPlayerClient : public blink::WebMediaPlayerClient { public: MockWebMediaPlayerClient() = default; MOCK_METHOD0(NetworkStateChanged, void()); MOCK_METHOD0(ReadyStateChanged, void()); MOCK_METHOD0(TimeChanged, void()); MOCK_METHOD0(Repaint, void()); MOCK_METHOD0(DurationChanged, void()); MOCK_METHOD0(SizeChanged, void()); MOCK_METHOD0(PlaybackStateChanged, void()); MOCK_METHOD1(SetWebLayer, void(blink::WebLayer*)); MOCK_METHOD5(AddAudioTrack, blink::WebMediaPlayer::TrackId( const blink::WebString&, blink::WebMediaPlayerClient::AudioTrackKind, const blink::WebString&, const blink::WebString&, bool)); MOCK_METHOD1(RemoveAudioTrack, void(blink::WebMediaPlayer::TrackId)); MOCK_METHOD5(AddVideoTrack, blink::WebMediaPlayer::TrackId( const blink::WebString&, blink::WebMediaPlayerClient::VideoTrackKind, const blink::WebString&, const blink::WebString&, bool)); MOCK_METHOD1(RemoveVideoTrack, void(blink::WebMediaPlayer::TrackId)); MOCK_METHOD1(AddTextTrack, void(blink::WebInbandTextTrack*)); MOCK_METHOD1(RemoveTextTrack, void(blink::WebInbandTextTrack*)); MOCK_METHOD1(MediaSourceOpened, void(blink::WebMediaSource*)); MOCK_METHOD1(RequestSeek, void(double)); MOCK_METHOD1(RemoteRouteAvailabilityChanged, void(blink::WebRemotePlaybackAvailability)); MOCK_METHOD0(ConnectedToRemoteDevice, void()); MOCK_METHOD0(DisconnectedFromRemoteDevice, void()); MOCK_METHOD0(CancelledRemotePlaybackRequest, void()); MOCK_METHOD0(RemotePlaybackStarted, void()); MOCK_METHOD2(RemotePlaybackCompatibilityChanged, void(const blink::WebURL&, bool)); MOCK_METHOD1(OnBecamePersistentVideo, void(bool)); MOCK_METHOD0(IsAutoplayingMuted, bool()); MOCK_METHOD0(HasSelectedVideoTrack, bool()); MOCK_METHOD0(GetSelectedVideoTrackId, blink::WebMediaPlayer::TrackId()); MOCK_METHOD0(HasNativeControls, bool()); MOCK_METHOD0(IsAudioElement, bool()); MOCK_CONST_METHOD0(DisplayType, blink::WebMediaPlayer::DisplayType()); MOCK_METHOD1(ActivateViewportIntersectionMonitoring, void(bool)); MOCK_METHOD1(MediaRemotingStarted, void(const blink::WebString&)); MOCK_METHOD1(MediaRemotingStopped, void(blink::WebLocalizedString::Name)); MOCK_METHOD0(PictureInPictureStarted, void()); MOCK_METHOD0(PictureInPictureStopped, void()); MOCK_METHOD0(IsInPictureInPictureMode, bool()); MOCK_CONST_METHOD0(CouldPlayIfEnoughData, bool()); void set_is_autoplaying_muted(bool value) { is_autoplaying_muted_ = value; } bool is_autoplaying_muted_ = false; private: DISALLOW_COPY_AND_ASSIGN(MockWebMediaPlayerClient); }; class MockWebMediaPlayerDelegate : public WebMediaPlayerDelegate { public: MockWebMediaPlayerDelegate() = default; ~MockWebMediaPlayerDelegate() = default; // WebMediaPlayerDelegate implementation. int AddObserver(Observer* observer) override { DCHECK_EQ(nullptr, observer_); observer_ = observer; return player_id_; } void RemoveObserver(int player_id) override { DCHECK_EQ(player_id_, player_id); observer_ = nullptr; } MOCK_METHOD4(DidPlay, void(int, bool, bool, MediaContentType)); MOCK_METHOD1(DidPause, void(int)); MOCK_METHOD1(PlayerGone, void(int)); void SetIdle(int player_id, bool is_idle) override { DCHECK_EQ(player_id_, player_id); is_idle_ = is_idle; is_stale_ &= is_idle; } bool IsIdle(int player_id) override { DCHECK_EQ(player_id_, player_id); return is_idle_; } void DidPlayerMutedStatusChange(int delegate_id, bool muted) override { DCHECK_EQ(player_id_, delegate_id); } MOCK_METHOD1(DidPictureInPictureSourceChange, void(int)); MOCK_METHOD1(DidPictureInPictureModeEnd, void(int)); void ClearStaleFlag(int player_id) override { DCHECK_EQ(player_id_, player_id); is_stale_ = false; } bool IsStale(int player_id) override { DCHECK_EQ(player_id_, player_id); return is_stale_; } void SetIsEffectivelyFullscreen( int player_id, blink::WebFullscreenVideoStatus fullscreen_video_status) override { DCHECK_EQ(player_id_, player_id); } void DidPlayerSizeChange(int player_id, const gfx::Size& size) override { DCHECK_EQ(player_id_, player_id); } bool IsFrameHidden() override { return is_hidden_; } bool IsFrameClosed() override { return is_closed_; } void SetIdleForTesting(bool is_idle) { is_idle_ = is_idle; } void SetStaleForTesting(bool is_stale) { is_idle_ |= is_stale; is_stale_ = is_stale; } // Returns true if the player does in fact expire. bool ExpireForTesting() { if (is_idle_ && !is_stale_) { is_stale_ = true; observer_->OnIdleTimeout(); } return is_stale_; } void SetFrameHiddenForTesting(bool is_hidden) { is_hidden_ = is_hidden; } void SetFrameClosedForTesting(bool is_closed) { is_closed_ = is_closed; } int player_id() { return player_id_; } private: Observer* observer_ = nullptr; int player_id_ = 1234; bool is_idle_ = false; bool is_stale_ = false; bool is_hidden_ = false; bool is_closed_ = false; }; class MockSurfaceLayerBridge : public blink::WebSurfaceLayerBridge { public: MOCK_CONST_METHOD0(GetWebLayer, blink::WebLayer*()); MOCK_CONST_METHOD0(GetFrameSinkId, const viz::FrameSinkId&()); }; class MockVideoFrameCompositor : public VideoFrameCompositor { public: MockVideoFrameCompositor( const scoped_refptr& task_runner) : VideoFrameCompositor(task_runner, nullptr) {} ~MockVideoFrameCompositor() = default; // MOCK_METHOD doesn't like OnceCallback. void SetOnNewProcessedFrameCallback(OnNewProcessedFrameCB cb) {} MOCK_METHOD0(GetCurrentFrameAndUpdateIfStale, scoped_refptr()); MOCK_METHOD2(EnableSubmission, void(const viz::FrameSinkId&, media::VideoRotation)); }; // We must use a custom blink::Platform that ensures the main thread scheduler // knows about the ScopedTaskEnvironment; the default one is not setup with a // ScopedTaskEnvironment to allow other tests to use mock MessageLoops. class BlinkPlatformWithTaskEnvironment : public blink::Platform { public: BlinkPlatformWithTaskEnvironment() : original_platform_(blink::Platform::Current()), scoped_task_environment_( std::make_unique()), main_thread_scheduler_( blink::scheduler::CreateWebMainThreadSchedulerForTests()), main_thread_(main_thread_scheduler_->CreateMainThread()) { DCHECK(original_platform_); blink::Platform::SetCurrentPlatformForTesting(this); } ~BlinkPlatformWithTaskEnvironment() override { main_thread_scheduler_->Shutdown(); main_thread_.reset(); main_thread_scheduler_.reset(); scoped_task_environment_.reset(); DCHECK_EQ(this, blink::Platform::Current()); blink::Platform::SetCurrentPlatformForTesting(original_platform_); } blink::WebThread* CurrentThread() override { EXPECT_TRUE(main_thread_->IsCurrentThread()); return main_thread_.get(); } scoped_refptr task_runner() { return scoped_task_environment_->GetMainThreadTaskRunner(); } private: blink::Platform* const original_platform_; // Must be constructed first; otherwise the main thread may stop pumping. std::unique_ptr scoped_task_environment_; std::unique_ptr main_thread_scheduler_; std::unique_ptr main_thread_; }; class WebMediaPlayerImplTest : public testing::Test { public: WebMediaPlayerImplTest() : media_thread_("MediaThreadForTest"), web_view_( blink::WebView::Create(nullptr, blink::mojom::PageVisibilityState::kVisible, nullptr)), web_local_frame_( blink::WebLocalFrame::CreateMainFrame(web_view_, &web_frame_client_, nullptr, nullptr)), context_provider_(viz::TestContextProvider::Create()), audio_parameters_(TestAudioParameters::Normal()) { media_thread_.StartAndWaitForTesting(); } void InitializeWebMediaPlayerImpl() { auto media_log = std::make_unique>(); surface_layer_bridge_ = std::make_unique>(); surface_layer_bridge_ptr_ = surface_layer_bridge_.get(); // Retain a raw pointer to |media_log| for use by tests. Meanwhile, give its // ownership to |wmpi_|. Reject attempts to reinitialize to prevent orphaned // expectations on previous |media_log_|. ASSERT_FALSE(media_log_) << "Reinitialization of media_log_ is disallowed"; media_log_ = media_log.get(); auto factory_selector = std::make_unique(); factory_selector->AddFactory( RendererFactorySelector::FactoryType::DEFAULT, std::make_unique( media_log.get(), nullptr, DefaultRendererFactory::GetGpuFactoriesCB())); factory_selector->SetBaseFactoryType( RendererFactorySelector::FactoryType::DEFAULT); mojom::MediaMetricsProviderPtr provider; MediaMetricsProvider::Create(nullptr, mojo::MakeRequest(&provider)); // Initialize provider since none of the tests below actually go through the // full loading/pipeline initialize phase. If this ever changes the provider // will start DCHECK failing. provider->Initialize(false, false, url::Origin()); audio_sink_ = base::WrapRefCounted(new NiceMock()); url_index_.reset(new UrlIndex(&mock_resource_fetch_context_)); auto params = std::make_unique( std::move(media_log), WebMediaPlayerParams::DeferLoadCB(), audio_sink_, media_thread_.task_runner(), platform_.task_runner(), platform_.task_runner(), media_thread_.task_runner(), base::BindRepeating(&WebMediaPlayerImplTest::OnAdjustAllocatedMemory, base::Unretained(this)), nullptr, nullptr, RequestRoutingTokenCallback(), nullptr, kMaxKeyframeDistanceToDisableBackgroundVideo, kMaxKeyframeDistanceToDisableBackgroundVideoMSE, false, false, std::move(provider), base::BindRepeating( &WebMediaPlayerImplTest::CreateMockSurfaceLayerBridge, base::Unretained(this)), viz::TestContextProvider::Create(), base::FeatureList::IsEnabled(media::kUseSurfaceLayerForVideo), base::BindRepeating(pip_surface_info_cb_.Get()), base::BindRepeating(exit_pip_cb_.Get())); auto compositor = std::make_unique>( params->video_frame_compositor_task_runner()); compositor_ = compositor.get(); wmpi_ = std::make_unique( web_local_frame_, &client_, nullptr, &delegate_, std::move(factory_selector), url_index_.get(), std::move(compositor), std::move(params)); #if defined(OS_ANDROID) wmpi_->SetMediaPlayerManager(&mock_media_player_manager_); #endif } ~WebMediaPlayerImplTest() override { EXPECT_CALL(client_, SetWebLayer(nullptr)); EXPECT_CALL(client_, MediaRemotingStopped(_)); // Destruct WebMediaPlayerImpl and pump the message loop to ensure that // objects passed to the message loop for destruction are released. // // NOTE: This should be done before any other member variables are // destructed since WMPI may reference them during destruction. wmpi_.reset(); base::RunLoop().RunUntilIdle(); web_view_->Close(); } protected: std::unique_ptr CreateMockSurfaceLayerBridge( blink::WebSurfaceLayerBridgeObserver*) { return std::move(surface_layer_bridge_); } int64_t OnAdjustAllocatedMemory(int64_t delta) { reported_memory_ += delta; return 0; } void SetNetworkState(blink::WebMediaPlayer::NetworkState state) { EXPECT_CALL(client_, NetworkStateChanged()); wmpi_->SetNetworkState(state); } void SetReadyState(blink::WebMediaPlayer::ReadyState state) { EXPECT_CALL(client_, ReadyStateChanged()); wmpi_->SetReadyState(state); } void SetDuration(base::TimeDelta value) { wmpi_->SetPipelineMediaDurationForTest(value); } base::TimeDelta GetCurrentTimeInternal() { return wmpi_->GetCurrentTimeInternal(); } void SetPaused(bool is_paused) { wmpi_->paused_ = is_paused; } void SetSeeking(bool is_seeking) { wmpi_->seeking_ = is_seeking; } void SetEnded(bool is_ended) { wmpi_->ended_ = is_ended; } void SetTickClock(const base::TickClock* clock) { wmpi_->SetTickClockForTest(clock); } void SetFullscreen(bool is_fullscreen) { wmpi_->overlay_enabled_ = is_fullscreen; } void SetMetadata(bool has_audio, bool has_video) { wmpi_->SetNetworkState(blink::WebMediaPlayer::kNetworkStateLoaded); EXPECT_CALL(client_, ReadyStateChanged()); wmpi_->SetReadyState(blink::WebMediaPlayer::kReadyStateHaveMetadata); EXPECT_CALL(client_, IsAutoplayingMuted()) .WillRepeatedly(Return(client_.is_autoplaying_muted_)); wmpi_->pipeline_metadata_.has_audio = has_audio; wmpi_->pipeline_metadata_.has_video = has_video; if (has_video) wmpi_->pipeline_metadata_.video_decoder_config = TestVideoConfig::Normal(); if (has_audio) wmpi_->pipeline_metadata_.audio_decoder_config = TestAudioConfig::Normal(); } void SetError(PipelineStatus status = PIPELINE_ERROR_DECODE) { wmpi_->OnError(status); } void OnMetadata(PipelineMetadata metadata) { wmpi_->OnMetadata(metadata); } void OnVideoNaturalSizeChange(const gfx::Size& size) { wmpi_->OnVideoNaturalSizeChange(size); } WebMediaPlayerImpl::PlayState ComputePlayState() { EXPECT_CALL(client_, IsAutoplayingMuted()) .WillRepeatedly(Return(client_.is_autoplaying_muted_)); return wmpi_->UpdatePlayState_ComputePlayState(false, true, false, false); } WebMediaPlayerImpl::PlayState ComputePlayState_FrameHidden() { EXPECT_CALL(client_, IsAutoplayingMuted()) .WillRepeatedly(Return(client_.is_autoplaying_muted_)); return wmpi_->UpdatePlayState_ComputePlayState(false, true, false, true); } WebMediaPlayerImpl::PlayState ComputePlayState_Suspended() { EXPECT_CALL(client_, IsAutoplayingMuted()) .WillRepeatedly(Return(client_.is_autoplaying_muted_)); return wmpi_->UpdatePlayState_ComputePlayState(false, true, true, false); } WebMediaPlayerImpl::PlayState ComputePlayState_Remote() { EXPECT_CALL(client_, IsAutoplayingMuted()) .WillRepeatedly(Return(client_.is_autoplaying_muted_)); return wmpi_->UpdatePlayState_ComputePlayState(true, true, false, false); } WebMediaPlayerImpl::PlayState ComputePlayState_BackgroundedStreaming() { EXPECT_CALL(client_, IsAutoplayingMuted()) .WillRepeatedly(Return(client_.is_autoplaying_muted_)); return wmpi_->UpdatePlayState_ComputePlayState(false, false, false, true); } bool IsSuspended() { return wmpi_->pipeline_controller_.IsSuspended(); } int64_t GetDataSourceMemoryUsage() const { return wmpi_->data_source_->GetMemoryUsage(); } void AddBufferedRanges() { wmpi_->buffered_data_source_host_.AddBufferedByteRange(0, 1); } void SetDelegateState(WebMediaPlayerImpl::DelegateState state) { wmpi_->SetDelegateState(state, false); } void SetUpMediaSuspend(bool enable) { #if defined(OS_ANDROID) if (!enable) { base::CommandLine::ForCurrentProcess()->AppendSwitch( switches::kDisableMediaSuspend); } #else if (enable) { base::CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableMediaSuspend); } #endif } bool IsVideoLockedWhenPausedWhenHidden() const { return wmpi_->video_locked_when_paused_when_hidden_; } void BackgroundPlayer() { delegate_.SetFrameHiddenForTesting(true); delegate_.SetFrameClosedForTesting(false); wmpi_->OnFrameHidden(); } void ForegroundPlayer() { delegate_.SetFrameHiddenForTesting(false); delegate_.SetFrameClosedForTesting(false); wmpi_->OnFrameShown(); } void Play() { wmpi_->Play(); } void Pause() { wmpi_->Pause(); } void ScheduleIdlePauseTimer() { wmpi_->ScheduleIdlePauseTimer(); } bool IsIdlePauseTimerRunning() { return wmpi_->background_pause_timer_.IsRunning(); } void SetSuspendState(bool is_suspended) { wmpi_->SetSuspendState(is_suspended); } void SetLoadType(blink::WebMediaPlayer::LoadType load_type) { wmpi_->load_type_ = load_type; } bool IsVideoTrackDisabled() const { return wmpi_->video_track_disabled_; } bool IsDisableVideoTrackPending() const { return !wmpi_->update_background_status_cb_.IsCancelled(); } gfx::Size GetNaturalSize() const { return wmpi_->pipeline_metadata_.natural_size; } void LoadAndWaitForMetadata(std::string data_file) { // URL doesn't matter, it's value is unknown to the underlying demuxer. const GURL kTestURL("file://example.com/sample.webm"); // This block sets up a fetch context which ultimately provides us a pointer // to the WebAssociatedURLLoaderClient handed out by the DataSource after it // requests loading of a resource. We then use that client as if we are the // network stack and "serve" an in memory file to the DataSource. blink::WebAssociatedURLLoaderClient* client = nullptr; EXPECT_CALL(mock_resource_fetch_context_, CreateUrlLoader(_)) .WillRepeatedly( Invoke([&client](const blink::WebAssociatedURLLoaderOptions&) { auto a = std::make_unique>(); EXPECT_CALL(*a, LoadAsynchronously(_, _)) .WillRepeatedly(testing::SaveArg<1>(&client)); return a; })); wmpi_->Load(blink::WebMediaPlayer::kLoadTypeURL, blink::WebMediaPlayerSource(blink::WebURL(kTestURL)), blink::WebMediaPlayer::kCORSModeUnspecified); base::RunLoop().RunUntilIdle(); // Load a real media file into memory. scoped_refptr data = ReadTestDataFile(data_file); // "Serve" the file to the DataSource. Note: We respond with 200 okay, which // will prevent range requests or partial responses from being used. blink::WebURLResponse response(kTestURL); response.SetHTTPHeaderField( blink::WebString::FromUTF8("Content-Length"), blink::WebString::FromUTF8(base::NumberToString(data->data_size()))); response.SetExpectedContentLength(data->data_size()); response.SetHTTPStatusCode(200); client->DidReceiveResponse(response); // Copy over the file data and indicate that's everything. client->DidReceiveData(reinterpret_cast(data->data()), data->data_size()); client->DidFinishLoading(0); // This runs until we reach the have metadata state. base::RunLoop loop; EXPECT_CALL(client_, ReadyStateChanged()) .WillOnce(RunClosure(loop.QuitClosure())); loop.Run(); // Verify we made it to pipeline startup. EXPECT_EQ(blink::WebMediaPlayer::kReadyStateHaveMetadata, wmpi_->GetReadyState()); EXPECT_TRUE(wmpi_->data_source_); EXPECT_TRUE(wmpi_->demuxer_); EXPECT_TRUE(wmpi_->seeking_); } BlinkPlatformWithTaskEnvironment platform_; // "Media" thread. This is necessary because WMPI destruction waits on a // WaitableEvent. base::Thread media_thread_; // Blink state. blink::WebFrameClient web_frame_client_; blink::WebView* web_view_; blink::WebLocalFrame* web_local_frame_; scoped_refptr context_provider_; StrictMock* compositor_; scoped_refptr> audio_sink_; MockResourceFetchContext mock_resource_fetch_context_; std::unique_ptr url_index_; // Audio hardware configuration. AudioParameters audio_parameters_; // The client interface used by |wmpi_|. NiceMock client_; #if defined(OS_ANDROID) NiceMock mock_media_player_manager_; #endif viz::FrameSinkId frame_sink_id_ = viz::FrameSinkId(1, 1); viz::LocalSurfaceId local_surface_id_ = viz::LocalSurfaceId(11, base::UnguessableToken::Deserialize(0x111111, 0)); viz::SurfaceId surface_id_ = viz::SurfaceId(frame_sink_id_, local_surface_id_); NiceMock delegate_; std::unique_ptr> surface_layer_bridge_; StrictMock* surface_layer_bridge_ptr_ = nullptr; // Only valid once set by InitializeWebMediaPlayerImpl(), this is for // verifying a subset of potential media logs. NiceMock* media_log_ = nullptr; // Total memory in bytes allocated by the WebMediaPlayerImpl instance. int64_t reported_memory_ = 0; // The WebMediaPlayerImpl instance under test. std::unique_ptr wmpi_; // Callback used for updating Picture-in-Picture about new Surface info. base::MockCallback pip_surface_info_cb_; base::MockCallback exit_pip_cb_; private: DISALLOW_COPY_AND_ASSIGN(WebMediaPlayerImplTest); }; TEST_F(WebMediaPlayerImplTest, ConstructAndDestroy) { InitializeWebMediaPlayerImpl(); EXPECT_FALSE(IsSuspended()); } // Verify LoadAndWaitForMetadata() functions without issue. TEST_F(WebMediaPlayerImplTest, LoadAndDestroy) { InitializeWebMediaPlayerImpl(); EXPECT_FALSE(IsSuspended()); LoadAndWaitForMetadata(kAudioOnlyTestFile); EXPECT_FALSE(IsSuspended()); base::RunLoop().RunUntilIdle(); // The data source contains the entire file, so subtract it from the memory // usage to ensure we're getting audio buffer and demuxer usage too. const int64_t data_source_size = GetDataSourceMemoryUsage(); EXPECT_GT(data_source_size, 0); EXPECT_GT(reported_memory_ - data_source_size, 0); } // Verify that preload=metadata suspend works properly. // Flaky on Linux MSan and TSan. http://crbug.com/831566. #if defined(OS_LINUX) && \ (defined(MEMORY_SANITIZER) || defined(THREAD_SANITIZER)) #define MAYBE_LoadPreloadMetadataSuspend DISABLED_LoadPreloadMetadataSuspend #else #define MAYBE_LoadPreloadMetadataSuspend LoadPreloadMetadataSuspend #endif TEST_F(WebMediaPlayerImplTest, MAYBE_LoadPreloadMetadataSuspend) { base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndEnableFeature(media::kPreloadMetadataSuspend); InitializeWebMediaPlayerImpl(); EXPECT_CALL(client_, CouldPlayIfEnoughData()).WillRepeatedly(Return(false)); wmpi_->SetPreload(blink::WebMediaPlayer::kPreloadMetaData); LoadAndWaitForMetadata(kAudioOnlyTestFile); testing::Mock::VerifyAndClearExpectations(&client_); EXPECT_CALL(client_, ReadyStateChanged()).Times(AnyNumber()); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(IsSuspended()); // The data source contains the entire file, so subtract it from the memory // usage to ensure there's no other memory usage. const int64_t data_source_size = GetDataSourceMemoryUsage(); EXPECT_GT(data_source_size, 0); EXPECT_EQ(reported_memory_ - data_source_size, 0); } // Verify that preload=metadata suspend video w/ poster uses zero video memory. TEST_F(WebMediaPlayerImplTest, LoadPreloadMetadataSuspendNoVideoMemoryUsage) { base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndEnableFeature(media::kPreloadMetadataSuspend); InitializeWebMediaPlayerImpl(); EXPECT_CALL(client_, CouldPlayIfEnoughData()).WillRepeatedly(Return(false)); wmpi_->SetPreload(blink::WebMediaPlayer::kPreloadMetaData); wmpi_->SetPoster(blink::WebURL(GURL("file://example.com/sample.jpg"))); LoadAndWaitForMetadata("bear-320x240-video-only.webm"); testing::Mock::VerifyAndClearExpectations(&client_); EXPECT_CALL(client_, ReadyStateChanged()).Times(AnyNumber()); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(IsSuspended()); // The data source contains the entire file, so subtract it from the memory // usage to ensure there's no other memory usage. const int64_t data_source_size = GetDataSourceMemoryUsage(); EXPECT_GT(data_source_size, 0); EXPECT_EQ(reported_memory_ - data_source_size, 0); } // Verify that preload=metadata suspend is aborted if we know the element will // play as soon as we reach kReadyStateHaveFutureData. TEST_F(WebMediaPlayerImplTest, LoadPreloadMetadataSuspendCouldPlay) { base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndEnableFeature(media::kPreloadMetadataSuspend); InitializeWebMediaPlayerImpl(); EXPECT_CALL(client_, CouldPlayIfEnoughData()).WillRepeatedly(Return(true)); wmpi_->SetPreload(blink::WebMediaPlayer::kPreloadMetaData); LoadAndWaitForMetadata(kAudioOnlyTestFile); testing::Mock::VerifyAndClearExpectations(&client_); EXPECT_CALL(client_, ReadyStateChanged()).Times(AnyNumber()); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(IsSuspended()); } TEST_F(WebMediaPlayerImplTest, IdleSuspendBeforeLoadingBegins) { InitializeWebMediaPlayerImpl(); EXPECT_FALSE(delegate_.ExpireForTesting()); } TEST_F(WebMediaPlayerImplTest, IdleSuspendIsDisabledIfLoadingProgressedRecently) { InitializeWebMediaPlayerImpl(); base::SimpleTestTickClock clock; clock.Advance(base::TimeDelta::FromSeconds(1)); SetTickClock(&clock); AddBufferedRanges(); wmpi_->DidLoadingProgress(); // Advance less than the loading timeout. clock.Advance(base::TimeDelta::FromSeconds(1)); EXPECT_FALSE(delegate_.ExpireForTesting()); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(IsSuspended()); } TEST_F(WebMediaPlayerImplTest, IdleSuspendIsEnabledIfLoadingHasStalled) { InitializeWebMediaPlayerImpl(); SetNetworkState(blink::WebMediaPlayer::kNetworkStateLoading); base::SimpleTestTickClock clock; clock.Advance(base::TimeDelta::FromSeconds(1)); SetTickClock(&clock); AddBufferedRanges(); wmpi_->DidLoadingProgress(); // Advance more than the loading timeout. clock.Advance(base::TimeDelta::FromSeconds(4)); EXPECT_TRUE(delegate_.ExpireForTesting()); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(IsSuspended()); } TEST_F(WebMediaPlayerImplTest, DidLoadingProgressTriggersResume) { // Same setup as IdleSuspendIsEnabledBeforeLoadingBegins. InitializeWebMediaPlayerImpl(); SetNetworkState(blink::WebMediaPlayer::kNetworkStateLoading); EXPECT_TRUE(delegate_.ExpireForTesting()); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(IsSuspended()); // Like IdleSuspendIsDisabledIfLoadingProgressedRecently, the idle timeout // should be rejected if it hasn't been long enough. AddBufferedRanges(); wmpi_->DidLoadingProgress(); EXPECT_FALSE(delegate_.ExpireForTesting()); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(IsSuspended()); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_Constructed) { InitializeWebMediaPlayerImpl(); WebMediaPlayerImpl::PlayState state = ComputePlayState(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::GONE, state.delegate_state); EXPECT_TRUE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_FALSE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_HaveMetadata) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); WebMediaPlayerImpl::PlayState state = ComputePlayState(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::GONE, state.delegate_state); EXPECT_TRUE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_FALSE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_HaveFutureData) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); WebMediaPlayerImpl::PlayState state = ComputePlayState(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::PAUSED, state.delegate_state); EXPECT_TRUE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_FALSE(state.is_memory_reporting_enabled); } // Ensure memory reporting is not running after an error. TEST_F(WebMediaPlayerImplTest, ComputePlayState_PlayingError) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); SetPaused(false); WebMediaPlayerImpl::PlayState state = ComputePlayState(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::PLAYING, state.delegate_state); EXPECT_FALSE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_TRUE(state.is_memory_reporting_enabled); SetError(); state = ComputePlayState(); EXPECT_TRUE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_FALSE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_Playing) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); SetPaused(false); WebMediaPlayerImpl::PlayState state = ComputePlayState(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::PLAYING, state.delegate_state); EXPECT_FALSE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_TRUE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_PlayingVideoOnly) { InitializeWebMediaPlayerImpl(); SetMetadata(false, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); SetPaused(false); WebMediaPlayerImpl::PlayState state = ComputePlayState(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::PLAYING, state.delegate_state); EXPECT_FALSE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_TRUE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_Underflow) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); SetPaused(false); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveCurrentData); WebMediaPlayerImpl::PlayState state = ComputePlayState(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::PLAYING, state.delegate_state); EXPECT_FALSE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_TRUE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_FrameHidden) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); SetPaused(false); WebMediaPlayerImpl::PlayState state = ComputePlayState_FrameHidden(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::PLAYING, state.delegate_state); EXPECT_FALSE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_TRUE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_FrameHiddenAudioOnly) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); SetPaused(false); SetMetadata(true, false); WebMediaPlayerImpl::PlayState state = ComputePlayState_FrameHidden(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::PLAYING, state.delegate_state); EXPECT_FALSE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_TRUE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_FrameHiddenSuspendNoResume) { SetUpMediaSuspend(true); base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndDisableFeature(kResumeBackgroundVideo); InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); SetPaused(false); WebMediaPlayerImpl::PlayState state = ComputePlayState_FrameHidden(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::PLAYING, state.delegate_state); EXPECT_FALSE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_TRUE(state.is_memory_reporting_enabled); SetPaused(true); state = ComputePlayState_FrameHidden(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::GONE, state.delegate_state); EXPECT_TRUE(state.is_idle); EXPECT_TRUE(state.is_suspended); EXPECT_FALSE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_FrameHiddenSuspendWithResume) { SetUpMediaSuspend(true); base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndEnableFeature(kResumeBackgroundVideo); InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); SetPaused(false); WebMediaPlayerImpl::PlayState state = ComputePlayState_FrameHidden(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::PLAYING, state.delegate_state); EXPECT_FALSE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_TRUE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_FrameClosed) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); SetPaused(false); delegate_.SetFrameClosedForTesting(true); WebMediaPlayerImpl::PlayState state = ComputePlayState(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::GONE, state.delegate_state); EXPECT_TRUE(state.is_idle); EXPECT_TRUE(state.is_suspended); EXPECT_FALSE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_PausedSeek) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); SetSeeking(true); WebMediaPlayerImpl::PlayState state = ComputePlayState(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::PAUSED, state.delegate_state); EXPECT_FALSE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_TRUE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_Ended) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); SetPaused(false); SetEnded(true); // Before Blink pauses us (or seeks for looping content), the media session // should be preserved. WebMediaPlayerImpl::PlayState state; state = ComputePlayState(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::PLAYING, state.delegate_state); EXPECT_FALSE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_TRUE(state.is_memory_reporting_enabled); SetPaused(true); state = ComputePlayState(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::GONE, state.delegate_state); EXPECT_TRUE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_FALSE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_StaysSuspended) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); // Should stay suspended even though not stale or backgrounded. WebMediaPlayerImpl::PlayState state = ComputePlayState_Suspended(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::PAUSED, state.delegate_state); EXPECT_TRUE(state.is_idle); EXPECT_TRUE(state.is_suspended); EXPECT_FALSE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_Remote) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); // Remote media is always suspended. // TODO(sandersd): Decide whether this should count as idle or not. WebMediaPlayerImpl::PlayState state = ComputePlayState_Remote(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::GONE, state.delegate_state); EXPECT_TRUE(state.is_suspended); EXPECT_FALSE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_Fullscreen) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); SetFullscreen(true); SetPaused(true); delegate_.SetStaleForTesting(true); // Fullscreen media is never suspended (Android only behavior). WebMediaPlayerImpl::PlayState state = ComputePlayState(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::PAUSED, state.delegate_state); EXPECT_TRUE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_FALSE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, ComputePlayState_Streaming) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); SetPaused(true); delegate_.SetStaleForTesting(true); // Streaming media should not suspend, even if paused, stale, and // backgrounded. WebMediaPlayerImpl::PlayState state; state = ComputePlayState_BackgroundedStreaming(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::PAUSED, state.delegate_state); EXPECT_TRUE(state.is_idle); EXPECT_FALSE(state.is_suspended); EXPECT_FALSE(state.is_memory_reporting_enabled); // Streaming media should suspend when the tab is closed, regardless. delegate_.SetFrameClosedForTesting(true); state = ComputePlayState_BackgroundedStreaming(); EXPECT_EQ(WebMediaPlayerImpl::DelegateState::GONE, state.delegate_state); EXPECT_TRUE(state.is_idle); EXPECT_TRUE(state.is_suspended); EXPECT_FALSE(state.is_memory_reporting_enabled); } TEST_F(WebMediaPlayerImplTest, AutoplayMuted_StartsAndStops) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); SetPaused(false); client_.set_is_autoplaying_muted(true); EXPECT_CALL(delegate_, DidPlay(_, true, false, _)); EXPECT_CALL(client_, IsAutoplayingMuted()) .WillOnce(Return(client_.is_autoplaying_muted_)); SetDelegateState(WebMediaPlayerImpl::DelegateState::PLAYING); client_.set_is_autoplaying_muted(false); EXPECT_CALL(delegate_, DidPlay(_, true, true, _)); EXPECT_CALL(client_, IsAutoplayingMuted()) .WillOnce(Return(client_.is_autoplaying_muted_)); SetDelegateState(WebMediaPlayerImpl::DelegateState::PLAYING); } TEST_F(WebMediaPlayerImplTest, AutoplayMuted_SetVolume) { InitializeWebMediaPlayerImpl(); SetMetadata(true, true); SetReadyState(blink::WebMediaPlayer::kReadyStateHaveFutureData); SetPaused(false); client_.set_is_autoplaying_muted(true); EXPECT_CALL(delegate_, DidPlay(_, true, false, _)); EXPECT_CALL(client_, IsAutoplayingMuted()) .WillOnce(Return(client_.is_autoplaying_muted_)); SetDelegateState(WebMediaPlayerImpl::DelegateState::PLAYING); client_.set_is_autoplaying_muted(false); EXPECT_CALL(client_, IsAutoplayingMuted()) .WillOnce(Return(client_.is_autoplaying_muted_)); EXPECT_CALL(delegate_, DidPlay(_, true, true, _)); wmpi_->SetVolume(1.0); } TEST_F(WebMediaPlayerImplTest, NoStreams) { InitializeWebMediaPlayerImpl(); PipelineMetadata metadata; EXPECT_CALL(client_, SetWebLayer(_)).Times(0); if (base::FeatureList::IsEnabled(media::kUseSurfaceLayerForVideo)) { EXPECT_CALL(*surface_layer_bridge_ptr_, GetFrameSinkId()).Times(0); EXPECT_CALL(*compositor_, EnableSubmission(_, _)).Times(0); } // Nothing should happen. In particular, no assertions should fail. OnMetadata(metadata); } TEST_F(WebMediaPlayerImplTest, NaturalSizeChange) { InitializeWebMediaPlayerImpl(); PipelineMetadata metadata; metadata.has_video = true; metadata.video_decoder_config = TestVideoConfig::Normal(); metadata.natural_size = gfx::Size(320, 240); if (base::FeatureList::IsEnabled(kUseSurfaceLayerForVideo)) { EXPECT_CALL(client_, SetWebLayer(_)).Times(0); EXPECT_CALL(*surface_layer_bridge_ptr_, GetFrameSinkId()) .WillOnce(ReturnRef(frame_sink_id_)); EXPECT_CALL(*compositor_, EnableSubmission(_, _)); } else { EXPECT_CALL(client_, SetWebLayer(NotNull())); } OnMetadata(metadata); ASSERT_EQ(blink::WebSize(320, 240), wmpi_->NaturalSize()); EXPECT_CALL(client_, SizeChanged()); OnVideoNaturalSizeChange(gfx::Size(1920, 1080)); ASSERT_EQ(blink::WebSize(1920, 1080), wmpi_->NaturalSize()); } TEST_F(WebMediaPlayerImplTest, NaturalSizeChange_Rotated) { InitializeWebMediaPlayerImpl(); PipelineMetadata metadata; metadata.has_video = true; metadata.video_decoder_config = TestVideoConfig::NormalRotated(VIDEO_ROTATION_90); metadata.natural_size = gfx::Size(320, 240); if (base::FeatureList::IsEnabled(kUseSurfaceLayerForVideo)) { EXPECT_CALL(client_, SetWebLayer(_)).Times(0); EXPECT_CALL(*surface_layer_bridge_ptr_, GetFrameSinkId()) .WillOnce(ReturnRef(frame_sink_id_)); EXPECT_CALL(*compositor_, EnableSubmission(_, _)); } else { EXPECT_CALL(client_, SetWebLayer(NotNull())); } OnMetadata(metadata); ASSERT_EQ(blink::WebSize(320, 240), wmpi_->NaturalSize()); EXPECT_CALL(client_, SizeChanged()); // For 90/270deg rotations, the natural size should be transposed. OnVideoNaturalSizeChange(gfx::Size(1920, 1080)); ASSERT_EQ(blink::WebSize(1080, 1920), wmpi_->NaturalSize()); } TEST_F(WebMediaPlayerImplTest, VideoLockedWhenPausedWhenHidden) { InitializeWebMediaPlayerImpl(); // Setting metadata initializes |watch_time_reporter_| used in play(). PipelineMetadata metadata; metadata.has_video = true; metadata.video_decoder_config = TestVideoConfig::Normal(); if (base::FeatureList::IsEnabled(kUseSurfaceLayerForVideo)) { EXPECT_CALL(client_, SetWebLayer(_)).Times(0); EXPECT_CALL(*surface_layer_bridge_ptr_, GetFrameSinkId()) .WillOnce(ReturnRef(frame_sink_id_)); EXPECT_CALL(*compositor_, EnableSubmission(_, _)); } else { EXPECT_CALL(client_, SetWebLayer(NotNull())); } OnMetadata(metadata); EXPECT_FALSE(IsVideoLockedWhenPausedWhenHidden()); // Backgrounding the player sets the lock. BackgroundPlayer(); EXPECT_TRUE(IsVideoLockedWhenPausedWhenHidden()); // Play without a user gesture doesn't unlock the player. Play(); EXPECT_TRUE(IsVideoLockedWhenPausedWhenHidden()); // With a user gesture it does unlock the player. { blink::WebScopedUserGesture user_gesture(nullptr); Play(); EXPECT_FALSE(IsVideoLockedWhenPausedWhenHidden()); } // Pause without a user gesture doesn't lock the player. Pause(); EXPECT_FALSE(IsVideoLockedWhenPausedWhenHidden()); // With a user gesture, pause does lock the player. { blink::WebScopedUserGesture user_gesture(nullptr); Pause(); EXPECT_TRUE(IsVideoLockedWhenPausedWhenHidden()); } // Foregrounding the player unsets the lock. ForegroundPlayer(); EXPECT_FALSE(IsVideoLockedWhenPausedWhenHidden()); } TEST_F(WebMediaPlayerImplTest, BackgroundIdlePauseTimerDependsOnAudio) { InitializeWebMediaPlayerImpl(); SetSuspendState(true); SetPaused(false); ASSERT_TRUE(IsSuspended()); // Video-only players are not paused when suspended. SetMetadata(false, true); ScheduleIdlePauseTimer(); EXPECT_FALSE(IsIdlePauseTimerRunning()); SetMetadata(true, true); ScheduleIdlePauseTimer(); EXPECT_TRUE(IsIdlePauseTimerRunning()); } // Verifies that an infinite duration doesn't muck up GetCurrentTimeInternal. TEST_F(WebMediaPlayerImplTest, InfiniteDuration) { InitializeWebMediaPlayerImpl(); SetDuration(kInfiniteDuration); // Send metadata so we have a watch time reporter created. PipelineMetadata metadata; metadata.has_video = true; metadata.video_decoder_config = TestVideoConfig::Normal(); metadata.has_audio = true; metadata.audio_decoder_config = TestAudioConfig::Normal(); metadata.natural_size = gfx::Size(400, 400); if (base::FeatureList::IsEnabled(kUseSurfaceLayerForVideo)) { EXPECT_CALL(client_, SetWebLayer(_)).Times(0); EXPECT_CALL(*surface_layer_bridge_ptr_, GetFrameSinkId()) .WillOnce(ReturnRef(frame_sink_id_)); EXPECT_CALL(*compositor_, EnableSubmission(_, _)); } else { EXPECT_CALL(client_, SetWebLayer(NotNull())); } OnMetadata(metadata); EXPECT_EQ(std::numeric_limits::infinity(), wmpi_->Duration()); EXPECT_EQ(0, wmpi_->CurrentTime()); EXPECT_EQ(base::TimeDelta(), GetCurrentTimeInternal()); SetEnded(true); EXPECT_EQ(0, wmpi_->CurrentTime()); EXPECT_EQ(base::TimeDelta(), GetCurrentTimeInternal()); // Pause should not pick up infinity for the current time. wmpi_->Pause(); EXPECT_EQ(0, wmpi_->CurrentTime()); EXPECT_EQ(base::TimeDelta(), GetCurrentTimeInternal()); } TEST_F(WebMediaPlayerImplTest, SetContentsLayerGetsWebLayerFromBridge) { base::test::ScopedFeatureList feature_list; feature_list.InitFromCommandLine(kUseSurfaceLayerForVideo.name, ""); InitializeWebMediaPlayerImpl(); std::unique_ptr web_layer = std::make_unique(); cc_blink::WebLayerImpl* web_layer_ptr = web_layer.get(); EXPECT_CALL(*surface_layer_bridge_ptr_, GetWebLayer()) .WillRepeatedly(Return(web_layer_ptr)); EXPECT_CALL(client_, SetWebLayer(Eq(web_layer_ptr))); wmpi_->RegisterContentsLayer(web_layer.get()); } TEST_F(WebMediaPlayerImplTest, PlaybackRateChangeMediaLogs) { InitializeWebMediaPlayerImpl(); { InSequence s; // Expect precisely one rate change log from this test case. EXPECT_MEDIA_LOG_ON(*media_log_, PlaybackRateChanged("0", "0.8")); EXPECT_MEDIA_LOG_ON(*media_log_, WmpiDestroyed()); wmpi_->SetRate(0.0); // No change from initial rate, so no log. wmpi_->SetRate(0.8); // This should log change from 0 -> 0.8 wmpi_->SetRate(0.8); // No change from previous rate, so no log. } } // Tests when the PipSurfaceInfoCB for |wmpi_| is triggered for // Picture-in-Picture. TEST_F(WebMediaPlayerImplTest, PictureInPictureTriggerCallback) { InitializeWebMediaPlayerImpl(); // These calls should do nothing since there is no SurfaceId set. wmpi_->EnterPictureInPicture(); wmpi_->ExitPictureInPicture(); EXPECT_CALL(client_, IsInPictureInPictureMode()); wmpi_->OnSurfaceIdUpdated(surface_id_); testing::Mock::VerifyAndClearExpectations(&client_); EXPECT_CALL(delegate_, DidPictureInPictureSourceChange(delegate_.player_id())); EXPECT_CALL(pip_surface_info_cb_, Run(surface_id_, GetNaturalSize())); // This call should trigger the callback since the SurfaceId is set. wmpi_->EnterPictureInPicture(); testing::Mock::VerifyAndClearExpectations(&client_); // Upon exiting Picture-in-Picture mode, functions to cleanup are expected to // be called. ~WMPI calls ExitPictureInPicture(). EXPECT_CALL(exit_pip_cb_, Run()); EXPECT_CALL(delegate_, DidPictureInPictureModeEnd(delegate_.player_id())); } class WebMediaPlayerImplBackgroundBehaviorTest : public WebMediaPlayerImplTest, public ::testing::WithParamInterface< std::tuple> { public: // Indices of the tuple parameters. static const int kIsMediaSuspendEnabled = 0; static const int kIsBackgroundOptimizationEnabled = 1; static const int kDurationSec = 2; static const int kAverageKeyframeDistanceSec = 3; static const int kIsResumeBackgroundVideoEnabled = 4; static const int kIsMediaSource = 5; static const int kIsBackgroundPauseEnabled = 6; void SetUp() override { WebMediaPlayerImplTest::SetUp(); SetUpMediaSuspend(IsMediaSuspendOn()); std::string enabled_features; std::string disabled_features; if (IsBackgroundOptimizationOn()) { enabled_features += kBackgroundVideoTrackOptimization.name; } else { disabled_features += kBackgroundVideoTrackOptimization.name; } if (IsBackgroundPauseOn()) { if (!enabled_features.empty()) enabled_features += ","; enabled_features += kBackgroundVideoPauseOptimization.name; } else { if (!disabled_features.empty()) disabled_features += ","; disabled_features += kBackgroundVideoPauseOptimization.name; } if (IsResumeBackgroundVideoEnabled()) { if (!enabled_features.empty()) enabled_features += ","; enabled_features += kResumeBackgroundVideo.name; } else { if (!disabled_features.empty()) disabled_features += ","; disabled_features += kResumeBackgroundVideo.name; } feature_list_.InitFromCommandLine(enabled_features, disabled_features); InitializeWebMediaPlayerImpl(); bool is_media_source = std::get(GetParam()); SetLoadType(is_media_source ? blink::WebMediaPlayer::kLoadTypeMediaSource : blink::WebMediaPlayer::kLoadTypeURL); SetVideoKeyframeDistanceAverage( base::TimeDelta::FromSeconds(GetAverageKeyframeDistanceSec())); SetDuration(base::TimeDelta::FromSeconds(GetDurationSec())); BackgroundPlayer(); } void SetVideoKeyframeDistanceAverage(base::TimeDelta value) { PipelineStatistics statistics; statistics.video_keyframe_distance_average = value; wmpi_->SetPipelineStatisticsForTest(statistics); } bool IsMediaSuspendOn() { return std::get(GetParam()); } bool IsBackgroundOptimizationOn() { return std::get(GetParam()); } bool IsResumeBackgroundVideoEnabled() { return std::get(GetParam()); } bool IsBackgroundPauseOn() { return std::get(GetParam()); } int GetDurationSec() const { return std::get(GetParam()); } int GetAverageKeyframeDistanceSec() const { return std::get(GetParam()); } int GetMaxKeyframeDistanceSec() const { base::TimeDelta max_keyframe_distance = std::get(GetParam()) ? kMaxKeyframeDistanceToDisableBackgroundVideoMSE : kMaxKeyframeDistanceToDisableBackgroundVideo; return max_keyframe_distance.InSeconds(); } bool IsAndroid() { #if defined(OS_ANDROID) return true; #else return false; #endif } bool ShouldDisableVideoWhenHidden() const { return wmpi_->ShouldDisableVideoWhenHidden(); } bool ShouldPauseVideoWhenHidden() const { return wmpi_->ShouldPauseVideoWhenHidden(); } bool IsBackgroundOptimizationCandidate() const { return wmpi_->IsBackgroundOptimizationCandidate(); } private: base::test::ScopedFeatureList feature_list_; }; TEST_P(WebMediaPlayerImplBackgroundBehaviorTest, AudioOnly) { // Never optimize or pause an audio-only player. SetMetadata(true, false); EXPECT_FALSE(IsBackgroundOptimizationCandidate()); EXPECT_FALSE(ShouldPauseVideoWhenHidden()); EXPECT_FALSE(ShouldDisableVideoWhenHidden()); } TEST_P(WebMediaPlayerImplBackgroundBehaviorTest, VideoOnly) { // Video only. SetMetadata(false, true); // Never disable video track for a video only stream. EXPECT_FALSE(ShouldDisableVideoWhenHidden()); // Video only is always optimized. EXPECT_TRUE(IsBackgroundOptimizationCandidate()); // Video is always paused when suspension is on and only if matches the // optimization criteria if the optimization is on. bool should_pause = IsMediaSuspendOn() || IsBackgroundPauseOn(); EXPECT_EQ(should_pause, ShouldPauseVideoWhenHidden()); } TEST_P(WebMediaPlayerImplBackgroundBehaviorTest, AudioVideo) { SetMetadata(true, true); // Optimization requirements are the same for all platforms. bool matches_requirements = (GetDurationSec() < GetMaxKeyframeDistanceSec()) || (GetAverageKeyframeDistanceSec() < GetMaxKeyframeDistanceSec()); EXPECT_EQ(matches_requirements, IsBackgroundOptimizationCandidate()); EXPECT_EQ(IsBackgroundOptimizationOn() && matches_requirements, ShouldDisableVideoWhenHidden()); // Only pause audible videos if both media suspend and resume background // videos is on. Both are on by default on Android and off on desktop. EXPECT_EQ(IsMediaSuspendOn() && IsResumeBackgroundVideoEnabled(), ShouldPauseVideoWhenHidden()); if (!IsBackgroundOptimizationOn() || !matches_requirements || !ShouldDisableVideoWhenHidden() || IsMediaSuspendOn()) { return; } // These tests start in background mode prior to having metadata, so put the // test back into a normal state. EXPECT_TRUE(IsDisableVideoTrackPending()); EXPECT_CALL(client_, IsAutoplayingMuted()) .WillRepeatedly(Return(client_.is_autoplaying_muted_)); ForegroundPlayer(); EXPECT_FALSE(IsVideoTrackDisabled()); EXPECT_FALSE(IsDisableVideoTrackPending()); // Should start background disable timer, but not disable immediately. BackgroundPlayer(); EXPECT_FALSE(IsVideoTrackDisabled()); EXPECT_TRUE(IsDisableVideoTrackPending()); } INSTANTIATE_TEST_CASE_P(BackgroundBehaviorTestInstances, WebMediaPlayerImplBackgroundBehaviorTest, ::testing::Combine(::testing::Bool(), ::testing::Bool(), ::testing::Values(5, 300), ::testing::Values(5, 100), ::testing::Bool(), ::testing::Bool(), ::testing::Bool())); } // namespace media