Commit fc233aae authored by evliu's avatar evliu Committed by Commit Bot

[Media History] Add Media History Contents Observer

This CL introduces a new MediaWatchTimeChanged event and a
MediaHistoryContentsObserver to watch for the event and save
the playback information to the media history store.

Bug: 997813
Change-Id: I20061e1d614b703752fca9327d010689209c9575
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1881977
Commit-Queue: Evan Liu <evliu@google.com>
Reviewed-by: default avatarTommy Steimel <steimel@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#711407}
parent a2bee5aa
......@@ -663,6 +663,8 @@ jumbo_static_library("browser") {
"media/cast_mirroring_service_host.h",
"media/cast_remoting_connector.cc",
"media/cast_remoting_connector.h",
"media/history/media_history_contents_observer.cc",
"media/history/media_history_contents_observer.h",
"media/history/media_history_engagement_table.cc",
"media/history/media_history_engagement_table.h",
"media/history/media_history_keyed_service.cc",
......@@ -677,8 +679,6 @@ jumbo_static_library("browser") {
"media/history/media_history_store.h",
"media/history/media_history_table_base.cc",
"media/history/media_history_table_base.h",
"media/history/media_player_watchtime.cc",
"media/history/media_player_watchtime.h",
"media/media_access_handler.cc",
"media/media_access_handler.h",
"media/media_device_id_salt.cc",
......
// Copyright 2019 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 "chrome/browser/media/history/media_history_contents_observer.h"
#include "chrome/browser/media/history/media_history_keyed_service_factory.h"
#include "content/public/browser/browser_thread.h"
MediaHistoryContentsObserver::MediaHistoryContentsObserver(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
service_ = media_history::MediaHistoryKeyedServiceFactory::GetForProfile(
Profile::FromBrowserContext(web_contents->GetBrowserContext()));
DCHECK(service_);
}
MediaHistoryContentsObserver::~MediaHistoryContentsObserver() = default;
void MediaHistoryContentsObserver::MediaWatchTimeChanged(
const content::MediaPlayerWatchTime& watch_time) {
service_->GetMediaHistoryStore()->SavePlayback(watch_time);
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(MediaHistoryContentsObserver)
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_MEDIA_HISTORY_MEDIA_HISTORY_CONTENTS_OBSERVER_H_
#define CHROME_BROWSER_MEDIA_HISTORY_MEDIA_HISTORY_CONTENTS_OBSERVER_H_
#include "chrome/browser/media/history/media_history_keyed_service.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
class MediaHistoryContentsObserver
: public content::WebContentsObserver,
public content::WebContentsUserData<MediaHistoryContentsObserver> {
public:
~MediaHistoryContentsObserver() override;
void MediaWatchTimeChanged(
const content::MediaPlayerWatchTime& watch_time) override;
private:
friend class content::WebContentsUserData<MediaHistoryContentsObserver>;
explicit MediaHistoryContentsObserver(content::WebContents* web_contents);
media_history::MediaHistoryKeyedService* service_;
WEB_CONTENTS_USER_DATA_KEY_DECL();
DISALLOW_COPY_AND_ASSIGN(MediaHistoryContentsObserver);
};
#endif // CHROME_BROWSER_MEDIA_HISTORY_MEDIA_HISTORY_CONTENTS_OBSERVER_H_
......@@ -23,6 +23,10 @@ class MediaHistoryKeyedService : public KeyedService {
// Returns the instance attached to the given |profile|.
static MediaHistoryKeyedService* Get(Profile* profile);
MediaHistoryStore* GetMediaHistoryStore() {
return media_history_store_.get();
}
private:
std::unique_ptr<MediaHistoryStore> media_history_store_;
......
......@@ -33,14 +33,10 @@ sql::InitStatus MediaHistoryOriginTable::CreateTableIfNonExistent() {
return sql::INIT_OK;
}
void MediaHistoryOriginTable::CreateOriginId(const std::string& origin) {
bool MediaHistoryOriginTable::CreateOriginId(const std::string& origin) {
DCHECK_LT(0, DB()->transaction_nesting());
if (!CanAccessDatabase())
return;
if (!DB()->BeginTransaction()) {
LOG(ERROR) << "Failed to begin the transaction to create an origin ID.";
return;
}
return false;
// Insert the origin into the table if it does not exist.
sql::Statement statement(
......@@ -50,12 +46,11 @@ void MediaHistoryOriginTable::CreateOriginId(const std::string& origin) {
"VALUES (?)"));
statement.BindString(0, origin);
if (!statement.Run()) {
DB()->RollbackTransaction();
LOG(ERROR) << "Failed to create the origin ID.";
return;
return false;
}
DB()->CommitTransaction();
return true;
}
} // namespace media_history
......@@ -22,7 +22,8 @@ class MediaHistoryOriginTable : public MediaHistoryTableBase {
// MediaHistoryTableBase:
sql::InitStatus CreateTableIfNonExistent() override;
void CreateOriginId(const std::string& origin);
// Returns a flag indicating whether the origin id was created successfully.
bool CreateOriginId(const std::string& origin);
DISALLOW_COPY_AND_ASSIGN(MediaHistoryOriginTable);
};
......
......@@ -6,8 +6,7 @@
#include "base/strings/stringprintf.h"
#include "base/updateable_sequenced_task_runner.h"
#include "chrome/browser/media/history/media_player_watchtime.h"
#include "content/public/browser/media_player_id.h"
#include "content/public/browser/media_player_watch_time.h"
#include "sql/statement.h"
namespace media_history {
......@@ -28,9 +27,7 @@ sql::InitStatus MediaHistoryPlaybackTable::CreateTableIfNonExistent() {
"origin_id INTEGER NOT NULL,"
"url TEXT,"
"timestamp_ms INTEGER,"
"has_audio INTEGER,"
"has_video INTEGER,"
"watchtime_ms INTEGER,"
"watch_time_ms INTEGER,"
"CONSTRAINT fk_origin "
"FOREIGN KEY (origin_id) "
"REFERENCES origin(id) "
......@@ -58,58 +55,26 @@ sql::InitStatus MediaHistoryPlaybackTable::CreateTableIfNonExistent() {
return sql::INIT_OK;
}
void MediaHistoryPlaybackTable::SavePlayback(
const content::MediaPlayerId& id,
const content::MediaPlayerWatchtime& watchtime) {
bool MediaHistoryPlaybackTable::SavePlayback(
const content::MediaPlayerWatchTime& watch_time) {
DCHECK_LT(0, DB()->transaction_nesting());
if (!CanAccessDatabase())
return;
if (!DB()->BeginTransaction())
return;
return false;
sql::Statement statement(DB()->GetCachedStatement(
SQL_FROM_HERE,
"INSERT INTO playback "
"(origin_id, has_audio, has_video, watchtime_ms, url, timestamp_ms) "
"VALUES ((SELECT id FROM origin WHERE origin = ?), ?, ?, ?, ?, ?)"));
statement.BindString(0, watchtime.origin);
statement.BindBool(1, watchtime.has_audio);
statement.BindBool(2, watchtime.has_video);
statement.BindInt(3, watchtime.cumulative_watchtime.InMilliseconds());
statement.BindString(4, watchtime.url);
statement.BindInt(5, watchtime.last_timestamp.InMilliseconds());
"(origin_id, url, watch_time_ms, timestamp_ms) "
"VALUES ((SELECT id FROM origin WHERE origin = ?), ?, ?, ?)"));
statement.BindString(0, watch_time.origin.spec());
statement.BindString(1, watch_time.url.spec());
statement.BindInt(2, watch_time.cumulative_watch_time.InMilliseconds());
statement.BindInt(3, watch_time.last_timestamp.InMilliseconds());
if (!statement.Run()) {
DB()->RollbackTransaction();
return;
}
DB()->CommitTransaction();
}
MediaHistoryPlaybackTable::MediaHistoryPlaybacks
MediaHistoryPlaybackTable::GetRecentPlaybacks(int num_results) {
MediaHistoryPlaybacks results;
sql::Statement statement(
DB()->GetCachedStatement(SQL_FROM_HERE,
"SELECT id, origin_id, has_audio, has_video, "
"watchtime_ms, timestamp_ms"
"FROM playback ORDER BY id DESC"
"LIMIT ?"));
statement.BindInt(0, num_results);
while (statement.Step()) {
MediaHistoryPlaybackTable::MediaHistoryPlayback playback;
playback.has_audio = statement.ColumnInt(2);
playback.has_video = statement.ColumnInt(3);
playback.watchtime =
base::TimeDelta::FromMilliseconds(statement.ColumnInt(4));
playback.timestamp =
base::TimeDelta::FromMilliseconds(statement.ColumnInt(5));
results.push_back(playback);
return false;
}
return results;
return true;
}
} // namespace media_history
......@@ -7,14 +7,14 @@
#include "chrome/browser/media/history/media_history_table_base.h"
#include "sql/init_status.h"
#include "url/gurl.h"
namespace base {
class UpdateableSequencedTaskRunner;
} // namespace base
namespace content {
struct MediaPlayerId;
struct MediaPlayerWatchtime;
struct MediaPlayerWatchTime;
} // namespace content
namespace media_history {
......@@ -24,9 +24,8 @@ class MediaHistoryPlaybackTable : public MediaHistoryTableBase {
struct MediaHistoryPlayback {
MediaHistoryPlayback() = default;
bool has_audio;
bool has_video;
base::TimeDelta watchtime;
GURL url;
base::TimeDelta watch_time;
base::TimeDelta timestamp;
};
......@@ -42,12 +41,8 @@ class MediaHistoryPlaybackTable : public MediaHistoryTableBase {
// MediaHistoryTableBase:
sql::InitStatus CreateTableIfNonExistent() override;
void SavePlayback(const content::MediaPlayerId& id,
const content::MediaPlayerWatchtime& watchtime);
// Returns |num_results| playbacks sorted by time with the
// newest first.
MediaHistoryPlaybacks GetRecentPlaybacks(int num_results);
// Returns a flag indicating whether the playback was created successfully.
bool SavePlayback(const content::MediaPlayerWatchTime& watch_time);
DISALLOW_COPY_AND_ASSIGN(MediaHistoryPlaybackTable);
};
......
......@@ -4,8 +4,7 @@
#include "chrome/browser/media/history/media_history_store.h"
#include "chrome/browser/media/history/media_player_watchtime.h"
#include "content/public/browser/media_player_id.h"
#include "content/public/browser/media_player_watch_time.h"
namespace {
......@@ -37,17 +36,19 @@ class MediaHistoryStoreInternal
scoped_refptr<base::UpdateableSequencedTaskRunner> db_task_runner);
virtual ~MediaHistoryStoreInternal();
void SavePlayback(const content::MediaPlayerId& id,
const content::MediaPlayerWatchtime& watchtime);
private:
// Opens the database file from the profile path. Separated from the
// constructor to ease construction/destruction of this object on one thread
// and database access on the DB sequence of |db_task_runner_|.
void Initialize();
sql::InitStatus CreateOrUpgradeIfNeeded();
sql::InitStatus InitializeTables();
void CreateOriginId(const std::string& origin);
sql::Database* DB();
// Returns a flag indicating whether the origin id was created successfully.
bool CreateOriginId(const std::string& origin);
void SavePlayback(const content::MediaPlayerWatchTime& watch_time);
scoped_refptr<base::UpdateableSequencedTaskRunner> db_task_runner_;
base::FilePath db_path_;
......@@ -78,17 +79,28 @@ MediaHistoryStoreInternal::~MediaHistoryStoreInternal() {
db_task_runner_->DeleteSoon(FROM_HERE, std::move(db_));
}
sql::Database* MediaHistoryStoreInternal::DB() {
DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
return db_.get();
}
void MediaHistoryStoreInternal::SavePlayback(
const content::MediaPlayerId& id,
const content::MediaPlayerWatchtime& watchtime) {
const content::MediaPlayerWatchTime& watch_time) {
DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
if (!initialization_successful_)
return;
CreateOriginId(watchtime.origin);
db_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&MediaHistoryPlaybackTable::SavePlayback,
playback_table_, id, watchtime));
if (!DB()->BeginTransaction()) {
LOG(ERROR) << "Failed to begin the transaction.";
return;
}
if (CreateOriginId(watch_time.origin.spec()) &&
playback_table_->SavePlayback(watch_time)) {
DB()->CommitTransaction();
} else {
DB()->RollbackTransaction();
}
}
void MediaHistoryStoreInternal::Initialize() {
......@@ -145,14 +157,12 @@ sql::InitStatus MediaHistoryStoreInternal::InitializeTables() {
return status;
}
void MediaHistoryStoreInternal::CreateOriginId(const std::string& origin) {
bool MediaHistoryStoreInternal::CreateOriginId(const std::string& origin) {
DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
if (!initialization_successful_)
return;
return false;
db_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&MediaHistoryOriginTable::CreateOriginId,
origin_table_, origin));
return origin_table_->CreateOriginId(origin);
}
MediaHistoryStore::MediaHistoryStore(
......@@ -165,4 +175,14 @@ MediaHistoryStore::MediaHistoryStore(
MediaHistoryStore::~MediaHistoryStore() {}
void MediaHistoryStore::SavePlayback(
const content::MediaPlayerWatchTime& watch_time) {
if (!db_->initialization_successful_)
return;
db_->db_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&MediaHistoryStoreInternal::SavePlayback, db_,
watch_time));
}
} // namespace media_history
......@@ -33,6 +33,8 @@ class MediaHistoryStore {
scoped_refptr<base::UpdateableSequencedTaskRunner> db_task_runner);
~MediaHistoryStore();
void SavePlayback(const content::MediaPlayerWatchTime& watch_time);
private:
scoped_refptr<MediaHistoryStoreInternal> db_;
};
......
......@@ -10,6 +10,7 @@
#include "base/task/thread_pool/pooled_sequenced_task_runner.h"
#include "base/test/test_timeouts.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/browser/media_player_watch_time.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "sql/database.h"
......@@ -46,6 +47,12 @@ class MediaHistoryStoreUnitTest : public testing::Test {
EXPECT_TRUE(db_.Open(db_file));
}
void TearDown() override { content::RunAllTasksUntilIdle(); }
MediaHistoryStore* GetMediaHistoryStore() {
return media_history_store_.get();
}
private:
base::ScopedTempDir temp_dir_;
......@@ -64,4 +71,50 @@ TEST_F(MediaHistoryStoreUnitTest, CreateDatabaseTables) {
ASSERT_TRUE(GetDB().DoesTableExist("playback"));
}
TEST_F(MediaHistoryStoreUnitTest, SavePlayback) {
// Create a media player watch time and save it to the playbacks table.
GURL url("http://google.com/test");
content::MediaPlayerWatchTime watch_time(
url, url.GetOrigin(), base::TimeDelta::FromMilliseconds(123),
base::TimeDelta::FromMilliseconds(321));
GetMediaHistoryStore()->SavePlayback(watch_time);
// Save the watch time a second time.
GetMediaHistoryStore()->SavePlayback(watch_time);
// Wait until the playbacks have finished saving.
content::RunAllTasksUntilIdle();
// Verify that the playback table contains the expected number of items
sql::Statement select_from_playback_statement(GetDB().GetUniqueStatement(
"SELECT id, url, origin_id, watch_time_ms, timestamp_ms FROM playback"));
ASSERT_TRUE(select_from_playback_statement.is_valid());
int playback_row_count = 0;
while (select_from_playback_statement.Step()) {
++playback_row_count;
EXPECT_EQ(playback_row_count, select_from_playback_statement.ColumnInt(0));
EXPECT_EQ("http://google.com/test",
select_from_playback_statement.ColumnString(1));
EXPECT_EQ(1, select_from_playback_statement.ColumnInt(2));
EXPECT_EQ(123, select_from_playback_statement.ColumnInt(3));
EXPECT_EQ(321, select_from_playback_statement.ColumnInt(4));
}
EXPECT_EQ(2, playback_row_count);
// Verify that the origin table contains the expected number of items
sql::Statement select_from_origin_statement(
GetDB().GetUniqueStatement("SELECT id, origin FROM origin"));
ASSERT_TRUE(select_from_origin_statement.is_valid());
int origin_row_count = 0;
while (select_from_origin_statement.Step()) {
++origin_row_count;
EXPECT_EQ(1, select_from_origin_statement.ColumnInt(0));
EXPECT_EQ("http://google.com/",
select_from_origin_statement.ColumnString(1));
}
EXPECT_EQ(1, origin_row_count);
}
} // namespace media_history
......@@ -16,6 +16,7 @@
#include "components/viz/common/surfaces/surface_id.h"
#include "content/browser/webui/web_ui_impl.h"
#include "content/common/content_export.h"
#include "content/public/browser/media_player_watch_time.h"
#include "content/public/browser/media_stream_request.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/visibility.h"
......@@ -447,6 +448,9 @@ class CONTENT_EXPORT RenderFrameHostDelegate {
virtual void AudioContextPlaybackStopped(RenderFrameHost* host,
int context_id) {}
virtual void MediaWatchTimeChanged(
const content::MediaPlayerWatchTime& watch_time) {}
// Returns the main frame of the inner delegate that is attached to this
// delegate using |frame_tree_node|. Returns nullptr if no such inner delegate
// exists.
......
......@@ -7192,6 +7192,12 @@ void WebContentsImpl::AudioContextPlaybackStopped(RenderFrameHost* host,
observer.AudioContextPlaybackStopped(audio_context_id);
}
void WebContentsImpl::MediaWatchTimeChanged(
const content::MediaPlayerWatchTime& watch_time) {
for (auto& observer : observers_)
observer.MediaWatchTimeChanged(watch_time);
}
RenderFrameHostImpl* WebContentsImpl::GetMainFrameForInnerDelegate(
FrameTreeNode* frame_tree_node) {
if (auto* web_contents = node_.GetInnerWebContentsInFrame(frame_tree_node))
......
......@@ -633,6 +633,8 @@ class CONTENT_EXPORT WebContentsImpl : public WebContents,
int context_id) override;
void AudioContextPlaybackStopped(RenderFrameHost* host,
int context_id) override;
void MediaWatchTimeChanged(
const content::MediaPlayerWatchTime& watch_time) override;
RenderFrameHostImpl* GetMainFrameForInnerDelegate(
FrameTreeNode* frame_tree_node) override;
bool IsFrameLowPriority(const RenderFrameHost* render_frame_host) override;
......
......@@ -202,6 +202,8 @@ jumbo_source_set("browser_sources") {
"media_keys_listener_manager.h",
"media_player_id.cc",
"media_player_id.h",
"media_player_watch_time.cc",
"media_player_watch_time.h",
"media_request_state.h",
"media_session.h",
"media_stream_request.cc",
......
......@@ -2,20 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/media/history/media_player_watchtime.h"
#include "content/public/browser/media_player_watch_time.h"
namespace content {
MediaPlayerWatchtime::MediaPlayerWatchtime(bool has_audio,
bool has_video,
base::TimeDelta cumulative_watchtime,
base::TimeDelta last_timestamp)
: has_audio(has_audio),
has_video(has_video),
cumulative_watchtime(cumulative_watchtime),
MediaPlayerWatchTime::MediaPlayerWatchTime(
GURL url,
GURL origin,
base::TimeDelta cumulative_watch_time,
base::TimeDelta last_timestamp)
: url(url),
origin(origin),
cumulative_watch_time(cumulative_watch_time),
last_timestamp(last_timestamp) {}
MediaPlayerWatchtime::MediaPlayerWatchtime(const MediaPlayerWatchtime& other) =
MediaPlayerWatchTime::MediaPlayerWatchTime(const MediaPlayerWatchTime& other) =
default;
} // namespace content
......@@ -2,31 +2,29 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_MEDIA_HISTORY_MEDIA_PLAYER_WATCHTIME_H_
#define CHROME_BROWSER_MEDIA_HISTORY_MEDIA_PLAYER_WATCHTIME_H_
#include <string>
#ifndef CONTENT_PUBLIC_BROWSER_MEDIA_PLAYER_WATCH_TIME_H_
#define CONTENT_PUBLIC_BROWSER_MEDIA_PLAYER_WATCH_TIME_H_
#include "base/time/time.h"
#include "content/common/content_export.h"
#include "url/gurl.h"
namespace content {
struct MediaPlayerWatchtime {
MediaPlayerWatchtime(bool has_audio,
bool has_video,
base::TimeDelta cumulative_watchtime,
struct CONTENT_EXPORT MediaPlayerWatchTime {
MediaPlayerWatchTime(GURL url,
GURL origin,
base::TimeDelta cumulative_watch_time,
base::TimeDelta last_timestamp);
MediaPlayerWatchtime(const MediaPlayerWatchtime& other);
~MediaPlayerWatchtime() = default;
MediaPlayerWatchTime(const MediaPlayerWatchTime& other);
~MediaPlayerWatchTime() = default;
bool has_audio;
bool has_video;
base::TimeDelta cumulative_watchtime;
GURL url;
GURL origin;
base::TimeDelta cumulative_watch_time;
base::TimeDelta last_timestamp;
std::string origin;
std::string url;
};
} // namespace content
#endif // CHROME_BROWSER_MEDIA_HISTORY_MEDIA_PLAYER_WATCHTIME_H_
#endif // CONTENT_PUBLIC_BROWSER_MEDIA_PLAYER_WATCH_TIME_H_
......@@ -53,6 +53,7 @@ struct FaviconURL;
struct FocusedNodeDetails;
struct LoadCommittedDetails;
struct MediaPlayerId;
struct MediaPlayerWatchTime;
struct PrunedDetails;
struct Referrer;
......@@ -506,6 +507,10 @@ class CONTENT_EXPORT WebContentsObserver : public IPC::Listener {
bool has_audio;
};
// Invoked when media playback is interrupted or completed.
virtual void MediaWatchTimeChanged(
const content::MediaPlayerWatchTime& watch_time) {}
virtual void MediaStartedPlaying(const MediaPlayerInfo& video_type,
const MediaPlayerId& id) {}
enum class MediaStoppedReason {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment