diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/FileMediaFormatterBase.php b/core/lib/Drupal/Core/Field/MediaFormatterTrait.php
similarity index 52%
rename from core/modules/file/src/Plugin/Field/FieldFormatter/FileMediaFormatterBase.php
rename to core/lib/Drupal/Core/Field/MediaFormatterTrait.php
index 8cbf88ba5a..738b3c9afb 100644
--- a/core/modules/file/src/Plugin/Field/FieldFormatter/FileMediaFormatterBase.php
+++ b/core/lib/Drupal/Core/Field/MediaFormatterTrait.php
@@ -1,28 +1,18 @@
 <?php
 
-namespace Drupal\file\Plugin\Field\FieldFormatter;
+namespace Drupal\Core\Field;
 
-use Drupal\Core\Cache\Cache;
-use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
-use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Template\Attribute;
+use Drupal\Core\Field\MediaFormatterTrait;
 
 /**
- * Base class for media file formatter.
+ * Useful methods for media formatters.
+ *
+ * @see \Drupal\file\Plugin\Field\FieldFormatter\FileMediaFormatterBase
  */
-abstract class FileMediaFormatterBase extends FileFormatterBase implements FileMediaFormatterInterface {
-
-  /**
-   * Gets the HTML tag for the formatter.
-   *
-   * @return string
-   *   The HTML tag of this formatter.
-   */
-  protected function getHtmlTag() {
-    return static::getMediaType();
-  }
+trait MediaFormatterTrait {
 
   /**
    * {@inheritdoc}
@@ -68,27 +58,6 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
     ];
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public static function isApplicable(FieldDefinitionInterface $field_definition) {
-    if (!parent::isApplicable($field_definition)) {
-      return FALSE;
-    }
-    /** @var \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface $extension_mime_type_guesser */
-    $extension_mime_type_guesser = \Drupal::service('file.mime_type.guesser.extension');
-    $extension_list = array_filter(preg_split('/\s+/', $field_definition->getSetting('file_extensions')));
-
-    foreach ($extension_list as $extension) {
-      $mime_type = $extension_mime_type_guesser->guess('fakedFile.' . $extension);
-
-      if (static::mimeTypeApplies($mime_type)) {
-        return TRUE;
-      }
-    }
-    return FALSE;
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -109,6 +78,16 @@ public function settingsSummary() {
     return $summary;
   }
 
+  /**
+   * Gets the HTML tag for the formatter.
+   *
+   * @return string
+   *   The HTML tag of this formatter.
+   */
+  protected function getHtmlTag() {
+    return static::getMediaType();
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -123,19 +102,11 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
     $attributes = $this->prepareAttributes();
     foreach ($source_files as $delta => $files) {
       $elements[$delta] = [
-        '#theme' => $this->getPluginId(),
+        '#theme' => 'file_' . $this->getHtmlTag(),
         '#attributes' => $attributes,
         '#files' => $files,
-        '#cache' => ['tags' => []],
       ];
-
-      $cache_tags = [];
-      foreach ($files as $file) {
-        $cache_tags = Cache::mergeTags($cache_tags, $file['file']->getCacheTags());
-      }
-      $elements[$delta]['#cache']['tags'] = $cache_tags;
     }
-
     return $elements;
   }
 
@@ -159,62 +130,4 @@ protected function prepareAttributes(array $additional_attributes = []) {
     return $attributes;
   }
 
-  /**
-   * Check if given MIME type applies to the media type of the formatter.
-   *
-   * @param string $mime_type
-   *   The complete MIME type.
-   *
-   * @return bool
-   *   TRUE if the MIME type applies, FALSE otherwise.
-   */
-  protected static function mimeTypeApplies($mime_type) {
-    list($type) = explode('/', $mime_type, 2);
-    return $type === static::getMediaType();
-  }
-
-  /**
-   * Gets source files with attributes.
-   *
-   * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items
-   *   The item list.
-   * @param string $langcode
-   *   The language code of the referenced entities to display.
-   *
-   * @return array
-   *   Numerically indexed array, which again contains an associative array with
-   *   the following key/values:
-   *     - file => \Drupal\file\Entity\File
-   *     - source_attributes => \Drupal\Core\Template\Attribute
-   */
-  protected function getSourceFiles(EntityReferenceFieldItemListInterface $items, $langcode) {
-    $source_files = [];
-    // Because we can have the files grouped in a single media tag, we do a
-    // grouping in case the multiple file behavior is not 'tags'.
-    /** @var \Drupal\file\Entity\File $file */
-    foreach ($this->getEntitiesToView($items, $langcode) as $file) {
-      if (static::mimeTypeApplies($file->getMimeType())) {
-        $source_attributes = new Attribute();
-        $source_attributes
-          ->setAttribute('src', file_url_transform_relative(file_create_url($file->getFileUri())))
-          ->setAttribute('type', $file->getMimeType());
-        if ($this->getSetting('multiple_file_display_type') === 'tags') {
-          $source_files[] = [
-            [
-              'file' => $file,
-              'source_attributes' => $source_attributes,
-            ],
-          ];
-        }
-        else {
-          $source_files[0][] = [
-            'file' => $file,
-            'source_attributes' => $source_attributes,
-          ];
-        }
-      }
-    }
-    return $source_files;
-  }
-
 }
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/AudioFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/AudioFormatter.php
new file mode 100644
index 0000000000..97f5cdca82
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/AudioFormatter.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
+
+/**
+ * Plugin implementation of the 'audio' formatter.
+ *
+ * @FieldFormatter(
+ *   id = "audio",
+ *   label = @Translation("Audio"),
+ *   description = @Translation("Display the URL using an HTML5 audio tag."),
+ *   field_types = {
+ *     "link"
+ *   }
+ * )
+ */
+class AudioFormatter extends MediaFormatterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getMediaType() {
+    return 'audio';
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/MediaFormatterBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/MediaFormatterBase.php
new file mode 100644
index 0000000000..f0291b5e7a
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/MediaFormatterBase.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Template\Attribute;
+use Drupal\Core\Field\MediaFormatterTrait;
+use Drupal\Core\Field\FormatterBase;
+use Drupal\file\Plugin\Field\FieldFormatter\FileMediaFormatterInterface;
+
+/**
+ * Base class for media file formatter.
+ *
+ * // TODO FieldFormatterInterface is really bad name in this context.
+ */
+abstract class MediaFormatterBase extends FormatterBase implements FileMediaFormatterInterface {
+
+  use MediaFormatterTrait {
+    MediaFormatterTrait::viewElements as traitViewElements;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function isApplicable(FieldDefinitionInterface $field_definition) {
+    // TODO see if we can hide formatter for irrelevant fields to improve UX.
+    // Maybe limit only to Media entities?
+    return TRUE;
+  }
+
+  /**
+   * Gets source files with attributes.
+   *
+   * @param \Drupal\Core\Field\FieldItemListInterface $items
+   *   The item list.
+   * @param string $langcode
+   *   The language code of the referenced entities to display.
+   *
+   * @return array
+   *   Numerically indexed array, which again contains an associative array with
+   *   the following key/values:
+   *     - file => Remote media URL
+   *     - source_attributes => \Drupal\Core\Template\Attribute
+   */
+  protected function getSourceFiles(FieldItemListInterface $items, $langcode) {
+    $source_files = [];
+    foreach ($items as $link) {
+      $mime_type = \Drupal::service('file.mime_type.guesser')->guess($link->uri);
+      // TODO some streams are application/octet-stream and possibly others?
+      if (TRUE || static::mimeTypeApplies($mime_type)) {
+        $source_attributes = new Attribute();
+        $source_attributes
+          ->setAttribute('src', file_url_transform_relative($link->uri));
+          // TODO wrong type will fail to play stream even if playable w/o
+          // the type attribute. Could we skip?
+          //->setAttribute('type', $mime_type);
+        if ($this->getSetting('multiple_file_display_type') === 'tags') {
+          $source_files[] = [
+            [
+              'file' => $link->uri,
+              'source_attributes' => $source_attributes,
+            ],
+          ];
+        }
+        else {
+          $source_files[0][] = [
+            'file' => $link->uri,
+            'source_attributes' => $source_attributes,
+          ];
+        }
+      }
+    }
+    return $source_files;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/VideoFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/VideoFormatter.php
new file mode 100644
index 0000000000..edd45e0029
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/VideoFormatter.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Plugin implementation of the 'video' formatter.
+ *
+ * @FieldFormatter(
+ *   id = "video",
+ *   label = @Translation("Video"),
+ *   description = @Translation("Display the URL using an HTML5 video tag."),
+ *   field_types = {
+ *     "link"
+ *   }
+ * )
+ */
+class VideoFormatter extends MediaFormatterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getMediaType() {
+    return 'video';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return [
+      'muted' => FALSE,
+      'width' => 640,
+      'height' => 480,
+    ] + parent::defaultSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    return parent::settingsForm($form, $form_state) + [
+      'muted' => [
+        '#title' => $this->t('Muted'),
+        '#type' => 'checkbox',
+        '#default_value' => $this->getSetting('muted'),
+      ],
+      'width' => [
+        '#type' => 'number',
+        '#title' => $this->t('Width'),
+        '#default_value' => $this->getSetting('width'),
+        '#size' => 5,
+        '#maxlength' => 5,
+        '#field_suffix' => $this->t('pixels'),
+        '#min' => 0,
+        '#required' => TRUE,
+      ],
+      'height' => [
+        '#type' => 'number',
+        '#title' => $this->t('Height'),
+        '#default_value' => $this->getSetting('height'),
+        '#size' => 5,
+        '#maxlength' => 5,
+        '#field_suffix' => $this->t('pixels'),
+        '#min' => 0,
+        '#required' => TRUE,
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsSummary() {
+    $summary = parent::settingsSummary();
+    $summary[] = $this->t('Muted: %muted', ['%muted' => $this->getSetting('muted') ? $this->t('yes') : $this->t('no')]);
+    $summary[] = $this->t('Size: %width x %height pixels', [
+      '%width' => $this->getSetting('width'),
+      '%height' => $this->getSetting('height'),
+    ]);
+    return $summary;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function prepareAttributes(array $additional_attributes = []) {
+    return parent::prepareAttributes(['muted'])
+      ->setAttribute('width', $this->getSetting('width'))
+      ->setAttribute('height', $this->getSetting('height'));
+  }
+
+}
