diff --git a/core/modules/media/config/schema/media.schema.yml b/core/modules/media/config/schema/media.schema.yml
index dad518985a..a27c9a3bd1 100644
--- a/core/modules/media/config/schema/media.schema.yml
+++ b/core/modules/media/config/schema/media.schema.yml
@@ -40,6 +40,17 @@ field.formatter.settings.media_thumbnail:
   type: field.formatter.settings.image
   label: 'Media thumbnail field display format settings'
 
+field.formatter.settings.oembed:
+  type: mapping
+  label: 'oEmbed display format settings'
+  mapping:
+    max_width:
+      type: integer
+      label: 'Maximum width'
+    max_height:
+      type: integer
+      label: 'Maximum height'
+
 media.source.*:
   type: mapping
   label: 'Media source settings'
@@ -52,6 +63,20 @@ media.source.image:
   type: media.source.field_aware
   label: '"Image" media source configuration'
 
+media.source.remote_video:
+  type: media.source.field_aware
+  label: 'Remote Video media source configuration'
+  mapping:
+    thumbnails_location:
+      type: string
+      label: 'Thumbnails location'
+    allowed_providers:
+      type: sequence
+      label: 'Allowed oEmbed providers'
+      sequence:
+        type: string
+        label: 'Provider name'
+
 media.source.field_aware:
   type: mapping
   mapping:
diff --git a/core/modules/media/media.info.yml b/core/modules/media/media.info.yml
index e69f14aaa1..94e2b0e78f 100644
--- a/core/modules/media/media.info.yml
+++ b/core/modules/media/media.info.yml
@@ -8,4 +8,5 @@ hidden: true
 dependencies:
   - file
   - image
+  - link
   - user
diff --git a/core/modules/media/media.module b/core/modules/media/media.module
index 5df059c284..0e4a6eaec0 100644
--- a/core/modules/media/media.module
+++ b/core/modules/media/media.module
@@ -9,9 +9,13 @@
 use Drupal\Core\Session\AccountInterface;
 use Drupal\field\FieldConfigInterface;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
+use Drupal\link\Plugin\Field\FieldWidget\LinkWidget;
+use Drupal\media\MediaInterface;
+use Drupal\media\Plugin\media\Source\OEmbed;
 
 /**
  * Implements hook_help().
@@ -71,9 +75,59 @@ function media_theme_suggestions_media(array $variables) {
   $suggestions[] = 'media__' . $media->bundle();
   $suggestions[] = 'media__' . $media->bundle() . '__' . $sanitized_view_mode;
 
+  /** @var \Drupal\media\Entity\MediaType $media_type */
+  $media_type = \Drupal::entityTypeManager()->getStorage('media_type')->load($media->bundle());
+  if ($media_type->getSource() instanceof OEmbed) {
+    $provider = $media_type->getSource()->getMetadata($media, 'provider_name');
+    $provider = \Drupal::transliteration()->transliterate($provider);
+    $suggestions[] = 'media__oembed';
+    $suggestions[] = 'media__oembed__' . strtolower($provider);
+  }
+
   return $suggestions;
 }
 
