diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java
index bf936cd5c35..12f1951f9fd 100644
--- a/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java
+++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java
@@ -418,13 +418,13 @@ public Builder setSubtitles(@Nullable List<Subtitle> subtitles) {
     /**
      * Sets the optional ad tag {@link Uri}.
      *
-     * <p>All ads media items in the playlist with the same ad tag URI and loader will share the
-     * same ad playback state. To resume ad playback when recreating the playlist on returning from
-     * the background, pass the same ad tag URI.
-     *
      * <p>If {@link #setUri} is passed a non-null {@code uri}, the ad tag URI is used to create a
      * {@link PlaybackProperties} object. Otherwise it will be ignored.
      *
+     * <p>Media items in the playlist with the same ad tag URI, media ID and ads loader will share
+     * the same ad playback state. To resume ad playback when recreating the playlist on returning
+     * from the background, pass media items with the same ad tag URIs and media IDs to the player.
+     *
      * @param adTagUri The ad tag URI to load.
      */
     public Builder setAdTagUri(@Nullable String adTagUri) {
@@ -434,34 +434,35 @@ public Builder setAdTagUri(@Nullable String adTagUri) {
     /**
      * Sets the optional ad tag {@link Uri}.
      *
-     * <p>All ads media items in the playlist with the same ad tag URI and loader will share the
-     * same ad playback state. To resume ad playback when recreating the playlist on returning from
-     * the background, pass the same ad tag URI.
-     *
      * <p>If {@link #setUri} is passed a non-null {@code uri}, the ad tag URI is used to create a
      * {@link PlaybackProperties} object. Otherwise it will be ignored.
      *
+     * <p>Media items in the playlist with the same ad tag URI, media ID and ads loader will share
+     * the same ad playback state. To resume ad playback when recreating the playlist on returning
+     * from the background, pass media items with the same ad tag URIs and media IDs to the player.
+     *
      * @param adTagUri The ad tag URI to load.
      */
     public Builder setAdTagUri(@Nullable Uri adTagUri) {
-      return setAdTagUri(adTagUri, /* adsId= */ adTagUri);
+      return setAdTagUri(adTagUri, /* adsId= */ null);
     }
 
     /**
      * Sets the optional ad tag {@link Uri} and ads identifier.
      *
-     * <p>All ads media items in the playlist with the same ads identifier and loader will share the
-     * same ad playback state.
-     *
      * <p>If {@link #setUri} is passed a non-null {@code uri}, the ad tag URI is used to create a
      * {@link PlaybackProperties} object. Otherwise it will be ignored.
      *
+     * <p>Media items in the playlist that have the same ads identifier and ads loader share the
+     * same ad playback state. To resume ad playback when recreating the playlist on returning from
+     * the background, pass the same ads IDs to the player.
+     *
      * @param adTagUri The ad tag URI to load.
-     * @param adsId An opaque identifier for ad playback state associated with this item. Must be
-     *     non-null if {@code adTagUri} is non-null. Ad loading and playback state is shared among
-     *     all media items that have the same ads id (by {@link Object#equals(Object) equality}) and
-     *     ads loader, so it is important to pass the same identifiers when constructing playlist
-     *     items each time the player returns to the foreground.
+     * @param adsId An opaque identifier for ad playback state associated with this item. Ad loading
+     *     and playback state is shared among all media items that have the same ads ID (by {@link
+     *     Object#equals(Object) equality}) and ads loader, so it is important to pass the same
+     *     identifiers when constructing playlist items each time the player returns to the
+     *     foreground.
      */
     public Builder setAdTagUri(@Nullable Uri adTagUri, @Nullable Object adsId) {
       this.adTagUri = adTagUri;
@@ -576,7 +577,7 @@ public MediaItem build() {
                         drmSessionForClearTypes,
                         drmKeySetId)
                     : null,
-                adTagUri != null ? new AdsConfiguration(adTagUri, checkNotNull(adsId)) : null,
+                adTagUri != null ? new AdsConfiguration(adTagUri, adsId) : null,
                 streamKeys,
                 customCacheKey,
                 subtitles,
@@ -700,19 +701,26 @@ public int hashCode() {
   /** Configuration for playing back linear ads with a media item. */
   public static final class AdsConfiguration {
 
+    /** The ad tag URI to load. */
     public final Uri adTagUri;
-    public final Object adsId;
+    /**
+     * An opaque identifier for ad playback state associated with this item, or {@code null} if the
+     * combination of the {@link MediaItem.Builder#setMediaId(String) media ID} and {@link #adTagUri
+     * ad tag URI} should be used as the ads identifier.
+     */
+    @Nullable public final Object adsId;
 
     /**
      * Creates an ads configuration with the given ad tag URI and ads identifier.
      *
      * @param adTagUri The ad tag URI to load.
      * @param adsId An opaque identifier for ad playback state associated with this item. Ad loading
-     *     and playback state is shared among all media items that have the same ads id (by {@link
-     *     Object#equals(Object) equality}), so it is important to pass the same identifiers when
-     *     constructing playlist items each time the player returns to the foreground.
+     *     and playback state is shared among all media items that have the same ads ID (by {@link
+     *     Object#equals(Object) equality}) and ads loader, so it is important to pass the same
+     *     identifiers when constructing playlist items each time the player returns to the
+     *     foreground.
      */
-    private AdsConfiguration(Uri adTagUri, Object adsId) {
+    private AdsConfiguration(Uri adTagUri, @Nullable Object adsId) {
       this.adTagUri = adTagUri;
       this.adsId = adsId;
     }
@@ -727,13 +735,13 @@ public boolean equals(@Nullable Object obj) {
       }
 
       AdsConfiguration other = (AdsConfiguration) obj;
-      return adTagUri.equals(other.adTagUri) && adsId.equals(other.adsId);
+      return adTagUri.equals(other.adTagUri) && Util.areEqual(adsId, other.adsId);
     }
 
     @Override
     public int hashCode() {
       int result = adTagUri.hashCode();
-      result = 31 * result + adsId.hashCode();
+      result = 31 * result + (adsId != null ? adsId.hashCode() : 0);
       return result;
     }
   }
diff --git a/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java b/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java
index 915462b27f0..0243fe50f47 100644
--- a/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java
+++ b/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java
@@ -282,7 +282,7 @@ public void builderSetAdTagUri_setsAdTagUri() {
     MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).setAdTagUri(adTagUri).build();
 
     assertThat(mediaItem.playbackProperties.adsConfiguration.adTagUri).isEqualTo(adTagUri);
-    assertThat(mediaItem.playbackProperties.adsConfiguration.adsId).isEqualTo(adTagUri);
+    assertThat(mediaItem.playbackProperties.adsConfiguration.adsId).isNull();
   }
 
   @Test
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java
index c9fa011b467..c0b82781df6 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java
@@ -18,6 +18,7 @@
 import static com.google.android.exoplayer2.util.Util.castNonNull;
 
 import android.content.Context;
+import android.util.Pair;
 import android.util.SparseArray;
 import androidx.annotation.Nullable;
 import com.google.android.exoplayer2.C;
@@ -411,7 +412,9 @@ private MediaSource maybeWrapWithAdsMediaSource(MediaItem mediaItem, MediaSource
     return new AdsMediaSource(
         mediaSource,
         new DataSpec(adsConfiguration.adTagUri),
-        adsConfiguration.adsId,
+        /* adsId= */ adsConfiguration.adsId != null
+            ? adsConfiguration.adsId
+            : Pair.create(mediaItem.mediaId, adsConfiguration.adTagUri),
         /* adMediaSourceFactory= */ this,
         adsLoader,
         adViewProvider);