 .../media/js/plugins/drupalmedia/plugin.es6.js     | 11 ++++++++-
 .../modules/media/js/plugins/drupalmedia/plugin.js | 10 +++++++-
 .../media/src/Controller/MediaFilterController.php | 27 +++++++++++++++++++---
 3 files changed, 43 insertions(+), 5 deletions(-)

diff --git a/core/modules/media/js/plugins/drupalmedia/plugin.es6.js b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
index ba7f78735a..f1b61febca 100644
--- a/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
+++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
@@ -163,6 +163,13 @@
           },
         },
 
+        getLabel() {
+          if (this.data.label) {
+            return this.data.label;
+          }
+          return Drupal.t('Embedded media');
+        },
+
         upcast(element, data) {
           const { attributes } = element;
           // This matches the behavior of the corresponding server-side text filter plugin.
@@ -181,6 +188,7 @@
           if (data.hasCaption && data.attributes['data-caption'] === '') {
             data.attributes['data-caption'] = ' ';
           }
+          data.label = null;
           data.link = null;
           if (element.parent.name === 'a') {
             data.link = CKEDITOR.tools.copy(element.parent.attributes);
@@ -496,8 +504,9 @@
               text: this.downcast().getOuterHtml(),
             },
             dataType: 'html',
-            success: previewHtml => {
+            success: (previewHtml, textStatus, jqXhr) => {
               this.element.setHtml(previewHtml);
+              this.setData('label', jqXhr.getResponseHeader('Drupal-Media-Label'));
               callback(this);
             },
             error: () => {
diff --git a/core/modules/media/js/plugins/drupalmedia/plugin.js b/core/modules/media/js/plugins/drupalmedia/plugin.js
index 4b875df35e..c3cafdd58d 100644
--- a/core/modules/media/js/plugins/drupalmedia/plugin.js
+++ b/core/modules/media/js/plugins/drupalmedia/plugin.js
@@ -124,6 +124,12 @@
           }
         },
 
+        getLabel: function getLabel() {
+          if (this.data.label) {
+            return this.data.label;
+          }
+          return Drupal.t('Embedded media');
+        },
         upcast: function upcast(element, data) {
           var attributes = element.attributes;
 
@@ -136,6 +142,7 @@
           if (data.hasCaption && data.attributes['data-caption'] === '') {
             data.attributes['data-caption'] = ' ';
           }
+          data.label = null;
           data.link = null;
           if (element.parent.name === 'a') {
             data.link = CKEDITOR.tools.copy(element.parent.attributes);
@@ -315,8 +322,9 @@
               text: this.downcast().getOuterHtml()
             },
             dataType: 'html',
-            success: function success(previewHtml) {
+            success: function success(previewHtml, textStatus, jqXhr) {
               _this2.element.setHtml(previewHtml);
+              _this2.setData('label', jqXhr.getResponseHeader('Drupal-Media-Label'));
               callback(_this2);
             },
             error: function error() {
diff --git a/core/modules/media/src/Controller/MediaFilterController.php b/core/modules/media/src/Controller/MediaFilterController.php
index 3350b32e78..59c263e1bc 100644
--- a/core/modules/media/src/Controller/MediaFilterController.php
+++ b/core/modules/media/src/Controller/MediaFilterController.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\ContentEntityStorageInterface;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\filter\FilterFormatInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -28,14 +29,24 @@ class MediaFilterController implements ContainerInjectionInterface {
    */
   protected $renderer;
 
+  /**
+   * The media storage.
+   *
+   * @var \Drupal\Core\Entity\ContentEntityStorageInterface
+   */
+  protected $mediaStorage;
+
   /**
    * Constructs an MediaFilterController instance.
    *
    * @param \Drupal\Core\Render\RendererInterface $renderer
    *   The renderer service.
+   * @param \Drupal\Core\Entity\ContentEntityStorageInterface
+   *   The media storage.
    */
-  public function __construct(RendererInterface $renderer) {
+  public function __construct(RendererInterface $renderer, ContentEntityStorageInterface $media_storage) {
     $this->renderer = $renderer;
+    $this->mediaStorage = $media_storage;
   }
 
   /**
@@ -43,7 +54,8 @@ public function __construct(RendererInterface $renderer) {
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('renderer')
+      $container->get('renderer'),
+      $container->get('entity_type.manager')->getStorage('media')
     );
   }
 
@@ -81,12 +93,21 @@ public function preview(Request $request, FilterFormatInterface $filter_format)
     ];
     $html = $this->renderer->renderPlain($build);
 
+    // Use the bubbled cache tags to determine which media was rendered (if any)
+    // so we can embed the label in the response, for use in an ARIA label.
+    $headers = [];
+    $media_cache_tags = array_filter($build['#cache']['tags'], function ($t) { return strpos($t, 'media:') === 0;});
+    if (!empty($media_cache_tags)) {
+      $embedded_media_id = substr(reset($media_cache_tags), 6);
+      $headers['Drupal-Media-Label'] = $this->mediaStorage->load($embedded_media_id)->label();
+    }
+
     // Note that we intentionally do not use:
     // - \Drupal\Core\Cache\CacheableResponse because caching it on the server
     //   side is wasteful, hence there is no need for cacheability metadata.
     // - \Drupal\Core\Render\HtmlResponse because there is no need for
     //   attachments nor cacheability metadata.
-    return (new Response($html))
+    return (new Response($html, 200, $headers))
       // Do not allow any intermediary to cache the response, only the end user.
       ->setPrivate()
       // Allow the end user to cache it for up to 5 minutes.
