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.MediaNotificationManager;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content_public.common.MediaMetadata;
import javax.annotation.Nullable;
@ -117,7 +118,7 @@ public class CastNotificationControl implements MediaRouteController.UiListener,
}
private void updateNotification() {
mNotificationBuilder.setTitle(mTitle);
mNotificationBuilder.setMetadata(new MediaMetadata(mTitle, "", ""));
if (mState == PlayerState.PAUSED || mState == PlayerState.PLAYING) {
mNotificationBuilder.setPaused(mState != PlayerState.PLAYING);
mNotificationBuilder.setActions(MediaNotificationInfo.ACTION_STOP
@ -208,4 +209,4 @@ public class CastNotificationControl implements MediaRouteController.UiListener,
boolean isShowingForTests() {
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.MediaNotificationManager;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content_public.common.MediaMetadata;
import org.json.JSONException;
import org.json.JSONObject;
@ -128,7 +130,7 @@ public class CastSessionImpl implements MediaNotificationListener, CastSession {
}
mNotificationBuilder = new MediaNotificationInfo.Builder()
.setTitle(mCastDevice.getFriendlyName())
.setMetadata(new MediaMetadata(mCastDevice.getFriendlyName(), "", ""))
.setPaused(false)
.setOrigin(origin)
// 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 org.chromium.chrome.browser.tab.Tab;
import org.chromium.content_public.common.MediaMetadata;
/**
* Exposes information about the current media notification to the external clients.
@ -42,7 +43,7 @@ public class MediaNotificationInfo {
*/
public static final class Builder {
private String mTitle = "";
private MediaMetadata mMetadata;
private boolean mIsPaused = false;
private String mOrigin = "";
private int mTabId = Tab.INVALID_TAB_ID;
@ -61,12 +62,12 @@ public class MediaNotificationInfo {
}
public MediaNotificationInfo build() {
assert mTitle != null;
assert mMetadata != null;
assert mOrigin != null;
assert mListener != null;
return new MediaNotificationInfo(
mTitle,
mMetadata,
mIsPaused,
mOrigin,
mTabId,
@ -79,8 +80,8 @@ public class MediaNotificationInfo {
mListener);
}
public Builder setTitle(String title) {
mTitle = title;
public Builder setMetadata(MediaMetadata metadata) {
mMetadata = metadata;
return this;
}
@ -141,9 +142,9 @@ public class MediaNotificationInfo {
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.
@ -213,7 +214,7 @@ public class 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 origin The origin 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.
*/
private MediaNotificationInfo(
String title,
MediaMetadata metadata,
boolean isPaused,
String origin,
int tabId,
@ -234,7 +235,7 @@ public class MediaNotificationInfo {
Bitmap image,
Intent contentIntent,
MediaNotificationListener listener) {
this.title = title;
this.metadata = metadata;
this.isPaused = isPaused;
this.origin = origin;
this.tabId = tabId;
@ -259,7 +260,7 @@ public class MediaNotificationInfo {
&& icon == other.icon
&& mActions == other.mActions
&& id == other.id
&& TextUtils.equals(title, other.title)
&& metadata.equals(other.metadata)
&& TextUtils.equals(origin, other.origin)
&& image == other.image || (image != null && image.sameAs(other.image))
&& contentIntent.equals(other.contentIntent)
@ -270,7 +271,7 @@ public class MediaNotificationInfo {
public int hashCode() {
int result = isPaused ? 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 + (image == null ? 0 : image.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.PlaybackStateCompat;
import android.support.v7.media.MediaRouter;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.View;
@ -502,7 +503,7 @@ public class MediaNotificationManager {
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);
if (mNotificationIcon != null) {
contentView.setImageViewBitmap(R.id.icon, mNotificationIcon);
@ -541,7 +542,7 @@ public class MediaNotificationManager {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE,
mMediaNotificationInfo.title);
mMediaNotificationInfo.metadata.getTitle());
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE,
mMediaNotificationInfo.origin);
metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON,
@ -554,12 +555,21 @@ public class MediaNotificationManager {
}
} else {
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE,
mMediaNotificationInfo.title);
mMediaNotificationInfo.metadata.getTitle());
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST,
mMediaNotificationInfo.origin);
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();
}

@ -6,6 +6,7 @@ package org.chromium.chrome.browser.media.ui;
import android.app.Activity;
import android.media.AudioManager;
import android.text.TextUtils;
import org.chromium.base.ApplicationStatus;
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.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsObserver;
import org.chromium.content_public.common.MediaMetadata;
import org.chromium.ui.base.WindowAndroid;
import java.net.URI;
@ -36,6 +38,7 @@ public class MediaSessionTabHelper {
private WebContentsObserver mWebContentsObserver;
private int mPreviousVolumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE;
private MediaNotificationInfo.Builder mNotificationInfoBuilder = null;
private MediaMetadata mFallbackMetadata;
private MediaNotificationListener mControlsListener = new MediaNotificationListener() {
@Override
@ -84,7 +87,8 @@ public class MediaSessionTabHelper {
}
@Override
public void mediaSessionStateChanged(boolean isControllable, boolean isPaused) {
public void mediaSessionStateChanged(boolean isControllable, boolean isPaused,
MediaMetadata metadata) {
if (!isControllable) {
hideNotification();
return;
@ -97,8 +101,20 @@ public class MediaSessionTabHelper {
+ "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()
.setTitle(sanitizeMediaTitle(mTab.getTitle()))
.setMetadata(metadata)
.setPaused(isPaused)
.setOrigin(origin)
.setTabId(mTab.getId())
@ -145,9 +161,11 @@ public class MediaSessionTabHelper {
@Override
public void onTitleUpdated(Tab tab) {
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(),
mNotificationInfoBuilder.build());
}

@ -19,6 +19,7 @@ import org.chromium.chrome.test.util.ChromeRestriction;
import org.chromium.chrome.test.util.browser.TabTitleObserver;
import org.chromium.content.browser.test.util.JavaScriptUtils;
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
@ -102,7 +103,7 @@ public class NotificationTitleUpdatedTest extends ChromeActivityTestCaseBase<Chr
tab.getWebContents().getObserversForTesting();
while (observers.hasNext()) {
observers.next().mediaSessionStateChanged(
isControllable, isSuspended);
isControllable, isSuspended, new MediaMetadata("", "", ""));
}
}
});
@ -125,7 +126,7 @@ public class NotificationTitleUpdatedTest extends ChromeActivityTestCaseBase<Chr
@Override
public void run() {
assertEquals(title, MediaNotificationManager
.getNotificationInfoForTesting(NOTIFICATION_ID).title);
.getNotificationInfoForTesting(NOTIFICATION_ID).metadata.getTitle());
}
});
}

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

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

@ -13,6 +13,7 @@
#include "content/common/content_export.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "content/public/common/media_metadata.h"
class MediaSessionBrowserTest;
@ -58,6 +59,11 @@ class MediaSession : public WebContentsObserver,
~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
// player was successfully added. If it returns false, AddPlayer() should be
// called again later.
@ -184,6 +190,8 @@ class MediaSession : public WebContentsObserver,
// multiply their volume with this multiplier to get the effective volume.
double volume_multiplier_;
MediaMetadata metadata_;
DISALLOW_COPY_AND_ASSIGN(MediaSession);
};

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

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

@ -8,10 +8,12 @@
#include "base/android/jni_registrar.h"
#include "base/macros.h"
#include "content/common/android/hash_set.h"
#include "content/common/android/media_metadata_android.h"
namespace {
base::android::RegistrationMethod kContentRegisteredMethods[] = {
{ "HashSet", content::RegisterHashSet },
{ "MediaMetadataAndroid", content::MediaMetadataAndroid::Register },
};
} // 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/hash_set.cc',
'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.h',
'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/WebContentsObserverProxy.java',
'public/android/java/src/org/chromium/content_public/browser/LoadUrlParams.java',
'public/android/java/src/org/chromium/content_public/common/MediaMetadata.java',
],
'variables': {
'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.MainDex;
import org.chromium.content_public.browser.WebContentsObserver;
import org.chromium.content_public.common.MediaMetadata;
/**
* Serves as a compound observer proxy for dispatching WebContentsObserver callbacks,
@ -233,9 +234,11 @@ class WebContentsObserverProxy extends WebContentsObserver {
@Override
@CalledByNative
public void mediaSessionStateChanged(boolean isControllable, boolean isSuspended) {
public void mediaSessionStateChanged(
boolean isControllable, boolean isSuspended, MediaMetadata metadata) {
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;
import org.chromium.content_public.common.MediaMetadata;
import java.lang.ref.WeakReference;
/**
@ -163,9 +165,11 @@ public abstract class WebContentsObserver {
/**
* Called when the media session state changed.
* @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.

@ -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/public/browser/navigation_controller.h"
#include "content/public/common/frame_navigate_params.h"
#include "content/public/common/media_metadata.h"
#include "content/public/common/security_style.h"
#include "ipc/ipc_listener.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.
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.
virtual void OnPageScaleFactorChanged(float page_scale_factor) {}