+/**
+ * Implements hook_field_widget_WIDGET_TYPE_form_alter().
+ */
+function media_field_widget_link_default_form_alter(array &$element, FormStateInterface $form_state, array $context) {
+  if (!empty($element['uri'])) {
+    /** @var \Drupal\media\MediaInterface $media */
+    $media = $context['items']->getEntity();
+    if (!($media instanceof MediaInterface)) {
+      return;
+    }
+
+    $field_definition = $context['items']->getFieldDefinition();
+    $source_config = $media->getSource()->getConfiguration();
+    // Only change the URI field description on the source field.
+    if ($media->getSource()->getSourceFieldDefinition()->getName() != $field_definition->getName()) {
+      return;
+    }
+
+    $source_definition = $media->getSource()->getPluginDefinition();
+    $allowed_providers = $source_definition['supported_providers'];
+    if (!empty($source_config['allowed_providers'])) {
+      $allowed_providers = $source_config['allowed_providers'];
+    }
+
+    $message = t('These oEmbed providers are allowed: @providers', ['@providers' => implode(', ', $allowed_providers)]);
+
+    if (is_array($element['uri']['#description']) && isset($element['uri']['#description']['#items'])) {
+      $element['uri']['#description']['#items'][] = $message;
+    }
+    else {
+      $element['uri']['#description'] = [
+        '#theme' => 'item_list',
+        '#items' => [
+          $element['uri']['#description'],
+          $message,
+        ],
+      ];
+    }
+  }
+}
+
 /**
  * Prepares variables for media templates.
  *
diff --git a/core/modules/media/media.services.yml b/core/modules/media/media.services.yml
index f22f90a124..f3d64ae4a5 100644
--- a/core/modules/media/media.services.yml
+++ b/core/modules/media/media.services.yml
@@ -2,9 +2,14 @@ services:
   plugin.manager.media.source:
     class: Drupal\media\MediaSourceManager
     parent: default_plugin_manager
-
   access_check.media.revision:
     class: Drupal\media\Access\MediaRevisionAccessCheck
     arguments: ['@entity_type.manager']
     tags:
       - { name: access_check, applies_to: _access_media_revision }
+  media.oembed.provider_discovery:
+    class: Drupal\media\OEmbed\ProviderDiscovery
+    arguments: ['@http_client', 'https://oembed.com/providers.json', '@cache.default']
+  media.oembed.resource_fetcher:
+    class: Drupal\media\OEmbed\ResourceFetcher
+    arguments: ['@media.oembed.provider_discovery', '@http_client', '@module_handler', '@cache.default']
diff --git a/core/modules/media/src/MediaSourceBase.php b/core/modules/media/src/MediaSourceBase.php
index 4f8301fbee..23e7a4f67b 100644
--- a/core/modules/media/src/MediaSourceBase.php
+++ b/core/modules/media/src/MediaSourceBase.php
@@ -301,7 +301,9 @@ public function createSourceField(MediaTypeInterface $type) {
    *   returned. Otherwise, a new, unused one is generated.
    */
   protected function getSourceFieldName() {
-    $base_id = 'field_media_' . $this->getPluginId();
+    // Some media sources are using a deriver, so their plugin IDs are
+    // containing a ':' which is not allowed for field names.
+    $base_id = 'field_media_' . str_replace(':', '_', $this->getPluginId());
     $tries = 0;
     $storage = $this->entityTypeManager->getStorage('field_storage_config');
 
@@ -319,6 +321,22 @@ protected function getSourceFieldName() {
     return $id;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getSourceFieldValue(MediaInterface $media) {
+    $source_field = $this->configuration['source_field'];
+    if (empty($source_field)) {
+      throw new \RuntimeException('Source field for media source is not defined.');
+    }
+    // Source value is stored as the main property of the source field in 99% of
+    // the cases so return that. 1% can override this function and tweak the
+    // logic.
+    /** @var \Drupal\Core\Field\FieldItemInterface $field_item */
+    $field_item = $media->get($source_field)->first();
+    return $field_item->get($field_item->mainPropertyName());
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/media/src/MediaSourceInterface.php b/core/modules/media/src/MediaSourceInterface.php
index 43dd8aa733..8c9d4df690 100644
--- a/core/modules/media/src/MediaSourceInterface.php
+++ b/core/modules/media/src/MediaSourceInterface.php
@@ -177,4 +177,20 @@ public function prepareViewDisplay(MediaTypeInterface $type, EntityViewDisplayIn
    */
   public function prepareFormDisplay(MediaTypeInterface $type, EntityFormDisplayInterface $display);
 
+  /**
+   * Gets source field value.
+   *
+   * Value that is stored in the source field.
+   *
+   * @param MediaInterface $media
+   *   A media item.
+   *
+   * @return \Drupal\Core\TypedData\TypedDataInterface
+   *   The source value.
+   *
+   * @throws \RuntimeException
+   *   If the source field for the media source is not defined.
+   */
+  public function getSourceFieldValue(MediaInterface $media);
+
 }
diff --git a/core/modules/media/src/OEmbed/ProviderDiscovery.php b/core/modules/media/src/OEmbed/ProviderDiscovery.php
new file mode 100644
index 0000000000..3e8b9e84f1
--- /dev/null
+++ b/core/modules/media/src/OEmbed/ProviderDiscovery.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace Drupal\media\OEmbed;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\UseCacheBackendTrait;
+use Drupal\Core\Url;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\RequestException;
+
+/**
+ * Retrieves and caches information about oEmbed providers.
+ */
+class ProviderDiscovery implements ProviderDiscoveryInterface {
+
+  use UseCacheBackendTrait;
+
+  /**
+   * The HTTP client.
+   *
+   * @var \GuzzleHttp\Client
+   */
+  protected $httpClient;
+
+  /**
+   * URL of the JSON with providers info.
+   *
+   * @var string
+   */
+  protected $providersUrl;
+
+  /**
+   * Constructs OEmbed class.
+   *
+   * @param \GuzzleHttp\Client $http_client
+   *   The HTTP client.
+   * @param string $providers_url
+   *   The URL of the remote database of oEmbed providers.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   (optional) The cache backend.
+   */
+  public function __construct(Client $http_client, $providers_url, CacheBackendInterface $cache_backend = NULL) {
+    $this->httpClient = $http_client;
+    $this->providersUrl = $providers_url;
+    $this->cacheBackend = $cache_backend;
+    $this->useCaches = isset($cache_backend);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAll() {
+    $cache_id = 'media:oembed_providers';
+
+    $cached = $this->cacheGet($cache_id);
+    if ($cached) {
+      return $cached->data;
+    }
+
+    try {
+      $response = $this->httpClient->request('GET', $this->providersUrl);
+    }
+    catch (RequestException $e) {
+      throw new ProviderException("Could not retrieve the oEmbed provider database from $this->providersUrl", [], $e);
+    }
+
+    $providers = Json::decode((string) $response->getBody());
+
+    if (!is_array($providers) || empty($providers)) {
+      throw new ProviderException('Remote oEmbed providers database returned invalid or empty list.');
+    }
+
+    $keyed_providers = [];
+    foreach ($providers as $provider) {
+      $name = $provider['provider_name'];
+      $keyed_providers[$name] = $provider;
+    }
+
+    $this->cacheSet($cache_id, $keyed_providers, time() + (60 * 60 * 24 * 7));
+    return $keyed_providers;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($provider_name) {
+    $providers = $this->getAll();
+
+    if (isset($providers[$provider_name])) {
+      return $providers[$provider_name];
+    }
+    else {
+      throw new \InvalidArgumentException("Unknown provider '$provider_name'");
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getProviderByUrl($url) {
+    if ($url instanceof Url) {
+      $url = $url->toString();
+    }
+
+    // Check the URL against every scheme of every endpoint of every provider
+    // until we find a match.
+    foreach ($this->getAll() as $provider_name => $provider_info) {
+      if (empty($provider_info['endpoints'])) {
+        continue;
+      }
+      foreach ($provider_info['endpoints'] as $endpoint) {
+        if (empty($endpoint['schemes'])) {
+          continue;
+        }
+        foreach ($endpoint['schemes'] as $scheme) {
+          // Convert scheme into a valid regular expression.
+          $regexp = str_replace(['.', '*'], ['\.', '.*'], $scheme);
+          if (preg_match("|$regexp|", $url)) {
+            return $provider_info;
+          }
+        }
+      }
+    }
+    return FALSE;
+  }
+
+}
diff --git a/core/modules/media/src/OEmbed/ProviderDiscoveryInterface.php b/core/modules/media/src/OEmbed/ProviderDiscoveryInterface.php
new file mode 100644
index 0000000000..25609e01c9
--- /dev/null
+++ b/core/modules/media/src/OEmbed/ProviderDiscoveryInterface.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\media\OEmbed;
+
+/**
+ * Interface for OEmbedProviderDiscovery service.
+ */
+interface ProviderDiscoveryInterface {
+
+  /**
+   * Gets oEmbed providers information.
+   *
+   * Returns a multi-dimensional array where each provider is represented as a
+   * single top-level array element. Each individual element is keyed by the
+   * provider's name, and has the values like in
+   * Drupal\media\OEmbed\ProviderDiscoveryInterface::get().
+   *
+   * @return array[]
+   *   Information about oEmbed providers.
+   *
+   * @see \Drupal\media\ProviderDiscoveryInterface::get()
+   *
+   * @throws \Drupal\media\OEmbed\ProviderException
+   */
+  public function getAll();
+
+  /**
+   * Gets information for a specific oEmbed provider.
+   *
+   * @param string $provider_name
+   *   The name of the provider.
+   *
+   * @return array
+   *   The provider information. Contains the following elements:
+   *   - provider_name: Human readable name of the provider.
+   *   - provider_url: Main URL of the provider.
+   *   - endpoints: List of endpoints this provider exposes. Each endpoint is an
+   *     array with the following values:
+   *     - url: The endpoint's URL.
+   *     - schemes: List of URL schemes supported by the provider.
+   *     - formats: List of supported formats. Can be "json", "xml" or both.
+   *     - discovery: Whether the provider supports oEmbed discovery.
+   *
+   * @throws \InvalidArgumentException
+   *   If there is no known oEmbed provider with the specified name.
+   */
+  public function get($provider_name);
+
+  /**
+   * Tries to get provider info from the provider list.
+   *
+   * @param string $url
+   *   The URL of a media object to match against the oEmbed providers.
+   *
+   * @return array|bool
+   *   Returns a single provider if found, otherwise FALSE.
+   *
+   * @see \Drupal\media\ProviderDiscoveryInterface::get()
+   */
+  public function getProviderByUrl($url);
+
+}
diff --git a/core/modules/media/src/OEmbed/ProviderException.php b/core/modules/media/src/OEmbed/ProviderException.php
new file mode 100644
index 0000000000..caeb9538fd
--- /dev/null
+++ b/core/modules/media/src/OEmbed/ProviderException.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\media\OEmbed;
+
+use Drupal\Component\Render\FormattableMarkup;
+
+/**
+ * Exception thrown if an oEmbed provider causes an error.
+ */
+class ProviderException extends \Exception {
+
+  /**
+   * Information about the oEmbed provider which caused the exception.
+   *
+   * @var array
+   *
+   * @see \Drupal\media\OEmbed\ProviderDiscoveryInterface::get()
+   */
+  protected $provider = [];
+
+  /**
+   * ProviderException constructor.
+   *
+   * @param string $message
+   *   The exception message. '@name' will be replaced with the provider name
+   *   if available, or '<unknown>' if not.
+   * @param array $provider
+   *   (optional) The provider information.
+   * @param \Exception $previous
+   *   (optional) The previous exception, if any.
+   */
+  public function __construct($message, array $provider = [], \Exception $previous = NULL) {
+    $variables = [
+      '@name' => isset($provider['provider_name']) ? $provider['provider_name'] : '<unknown>',
+    ];
+    $this->provider = $provider;
+    $message = (string) new FormattableMarkup($message, $variables);
+    parent::__construct($message, 0, $previous);
+  }
+
+  /**
+   * Gets the provider information, if available.
+   *
+   * @return array
+   *   The information about the oEmbed provider which caused the exception.
+   */
+  public function getProvider() {
+    return $this->provider;
+  }
+
+}
diff --git a/core/modules/media/src/OEmbed/ResourceException.php b/core/modules/media/src/OEmbed/ResourceException.php
new file mode 100644
index 0000000000..f896926fac
--- /dev/null
+++ b/core/modules/media/src/OEmbed/ResourceException.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Drupal\media\OEmbed;
+
+/**
+ * Exception thrown if an oEmbed resource causes an error.
+ */
+class ResourceException extends \Exception {
+
+  /**
+   * The URL of the oEmbed resource which caused the exception.
+   *
+   * @var string
+   */
+  protected $url;
+
+  /**
+   * The oEmbed resource which caused the exception.
+   *
+   * @var array
+   */
+  protected $resource = [];
+
+  /**
+   * ResourceException constructor.
+   *
+   * @param string $message
+   *   The exception message.
+   * @param string $url
+   *   The URL of the oEmbed resource which caused the exception. Can be the
+   *   actual endpoint URL for the resource, or its canonical URL.
+   * @param array $resource
+   *   (optional) The oEmbed resource which caused the exception.
+   * @param \Exception $previous
+   *   (optional) The previous exception, if any.
+   */
+  public function __construct($message, $url, array $resource = [], \Exception $previous = NULL) {
+    $this->url = $url;
+    $this->resource = $resource;
+    parent::__construct($message, 0, $previous);
+  }
+
+  /**
+   * Gets the URL of the oEmbed resource which caused the exception.
+   *
+   * @return string
+   *   The URL of the oEmbed resource which caused the exception.
+   */
+  public function getUrl() {
+    return $this->url;
+  }
+
+  /**
+   * Gets the oEmbed resource which caused the exception, if available.
+   *
+   * @return array
+   *   The oEmbed resource which caused the exception.
+   */
+  public function getResource() {
+    return $this->resource;
+  }
+
+}
diff --git a/core/modules/media/src/OEmbed/ResourceFetcher.php b/core/modules/media/src/OEmbed/ResourceFetcher.php
new file mode 100644
index 0000000000..948254f9eb
--- /dev/null
+++ b/core/modules/media/src/OEmbed/ResourceFetcher.php
@@ -0,0 +1,248 @@
+<?php
+
+namespace Drupal\media\OEmbed;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\UseCacheBackendTrait;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Url;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\RequestException;
+
+/**
+ * Fetches and caches oEmbed resources.
+ */
+class ResourceFetcher implements ResourceFetcherInterface {
+
+  use UseCacheBackendTrait;
+
+  /**
+   * The OEmbed provider discovery service.
+   *
+   * @var \Drupal\media\OEmbed\ProviderDiscoveryInterface
+   */
+  protected $providerDiscovery;
+
+  /**
+   * The HTTP client.
+   *
+   * @var \GuzzleHttp\Client
+   */
+  protected $httpClient;
+
+  /**
+   * The module handler service.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Static cache of discovered oEmbed resources.
+   *
+   * A discovered oEmbed resource is the oEmbed URL for a specific media object.
+   * This oEmbed URL is fetched from the canonical URL of the media object.
+   *
+   * @var array
+   */
+  protected $urlCache;
+
+  /**
+   * Constructs OEmbed class.
+   *
+   * @param \Drupal\media\OEmbed\ProviderDiscoveryInterface $provider_discovery
+   *   The OEmbed provider discovery service.
+   * @param \GuzzleHttp\Client $http_client
+   *   The HTTP client.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler service.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   (optional) The cache backend.
+   */
+  public function __construct(ProviderDiscoveryInterface $provider_discovery, Client $http_client, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend = NULL) {
+    $this->providerDiscovery = $provider_discovery;
+    $this->httpClient = $http_client;
+    $this->moduleHandler = $module_handler;
+    $this->cacheBackend = $cache_backend;
+    $this->useCaches = isset($cache_backend);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getResourceUrl($url, $max_width = NULL, $max_height = NULL) {
+    $cache_id = 'media:oembed_resource_url:' . hash('sha256', $url);
+
+    if ($this->urlCache === NULL) {
+      $this->urlCache = [];
+      if ($cached = $this->cacheGet($cache_id)) {
+        $this->urlCache[$url] = $cached->data;
+      }
+    }
+
+    if ($url instanceof Url) {
+      $url = $url->toString();
+    }
+
+    if (isset($this->urlCache[$url])) {
+      return $this->urlCache[$url];
+    }
+
+    $provider = $this->providerDiscovery->getProviderByUrl($url);
+    if ($provider) {
+      $resource_url = $this->buildResourceUrl($provider, $url);
+    }
+    else {
+      $resource_url = $this->discoverResourceUrl($url);
+    }
+
+    if (empty($resource_url)) {
+      throw new ResourceException('Could not determine the oEmbed resource URL.', $url);
+    }
+
+    $parsed_url = UrlHelper::parse($resource_url);
+    if ($max_width) {
+      $parsed_url['query']['max_width'] = $max_width;
+    }
+    if ($max_height) {
+      $parsed_url['query']['max_height'] = $max_height;
+    }
+    // Let other modules alter the query string, because some oEmbed providers
+    // are providing extra parameters. For example, Instagram also supports the
+    // 'omitscript' parameter.
+    $this->moduleHandler->alter('oembed_resource_url', $parsed_url);
+    $resource_url = $parsed_url['path'] . '?' . UrlHelper::buildQuery($parsed_url['query']);
+
+    $this->urlCache[$url] = $resource_url;
+    $this->cacheSet($cache_id, $resource_url);
+    return $resource_url;
+  }
+
+  /**
+   * Builds the endpoint URL with the oEmebed provider info and returns it.
+   *
+   * @param array $provider_info
+   *   Provider info returned form
+   *   Drupal\media\OEmbed\ProviderDiscoveryInterface::get().
+   * @param string $url
+   *   The canonical media URL.
+   *
+   * @return bool|string
+   *   URL of the oEmbed endpoint, or FALSE if the building was not successful.
+   *
+   * @throws \Drupal\media\OEmbed\ProviderException
+   */
+  protected function buildResourceUrl(array $provider_info, $url) {
+    if (empty($provider_info['endpoints'])) {
+      throw new ProviderException('Provider @name does not define any endpoints.', $provider_info);
+    }
+
+    $endpoints = array_filter($provider_info['endpoints'], function (array $endpoint) {
+      return isset($endpoint['url']);
+    });
+    if (empty($endpoints)) {
+      throw new ProviderException('Provider @name does not provide any endpoint URLs.', $provider_info);
+    }
+
+    $query = [
+      'url' => $url,
+    ];
+    foreach ($endpoints as $endpoint) {
+      $format = 'json';
+      if (!empty($endpoint['formats']) && is_array($endpoint['formats'])) {
+        $format = reset($endpoint['formats']);
+      }
+      $endpoint_url = str_replace('{format}', $format, $endpoint['url']);
+
+      return $endpoint_url . '?' . UrlHelper::buildQuery($query);
+    }
+    return FALSE;
+  }
+
+  /**
+   * Runs oEmbed discovery and returns the endpoint URL if successful.
+   *
+   * @param string $url
+   *   The resource's URL.
+   *
+   * @return string|bool
+   *   URL of the oEmbed endpoint, or FALSE if the discovery was not successful.
+   *
+   * @throws \Drupal\media\OEmbed\ResourceException
+   */
+  protected function discoverResourceUrl($url) {
+    try {
+      $response = $this->httpClient->get($url);
+    }
+    catch (RequestException $e) {
+      throw new ResourceException('Could not fetch oEmbed resource.', $url, [], $e);
+    }
+
+    $document = Html::load((string) $response->getBody());
+    $xpath = new \DOMXpath($document);
+
+    return $this->findUrl($xpath, 'json') ?: $this->findUrl($xpath, 'xml');
+  }
+
+  /**
+   * Tries to find the oEmbed URL in a DOM.
+   *
+   * @param \DOMXPath $xpath
+   *   Page HTML as DOMXPath.
+   * @param string $format
+   *   Format of oEmbed resource. Possible values are 'json' and 'xml'.
+   *
+   * @return bool|string
+   *   A URL to an oEmbed resource or FALSE if not found.
+   */
+  protected function findUrl(\DOMXPath $xpath, $format) {
+    $result = $xpath->query("//link[@type='application/$format+oembed']");
+    return $result->length ? $result->item(0)->getAttribute('href') : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fetchResource($url) {
+    if ($url instanceof Url) {
+      $url = $url->toString();
+    }
+
+    $cache_id = 'media:oembed:' . hash('sha256', $url);
+
+    $cached = $this->cacheGet($cache_id);
+    if ($cached) {
+      return $cached->data;
+    }
+
+    try {
+      $response = $this->httpClient->get($url);
+    }
+    catch (RequestException $e) {
+      throw new ResourceException('Could not retrieve the oEmbed resource.', $url, [], $e);
+    }
+
+    $format = $response->getHeader('Content-Type');
+    $content = (string) $response->getBody();
+
+    // If the response is XML, magically convert it to JSON.
+    if (strpos($format[0], 'text/xml') !== FALSE) {
+      // Load XML into SimpleXMLElement.
+      $content = simplexml_load_string($content, "SimpleXMLElement", LIBXML_NOCDATA);
+      // Convert the SimpleXMLElement to JSON.
+      $content = Json::encode($content);
+    }
+    // If the response wasn't XML or JSON, we are in bat country.
+    elseif (strpos($format[0], 'application/json') === FALSE && strpos($format[0], 'text/javascript') === FALSE) {
+      throw new ResourceException('The fetched resource did not have a valid Content-Type header.', $url);
+    }
+
+    $resource = Json::decode($content);
+    $this->cacheSet($cache_id, $resource);
+    return $resource;
+  }
+
+}
diff --git a/core/modules/media/src/OEmbed/ResourceFetcherInterface.php b/core/modules/media/src/OEmbed/ResourceFetcherInterface.php
new file mode 100644
index 0000000000..466828b366
--- /dev/null
+++ b/core/modules/media/src/OEmbed/ResourceFetcherInterface.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\media\OEmbed;
+
+/**
+ * Interface for oEmbed service.
+ */
+interface ResourceFetcherInterface {
+
+  /**
+   * Tries to match the URL against the database of oEmbed providers.
+   *
+   * @param string $url
+   *   The URL to be tested.
+   * @param int|null $max_width
+   *   Max width for the oEmbed resource.
+   * @param int|null $max_height
+   *   Max height for the oEmbed resource.
+   *
+   * @return bool
+   *   If a match was found, will return TRUE, otherwise FALSE.
+   *
+   * @see \Drupal\media\ResourceFetcherInterface::getProviders()
+   *
+   * @throws \Drupal\media\OEmbed\ResourceException
+   */
+  public function getResourceUrl($url, $max_width = NULL, $max_height = NULL);
+
+  /**
+   * Fetches information about the oEmbed resource.
+   *
+   * @param string $endpoint_url
+   *   Resource-specific URL of the oEmbed endpoint.
+   *
+   * @return array|bool
+   *   Resource information as returned from the oEmbed endpoint, or FALSE if
+   *   the resource could not be fetched.
+   *
+   * @throws \Drupal\media\OEmbed\ResourceException
+   */
+  public function fetchResource($endpoint_url);
+
+}
diff --git a/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php b/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php
new file mode 100644
index 0000000000..6c29a3aac8
--- /dev/null
+++ b/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php
@@ -0,0 +1,213 @@
+<?php
+
+namespace Drupal\media\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\FormatterBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Url;
+use Drupal\media\OEmbed\ResourceException;
+use Drupal\media\OEmbed\ResourceFetcherInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Plugin implementation of the 'oembed' formatter.
+ *
+ * @FieldFormatter(
+ *   id = "oembed",
+ *   label = @Translation("oEmbed content"),
+ *   field_types = {
+ *     "link",
+ *     "string",
+ *     "string_long",
+ *   }
+ * )
+ */
+class OEmbedFormatter extends FormatterBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The oEmbed resource fetcher.
+   *
+   * @var \Drupal\media\OEmbed\ResourceFetcherInterface
+   */
+  protected $resourceFetcher;
+
+  /**
+   * The messenger service.
+   *
+   * @var \Drupal\Core\Messenger\MessengerInterface $messenger
+   */
+  protected $messenger;
+
+  /**
+   * Constructs a OEmbedFormatter instance.
+   *
+   * @param string $plugin_id
+   *   The plugin ID for the formatter.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+   *   The definition of the field to which the formatter is associated.
+   * @param array $settings
+   *   The formatter settings.
+   * @param string $label
+   *   The formatter label display setting.
+   * @param string $view_mode
+   *   The view mode.
+   * @param array $third_party_settings
+   *   Any third party settings settings.
+   * @param \Drupal\media\OEmbed\ResourceFetcherInterface $resource_fetcher
+   *   The oEmbed service.
+   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
+   *   The messenger service.
+   */
+  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, ResourceFetcherInterface $resource_fetcher, MessengerInterface $messenger) {
+    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
+    $this->resourceFetcher = $resource_fetcher;
+    $this->messenger = $messenger;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $plugin_id,
+      $plugin_definition,
+      $configuration['field_definition'],
+      $configuration['settings'],
+      $configuration['label'],
+      $configuration['view_mode'],
+      $configuration['third_party_settings'],
+      $container->get('media.oembed.resource_fetcher'),
+      $container->get('messenger')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return [
+      'max_width' => 0,
+      'max_height' => 0,
+    ] + parent::defaultSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function viewElements(FieldItemListInterface $items, $langcode) {
+    $element = [];
+
+    foreach ($items as $delta => $item) {
+      $main_property = $item->getFieldDefinition()->getFieldStorageDefinition()->getMainPropertyName();
+
+      if (empty($item->{$main_property})) {
+        continue;
+      }
+
+      try {
+        $resource = $this->resourceFetcher->fetchResource($this->resourceFetcher->getResourceUrl($item->{$main_property}, $this->getSetting('max_width'), $this->getSetting('max_height')));
+      }
+      catch (ResourceException $exception) {
+        $this->messenger->addError($this->t("Could not retrieve the remote URL."));
+        continue;
+      }
+
+      if (empty($resource['type'])) {
+        $this->messenger->addError($this->t("oEmbed type couldn't be identified."));
+        return $element;
+      }
+
+      switch ($resource['type']) {
+        case 'link':
+          $element[$delta] = [
+            '#title' => $resource['title'],
+            '#type' => 'link',
+            '#url' => Url::fromUri($item->{$main_property}),
+          ];
+          break;
+
+        case 'photo':
+          $element[$delta] = [
+            '#theme' => 'image',
+            '#uri' => $resource['url'],
+            '#width' => $resource['width'],
+            '#height' => $resource['height'],
+          ];
+          break;
+
+        case 'video':
+        case 'rich':
+          $element[$delta] = [
+            '#type' => 'inline_template',
+            '#template' => '{{ html }}',
+            '#context' => [
+              'html' => $resource['html'],
+            ],
+          ];
+          break;
+
+        default:
+          throw new \UnexpectedValueException(sprintf('Unknown oEmbed resource type "%s"', $resource['type']));
+      }
+    }
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    return parent::settingsForm($form, $form_state) + [
+      'max_width' => [
+        '#type' => 'number',
+        '#title' => $this->t('Maximum width'),
+        '#default_value' => $this->getSetting('max_width'),
+        '#size' => 5,
+        '#maxlength' => 5,
+        '#field_suffix' => $this->t('pixels'),
+        '#min' => 0,
+      ],
+      'max_height' => [
+        '#type' => 'number',
+        '#title' => $this->t('Maximum height'),
+        '#default_value' => $this->getSetting('max_height'),
+        '#size' => 5,
+        '#maxlength' => 5,
+        '#field_suffix' => $this->t('pixels'),
+        '#min' => 0,
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsSummary() {
+    $summary = parent::settingsSummary();
+    if ($this->getSetting('max_width') && $this->getSetting('max_height')) {
+      $summary[] = $this->t('Maximum size: %max_width x %max_height pixels', [
+        '%max_width' => $this->getSetting('max_width'),
+        '%max_height' => $this->getSetting('max_height'),
+      ]);
+    }
+    elseif ($this->getSetting('max_width')) {
+      $summary[] = $this->t('Maximum width: %max_width pixels', [
+        '%max_width' => $this->getSetting('max_width'),
+      ]);
+    }
+    elseif ($this->getSetting('max_height')) {
+      $summary[] = $this->t('Maximum height: %max_height pixels', [
+        '%max_height' => $this->getSetting('max_height'),
+      ]);
+    }
+    return $summary;
+  }
+
+}
diff --git a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraint.php b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraint.php
new file mode 100644
index 0000000000..9600d661d7
--- /dev/null
+++ b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraint.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\media\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Checks if a value belongs to an allowed oEmbed provider.
+ *
+ * @Constraint(
+ *   id = "oembed_provider",
+ *   label = @Translation("oEmbed provider", context = "Validation"),
+ *   type = {"link", "string", "string_long"}
+ * )
+ */
+class OEmbedProviderConstraint extends Constraint {
+
+  /**
+   * The default violation message.
+   *
+   * @var string
+   */
+  public $message = 'The @name provider is not allowed.';
+
+}
diff --git a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraintValidator.php b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraintValidator.php
new file mode 100644
index 0000000000..2c7feb349a
--- /dev/null
+++ b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraintValidator.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Drupal\media\Plugin\Validation\Constraint;
+
+use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Logger\LoggerChannelFactoryInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\media\OEmbed\ProviderDiscoveryInterface;
+use Drupal\media\OEmbed\ProviderException;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Validates the OEmbedProvider constraint.
+ */
+class OEmbedProviderConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * The oEmbed provider discovery service.
+   *
+   * @var \Drupal\media\OEmbed\ProviderDiscoveryInterface
+   */
+  protected $providerDiscovery;
+
+  /**
+   * The logger service.
+   *
+   * @var \Drupal\Core\Logger\LoggerChannelInterface
+   */
+  protected $logger;
+
+  /**
+   * Constructs a new OEmbedProviderConstraintValidator.
+   *
+   * @param \Drupal\media\OEmbed\ProviderDiscoveryInterface $provider_discovery
+   *   The oEmbed provider discovery service.
+   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
+   *   The logger service.
+   */
+  public function __construct(ProviderDiscoveryInterface $provider_discovery, LoggerChannelFactoryInterface $logger_factory) {
+    $this->providerDiscovery = $provider_discovery;
+    $this->logger = $logger_factory->get('media');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('media.oembed.provider_discovery'),
+      $container->get('logger.factory')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($value, Constraint $constraint) {
+    /** @var \Drupal\media\MediaInterface $media */
+    $media = $value->getEntity();
+    $main_property = $value->getFieldDefinition()->getFieldStorageDefinition()->getMainPropertyName();
+    $url = $value->first()->get($main_property)->getString();
+
+    try {
+      $provider = $this->providerDiscovery->getProviderByUrl($url);
+    }
+    catch (ProviderException $e) {
+      $this->context->addViolation($this->t('An error occurred while trying to retrieve the oEmbed provider database.'));
+      $this->logger->error($e->getMessage());
+      return;
+    }
+
+    if (empty($provider)) {
+      $this->context->addViolation($this->t('The given URL does not match any known oEmbed providers.'));
+      return;
+    }
+
+    $source_config = $media->getSource()->getConfiguration();
+    $source_definition = $media->getSource()->getPluginDefinition();
+
+    $allowed_providers = $source_definition['supported_providers'];
+    if (!empty($source_config['allowed_providers'])) {
+      $allowed_providers = $source_config['allowed_providers'];
+    }
+
+    if (!in_array($provider['provider_name'], $allowed_providers, TRUE)) {
+      $message = new FormattableMarkup($constraint->message, [
+        '@name' => $provider['provider_name'],
+      ]);
+      $this->context->addViolation((string) $message);
+    }
+  }
+
+}
diff --git a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraint.php b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraint.php
new file mode 100644
index 0000000000..2b9d75d91c
--- /dev/null
+++ b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraint.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\media\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Checks if a value belongs to an allowed oEmbed provider.
+ *
+ * @Constraint(
+ *   id = "oembed_resource",
+ *   label = @Translation("oEmbed resource", context = "Validation"),
+ *   type = {"link", "string", "string_long"}
+ * )
+ */
+class OEmbedResourceConstraint extends Constraint {
+
+  /**
+   * The default violation message.
+   *
+   * @var string
+   */
+  public $message = 'The provided URL does not represent a valid oEmbed resource.';
+
+}
diff --git a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraintValidator.php b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraintValidator.php
new file mode 100644
index 0000000000..63f7f3d141
--- /dev/null
+++ b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraintValidator.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Drupal\media\Plugin\Validation\Constraint;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\media\OEmbed\ResourceException;
+use Drupal\media\OEmbed\ResourceFetcherInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Validates the OEmbedResource constraint.
+ */
+class OEmbedResourceConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
+
+  /**
+   * The oEmbed resource fetcher service.
+   *
+   * @var \Drupal\media\OEmbed\ResourceFetcherInterface
+   */
+  protected $resourceFetcher;
+
+  /**
+   * Constructs a new OEmbedResourceConstraintValidator instance.
+   *
+   * @param \Drupal\media\OEmbed\ResourceFetcherInterface $resource_fetcher
+   *   The oEmbed resource fetcher service.
+   */
+  public function __construct(ResourceFetcherInterface $resource_fetcher) {
+    $this->resourceFetcher = $resource_fetcher;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('media.oembed.resource_fetcher'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($value, Constraint $constraint) {
+    $main_property = $value->getFieldDefinition()->getFieldStorageDefinition()->getMainPropertyName();
+    $url = $value->first()->get($main_property)->getString();
+
+    try {
+      $this->resourceFetcher->getResourceUrl($url);
+    }
+    catch (ResourceException $e) {
+      $this->context->addViolation($constraint->message);
+    }
+  }
+
+}
diff --git a/core/modules/media/src/Plugin/media/Source/OEmbed.php b/core/modules/media/src/Plugin/media/Source/OEmbed.php
new file mode 100644
index 0000000000..fdc0861640
--- /dev/null
+++ b/core/modules/media/src/Plugin/media/Source/OEmbed.php
@@ -0,0 +1,331 @@
+<?php
+
+namespace Drupal\media\Plugin\media\Source;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\FieldTypePluginManagerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Logger\LoggerChannelInterface;
+use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\link\LinkItemInterface;
+use Drupal\media\OEmbed\ProviderException;
+use Drupal\media\OEmbed\ResourceException;
+use Drupal\media\MediaSourceBase;
+use Drupal\media\MediaInterface;
+use Drupal\media\MediaSourceFieldConstraintsInterface;
+use Drupal\media\MediaTypeInterface;
+use Drupal\media\OEmbed\ResourceFetcherInterface;
+use Drupal\media\OEmbed\ProviderDiscoveryInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides the media source plugin for oEmbed resources.
+ *
+ * @MediaSource(
+ *   id = "oembed",
+ *   label = @Translation("oEmbed source"),
+ *   description = @Translation("Use oEmbed URL for reusable media."),
+ *   allowed_field_types = {"link"},
+ *   default_thumbnail_filename = "no-thumbnail.png",
+ *   deriver = "Drupal\media\Plugin\media\Source\OEmbedDeriver"
+ * )
+ */
+class OEmbed extends MediaSourceBase implements MediaSourceFieldConstraintsInterface {
+
+  /**
+   * The logger channel for media.
+   *
+   * @var \Drupal\Core\Logger\LoggerChannelInterface
+   */
+  protected $logger;
+
+  /**
+   * The messenger service.
+   *
+   * @var \Drupal\Core\Messenger\MessengerInterface
+   */
+  protected $messenger;
+
+  /**
+   * The oEmbed resource fetcher service.
+   *
+   * @var \Drupal\media\OEmbed\ResourceFetcherInterface
+   */
+  protected $resourceFetcher;
+
+  /**
+   * The OEmbed provider discovery service.
+   *
+   * @var \Drupal\media\OEmbed\ProviderDiscoveryInterface
+   */
+  protected $providerDiscovery;
+
+  /**
+   * Constructs a new OEmbed instance.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   Entity type manager service.
+   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
+   *   Entity field manager service.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory service.
+   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
+   *   The field type plugin manager service.
+   * @param \Drupal\Core\Logger\LoggerChannelInterface $logger
+   *   The logger channel for media.
+   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
+   *   The messenger service.
+   * @param \Drupal\media\OEmbed\ResourceFetcherInterface $resource_fetcher
+   *   The oEmbed resource fetcher service.
+   * @param \Drupal\media\OEmbed\ProviderDiscoveryInterface $provider_discovery
+   *   The oEmbed provider discovery service.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, ConfigFactoryInterface $config_factory, FieldTypePluginManagerInterface $field_type_manager, LoggerChannelInterface $logger, MessengerInterface $messenger, ResourceFetcherInterface $resource_fetcher, ProviderDiscoveryInterface $provider_discovery) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $entity_field_manager, $field_type_manager, $config_factory);
+    $this->logger = $logger;
+    $this->messenger = $messenger;
+    $this->resourceFetcher = $resource_fetcher;
+    $this->providerDiscovery = $provider_discovery;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('entity_type.manager'),
+      $container->get('entity_field.manager'),
+      $container->get('config.factory'),
+      $container->get('plugin.manager.field.field_type'),
+      $container->get('logger.factory')->get('media'),
+      $container->get('messenger'),
+      $container->get('media.oembed.resource_fetcher'),
+      $container->get('media.oembed.provider_discovery')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMetadataAttributes() {
+    return [
+      'type' => $this->t('Resource type'),
+      'title' => $this->t('Resource title'),
+      'author_name' => $this->t('The name of the author/owner'),
+      'author_url' => $this->t('The URL of the author/owner'),
+      'provider_name' => $this->t("The provider's name"),
+      'provider_url' => $this->t('The URL of the provider'),
+      'cache_age' => $this->t('Suggested cache lifetime'),
+      'thumbnail_url' => $this->t('The remote URL of the thumbnail'),
+      'thumbnail_local_uri' => $this->t('The local URI of the thumbnail'),
+      'thumbnail_local' => $this->t('The local URL of the thumbnail'),
+      'thumbnail_width' => $this->t('Thumbnail width'),
+      'thumbnail_height' => $this->t('Thumbnail height'),
+      'url' => $this->t('The source URL of the resource'),
+      'width' => $this->t('The width of the resource'),
+      'height' => $this->t('The height of the resource'),
+      'html' => $this->t('The HTML representation of the resource'),
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMetadata(MediaInterface $media, $name) {
+    $resource_url = $this->getSourceFieldValue($media);
+
+    try {
+      $resource_url = $this->resourceFetcher->getResourceUrl($resource_url);
+      $resource = $this->resourceFetcher->fetchResource($resource_url);
+    }
+    catch (ResourceException $e) {
+      $this->messenger->addError($e->getMessage());
+      return NULL;
+    }
+
+    switch ($name) {
+      case 'thumbnail_local':
+        $local_uri = $this->getMetadata($media, 'thumbnail_local_uri');
+
+        if ($local_uri) {
+          if (file_exists($local_uri)) {
+            return $local_uri;
+          }
+          else {
+            $image_data = file_get_contents($this->getMetadata($media, 'thumbnail_url'));
+            if ($image_data) {
+              return file_unmanaged_save_data($image_data, $local_uri, FILE_EXISTS_REPLACE) ?: NULL;
+            }
+          }
+        }
+        return NULL;
+
+      case 'thumbnail_local_uri':
+        $image_url = $this->getMetadata($media, 'thumbnail_url');
+        if ($image_url) {
+          $filename = $this->getMetadata($media, 'provider_name') . '_' . substr(md5($resource_url), 0, 5);
+          return $this->getLocalImageUri($filename, $image_url);
+        }
+        return NULL;
+
+      case 'default_name':
+        if ($title = $this->getMetadata($media, 'title')) {
+          return $title;
+        }
+        elseif ($url = $this->getMetadata($media, 'url')) {
+          return $url;
+        }
+        return parent::getMetadata($media, 'default_name');
+
+      case 'thumbnail_uri':
+        if ($uri = $this->getMetadata($media, 'thumbnail_local')) {
+          return $uri;
+        }
+        return parent::getMetadata($media, 'thumbnail_uri');
+
+      default:
+        if (!empty($resource[$name])) {
+          return $resource[$name];
+        }
+        break;
+    }
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $form = parent::buildConfigurationForm($form, $form_state);
+
+    $form['thumbnails_location'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Thumbnails location'),
+      '#default_value' => $this->configuration['thumbnails_location'],
+      '#description' => $this->t('Thumbnails will be fetched from the provider for local usage. This is the location where they will be placed.'),
+    ];
+
+    $form['allowed_providers'] = [
+      '#type' => 'checkboxes',
+      '#title' => $this->t('Allowed providers'),
+      '#default_value' => $this->configuration['allowed_providers'],
+      '#options' => array_combine($this->pluginDefinition['supported_providers'], $this->pluginDefinition['supported_providers']),
+      '#description' => $this->t('Optionally select the allowed oEmbed providers for this media type. If left blank, all providers will be allowed.'),
+    ];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+    parent::submitConfigurationForm($form, $form_state);
+    $this->configuration['allowed_providers'] = array_filter(array_values($this->configuration['allowed_providers']));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+    $thumbnails_location = $form_state->getValue('thumbnails_location');
+    if (!file_valid_uri($thumbnails_location)) {
+      $form_state->setErrorByName('thumbnails_location', $this->t('@path is not a valid path.', ['@path' => $thumbnails_location]));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return [
+      'thumbnails_location' => 'public://oembed_thumbnails',
+      'allowed_providers' => [],
+    ] + parent::defaultConfiguration();
+  }
+
+  /**
+   * Computes the destination URI for a thumbnail.
+   *
+   * @param string $filename
+   *   Filename without the extension.
+   * @param string $remote_url
+   *   The remote URL of the thumbnail.
+   *
+   * @return string
+   *   The local URI.
+   */
+  protected function getLocalImageUri($filename, $remote_url) {
+    $directory = $this->configuration['thumbnails_location'];
+    // Ensure that the destination directory is writable. If not, log a warning
+    // and return the default thumbnail.
+    $ready = file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+    if (!$ready) {
+      $this->logger->warning('Could not prepare thumbnail destination directory @dir for oEmbed media.', [
+        '@dir' => $directory,
+      ]);
+      $default_thumbnail_filename = $this->pluginDefinition['default_thumbnail_filename'];
+      return $this->configFactory->get('media.settings')->get('icon_base_uri') . '/' . $default_thumbnail_filename;
+    }
+
+    $local_uri = $this->configuration['thumbnails_location'] . '/' . $filename . '.';
+    $local_uri .= pathinfo($remote_url, PATHINFO_EXTENSION);
+
+    return $local_uri;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createSourceField(MediaTypeInterface $type) {
+    $field = parent::createSourceField($type);
+
+    $settings = $field->getSettings();
+    $settings['title'] = DRUPAL_DISABLED;
+    $settings['link_type'] = LinkItemInterface::LINK_EXTERNAL;
+
+    return $field->set('settings', $settings);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSourceFieldConstraints() {
+    return [
+      'oembed_resource' => [],
+      'oembed_provider' => [],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareViewDisplay(MediaTypeInterface $type, EntityViewDisplayInterface $display) {
+    $display->setComponent($this->getSourceFieldDefinition($type)->getName(), [
+      'type' => 'oembed',
+    ]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareFormDisplay(MediaTypeInterface $type, EntityFormDisplayInterface $display) {
+    $display->removeComponent('name');
+    $display->setComponent($this->getSourceFieldDefinition($type)->getName(), [
+      'weight' => 1,
+    ]);
+  }
+
+}
diff --git a/core/modules/media/src/Plugin/media/Source/OEmbedDeriver.php b/core/modules/media/src/Plugin/media/Source/OEmbedDeriver.php
new file mode 100644
index 0000000000..b22f1c18fa
--- /dev/null
+++ b/core/modules/media/src/Plugin/media/Source/OEmbedDeriver.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\media\Plugin\media\Source;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+
+/**
+ * Class OEmbedDeriver.
+ */
+class OEmbedDeriver extends DeriverBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+
+    $this->derivatives = [
+      'remote_video' => [
+        'id' => 'remote_video',
+        'label' => t('Remote video'),
+        'description' => t('Use remote video URL for reusable media.'),
+        'supported_providers' => ['YouTube', 'Vimeo'],
+        'settings' => [
+        ],
+      ] + $base_plugin_definition,
+    ];
+
+    return parent::getDerivativeDefinitions($base_plugin_definition);
+  }
+
+}
diff --git a/core/modules/media/tests/src/Functional/FieldFormatter/OEmbedFormatterTest.php b/core/modules/media/tests/src/Functional/FieldFormatter/OEmbedFormatterTest.php
new file mode 100644
index 0000000000..5c119ad3dd
--- /dev/null
+++ b/core/modules/media/tests/src/Functional/FieldFormatter/OEmbedFormatterTest.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace Drupal\Tests\media\Functional\FieldFormatter;
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\link\LinkItemInterface;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * @coversDefaultClass \Drupal\media\Plugin\Field\FieldFormatter\OEmbedFormatter
+ * @group media
+ */
+class OEmbedFormatterTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'entity_test',
+    'field',
+    'link',
+    'media',
+    'user',
+    'system',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->drupalLogin($this->drupalCreateUser(['view test entity']));
+  }
+
+  /**
+   * @covers ::viewElements
+   */
+  public function testRender() {
+    $entity_type = $bundle = 'entity_test';
+    $field_name = Unicode::strtolower($this->randomMachineName());
+
+    FieldStorageConfig::create([
+      'entity_type' => $entity_type,
+      'field_name' => $field_name,
+      'type' => 'link',
+      'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
+    ])->save();
+    $field_config = FieldConfig::create([
+      'entity_type' => $entity_type,
+      'field_name' => $field_name,
+      'bundle' => $bundle,
+      'settings' => [
+        'title' => DRUPAL_DISABLED,
+        'link_type' => LinkItemInterface::LINK_EXTERNAL,
+      ],
+    ]);
+    $field_config->save();
+
+    $display = entity_get_display('entity_test', 'entity_test', 'full');
+    $display->setComponent($field_name, ['type' => 'oembed'])->save();
+
+    $entity = EntityTest::create([
+      $field_config->getName() => [
+        [
+          'uri' => 'https://twitter.com/drupaldevdays/status/935643039741202432',
+        ],
+      ],
+    ]);
+    $entity->save();
+
+    $this->drupalGet($entity->toUrl());
+    $assert_session = $this->assertSession();
+    $assert_session->elementExists('css', "blockquote.twitter-tweet");
+    $assert_session->elementContains('css', "blockquote.twitter-tweet > p", 'Save the date for Drupal Developer Days Lisbon 2018');
+  }
+
+}
diff --git a/core/modules/media/tests/src/Functional/OEmbedProviderDiscoveryTest.php b/core/modules/media/tests/src/Functional/OEmbedProviderDiscoveryTest.php
new file mode 100644
index 0000000000..e4d9f45b1d
--- /dev/null
+++ b/core/modules/media/tests/src/Functional/OEmbedProviderDiscoveryTest.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Drupal\Tests\media\Functional;
+
+use Drupal\media\OEmbed\ProviderDiscovery;
+
+/**
+ * Tests the oEmbed provider discovery.
+ *
+ * @covers \Drupal\media\ProviderDiscovery
+ *
+ * @group media
+ */
+class OEmbedProviderDiscoveryTest extends MediaFunctionalTestBase {
+
+  /**
+   * Test provider discovery.
+   *
+   * @dataProvider dataProviderEmptyProviderList
+   */
+  public function testEmptyProviderList($content, $providerUrl, $exceptionClass, $exceptionMessage) {
+
+    $cacheBackend = $this->container->get('cache.default');
+
+    $body = $this->getMockBuilder('\GuzzleHttp\Psr7\Stream')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $body->method('getContents')->willReturn($content);
+
+    $response = $this->getMockBuilder('\GuzzleHttp\Psr7\Response')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $response->method('getBody')->willReturn($body);
+
+    $client = $this->getMockBuilder('\GuzzleHttp\Client')
+      ->getMock();
+    $client->method('request')->withAnyParameters()->willReturn($response);
+
+    $this->setExpectedException($exceptionClass, $exceptionMessage);
+
+    $providerDiscovery = new ProviderDiscovery($client, $providerUrl, $cacheBackend);
+    $providerDiscovery->getAll();
+  }
+
+  /**
+   * Data provider for testEmptyProviderList.
+   *
+   * @return array
+   *   Data.
+   */
+  public function dataProviderEmptyProviderList() {
+    return [
+      [
+        json_encode([]),
+        'http://oembed.com/providers.json',
+        'Drupal\media\OEmbed\ProviderException',
+        'Remote oEmbed providers database returned invalid or empty list.',
+      ],
+      [
+        '',
+        'http://oembed.com/providers.json',
+        'Drupal\media\OEmbed\ProviderException',
+        'Remote oEmbed providers database returned invalid or empty list.',
+      ],
+    ];
+  }
+
+  /**
+   * Test provider discovery.
+   *
+   * @dataProvider dataProviderNonExistingProviderDatabase
+   */
+  public function testNonExistingProviderDatabase($providerUrl, $exceptionClass, $exceptionMessage) {
+
+    $cacheBackend = $this->container->get('cache.default');
+    $client = $this->container->get('http_client');
+
+    $this->setExpectedException($exceptionClass, $exceptionMessage);
+
+    $providerDiscovery = new ProviderDiscovery($client, $providerUrl, $cacheBackend);
+    $providerDiscovery->getAll();
+  }
+
+  /**
+   * Data provider for testEmptyProviderList.
+   *
+   * @return array
+   *   Data.
+   */
+  public function dataProviderNonExistingProviderDatabase() {
+    return [
+      [
+        'http://oembed1.com/providers.json',
+        'Drupal\media\OEmbed\ProviderException',
+        'Could not retrieve the oEmbed provider database from http://oembed1.com/providers.json',
+      ],
+      [
+        'http://oembed.com/providers1.json',
+        'Drupal\media\OEmbed\ProviderException',
+        'Could not retrieve the oEmbed provider database from http://oembed.com/providers1.json',
+      ],
+    ];
+  }
+
+}
diff --git a/core/modules/media/tests/src/Functional/OEmbedTest.php b/core/modules/media/tests/src/Functional/OEmbedTest.php
new file mode 100644
index 0000000000..16620ea021
--- /dev/null
+++ b/core/modules/media/tests/src/Functional/OEmbedTest.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\Tests\media\Functional;
+
+use Drupal\media\OEmbed\ResourceFetcher;
+
+/**
+ * Tests the oEmbed service.
+ *
+ * @covers \Drupal\media\OEmbed\ResourceFetcher
+ *
+ * @group media
+ */
+class OEmbedTest extends MediaFunctionalTestBase {
+
+  /**
+   * Test urlDiscovery method.
+   */
+  public function testUrlDiscovery() {
+    $cacheBackend = $this->container->get('cache.default');
+    $module_handler = $this->container->get('module_handler');
+    $client = $this->container->get('http_client');
+    $oEmbedProviderDiscovery = $this->container->get('media.oembed.provider_discovery');
+
+    $oEmbed = new ResourceFetcher($oEmbedProviderDiscovery, $client, $module_handler, $cacheBackend);
+
+    // Received by discovery.
+    $this->assertSame('https://vimeo.com/api/oembed.json?url=https%3A//vimeo.com/7073899', $oEmbed->getResourceUrl('https://vimeo.com/7073899'));
+    // Received by URL building.
+    $this->assertSame('https://publish.twitter.com/oembed?url=https%3A//twitter.com/drupaldevdays/status/935643039741202432', $oEmbed->getResourceUrl('https://twitter.com/drupaldevdays/status/935643039741202432'));
+    // URL building with format replacement.
+    $this->assertSame('http://www.collegehumor.com/oembed.json?url=http%3A//www.collegehumor.com/video/40002870/lets-not-get-a-drink-sometime', $oEmbed->getResourceUrl('http://www.collegehumor.com/video/40002870/lets-not-get-a-drink-sometime'));
+    // URL building with height and width.
+    $this->assertSame('https://www.flickr.com/services/oembed?url=https%3A//www.flickr.com/photos/amazeelabs/30530130354&format=json&max_width=320&max_height=480', $oEmbed->getResourceUrl('https://www.flickr.com/photos/amazeelabs/30530130354/in/album-72157675936437840', 320, 480));
+  }
+
+  /**
+   * Test fetchResource method.
+   */
+  public function testFetchResource() {
+    $cacheBackend = $this->container->get('cache.default');
+    $module_handler = $this->container->get('module_handler');
+    $client = $this->container->get('http_client');
+    $oEmbedProviderDiscovery = $this->container->get('media.oembed.provider_discovery');
+
+    $oEmbed = new ResourceFetcher($oEmbedProviderDiscovery, $client, $module_handler, $cacheBackend);
+
+    // Fetch JSON resource.
+    $resource = $oEmbed->fetchResource('https://vimeo.com/api/oembed.json?url=https%3A%2F%2Fvimeo.com%2F7073899');
+    $this->assertSame('video', $resource['type']);
+    $this->assertSame('Vimeo', $resource['provider_name']);
+    $this->assertSame('Drupal Rap Video - Schipulcon09', $resource['title']);
+
+    // Fetch XML resource.
+    $resource = $oEmbed->fetchResource('http://www.collegehumor.com/oembed.xml?url=http%3A//www.collegehumor.com/video/40002870/lets-not-get-a-drink-sometime');
+    $this->assertSame('video', $resource['type']);
+    $this->assertSame('CollegeHumor', $resource['provider_name']);
+    $this->assertSame('Let\'s Not Get a Drink Sometime', $resource['title']);
+  }
+
+}
diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaSourceRemoteVideoTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaSourceRemoteVideoTest.php
new file mode 100644
index 0000000000..9253358cd5
--- /dev/null
+++ b/core/modules/media/tests/src/FunctionalJavascript/MediaSourceRemoteVideoTest.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Drupal\Tests\media\FunctionalJavascript;
+
+use Drupal\media\Entity\Media;
+
+/**
+ * Tests the oembed media source.
+ *
+ * @group media
+ */
+class MediaSourceRemoteVideoTest extends MediaSourceTestBase {
+
+  public static $modules = [
+    'link',
+  ];
+
+  /**
+   * Tests the oembed media source.
+   */
+  public function testMediaRemoteVideoSource() {
+    $media_type_id = 'test_media_oembed_type';
+    $provided_fields = [
+      'type',
+      'title',
+      'author_name',
+      'author_url',
+      'provider_name',
+      'provider_url',
+      'cache_age',
+      'thumbnail_url',
+      'thumbnail_local_uri',
+      'thumbnail_local',
+      'thumbnail_width',
+      'thumbnail_height',
+      'url',
+      'width',
+      'height',
+      'html',
+    ];
+
+    $session = $this->getSession();
+    $page = $session->getPage();
+    $assert_session = $this->assertSession();
+
+    $this->doTestCreateMediaType($media_type_id, 'remote_video', $provided_fields);
+
+    // Create custom fields for the media type to store metadata attributes.
+    $fields = [
+      'field_string_width' => 'string',
+      'field_string_height' => 'string',
+      'field_string_author_name' => 'string',
+    ];
+    $this->createMediaTypeFields($fields, $media_type_id);
+
+    // Hide the name field widget to test default name generation.
+    $this->hideMediaTypeFieldWidget('name', $media_type_id);
+
+    $this->drupalGet("admin/structure/media/manage/{$media_type_id}");
+    // Only accept vimeo videos.
+    $page->checkField("source_configuration[allowed_providers][Vimeo]");
+
+    $page->selectFieldOption("field_map[width]", 'field_string_width');
+    $page->selectFieldOption("field_map[height]", 'field_string_height');
+    $page->selectFieldOption("field_map[author_name]", 'field_string_author_name');
+    $page->pressButton('Save');
+
+    // Create a media item.
+    $this->drupalGet("media/add/{$media_type_id}");
+    $page->fillField("Remote video", 'https://vimeo.com/7073899');
+    $page->pressButton('Save');
+
+    $assert_session->addressEquals('media/1');
+
+    // Make sure the thumbnail is displayed from uploaded image.
+    $assert_session->elementAttributeContains('css', '.image-style-thumbnail', 'src', 'oembed_thumbnails/Vimeo_1d58b.jpg');
+
+    // Load the media and check that all fields are properly populated.
+    $media = Media::load(1);
+    $this->assertEquals('Drupal Rap Video - Schipulcon09', $media->getName());
+    $this->assertEquals('480', $media->get('field_string_width')->value);
+    $this->assertEquals('360', $media->get('field_string_height')->value);
+
+    // Try to create a youtube item, which should not be allowed.
+    $this->drupalGet("media/add/{$media_type_id}");
+    $page->fillField("Remote video", 'https://clips.twitch.tv/SleepyBoringLapwingRuleFive');
+    $page->pressButton('Save');
+
+    $assert_session->pageTextContains('The Twitch provider is not allowed.');
+  }
+
+}
diff --git a/core/profiles/standard/config/optional/core.entity_form_display.media.video.default.yml b/core/profiles/standard/config/optional/core.entity_form_display.media.video.default.yml
new file mode 100644
index 0000000000..f82730125c
--- /dev/null
+++ b/core/profiles/standard/config/optional/core.entity_form_display.media.video.default.yml
@@ -0,0 +1,45 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.video.field_media_oembed_remote_video
+    - media.type.video
+  module:
+    - link
+id: media.video.default
+targetEntityType: media
+bundle: video
+mode: default
+content:
+  created:
+    type: datetime_timestamp
+    weight: 1
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  field_media_oembed_remote_video:
+    settings:
+      placeholder_url: ''
+      placeholder_title: ''
+    third_party_settings: {  }
+    type: link_default
+    weight: 2
+    region: content
+  status:
+    type: boolean_checkbox
+    settings:
+      display_label: true
+    weight: 100
+    region: content
+    third_party_settings: {  }
+  uid:
+    type: entity_reference_autocomplete
+    weight: 0
+    settings:
+      match_operator: CONTAINS
+      size: 60
+      placeholder: ''
+    region: content
+    third_party_settings: {  }
+hidden:
+  name: true
diff --git a/core/profiles/standard/config/optional/core.entity_view_display.media.video.default.yml b/core/profiles/standard/config/optional/core.entity_view_display.media.video.default.yml
new file mode 100644
index 0000000000..49a08bf3ab
--- /dev/null
+++ b/core/profiles/standard/config/optional/core.entity_view_display.media.video.default.yml
@@ -0,0 +1,29 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.video.field_media_oembed_remote_video
+    - image.style.thumbnail
+    - media.type.video
+  module:
+    - image
+    - media
+    - user
+id: media.video.default
+targetEntityType: media
+bundle: video
+mode: default
+content:
+  field_media_oembed_remote_video:
+    label: hidden
+    settings: {  }
+    third_party_settings: {  }
+    type: oembed
+    weight: 0
+    region: content
+hidden:
+  created: true
+  langcode: true
+  name: true
+  thumbnail: true
+  uid: true
diff --git a/core/profiles/standard/config/optional/field.field.media.video.field_media_oembed_remote_video.yml b/core/profiles/standard/config/optional/field.field.media.video.field_media_oembed_remote_video.yml
new file mode 100644
index 0000000000..441bb8ae2c
--- /dev/null
+++ b/core/profiles/standard/config/optional/field.field.media.video.field_media_oembed_remote_video.yml
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_oembed_remote_video
+    - media.type.video
+  module:
+    - link
+id: media.video.field_media_oembed_remote_video
+field_name: field_media_oembed_remote_video
+entity_type: media
+bundle: video
+label: Remote Video URL
+description: ''
+required: true
+translatable: true
+default_value: {  }
+default_value_callback: ''
+settings:
+  title: 0
+  link_type: 16
+field_type: link
diff --git a/core/profiles/standard/config/optional/field.storage.media.field_media_oembed_remote_video.yml b/core/profiles/standard/config/optional/field.storage.media.field_media_oembed_remote_video.yml
new file mode 100644
index 0000000000..86602de868
--- /dev/null
+++ b/core/profiles/standard/config/optional/field.storage.media.field_media_oembed_remote_video.yml
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - link
+    - media
+id: media.field_media_oembed_remote_video
+field_name: field_media_oembed_remote_video
+entity_type: media
+type: link
+settings: {  }
+module: link
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/core/profiles/standard/config/optional/media.type.video.yml b/core/profiles/standard/config/optional/media.type.video.yml
new file mode 100644
index 0000000000..fafbcfee97
--- /dev/null
+++ b/core/profiles/standard/config/optional/media.type.video.yml
@@ -0,0 +1,13 @@
+langcode: en
+status: true
+dependencies: {  }
+id: video
+label: Video
+description: "Use this media type to store remote content such as YouTube videos."
+source: oembed:remote_video
+queue_thumbnail_downloads: false
+new_revision: true
+source_configuration:
+  thumbnails_location: 'public://oembed_thumbnails'
+  source_field: field_media_oembed_remote_video
+field_map: {  }
