0

Media Session API: use MediaMetadata in the browser process.

This is adding a Java counterpart to the content::MediaMetadata added in Java
with a proxy to pass the object from C++ to Java.

The MediaMetadata information are only used to replace the title from the media
notification and set some Android metadata. Other UI changes will require UI
review.

BUG=497735,581728

Review URL: https://codereview.chromium.org/1458703003

Cr-Commit-Position: refs/heads/master@{#381697}
This commit is contained in:
mlamouri
2016-03-17 06:14:54 -07:00
committed by Commit bot
parent 8d689ca9b2
commit 2d1bfbd36e
20 changed files with 312 additions and 81 deletions

@ -16,6 +16,7 @@ import org.chromium.chrome.browser.media.ui.MediaNotificationInfo;
import org.chromium.chrome.browser.media.ui.MediaNotificationListener; import org.chromium.chrome.browser.media.ui.MediaNotificationListener;
import org.chromium.chrome.browser.media.ui.MediaNotificationManager; import org.chromium.chrome.browser.media.ui.MediaNotificationManager;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content_public.common.MediaMetadata;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -117,7 +118,7 @@ public class CastNotificationControl implements MediaRouteController.UiListener,
} }
private void updateNotification() { private void updateNotification() {
mNotificationBuilder.setTitle(mTitle); mNotificationBuilder.setMetadata(new MediaMetadata(mTitle, "", ""));
if (mState == PlayerState.PAUSED || mState == PlayerState.PLAYING) { if (mState == PlayerState.PAUSED || mState == PlayerState.PLAYING) {
mNotificationBuilder.setPaused(mState != PlayerState.PLAYING); mNotificationBuilder.setPaused(mState != PlayerState.PLAYING);
mNotificationBuilder.setActions(MediaNotificationInfo.ACTION_STOP mNotificationBuilder.setActions(MediaNotificationInfo.ACTION_STOP
@ -208,4 +209,4 @@ public class CastNotificationControl implements MediaRouteController.UiListener,
boolean isShowingForTests() { boolean isShowingForTests() {
return mIsShowing; return mIsShowing;
} }
} }

@ -22,6 +22,8 @@ import org.chromium.chrome.browser.media.ui.MediaNotificationInfo;
import org.chromium.chrome.browser.media.ui.MediaNotificationListener; import org.chromium.chrome.browser.media.ui.MediaNotificationListener;
import org.chromium.chrome.browser.media.ui.MediaNotificationManager; import org.chromium.chrome.browser.media.ui.MediaNotificationManager;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content_public.common.MediaMetadata;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -128,7 +130,7 @@ public class CastSessionImpl implements MediaNotificationListener, CastSession {
} }
mNotificationBuilder = new MediaNotificationInfo.Builder() mNotificationBuilder = new MediaNotificationInfo.Builder()
.setTitle(mCastDevice.getFriendlyName()) .setMetadata(new MediaMetadata(mCastDevice.getFriendlyName(), "", ""))
.setPaused(false) .setPaused(false)
.setOrigin(origin) .setOrigin(origin)
// TODO(avayvod): the same session might have more than one tab id. Should we track // TODO(avayvod): the same session might have more than one tab id. Should we track

@ -9,6 +9,7 @@ import android.graphics.Bitmap;
import android.text.TextUtils; import android.text.TextUtils;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content_public.common.MediaMetadata;
/** /**
* Exposes information about the current media notification to the external clients. * Exposes information about the current media notification to the external clients.
@ -42,7 +43,7 @@ public class MediaNotificationInfo {
*/ */
public static final class Builder { public static final class Builder {
private String mTitle = ""; private MediaMetadata mMetadata;
private boolean mIsPaused = false; private boolean mIsPaused = false;
private String mOrigin = ""; private String mOrigin = "";
private int mTabId = Tab.INVALID_TAB_ID; private int mTabId = Tab.INVALID_TAB_ID;
@ -61,12 +62,12 @@ public class MediaNotificationInfo {
} }
public MediaNotificationInfo build() { public MediaNotificationInfo build() {
assert mTitle != null; assert mMetadata != null;
assert mOrigin != null; assert mOrigin != null;
assert mListener != null; assert mListener != null;
return new MediaNotificationInfo( return new MediaNotificationInfo(
mTitle, mMetadata,
mIsPaused, mIsPaused,
mOrigin, mOrigin,
mTabId, mTabId,
@ -79,8 +80,8 @@ public class MediaNotificationInfo {
mListener); mListener);
} }
public Builder setTitle(String title) { public Builder setMetadata(MediaMetadata metadata) {
mTitle = title; mMetadata = metadata;
return this; return this;
} }
@ -141,9 +142,9 @@ public class MediaNotificationInfo {
private final int mActions; private final int mActions;
/** /**
* The title of the media. * The metadata associated with the media.
*/ */
public final String title; public final MediaMetadata metadata;
/** /**
* The current state of the media, paused or not. * The current state of the media, paused or not.
@ -213,7 +214,7 @@ public class MediaNotificationInfo {
/** /**
* Create a new MediaNotificationInfo. * Create a new MediaNotificationInfo.
* @param title The title of the media. * @param metadata The metadata associated with the media.
* @param isPaused The current state of the media, paused or not. * @param isPaused The current state of the media, paused or not.
* @param origin The origin of the tab containing the media. * @param origin The origin of the tab containing the media.
* @param tabId The id of the tab containing the media. * @param tabId The id of the tab containing the media.
@ -223,7 +224,7 @@ public class MediaNotificationInfo {
* @param listener The listener for the control events. * @param listener The listener for the control events.
*/ */
private MediaNotificationInfo( private MediaNotificationInfo(
String title, MediaMetadata metadata,
boolean isPaused, boolean isPaused,
String origin, String origin,
int tabId, int tabId,
@ -234,7 +235,7 @@ public class MediaNotificationInfo {
Bitmap image, Bitmap image,
Intent contentIntent, Intent contentIntent,
MediaNotificationListener listener) { MediaNotificationListener listener) {
this.title = title; this.metadata = metadata;
this.isPaused = isPaused; this.isPaused = isPaused;
this.origin = origin; this.origin = origin;
this.tabId = tabId; this.tabId = tabId;
@ -259,7 +260,7 @@ public class MediaNotificationInfo {
&& icon == other.icon && icon == other.icon
&& mActions == other.mActions && mActions == other.mActions
&& id == other.id && id == other.id
&& TextUtils.equals(title, other.title) && metadata.equals(other.metadata)
&& TextUtils.equals(origin, other.origin) && TextUtils.equals(origin, other.origin)
&& image == other.image || (image != null && image.sameAs(other.image)) && image == other.image || (image != null && image.sameAs(other.image))
&& contentIntent.equals(other.contentIntent) && contentIntent.equals(other.contentIntent)
@ -270,7 +271,7 @@ public class MediaNotificationInfo {
public int hashCode() { public int hashCode() {
int result = isPaused ? 1 : 0; int result = isPaused ? 1 : 0;
result = 31 * result + (isPrivate ? 1 : 0); result = 31 * result + (isPrivate ? 1 : 0);
result = 31 * result + (title == null ? 0 : title.hashCode()); result = 31 * result + (metadata == null ? 0 : metadata.hashCode());
result = 31 * result + (origin == null ? 0 : origin.hashCode()); result = 31 * result + (origin == null ? 0 : origin.hashCode());
result = 31 * result + (image == null ? 0 : image.hashCode()); result = 31 * result + (image == null ? 0 : image.hashCode());
result = 31 * result + (contentIntent == null ? 0 : contentIntent.hashCode()); result = 31 * result + (contentIntent == null ? 0 : contentIntent.hashCode());

@ -24,6 +24,7 @@ import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat; import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v7.media.MediaRouter; import android.support.v7.media.MediaRouter;
import android.text.TextUtils;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
@ -502,7 +503,7 @@ public class MediaNotificationManager {
playPauseButtonId = R.id.button2; playPauseButtonId = R.id.button2;
} }
contentView.setTextViewText(R.id.title, mMediaNotificationInfo.title); contentView.setTextViewText(R.id.title, mMediaNotificationInfo.metadata.getTitle());
contentView.setTextViewText(R.id.status, mMediaNotificationInfo.origin); contentView.setTextViewText(R.id.status, mMediaNotificationInfo.origin);
if (mNotificationIcon != null) { if (mNotificationIcon != null) {
contentView.setImageViewBitmap(R.id.icon, mNotificationIcon); contentView.setImageViewBitmap(R.id.icon, mNotificationIcon);
@ -541,7 +542,7 @@ public class MediaNotificationManager {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE,
mMediaNotificationInfo.title); mMediaNotificationInfo.metadata.getTitle());
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE,
mMediaNotificationInfo.origin); mMediaNotificationInfo.origin);
metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON,
@ -554,12 +555,21 @@ public class MediaNotificationManager {
} }
} else { } else {
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE,
mMediaNotificationInfo.title); mMediaNotificationInfo.metadata.getTitle());
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST,
mMediaNotificationInfo.origin); mMediaNotificationInfo.origin);
metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, mediaSessionImage); metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, mediaSessionImage);
} }
if (!TextUtils.isEmpty(mMediaNotificationInfo.metadata.getArtist())) {
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST,
mMediaNotificationInfo.metadata.getArtist());
}
if (!TextUtils.isEmpty(mMediaNotificationInfo.metadata.getAlbum())) {
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM,
mMediaNotificationInfo.metadata.getAlbum());
}
return metadataBuilder.build(); return metadataBuilder.build();
} }

