diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/CastNotificationControl.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/CastNotificationControl.java
index 9864d7b4a7c49..bb8ab9b481e78 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/CastNotificationControl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/CastNotificationControl.java
@@ -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;
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSessionImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSessionImpl.java
index 47cdc4db30a65..2427eca5c2fec 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSessionImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSessionImpl.java
@@ -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
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationInfo.java b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationInfo.java
index 4269336e414fb..e55bc122f40b8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationInfo.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationInfo.java
@@ -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());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
index 67cd06f5aa0b8..fa664a7eda125 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
@@ -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();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java
index db674b73dd62c..657410fef750a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java
@@ -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());
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/NotificationTitleUpdatedTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/NotificationTitleUpdatedTest.java
index 3838d5db66885..58ab5f77f38bb 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/NotificationTitleUpdatedTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/NotificationTitleUpdatedTest.java
@@ -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());
                 }
             });
     }
diff --git a/content/browser/android/web_contents_observer_proxy.cc b/content/browser/android/web_contents_observer_proxy.cc
index 0483fd867f004..dfa52cfb2112a 100644
--- a/content/browser/android/web_contents_observer_proxy.cc
+++ b/content/browser/android/web_contents_observer_proxy.cc
@@ -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(
diff --git a/content/browser/android/web_contents_observer_proxy.h b/content/browser/android/web_contents_observer_proxy.h
index d37f718c64d04..af0895dc6693f 100644
--- a/content/browser/android/web_contents_observer_proxy.h
+++ b/content/browser/android/web_contents_observer_proxy.h
@@ -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,
diff --git a/content/browser/media/session/media_session.h b/content/browser/media/session/media_session.h
index 4fb5d42af49f5..2553c6d7885d5 100644
--- a/content/browser/media/session/media_session.h
+++ b/content/browser/media/session/media_session.h
@@ -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);
 };
 
diff --git a/content/browser/media/session/media_session_browsertest.cc b/content/browser/media/session/media_session_browsertest.cc
index f75342d7bfd8f..c8388f733ef6f 100644
--- a/content/browser/media/session/media_session_browsertest.cc
+++ b/content/browser/media/session/media_session_browsertest.cc
@@ -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(
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 83c786214b17d..0a748f1078548 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -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() {
diff --git a/content/common/android/common_jni_registrar.cc b/content/common/android/common_jni_registrar.cc
index 2ba3387c98db5..0feaf381eca85 100644
--- a/content/common/android/common_jni_registrar.cc
+++ b/content/common/android/common_jni_registrar.cc
@@ -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
diff --git a/content/common/android/media_metadata_android.cc b/content/common/android/media_metadata_android.cc
new file mode 100644
index 0000000000000..0bd1563175891
--- /dev/null
+++ b/content/common/android/media_metadata_android.cc
@@ -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
diff --git a/content/common/android/media_metadata_android.h b/content/common/android/media_metadata_android.h
new file mode 100644
index 0000000000000..e580e53af0fca
--- /dev/null
+++ b/content/common/android/media_metadata_android.h
@@ -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_
diff --git a/content/content_common.gypi b/content/content_common.gypi
index 94fc7a47ce975..75fbe5f53d3db 100644
--- a/content/content_common.gypi
+++ b/content/content_common.gypi
@@ -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',
diff --git a/content/content_jni.gypi b/content/content_jni.gypi
index a34d63b5a330a..2f1ccf829ffe6 100644
--- a/content/content_jni.gypi
+++ b/content/content_jni.gypi
@@ -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',
diff --git a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java
index 6a0ed1ffaf406..6919298023a23 100644
--- a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java
+++ b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java
@@ -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);
         }
     }
 
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java b/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java
index 158ddf99f76ec..85c8819efb1b6 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java
@@ -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.
diff --git a/content/public/android/java/src/org/chromium/content_public/common/MediaMetadata.java b/content/public/android/java/src/org/chromium/content_public/common/MediaMetadata.java
new file mode 100644
index 0000000000000..fca2464e27e64
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content_public/common/MediaMetadata.java
@@ -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;
+    }
+}
diff --git a/content/public/browser/web_contents_observer.h b/content/public/browser/web_contents_observer.h
index 0d7148c37782f..226f309d05121 100644
--- a/content/public/browser/web_contents_observer.h
+++ b/content/public/browser/web_contents_observer.h
@@ -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) {}