summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordalecurtis <dalecurtis@chromium.org>2016-02-26 20:15:05 -0800
committerCommit bot <commit-bot@chromium.org>2016-02-27 04:17:42 +0000
commit5bbc487ef9a375236c2b8ca9196d34c60d767323 (patch)
treebe89da8a6490c9fa4e9df55c2c65dfb5263ed963
parenta2ef7fd18d48cf3b2d0bd88e30c9954423b571ef (diff)
downloadchromium_src-5bbc487ef9a375236c2b8ca9196d34c60d767323.zip
chromium_src-5bbc487ef9a375236c2b8ca9196d34c60d767323.tar.gz
chromium_src-5bbc487ef9a375236c2b8ca9196d34c60d767323.tar.bz2
Suspend idle WebMediaPlayer instances after some time.
Releases resources which may slowly accumulate for carelessly created players that a user will never see. For now, only enabled on Android, but long term we'll want this on all platforms since it can avoid major OOM crashes on sites which leak elements. Since RendererWebMediaPlayerDelegate is now getting a bit complicated, adds a browser test (necessary to construct a RenderViewTest) to cover old and new functionality alike. Currently players will be suspended after 15 seconds of being paused, and resumed upon playback, playback rate changes from 0 to above 0, and seek events. Modifies WebMediaPlayerImpl to handle suspensions when a page might be in the foreground. Covers the following: - Seek will now resume the pipeline if initiated in the foreground. - Play will now resume the pipeline if initiated in the foreground. Due to the above it's no longer necessary for OnShown() to resume the player when it's in the paused or ended state. BUG=590099 TEST=new unittests, manual testing. Review URL: https://codereview.chromium.org/1739473003 Cr-Commit-Position: refs/heads/master@{#378113}
-rw-r--r--content/content_tests.gypi1
-rw-r--r--content/renderer/media/renderer_webmediaplayer_delegate.cc96
-rw-r--r--content/renderer/media/renderer_webmediaplayer_delegate.h52
-rw-r--r--content/renderer/media/renderer_webmediaplayer_delegate_browsertest.cc214
-rw-r--r--media/blink/webmediaplayer_impl.cc40
5 files changed, 383 insertions, 20 deletions
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index bdf3453..600328b 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -274,6 +274,7 @@
'renderer/devtools/v8_sampling_profiler_browsertest.cc',
'renderer/gin_browsertest.cc',
'renderer/history_controller_browsertest.cc',
+ 'renderer/media/renderer_webmediaplayer_delegate_browsertest.cc',
'renderer/mouse_lock_dispatcher_browsertest.cc',
'renderer/render_frame_impl_browsertest.cc',
'renderer/render_thread_impl_browsertest.cc',
diff --git a/content/renderer/media/renderer_webmediaplayer_delegate.cc b/content/renderer/media/renderer_webmediaplayer_delegate.cc
index 9788a65..edda2e2 100644
--- a/content/renderer/media/renderer_webmediaplayer_delegate.cc
+++ b/content/renderer/media/renderer_webmediaplayer_delegate.cc
@@ -6,6 +6,7 @@
#include <stdint.h>
+#include "base/auto_reset.h"
#include "content/common/media/media_player_delegate_messages.h"
#include "content/public/renderer/render_frame.h"
#include "third_party/WebKit/public/platform/WebMediaPlayer.h"
@@ -14,17 +15,30 @@ namespace media {
RendererWebMediaPlayerDelegate::RendererWebMediaPlayerDelegate(
content::RenderFrame* render_frame)
- : RenderFrameObserver(render_frame) {}
+ : RenderFrameObserver(render_frame),
+ default_tick_clock_(new base::DefaultTickClock()),
+ tick_clock_(default_tick_clock_.get()) {
+#if defined(OS_ANDROID)
+ // On Android the idle cleanup timer is enabled by default.
+ // TODO(dalecurtis): Eventually this should be enabled on all platforms.
+ idle_cleanup_enabled_ = true;
+ idle_cleanup_interval_ = base::TimeDelta::FromSeconds(5);
+ idle_timeout_ = base::TimeDelta::FromSeconds(15);
+#endif
+}
RendererWebMediaPlayerDelegate::~RendererWebMediaPlayerDelegate() {}
int RendererWebMediaPlayerDelegate::AddObserver(Observer* observer) {
- return id_map_.Add(observer);
+ const int delegate_id = id_map_.Add(observer);
+ AddIdleDelegate(delegate_id);
+ return delegate_id;
}
void RendererWebMediaPlayerDelegate::RemoveObserver(int delegate_id) {
DCHECK(id_map_.Lookup(delegate_id));
id_map_.Remove(delegate_id);
+ RemoveIdleDelegate(delegate_id);
}
void RendererWebMediaPlayerDelegate::DidPlay(int delegate_id,
@@ -34,6 +48,7 @@ void RendererWebMediaPlayerDelegate::DidPlay(int delegate_id,
base::TimeDelta duration) {
DCHECK(id_map_.Lookup(delegate_id));
has_played_media_ = true;
+ RemoveIdleDelegate(delegate_id);
Send(new MediaPlayerDelegateHostMsg_OnMediaPlaying(
routing_id(), delegate_id, has_video, has_audio, is_remote, duration));
}
@@ -41,12 +56,14 @@ void RendererWebMediaPlayerDelegate::DidPlay(int delegate_id,
void RendererWebMediaPlayerDelegate::DidPause(int delegate_id,
bool reached_end_of_stream) {
DCHECK(id_map_.Lookup(delegate_id));
+ AddIdleDelegate(delegate_id);
Send(new MediaPlayerDelegateHostMsg_OnMediaPaused(routing_id(), delegate_id,
reached_end_of_stream));
}
void RendererWebMediaPlayerDelegate::PlayerGone(int delegate_id) {
DCHECK(id_map_.Lookup(delegate_id));
+ RemoveIdleDelegate(delegate_id);
Send(new MediaPlayerDelegateHostMsg_OnMediaDestroyed(routing_id(),
delegate_id));
}
@@ -69,17 +86,26 @@ bool RendererWebMediaPlayerDelegate::OnMessageReceived(
const IPC::Message& msg) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(RendererWebMediaPlayerDelegate, msg)
- IPC_MESSAGE_HANDLER(MediaPlayerDelegateMsg_Pause, OnMediaDelegatePause)
- IPC_MESSAGE_HANDLER(MediaPlayerDelegateMsg_Play, OnMediaDelegatePlay)
- IPC_MESSAGE_HANDLER(MediaPlayerDelegateMsg_SuspendAllMediaPlayers,
- OnMediaDelegateSuspendAllMediaPlayers)
- IPC_MESSAGE_HANDLER(MediaPlayerDelegateMsg_UpdateVolumeMultiplier,
- OnMediaDelegateVolumeMultiplierUpdate)
- IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_MESSAGE_HANDLER(MediaPlayerDelegateMsg_Pause, OnMediaDelegatePause)
+ IPC_MESSAGE_HANDLER(MediaPlayerDelegateMsg_Play, OnMediaDelegatePlay)
+ IPC_MESSAGE_HANDLER(MediaPlayerDelegateMsg_SuspendAllMediaPlayers,
+ OnMediaDelegateSuspendAllMediaPlayers)
+ IPC_MESSAGE_HANDLER(MediaPlayerDelegateMsg_UpdateVolumeMultiplier,
+ OnMediaDelegateVolumeMultiplierUpdate)
+ IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
+void RendererWebMediaPlayerDelegate::EnableInstantIdleCleanupForTesting(
+ base::TimeDelta idle_timeout,
+ base::TickClock* tick_clock) {
+ idle_cleanup_enabled_ = true;
+ idle_cleanup_interval_ = base::TimeDelta();
+ idle_timeout_ = idle_timeout;
+ tick_clock_ = tick_clock;
+}
+
void RendererWebMediaPlayerDelegate::OnMediaDelegatePause(int delegate_id) {
Observer* observer = id_map_.Lookup(delegate_id);
if (observer)
@@ -105,4 +131,56 @@ void RendererWebMediaPlayerDelegate::OnMediaDelegateVolumeMultiplierUpdate(
observer->OnVolumeMultiplierUpdate(multiplier);
}
+void RendererWebMediaPlayerDelegate::AddIdleDelegate(int delegate_id) {
+ if (!idle_cleanup_enabled_)
+ return;
+
+ idle_delegate_map_[delegate_id] = tick_clock_->NowTicks();
+ if (!idle_cleanup_timer_.IsRunning()) {
+ idle_cleanup_timer_.Start(
+ FROM_HERE, idle_cleanup_interval_, this,
+ &RendererWebMediaPlayerDelegate::CleanupIdleDelegates);
+ }
+}
+
+void RendererWebMediaPlayerDelegate::RemoveIdleDelegate(int delegate_id) {
+ if (!idle_cleanup_enabled_)
+ return;
+
+ // To avoid invalidating the iterator, just mark the delegate for deletion
+ // using a sentinel value of an empty TimeTicks.
+ if (idle_cleanup_running_) {
+ idle_delegate_map_[delegate_id] = base::TimeTicks();
+ return;
+ }
+
+ idle_delegate_map_.erase(delegate_id);
+ if (idle_delegate_map_.empty())
+ idle_cleanup_timer_.Stop();
+}
+
+void RendererWebMediaPlayerDelegate::CleanupIdleDelegates() {
+ // Iterate over the delegates and suspend the idle ones. Note: The call to
+ // OnHidden() can trigger calls into RemoveIdleDelegate(), so for iterator
+ // validity we set |idle_cleanup_running_| to true and defer deletions.
+ base::AutoReset<bool> scoper(&idle_cleanup_running_, true);
+ const base::TimeTicks now = tick_clock_->NowTicks();
+ for (const auto& kv : idle_delegate_map_) {
+ if (now - kv.second > idle_timeout_)
+ id_map_.Lookup(kv.first)->OnHidden(true);
+ }
+
+ // Take care of any removals that happened during the above iteration.
+ for (auto it = idle_delegate_map_.begin(); it != idle_delegate_map_.end();) {
+ if (it->second.is_null())
+ it = idle_delegate_map_.erase(it);
+ else
+ ++it;
+ }
+
+ // Shutdown the timer if no delegates are left.
+ if (idle_delegate_map_.empty())
+ idle_cleanup_timer_.Stop();
+}
+
} // namespace media
diff --git a/content/renderer/media/renderer_webmediaplayer_delegate.h b/content/renderer/media/renderer_webmediaplayer_delegate.h
index 54a5bc9..2ca3d46 100644
--- a/content/renderer/media/renderer_webmediaplayer_delegate.h
+++ b/content/renderer/media/renderer_webmediaplayer_delegate.h
@@ -5,9 +5,14 @@
#ifndef CONTENT_RENDERER_MEDIA_RENDERER_WEBMEDIAPLAYER_DELEGATE_H_
#define CONTENT_RENDERER_MEDIA_RENDERER_WEBMEDIAPLAYER_DELEGATE_H_
+#include <map>
+
#include "base/id_map.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
+#include "base/time/default_tick_clock.h"
+#include "base/timer/timer.h"
+#include "content/common/content_export.h"
#include "content/public/renderer/render_frame_observer.h"
#include "media/blink/webmediaplayer_delegate.h"
@@ -19,10 +24,11 @@ namespace media {
// An interface to allow a WebMediaPlayerImpl to communicate changes of state
// to objects that need to know.
-class RendererWebMediaPlayerDelegate
+class CONTENT_EXPORT RendererWebMediaPlayerDelegate
: public content::RenderFrameObserver,
- public WebMediaPlayerDelegate,
- public base::SupportsWeakPtr<RendererWebMediaPlayerDelegate> {
+ public NON_EXPORTED_BASE(WebMediaPlayerDelegate),
+ public NON_EXPORTED_BASE(
+ base::SupportsWeakPtr<RendererWebMediaPlayerDelegate>) {
public:
explicit RendererWebMediaPlayerDelegate(content::RenderFrame* render_frame);
~RendererWebMediaPlayerDelegate() override;
@@ -47,6 +53,15 @@ class RendererWebMediaPlayerDelegate
void WasShown() override;
bool OnMessageReceived(const IPC::Message& msg) override;
+ // Sets |idle_cleanup_enabled_| to true, zeros out |idle_cleanup_interval_|,
+ // and sets |idle_timeout_| to |idle_timeout|. A zero cleanup interval will
+ // cause the idle timer to run with each run of the message loop.
+ void EnableInstantIdleCleanupForTesting(base::TimeDelta idle_timeout,
+ base::TickClock* tick_clock);
+ bool IsIdleCleanupTimerRunningForTesting() const {
+ return idle_cleanup_timer_.IsRunning();
+ }
+
private:
void OnMediaDelegatePause(int delegate_id);
void OnMediaDelegatePlay(int delegate_id);
@@ -54,9 +69,40 @@ class RendererWebMediaPlayerDelegate
void OnMediaDelegateVolumeMultiplierUpdate(int delegate_id,
double multiplier);
+ // Adds or removes a delegate from |idle_delegate_map_|. The first insertion
+ // or last removal will start or stop |idle_cleanup_timer_| respectively.
+ void AddIdleDelegate(int delegate_id);
+ void RemoveIdleDelegate(int delegate_id);
+
+ // Runs periodically to suspend idle delegates in |idle_delegate_map_|.
+ void CleanupIdleDelegates();
+
bool has_played_media_ = false;
IDMap<Observer> id_map_;
+ // Tracks which delegates have entered an idle state. After some period of
+ // inactivity these players will be suspended to release unused resources.
+ bool idle_cleanup_running_ = false;
+ std::map<int, base::TimeTicks> idle_delegate_map_;
+ base::RepeatingTimer idle_cleanup_timer_;
+
+ // Controls whether cleanup of idle delegates is enabled or not as well as the
+ // polling interval and timeout period for delegates; overridden for testing.
+ bool idle_cleanup_enabled_ = false;
+
+ // Amount of time allowed to elapse after a delegate enters the paused before
+ // the delegate is suspended.
+ base::TimeDelta idle_timeout_;
+
+ // The polling interval used for checking the delegates to see if any have
+ // exceeded |idle_timeout_| since their last pause state.
+ base::TimeDelta idle_cleanup_interval_;
+
+ // Clock used for calculating when delegates have expired. May be overridden
+ // for testing.
+ scoped_ptr<base::DefaultTickClock> default_tick_clock_;
+ base::TickClock* tick_clock_;
+
DISALLOW_COPY_AND_ASSIGN(RendererWebMediaPlayerDelegate);
};
diff --git a/content/renderer/media/renderer_webmediaplayer_delegate_browsertest.cc b/content/renderer/media/renderer_webmediaplayer_delegate_browsertest.cc
new file mode 100644
index 0000000..d36112e
--- /dev/null
+++ b/content/renderer/media/renderer_webmediaplayer_delegate_browsertest.cc
@@ -0,0 +1,214 @@
+// 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 "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/run_loop.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/tuple.h"
+#include "content/common/media/media_player_delegate_messages.h"
+#include "content/public/renderer/render_view.h"
+#include "content/public/test/render_view_test.h"
+#include "content/renderer/media/renderer_webmediaplayer_delegate.h"
+#include "content/renderer/render_process.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+ACTION_P(RunClosure, closure) {
+ closure.Run();
+}
+
+class MockWebMediaPlayerDelegateObserver
+ : public WebMediaPlayerDelegate::Observer {
+ public:
+ MockWebMediaPlayerDelegateObserver() {}
+ ~MockWebMediaPlayerDelegateObserver() {}
+
+ // WebMediaPlayerDelegate::Observer implementation.
+ MOCK_METHOD1(OnHidden, void(bool));
+ MOCK_METHOD0(OnShown, void());
+ MOCK_METHOD0(OnPlay, void());
+ MOCK_METHOD0(OnPause, void());
+ MOCK_METHOD1(OnVolumeMultiplierUpdate, void(double));
+};
+
+class RendererWebMediaPlayerDelegateTest : public content::RenderViewTest {
+ public:
+ RendererWebMediaPlayerDelegateTest() {}
+ ~RendererWebMediaPlayerDelegateTest() override {}
+
+ void SetUp() override {
+ RenderViewTest::SetUp();
+ delegate_manager_.reset(
+ new RendererWebMediaPlayerDelegate(view_->GetMainRenderFrame()));
+ }
+
+ void TearDown() override {
+ // Destruct the controller prior to any other tear down to avoid out of
+ // order destruction relative to the test render frame.
+ delegate_manager_.reset();
+ RenderViewTest::TearDown();
+ }
+
+ protected:
+ IPC::TestSink& test_sink() { return render_thread_->sink(); }
+
+ scoped_ptr<RendererWebMediaPlayerDelegate> delegate_manager_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RendererWebMediaPlayerDelegateTest);
+};
+
+TEST_F(RendererWebMediaPlayerDelegateTest, SendsMessagesCorrectly) {
+ testing::StrictMock<MockWebMediaPlayerDelegateObserver> observer;
+ const int delegate_id = delegate_manager_->AddObserver(&observer);
+
+ // Verify the playing message.
+ {
+ const bool kHasVideo = true, kHasAudio = false, kIsRemote = false;
+ const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(5);
+ delegate_manager_->DidPlay(delegate_id, kHasVideo, kHasAudio, kIsRemote,
+ kDuration);
+
+ const IPC::Message* msg = test_sink().GetUniqueMessageMatching(
+ MediaPlayerDelegateHostMsg_OnMediaPlaying::ID);
+ ASSERT_TRUE(msg);
+
+ base::Tuple<int, bool, bool, bool, base::TimeDelta> result;
+ ASSERT_TRUE(MediaPlayerDelegateHostMsg_OnMediaPlaying::Read(msg, &result));
+ EXPECT_EQ(delegate_id, base::get<0>(result));
+ EXPECT_EQ(kHasVideo, base::get<1>(result));
+ EXPECT_EQ(kHasAudio, base::get<2>(result));
+ EXPECT_EQ(kIsRemote, base::get<3>(result));
+ EXPECT_EQ(kDuration, base::get<4>(result));
+ }
+
+ // Verify the paused message.
+ {
+ test_sink().ClearMessages();
+ const bool kReachedEndOfStream = true;
+ delegate_manager_->DidPause(delegate_id, kReachedEndOfStream);
+
+ const IPC::Message* msg = test_sink().GetUniqueMessageMatching(
+ MediaPlayerDelegateHostMsg_OnMediaPaused::ID);
+ ASSERT_TRUE(msg);
+
+ base::Tuple<int, bool> result;
+ ASSERT_TRUE(MediaPlayerDelegateHostMsg_OnMediaPaused::Read(msg, &result));
+ EXPECT_EQ(delegate_id, base::get<0>(result));
+ EXPECT_EQ(kReachedEndOfStream, base::get<1>(result));
+ }
+
+ // Verify the destruction message.
+ {
+ test_sink().ClearMessages();
+ delegate_manager_->PlayerGone(delegate_id);
+ const IPC::Message* msg = test_sink().GetUniqueMessageMatching(
+ MediaPlayerDelegateHostMsg_OnMediaDestroyed::ID);
+ ASSERT_TRUE(msg);
+
+ base::Tuple<int> result;
+ ASSERT_TRUE(
+ MediaPlayerDelegateHostMsg_OnMediaDestroyed::Read(msg, &result));
+ EXPECT_EQ(delegate_id, base::get<0>(result));
+ }
+
+ delegate_manager_->RemoveObserver(delegate_id);
+}
+
+TEST_F(RendererWebMediaPlayerDelegateTest, DeliversObserverNotifications) {
+ testing::StrictMock<MockWebMediaPlayerDelegateObserver> observer;
+ const int delegate_id = delegate_manager_->AddObserver(&observer);
+
+ EXPECT_CALL(observer, OnHidden(false));
+ delegate_manager_->WasHidden();
+
+ EXPECT_CALL(observer, OnShown());
+ delegate_manager_->WasShown();
+
+ EXPECT_CALL(observer, OnPause());
+ MediaPlayerDelegateMsg_Pause pause_msg(0, delegate_id);
+ delegate_manager_->OnMessageReceived(pause_msg);
+
+ EXPECT_CALL(observer, OnPlay());
+ MediaPlayerDelegateMsg_Play play_msg(0, delegate_id);
+ delegate_manager_->OnMessageReceived(play_msg);
+
+ const double kTestMultiplier = 0.5;
+ EXPECT_CALL(observer, OnVolumeMultiplierUpdate(kTestMultiplier));
+ MediaPlayerDelegateMsg_UpdateVolumeMultiplier volume_msg(0, delegate_id,
+ kTestMultiplier);
+ delegate_manager_->OnMessageReceived(volume_msg);
+
+ EXPECT_CALL(observer, OnHidden(true));
+ MediaPlayerDelegateMsg_SuspendAllMediaPlayers suspend_msg(0);
+ delegate_manager_->OnMessageReceived(suspend_msg);
+
+ delegate_manager_->RemoveObserver(delegate_id);
+}
+
+TEST_F(RendererWebMediaPlayerDelegateTest, IdleDelegatesAreSuspended) {
+ // Start the tick clock off at a non-null value.
+ base::SimpleTestTickClock tick_clock;
+ tick_clock.Advance(base::TimeDelta::FromSeconds(1234));
+
+ const base::TimeDelta kIdleTimeout = base::TimeDelta::FromSeconds(2);
+ delegate_manager_->EnableInstantIdleCleanupForTesting(kIdleTimeout,
+ &tick_clock);
+ EXPECT_FALSE(delegate_manager_->IsIdleCleanupTimerRunningForTesting());
+
+ testing::StrictMock<MockWebMediaPlayerDelegateObserver> observer_1;
+ const int delegate_id_1 = delegate_manager_->AddObserver(&observer_1);
+ EXPECT_TRUE(delegate_manager_->IsIdleCleanupTimerRunningForTesting());
+ delegate_manager_->DidPlay(delegate_id_1, true, true, false,
+ base::TimeDelta());
+ EXPECT_FALSE(delegate_manager_->IsIdleCleanupTimerRunningForTesting());
+
+ // Never playing should count as idle.
+ testing::StrictMock<MockWebMediaPlayerDelegateObserver> observer_2;
+ const int delegate_id_2 = delegate_manager_->AddObserver(&observer_2);
+ EXPECT_TRUE(delegate_manager_->IsIdleCleanupTimerRunningForTesting());
+
+ // Adding the observer should instantly queue the timeout task, once run the
+ // second delegate should be expired while the first is kept alive.
+ {
+ EXPECT_CALL(observer_2, OnHidden(true))
+ .WillOnce(RunClosure(base::Bind(
+ &RendererWebMediaPlayerDelegate::PlayerGone,
+ base::Unretained(delegate_manager_.get()), delegate_id_2)));
+ base::RunLoop run_loop;
+ base::MessageLoop::current()->PostTask(FROM_HERE, run_loop.QuitClosure());
+ tick_clock.Advance(kIdleTimeout + base::TimeDelta::FromMicroseconds(1));
+ run_loop.Run();
+ }
+
+ // Pausing should also count as idle.
+ delegate_manager_->DidPause(delegate_id_1, false);
+ testing::StrictMock<MockWebMediaPlayerDelegateObserver> observer_3;
+ const int delegate_id_3 = delegate_manager_->AddObserver(&observer_3);
+ delegate_manager_->DidPlay(delegate_id_3, true, true, false,
+ base::TimeDelta());
+
+ // Adding the observer should instantly queue the timeout task, once run the
+ // first delegate should be expired while the third is kept alive.
+ {
+ EXPECT_CALL(observer_1, OnHidden(true))
+ .WillOnce(RunClosure(base::Bind(
+ &RendererWebMediaPlayerDelegate::PlayerGone,
+ base::Unretained(delegate_manager_.get()), delegate_id_1)));
+ base::RunLoop run_loop;
+ base::MessageLoop::current()->PostTask(FROM_HERE, run_loop.QuitClosure());
+ tick_clock.Advance(kIdleTimeout + base::TimeDelta::FromMicroseconds(1));
+ run_loop.Run();
+ }
+
+ delegate_manager_->RemoveObserver(delegate_id_1);
+ delegate_manager_->RemoveObserver(delegate_id_2);
+ delegate_manager_->RemoveObserver(delegate_id_3);
+ EXPECT_FALSE(delegate_manager_->IsIdleCleanupTimerRunningForTesting());
+}
+
+} // namespace media
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index 102c432..1642d88 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -347,8 +347,15 @@ void WebMediaPlayerImpl::play() {
media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::PLAY));
- if (playback_rate_ > 0)
+ if (playback_rate_ > 0) {
+ // Resume the player if playback was initiated in the foreground.
+ if (suspended_ && !resuming_ && delegate_ && !delegate_->IsHidden()) {
+ Resume();
+ return;
+ }
+
NotifyPlaybackStarted();
+ }
}
void WebMediaPlayerImpl::pause() {
@@ -437,6 +444,11 @@ void WebMediaPlayerImpl::seek(double seconds) {
if (is_suspended) {
seeking_ = true;
seek_time_ = new_seek_time;
+
+ // Resume the pipeline if the seek is initiated in the foreground so that
+ // the correct frame is displayed.
+ if (delegate_ && !delegate_->IsHidden())
+ Resume();
}
return;
@@ -880,14 +892,15 @@ void WebMediaPlayerImpl::OnPipelineSeeked(bool time_changed,
return;
}
- // If we we're resuming into the playing state, notify the delegate.
- if (resuming_ && playback_rate_ > 0 && !paused_)
- NotifyPlaybackStarted();
-
// Whether or not the seek was caused by a resume, we're not suspended now.
+ const bool was_resuming = resuming_;
resuming_ = false;
suspended_ = false;
+ // If we we're resuming into the playing state, notify the delegate.
+ if (was_resuming && playback_rate_ > 0 && !paused_)
+ NotifyPlaybackStarted();
+
// If there is a pending suspend, the seek does not complete until after the
// next resume.
if (pending_suspend_) {
@@ -1144,7 +1157,8 @@ void WebMediaPlayerImpl::OnShown() {
return;
#endif
- ScheduleResume();
+ if (!ended_ && !paused_)
+ ScheduleResume();
}
void WebMediaPlayerImpl::ScheduleResume() {
@@ -1515,11 +1529,18 @@ void WebMediaPlayerImpl::UpdatePausedTime() {
void WebMediaPlayerImpl::NotifyPlaybackStarted() {
#if defined(OS_ANDROID) // WMPI_CAST
- // We do not tell our delegates about remote playback, becuase that would
+ // We do not tell our delegates about remote playback, because that would
// keep the device awake, which is not what we want.
if (isRemote())
return;
#endif
+
+ // Don't send delegate notifications when suspended; upon suspend we send
+ // PlayerGone() to the delegate -- no more notifications should be sent until
+ // after resume.
+ if (suspended_)
+ return;
+
if (delegate_) {
delegate_->DidPlay(delegate_id_, hasVideo(), hasAudio(), false,
pipeline_.GetMediaDuration());
@@ -1536,7 +1557,10 @@ void WebMediaPlayerImpl::NotifyPlaybackPaused() {
if (isRemote())
return;
#endif
- if (delegate_)
+ // Don't send delegate notifications when suspended; upon suspend we send
+ // PlayerGone() to the delegate -- no more notifications should be sent until
+ // after resume.
+ if (!suspended_ && delegate_)
delegate_->DidPause(delegate_id_, ended_);
memory_usage_reporting_timer_.Stop();
ReportMemoryUsage();