@ -6,6 +6,7 @@ package org.chromium.chrome.browser.media.ui;
import android.app.Activity; import android.app.Activity;
import android.media.AudioManager; import android.media.AudioManager;
import android.text.TextUtils;
import org.chromium.base.ApplicationStatus; import org.chromium.base.ApplicationStatus;
import org.chromium.base.Log; import org.chromium.base.Log;
@ -17,6 +18,7 @@ import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.util.UrlUtilities; import org.chromium.chrome.browser.util.UrlUtilities;
import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsObserver; import org.chromium.content_public.browser.WebContentsObserver;
import org.chromium.content_public.common.MediaMetadata;
import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.base.WindowAndroid;
import java.net.URI; import java.net.URI;
@ -36,6 +38,7 @@ public class MediaSessionTabHelper {
private WebContentsObserver mWebContentsObserver; private WebContentsObserver mWebContentsObserver;
private int mPreviousVolumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE; private int mPreviousVolumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE;
private MediaNotificationInfo.Builder mNotificationInfoBuilder = null; private MediaNotificationInfo.Builder mNotificationInfoBuilder = null;
private MediaMetadata mFallbackMetadata;
private MediaNotificationListener mControlsListener = new MediaNotificationListener() { private MediaNotificationListener mControlsListener = new MediaNotificationListener() {
@Override @Override
@ -84,7 +87,8 @@ public class MediaSessionTabHelper {
} }
@Override @Override
public void mediaSessionStateChanged(boolean isControllable, boolean isPaused) { public void mediaSessionStateChanged(boolean isControllable, boolean isPaused,
MediaMetadata metadata) {
if (!isControllable) { if (!isControllable) {
hideNotification(); hideNotification();
return; return;
@ -97,8 +101,20 @@ public class MediaSessionTabHelper {
+ "Showing the full URL instead."); + "Showing the full URL instead.");
} }
mFallbackMetadata = null;
// The page's title is used as a placeholder if no title is specified in the
// metadata.
if (TextUtils.isEmpty(metadata.getTitle())) {
mFallbackMetadata = new MediaMetadata(
sanitizeMediaTitle(mTab.getTitle()),
metadata.getArtist(),
metadata.getAlbum());
metadata = mFallbackMetadata;
}
mNotificationInfoBuilder = new MediaNotificationInfo.Builder() mNotificationInfoBuilder = new MediaNotificationInfo.Builder()
.setTitle(sanitizeMediaTitle(mTab.getTitle())) .setMetadata(metadata)
.setPaused(isPaused) .setPaused(isPaused)
.setOrigin(origin) .setOrigin(origin)
.setTabId(mTab.getId()) .setTabId(mTab.getId())
@ -145,9 +161,11 @@ public class MediaSessionTabHelper {
@Override @Override
public void onTitleUpdated(Tab tab) { public void onTitleUpdated(Tab tab) {
assert tab == mTab; assert tab == mTab;
if (mNotificationInfoBuilder == null) return; if (mNotificationInfoBuilder == null || mFallbackMetadata == null) return;
mFallbackMetadata.setTitle(sanitizeMediaTitle(mTab.getTitle()));
mNotificationInfoBuilder.setMetadata(mFallbackMetadata);
mNotificationInfoBuilder.setTitle(sanitizeMediaTitle(mTab.getTitle()));
MediaNotificationManager.show(ApplicationStatus.getApplicationContext(), MediaNotificationManager.show(ApplicationStatus.getApplicationContext(),
mNotificationInfoBuilder.build()); mNotificationInfoBuilder.build());
} }

@ -19,6 +19,7 @@ import org.chromium.chrome.test.util.ChromeRestriction;
import org.chromium.chrome.test.util.browser.TabTitleObserver; import org.chromium.chrome.test.util.browser.TabTitleObserver;
import org.chromium.content.browser.test.util.JavaScriptUtils; import org.chromium.content.browser.test.util.JavaScriptUtils;
import org.chromium.content_public.browser.WebContentsObserver; import org.chromium.content_public.browser.WebContentsObserver;
import org.chromium.content_public.common.MediaMetadata;
/** /**
* Test of media notifications to see whether the text updates when the tab title changes * Test of media notifications to see whether the text updates when the tab title changes
@ -102,7 +103,7 @@ public class NotificationTitleUpdatedTest extends ChromeActivityTestCaseBase<Chr
tab.getWebContents().getObserversForTesting(); tab.getWebContents().getObserversForTesting();
while (observers.hasNext()) { while (observers.hasNext()) {
observers.next().mediaSessionStateChanged( observers.next().mediaSessionStateChanged(
isControllable, isSuspended); isControllable, isSuspended, new MediaMetadata("", "", ""));
} }
} }
}); });
@ -125,7 +126,7 @@ public class NotificationTitleUpdatedTest extends ChromeActivityTestCaseBase<Chr
@Override @Override
public void run() { public void run() {
assertEquals(title, MediaNotificationManager assertEquals(title, MediaNotificationManager
.getNotificationInfoForTesting(NOTIFICATION_ID).title); .getNotificationInfoForTesting(NOTIFICATION_ID).metadata.getTitle());
} }
}); });
} }

@ -11,6 +11,7 @@
#include "base/android/scoped_java_ref.h" #include "base/android/scoped_java_ref.h"
#include "content/browser/renderer_host/render_widget_host_impl.h" #include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h" #include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/android/media_metadata_android.h"
#include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h" #include "content/public/browser/navigation_handle.h"
@ -307,14 +308,18 @@ void WebContentsObserverProxy::DidStartNavigationToPendingEntry(
env, obj.obj(), jstring_url.obj()); env, obj.obj(), jstring_url.obj());
} }
void WebContentsObserverProxy::MediaSessionStateChanged(bool is_controllable, void WebContentsObserverProxy::MediaSessionStateChanged(
bool is_suspended) { bool is_controllable,
bool is_suspended,
const MediaMetadata& metadata) {
JNIEnv* env = AttachCurrentThread(); JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jobject> obj(java_observer_); ScopedJavaLocalRef<jobject> obj(java_observer_);
ScopedJavaLocalRef<jobject> j_metadata =
MediaMetadataAndroid::CreateJavaObject(env, metadata);
Java_WebContentsObserverProxy_mediaSessionStateChanged( Java_WebContentsObserverProxy_mediaSessionStateChanged(
env, obj.obj(), is_controllable, is_suspended); env, obj.obj(), is_controllable, is_suspended, j_metadata.obj());
} }
void WebContentsObserverProxy::SetToBaseURLForDataURLIfNeeded( void WebContentsObserverProxy::SetToBaseURLForDataURLIfNeeded(

@ -73,7 +73,8 @@ class WebContentsObserverProxy : public WebContentsObserver {
const GURL& url, const GURL& url,
NavigationController::ReloadType reload_type) override; NavigationController::ReloadType reload_type) override;
void MediaSessionStateChanged(bool is_controllable, void MediaSessionStateChanged(bool is_controllable,
bool is_suspended) override; bool is_suspended,
const MediaMetadata& metadata) override;
void SetToBaseURLForDataURLIfNeeded(std::string* url); void SetToBaseURLForDataURLIfNeeded(std::string* url);
void DidFailLoadInternal(bool is_provisional_load, void DidFailLoadInternal(bool is_provisional_load,

@ -13,6 +13,7 @@
#include "content/common/content_export.h" #include "content/common/content_export.h"
#include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h" #include "content/public/browser/web_contents_user_data.h"
#include "content/public/common/media_metadata.h"
class MediaSessionBrowserTest; class MediaSessionBrowserTest;
@ -58,6 +59,11 @@ class MediaSession : public WebContentsObserver,
~MediaSession() override; ~MediaSession() override;
void setMetadata(const MediaMetadata& metadata) {
metadata_ = metadata;
}
const MediaMetadata& metadata() const { return metadata_; }
// Adds the given player to the current media session. Returns whether the // Adds the given player to the current media session. Returns whether the
// player was successfully added. If it returns false, AddPlayer() should be // player was successfully added. If it returns false, AddPlayer() should be
// called again later. // called again later.
@ -184,6 +190,8 @@ class MediaSession : public WebContentsObserver,
// multiply their volume with this multiplier to get the effective volume. // multiply their volume with this multiplier to get the effective volume.
double volume_multiplier_; double volume_multiplier_;
MediaMetadata metadata_;
DISALLOW_COPY_AND_ASSIGN(MediaSession); DISALLOW_COPY_AND_ASSIGN(MediaSession);
}; };

@ -48,8 +48,9 @@ class MockWebContentsObserver : public WebContentsObserver {
MockWebContentsObserver(WebContents* web_contents) MockWebContentsObserver(WebContents* web_contents)
: WebContentsObserver(web_contents) {} : WebContentsObserver(web_contents) {}
MOCK_METHOD2(MediaSessionStateChanged, MOCK_METHOD3(MediaSessionStateChanged,
void(bool is_controllable, bool is_suspended)); void(bool is_controllable, bool is_suspended,
const content::MediaMetadata& metadata));
}; };
} // namespace } // namespace
@ -488,7 +489,7 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, MediaSessionType) {
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsShowForContent) { IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsShowForContent) {
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
new MockMediaSessionObserver); new MockMediaSessionObserver);
@ -502,7 +503,7 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsShowForContent) {
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsNoShowForTransient) { IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsNoShowForTransient) {
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(false, false)); MediaSessionStateChanged(false, false, testing::_));
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
new MockMediaSessionObserver); new MockMediaSessionObserver);
@ -516,9 +517,9 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsNoShowForTransient) {
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsHideWhenStopped) { IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsHideWhenStopped) {
Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(false, true)) MediaSessionStateChanged(false, true, testing::_))
.After(showControls); .After(showControls);
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
@ -534,7 +535,7 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsHideWhenStopped) {
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsShownAcceptTransient) { IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsShownAcceptTransient) {
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
new MockMediaSessionObserver); new MockMediaSessionObserver);
@ -550,10 +551,10 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsShownAcceptTransient) {
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
ControlsShownAfterContentAdded) { ControlsShownAfterContentAdded) {
Expectation dontShowControls = EXPECT_CALL( Expectation dontShowControls = EXPECT_CALL(*mock_web_contents_observer(),
*mock_web_contents_observer(), MediaSessionStateChanged(false, false)); MediaSessionStateChanged(false, false, testing::_));
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)) MediaSessionStateChanged(true, false, testing::_))
.After(dontShowControls); .After(dontShowControls);
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
@ -571,7 +572,7 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
ControlsStayIfOnlyOnePlayerHasBeenPaused) { ControlsStayIfOnlyOnePlayerHasBeenPaused) {
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
new MockMediaSessionObserver); new MockMediaSessionObserver);
@ -590,9 +591,9 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
ControlsHideWhenTheLastPlayerIsRemoved) { ControlsHideWhenTheLastPlayerIsRemoved) {
Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(false, true)) MediaSessionStateChanged(false, true, testing::_))
.After(showControls); .After(showControls);
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
new MockMediaSessionObserver); new MockMediaSessionObserver);
@ -614,9 +615,9 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
ControlsHideWhenAllThePlayersAreRemoved) { ControlsHideWhenAllThePlayersAreRemoved) {
Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(false, true)) MediaSessionStateChanged(false, true, testing::_))
.After(showControls); .After(showControls);
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
@ -634,9 +635,9 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
ControlsNotHideWhenTheLastPlayerIsPaused) { ControlsNotHideWhenTheLastPlayerIsPaused) {
Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, true)) MediaSessionStateChanged(true, true, testing::_))
.After(showControls); .After(showControls);
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
@ -659,9 +660,9 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
SuspendTemporaryUpdatesControls) { SuspendTemporaryUpdatesControls) {
Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, true)) MediaSessionStateChanged(true, true, testing::_))
.After(showControls); .After(showControls);
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
@ -677,12 +678,11 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsUpdatedWhenResumed) { IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsUpdatedWhenResumed) {
Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, true)) MediaSessionStateChanged(true, true, testing::_)).After(showControls);
.After(showControls);
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)) MediaSessionStateChanged(true, false, testing::_))
.After(pauseControls); .After(pauseControls);
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
@ -699,9 +699,9 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsUpdatedWhenResumed) {
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
ControlsHideWhenSessionSuspendedPermanently) { ControlsHideWhenSessionSuspendedPermanently) {
Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(false, true)) MediaSessionStateChanged(false, true, testing::_))
.After(showControls); .After(showControls);
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
@ -718,12 +718,11 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
ConstrolsHideWhenSessionStops) { ConstrolsHideWhenSessionStops) {
Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, true)) MediaSessionStateChanged(true, true, testing::_)).After(showControls);
.After(showControls);
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(false, true)) MediaSessionStateChanged(false, true, testing::_))
.After(pauseControls); .After(pauseControls);
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
@ -740,12 +739,11 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
ControlsHideWhenSessionChangesFromContentToTransient) { ControlsHideWhenSessionChangesFromContentToTransient) {
Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, true)) MediaSessionStateChanged(true, true, testing::_)).After(showControls);
.After(showControls);
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(false, false)) MediaSessionStateChanged(false, false, testing::_))
.After(pauseControls); .After(pauseControls);
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
@ -765,12 +763,11 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
ControlsUpdatedWhenNewPlayerResetsSession) { ControlsUpdatedWhenNewPlayerResetsSession) {
Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, true)) MediaSessionStateChanged(true, true, testing::_)).After(showControls);
.After(showControls);
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)) MediaSessionStateChanged(true, false, testing::_))
.After(pauseControls); .After(pauseControls);
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
@ -789,12 +786,11 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
ControlsResumedWhenPlayerIsResumed) { ControlsResumedWhenPlayerIsResumed) {
Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, true)) MediaSessionStateChanged(true, true, testing::_)).After(showControls);
.After(showControls);
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)) MediaSessionStateChanged(true, false, testing::_))
.After(pauseControls); .After(pauseControls);
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
@ -813,9 +809,10 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
ControlsUpdatedDueToResumeSessionAction) { ControlsUpdatedDueToResumeSessionAction) {
Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, true)).After(showControls); MediaSessionStateChanged(true, true, testing::_))
.After(showControls);
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(
new MockMediaSessionObserver); new MockMediaSessionObserver);
@ -830,12 +827,11 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
ControlsUpdatedDueToSuspendSessionAction) { ControlsUpdatedDueToSuspendSessionAction) {
Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)); MediaSessionStateChanged(true, false, testing::_));
Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(), Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, true)) MediaSessionStateChanged(true, true, testing::_)).After(showControls);
.After(showControls);
EXPECT_CALL(*mock_web_contents_observer(), EXPECT_CALL(*mock_web_contents_observer(),
MediaSessionStateChanged(true, false)) MediaSessionStateChanged(true, false, testing::_))
.After(pauseControls); .After(pauseControls);
scoped_ptr<MockMediaSessionObserver> media_session_observer( scoped_ptr<MockMediaSessionObserver> media_session_observer(

@ -3586,7 +3586,8 @@ void WebContentsImpl::OnMediaSessionStateChanged() {
MediaSession* session = MediaSession::Get(this); MediaSession* session = MediaSession::Get(this);
FOR_EACH_OBSERVER(WebContentsObserver, observers_, FOR_EACH_OBSERVER(WebContentsObserver, observers_,
MediaSessionStateChanged(session->IsControllable(), MediaSessionStateChanged(session->IsControllable(),
session->IsSuspended())); session->IsSuspended(),
session->metadata()));
} }
void WebContentsImpl::ResumeMediaSession() { void WebContentsImpl::ResumeMediaSession() {

@ -8,10 +8,12 @@
#include "base/android/jni_registrar.h" #include "base/android/jni_registrar.h"
#include "base/macros.h" #include "base/macros.h"
#include "content/common/android/hash_set.h" #include "content/common/android/hash_set.h"
#include "content/common/android/media_metadata_android.h"
namespace { namespace {
base::android::RegistrationMethod kContentRegisteredMethods[] = { base::android::RegistrationMethod kContentRegisteredMethods[] = {
{ "HashSet", content::RegisterHashSet }, { "HashSet", content::RegisterHashSet },
{ "MediaMetadataAndroid", content::MediaMetadataAndroid::Register },
}; };
} // namespace } // namespace

@ -0,0 +1,33 @@
// 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 "content/common/android/media_metadata_android.h"
#include "base/android/jni_string.h"
#include "content/public/common/media_metadata.h"
#include "jni/MediaMetadata_jni.h"
namespace content {
// static
base::android::ScopedJavaLocalRef<jobject>
MediaMetadataAndroid::CreateJavaObject(
JNIEnv* env, const MediaMetadata& metadata) {
ScopedJavaLocalRef<jstring> j_title(
base::android::ConvertUTF16ToJavaString(env, metadata.title));
ScopedJavaLocalRef<jstring> j_artist(
base::android::ConvertUTF16ToJavaString(env, metadata.artist));
ScopedJavaLocalRef<jstring> j_album(
base::android::ConvertUTF16ToJavaString(env, metadata.album));
return Java_MediaMetadata_create(
env, j_title.obj(), j_artist.obj(), j_album.obj());
}
// static
bool MediaMetadataAndroid::Register(JNIEnv* env) {
return RegisterNativesImpl(env);
}
} // namespace content

@ -0,0 +1,29 @@
// 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.
#ifndef CONTENT_COMMON_ANDROID_MEDIA_METADATA_ANDROID_H_
#define CONTENT_COMMON_ANDROID_MEDIA_METADATA_ANDROID_H_
#include <jni.h>
#include "base/android/scoped_java_ref.h"
namespace content {
struct MediaMetadata;
class MediaMetadataAndroid {
public:
static base::android::ScopedJavaLocalRef<jobject> CreateJavaObject(
JNIEnv* env, const MediaMetadata& metadata);
static bool Register(JNIEnv* env);
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(MediaMetadataAndroid);
};
} // namespace content
#endif // CONTENT_COMMON_ANDROID_MEDIA_METADATA_ANDROID_H_

@ -234,6 +234,8 @@
'common/android/gin_java_bridge_value.h', 'common/android/gin_java_bridge_value.h',
'common/android/hash_set.cc', 'common/android/hash_set.cc',
'common/android/hash_set.h', 'common/android/hash_set.h',
'common/android/media_metadata_android.cc',
'common/android/media_metadata_android.h',
'common/android/sync_compositor_messages.cc', 'common/android/sync_compositor_messages.cc',
'common/android/sync_compositor_messages.h', 'common/android/sync_compositor_messages.h',
'common/appcache_interfaces.cc', 'common/appcache_interfaces.cc',

@ -42,6 +42,7 @@
'public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java', 'public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java',
'public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java', 'public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java',
'public/android/java/src/org/chromium/content_public/browser/LoadUrlParams.java', 'public/android/java/src/org/chromium/content_public/browser/LoadUrlParams.java',
'public/android/java/src/org/chromium/content_public/common/MediaMetadata.java',
], ],
'variables': { 'variables': {
'jni_gen_package': 'content', 'jni_gen_package': 'content',

@ -12,6 +12,7 @@ import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.MainDex; import org.chromium.base.annotations.MainDex;
import org.chromium.content_public.browser.WebContentsObserver; import org.chromium.content_public.browser.WebContentsObserver;
import org.chromium.content_public.common.MediaMetadata;
/** /**
* Serves as a compound observer proxy for dispatching WebContentsObserver callbacks, * Serves as a compound observer proxy for dispatching WebContentsObserver callbacks,
@ -233,9 +234,11 @@ class WebContentsObserverProxy extends WebContentsObserver {
@Override @Override
@CalledByNative @CalledByNative
public void mediaSessionStateChanged(boolean isControllable, boolean isSuspended) { public void mediaSessionStateChanged(
boolean isControllable, boolean isSuspended, MediaMetadata metadata) {
for (mObserversIterator.rewind(); mObserversIterator.hasNext();) { for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {
mObserversIterator.next().mediaSessionStateChanged(isControllable, isSuspended); mObserversIterator.next().mediaSessionStateChanged(
isControllable, isSuspended, metadata);
} }
} }

@ -4,6 +4,8 @@
package org.chromium.content_public.browser; package org.chromium.content_public.browser;
import org.chromium.content_public.common.MediaMetadata;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
/** /**
@ -163,9 +165,11 @@ public abstract class WebContentsObserver {
/** /**
* Called when the media session state changed. * Called when the media session state changed.
* @param isControllable if the session can be resumed or suspended. * @param isControllable if the session can be resumed or suspended.
* @param isSuspended if the session currently suspended or not * @param isSuspended if the session currently suspended or not.
* @param metadata of the media session.
*/ */
public void mediaSessionStateChanged(boolean isControllable, boolean isSuspended) {} public void mediaSessionStateChanged(
boolean isControllable, boolean isSuspended, MediaMetadata metadata) {}
/** /**
* Stop observing the web contents and clean up associated references. * Stop observing the web contents and clean up associated references.

@ -0,0 +1,111 @@
// 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.
package org.chromium.content_public.common;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
/**
* The MediaMetadata class carries information related to a media session. It is
* the Java counterpart of content::MediaMetadata.
*/
@JNINamespace("content")
public class MediaMetadata {
@NonNull
private String mTitle;
@NonNull
private String mArtist;
@NonNull
private String mAlbum;
/**
* Returns the title associated with the media session.
*/
public String getTitle() {
return mTitle;
}
/**
* Returns the artist name associated with the media session.
*/
public String getArtist() {
return mArtist;
}
/**
* Returns the album name associated with the media session.
*/
public String getAlbum() {
return mAlbum;
}
/**
* Sets the title associated with the media session.
* @param title The title to use for the media session.
*/
public void setTitle(String title) {
mTitle = title;
}
/**
* Sets the arstist name associated with the media session.
* @param arstist The artist name to use for the media session.
*/
public void setArtist(String artist) {
mArtist = artist;
}
/**
* Sets the album name associated with the media session.
* @param album The album name to use for the media session.
*/
public void setAlbum(String album) {
mAlbum = album;
}
/**
* Creates a new MediaMetadata from the C++ code. This is exactly like the
* constructor below apart that it can be called by native code.
*/
@CalledByNative
private static MediaMetadata create(String title, String artist, String album) {
return new MediaMetadata(title == null ? "" : title, artist == null ? "" : artist,
album == null ? "" : album);
}
/**
* Creates a new MediaMetadata.
*/
public MediaMetadata(@NonNull String title, @NonNull String artist, @NonNull String album) {
mTitle = title;
mArtist = artist;
mAlbum = album;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (!(obj instanceof MediaMetadata)) return false;
MediaMetadata other = (MediaMetadata) obj;
return TextUtils.equals(mTitle, other.mTitle)
&& TextUtils.equals(mArtist, other.mArtist)
&& TextUtils.equals(mAlbum, other.mAlbum);
}
@Override
public int hashCode() {
int result = mTitle.hashCode();
result = 31 * result + mArtist.hashCode();
result = 31 * result + mAlbum.hashCode();
return result;
}
}

@ -13,6 +13,7 @@
#include "content/common/content_export.h" #include "content/common/content_export.h"
#include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_controller.h"
#include "content/public/common/frame_navigate_params.h" #include "content/public/common/frame_navigate_params.h"
#include "content/public/common/media_metadata.h"
#include "content/public/common/security_style.h" #include "content/public/common/security_style.h"
#include "ipc/ipc_listener.h" #include "ipc/ipc_listener.h"
#include "ipc/ipc_sender.h" #include "ipc/ipc_sender.h"
@ -445,7 +446,8 @@ class CONTENT_EXPORT WebContentsObserver : public IPC::Listener,
// Invoked when media session has changed its state. // Invoked when media session has changed its state.
virtual void MediaSessionStateChanged(bool is_controllable, virtual void MediaSessionStateChanged(bool is_controllable,
bool is_suspended) {} bool is_suspended,
const MediaMetadata& metadata) {}
// Invoked when the renderer process changes the page scale factor. // Invoked when the renderer process changes the page scale factor.
virtual void OnPageScaleFactorChanged(float page_scale_factor) {} virtual void OnPageScaleFactorChanged(float page_scale_factor) {}