[video_player_android] Implement backBufferDurationMs in ExoPlayer (#12124)

Adds `backBufferDurationMs` to `video_player_android`

This PR has only the android package changes from the main PR: https://github.com/flutter/packages/pull/11810.

Following PR split as per [Changing federated plugins](https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins)

## Pre-Review Checklist
diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md
index 05a1c5a..467d0b1 100644
--- a/packages/video_player/video_player_android/CHANGELOG.md
+++ b/packages/video_player/video_player_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.11.0
+
+* Adds `backBufferDurationMs` to `CreationOptions` to configure ExoPlayer `DefaultLoadControl` back buffer duration.
+
 ## 2.10.0
 
 * Implements `getVideoTracks()` and `selectVideoTrack()` methods for video track (quality) selection using ExoPlayer.
diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerOptions.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerOptions.java
index 20f7c5d..851ed11 100644
--- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerOptions.java
+++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerOptions.java
@@ -4,6 +4,22 @@
 
 package io.flutter.plugins.videoplayer;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 public class VideoPlayerOptions {
   public boolean mixWithOthers;
+
+  /**
+   * The duration of the back buffer in milliseconds, used to configure ExoPlayer's load control.
+   */
+  @Nullable public Long backBufferDurationMs;
+
+  public VideoPlayerOptions() {}
+
+  /** Copy constructor to ensure all options are reliably copied. */
+  public VideoPlayerOptions(@NonNull VideoPlayerOptions other) {
+    this.mixWithOthers = other.mixWithOthers;
+    this.backBufferDurationMs = other.backBufferDurationMs;
+  }
 }
diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java
index 49adaf4..5d520ac 100644
--- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java
+++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java
@@ -87,12 +87,15 @@
 
     long id = nextPlayerIdentifier++;
     final String streamInstance = Long.toString(id);
+    VideoPlayerOptions playerOptions = new VideoPlayerOptions(sharedOptions);
+    playerOptions.backBufferDurationMs = options.getBackBufferDurationMs();
+
     VideoPlayer videoPlayer =
         PlatformViewVideoPlayer.create(
             flutterState.applicationContext,
             VideoPlayerEventCallbacks.bindTo(flutterState.binaryMessenger, streamInstance),
             videoAsset,
-            sharedOptions);
+            playerOptions);
 
     registerPlayerInstance(videoPlayer, id);
     return id;
@@ -106,13 +109,16 @@
     long id = nextPlayerIdentifier++;
     final String streamInstance = Long.toString(id);
     TextureRegistry.SurfaceProducer handle = flutterState.textureRegistry.createSurfaceProducer();
+    VideoPlayerOptions playerOptions = new VideoPlayerOptions(sharedOptions);
+    playerOptions.backBufferDurationMs = options.getBackBufferDurationMs();
+
     VideoPlayer videoPlayer =
         TextureVideoPlayer.create(
             flutterState.applicationContext,
             VideoPlayerEventCallbacks.bindTo(flutterState.binaryMessenger, streamInstance),
             handle,
             videoAsset,
-            sharedOptions);
+            playerOptions);
 
     registerPlayerInstance(videoPlayer, id);
     return new TexturePlayerIds(id, handle.id());
diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewVideoPlayer.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewVideoPlayer.java
index a7c0797..a4b5a93 100644
--- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewVideoPlayer.java
+++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewVideoPlayer.java
@@ -10,6 +10,7 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.media3.common.MediaItem;
 import androidx.media3.common.util.UnstableApi;
+import androidx.media3.exoplayer.DefaultLoadControl;
 import androidx.media3.exoplayer.ExoPlayer;
 import io.flutter.plugins.videoplayer.ExoPlayerEventListener;
 import io.flutter.plugins.videoplayer.VideoAsset;
@@ -56,12 +57,28 @@
         asset.getMediaItem(),
         options,
         () -> {
+          ExoPlayer.Builder builder = new ExoPlayer.Builder(context);
+          if (options.backBufferDurationMs != null) {
+            if (options.backBufferDurationMs < 0) {
+              throw new IllegalArgumentException("backBufferDurationMs must be at least 0");
+            }
+            if (options.backBufferDurationMs > 0) {
+              // Clamp the value to ensure it fits within the int range expected by
+              // DefaultLoadControl.
+              int backBufferInt =
+                  (int) Math.min(options.backBufferDurationMs.longValue(), Integer.MAX_VALUE);
+              DefaultLoadControl loadControl =
+                  new DefaultLoadControl.Builder()
+                      .setBackBuffer(backBufferInt, /* retainBackBufferFromKeyframe= */ true)
+                      .build();
+              builder.setLoadControl(loadControl);
+            }
+          }
           androidx.media3.exoplayer.trackselection.DefaultTrackSelector trackSelector =
               new androidx.media3.exoplayer.trackselection.DefaultTrackSelector(context);
-          ExoPlayer.Builder builder =
-              new ExoPlayer.Builder(context)
-                  .setTrackSelector(trackSelector)
-                  .setMediaSourceFactory(asset.getMediaSourceFactory(context));
+          builder
+              .setTrackSelector(trackSelector)
+              .setMediaSourceFactory(asset.getMediaSourceFactory(context));
           return builder.build();
         });
   }
diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureVideoPlayer.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureVideoPlayer.java
index d623ddc..c0a2d41 100644
--- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureVideoPlayer.java
+++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureVideoPlayer.java
@@ -12,6 +12,7 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.media3.common.MediaItem;
 import androidx.media3.common.util.UnstableApi;
+import androidx.media3.exoplayer.DefaultLoadControl;
 import androidx.media3.exoplayer.ExoPlayer;
 import io.flutter.plugins.videoplayer.ExoPlayerEventListener;
 import io.flutter.plugins.videoplayer.VideoAsset;
@@ -56,12 +57,28 @@
         asset.getMediaItem(),
         options,
         () -> {
+          ExoPlayer.Builder builder = new ExoPlayer.Builder(context);
+          if (options.backBufferDurationMs != null) {
+            if (options.backBufferDurationMs < 0) {
+              throw new IllegalArgumentException("backBufferDurationMs must be at least 0");
+            }
+            if (options.backBufferDurationMs > 0) {
+              // Clamp the value to ensure it fits within the int range expected by
+              // DefaultLoadControl.
+              int backBufferInt =
+                  (int) Math.min(options.backBufferDurationMs.longValue(), Integer.MAX_VALUE);
+              DefaultLoadControl loadControl =
+                  new DefaultLoadControl.Builder()
+                      .setBackBuffer(backBufferInt, /* retainBackBufferFromKeyframe= */ true)
+                      .build();
+              builder.setLoadControl(loadControl);
+            }
+          }
           androidx.media3.exoplayer.trackselection.DefaultTrackSelector trackSelector =
               new androidx.media3.exoplayer.trackselection.DefaultTrackSelector(context);
-          ExoPlayer.Builder builder =
-              new ExoPlayer.Builder(context)
-                  .setTrackSelector(trackSelector)
-                  .setMediaSourceFactory(asset.getMediaSourceFactory(context));
+          builder
+              .setTrackSelector(trackSelector)
+              .setMediaSourceFactory(asset.getMediaSourceFactory(context));
           return builder.build();
         });
   }
diff --git a/packages/video_player/video_player_android/android/src/main/kotlin/io/flutter/plugins/videoplayer/Messages.kt b/packages/video_player/video_player_android/android/src/main/kotlin/io/flutter/plugins/videoplayer/Messages.kt
index e1a73f1..40ffc57 100644
--- a/packages/video_player/video_player_android/android/src/main/kotlin/io/flutter/plugins/videoplayer/Messages.kt
+++ b/packages/video_player/video_player_android/android/src/main/kotlin/io/flutter/plugins/videoplayer/Messages.kt
@@ -1,7 +1,7 @@
 // Copyright 2013 The Flutter Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v26.1.5), do not edit directly.
+// Autogenerated from Pigeon (v26.3.4), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
 
@@ -34,7 +34,36 @@
     }
   }
 
+  fun doubleEquals(a: Double, b: Double): Boolean {
+    // Normalize -0.0 to 0.0 and handle NaN equality.
+    return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN())
+  }
+
+  fun floatEquals(a: Float, b: Float): Boolean {
+    // Normalize -0.0 to 0.0 and handle NaN equality.
+    return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN())
+  }
+
+  fun doubleHash(d: Double): Int {
+    // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
+    val normalized = if (d == 0.0) 0.0 else d
+    val bits = java.lang.Double.doubleToLongBits(normalized)
+    return (bits xor (bits ushr 32)).toInt()
+  }
+
+  fun floatHash(f: Float): Int {
+    // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
+    val normalized = if (f == 0.0f) 0.0f else f
+    return java.lang.Float.floatToIntBits(normalized)
+  }
+
   fun deepEquals(a: Any?, b: Any?): Boolean {
+    if (a === b) {
+      return true
+    }
+    if (a == null || b == null) {
+      return false
+    }
     if (a is ByteArray && b is ByteArray) {
       return a.contentEquals(b)
     }
@@ -45,20 +74,109 @@
       return a.contentEquals(b)
     }
     if (a is DoubleArray && b is DoubleArray) {
-      return a.contentEquals(b)
+      if (a.size != b.size) return false
+      for (i in a.indices) {
+        if (!doubleEquals(a[i], b[i])) return false
+      }
+      return true
+    }
+    if (a is FloatArray && b is FloatArray) {
+      if (a.size != b.size) return false
+      for (i in a.indices) {
+        if (!floatEquals(a[i], b[i])) return false
+      }
+      return true
     }
     if (a is Array<*> && b is Array<*>) {
-      return a.size == b.size && a.indices.all { deepEquals(a[it], b[it]) }
+      if (a.size != b.size) return false
+      for (i in a.indices) {
+        if (!deepEquals(a[i], b[i])) return false
+      }
+      return true
     }
     if (a is List<*> && b is List<*>) {
-      return a.size == b.size && a.indices.all { deepEquals(a[it], b[it]) }
+      if (a.size != b.size) return false
+      val iterA = a.iterator()
+      val iterB = b.iterator()
+      while (iterA.hasNext() && iterB.hasNext()) {
+        if (!deepEquals(iterA.next(), iterB.next())) return false
+      }
+      return true
     }
     if (a is Map<*, *> && b is Map<*, *>) {
-      return a.size == b.size &&
-          a.all { (b as Map<Any?, Any?>).contains(it.key) && deepEquals(it.value, b[it.key]) }
+      if (a.size != b.size) return false
+      for (entry in a) {
+        val key = entry.key
+        var found = false
+        for (bEntry in b) {
+          if (deepEquals(key, bEntry.key)) {
+            if (deepEquals(entry.value, bEntry.value)) {
+              found = true
+              break
+            } else {
+              return false
+            }
+          }
+        }
+        if (!found) return false
+      }
+      return true
+    }
+    if (a is Double && b is Double) {
+      return doubleEquals(a, b)
+    }
+    if (a is Float && b is Float) {
+      return floatEquals(a, b)
     }
     return a == b
   }
+
+  fun deepHash(value: Any?): Int {
+    return when (value) {
+      null -> 0
+      is ByteArray -> value.contentHashCode()
+      is IntArray -> value.contentHashCode()
+      is LongArray -> value.contentHashCode()
+      is DoubleArray -> {
+        var result = 1
+        for (item in value) {
+          result = 31 * result + doubleHash(item)
+        }
+        result
+      }
+      is FloatArray -> {
+        var result = 1
+        for (item in value) {
+          result = 31 * result + floatHash(item)
+        }
+        result
+      }
+      is Array<*> -> {
+        var result = 1
+        for (item in value) {
+          result = 31 * result + deepHash(item)
+        }
+        result
+      }
+      is List<*> -> {
+        var result = 1
+        for (item in value) {
+          result = 31 * result + deepHash(item)
+        }
+        result
+      }
+      is Map<*, *> -> {
+        var result = 0
+        for (entry in value) {
+          result += ((deepHash(entry.key) * 31) xor deepHash(entry.value))
+        }
+        result
+      }
+      is Double -> doubleHash(value)
+      is Float -> floatHash(value)
+      else -> value.hashCode()
+    }
+  }
 }
 
 /**
@@ -72,7 +190,7 @@
     val code: String,
     override val message: String? = null,
     val details: Any? = null
-) : Throwable()
+) : RuntimeException()
 
 /** Pigeon equivalent of video_platform_interface's VideoFormat. */
 enum class PlatformVideoFormat(val raw: Int) {
@@ -145,16 +263,27 @@
   }
 
   override fun equals(other: Any?): Boolean {
-    if (other !is InitializationEvent) {
+    if (other == null || other.javaClass != javaClass) {
       return false
     }
     if (this === other) {
       return true
     }
-    return MessagesPigeonUtils.deepEquals(toList(), other.toList())
+    val other = other as InitializationEvent
+    return MessagesPigeonUtils.deepEquals(this.duration, other.duration) &&
+        MessagesPigeonUtils.deepEquals(this.width, other.width) &&
+        MessagesPigeonUtils.deepEquals(this.height, other.height) &&
+        MessagesPigeonUtils.deepEquals(this.rotationCorrection, other.rotationCorrection)
   }
 
-  override fun hashCode(): Int = toList().hashCode()
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.duration)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.width)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.height)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.rotationCorrection)
+    return result
+  }
 }
 
 /**
@@ -179,16 +308,21 @@
   }
 
   override fun equals(other: Any?): Boolean {
-    if (other !is PlaybackStateChangeEvent) {
+    if (other == null || other.javaClass != javaClass) {
       return false
     }
     if (this === other) {
       return true
     }
-    return MessagesPigeonUtils.deepEquals(toList(), other.toList())
+    val other = other as PlaybackStateChangeEvent
+    return MessagesPigeonUtils.deepEquals(this.state, other.state)
   }
 
-  override fun hashCode(): Int = toList().hashCode()
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.state)
+    return result
+  }
 }
 
 /**
@@ -213,16 +347,21 @@
   }
 
   override fun equals(other: Any?): Boolean {
-    if (other !is IsPlayingStateEvent) {
+    if (other == null || other.javaClass != javaClass) {
       return false
     }
     if (this === other) {
       return true
     }
-    return MessagesPigeonUtils.deepEquals(toList(), other.toList())
+    val other = other as IsPlayingStateEvent
+    return MessagesPigeonUtils.deepEquals(this.isPlaying, other.isPlaying)
   }
 
-  override fun hashCode(): Int = toList().hashCode()
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.isPlaying)
+    return result
+  }
 }
 
 /**
@@ -251,16 +390,21 @@
   }
 
   override fun equals(other: Any?): Boolean {
-    if (other !is AudioTrackChangedEvent) {
+    if (other == null || other.javaClass != javaClass) {
       return false
     }
     if (this === other) {
       return true
     }
-    return MessagesPigeonUtils.deepEquals(toList(), other.toList())
+    val other = other as AudioTrackChangedEvent
+    return MessagesPigeonUtils.deepEquals(this.selectedTrackId, other.selectedTrackId)
   }
 
-  override fun hashCode(): Int = toList().hashCode()
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.selectedTrackId)
+    return result
+  }
 }
 
 /**
@@ -292,16 +436,21 @@
   }
 
   override fun equals(other: Any?): Boolean {
-    if (other !is VideoTrackChangedEvent) {
+    if (other == null || other.javaClass != javaClass) {
       return false
     }
     if (this === other) {
       return true
     }
-    return MessagesPigeonUtils.deepEquals(toList(), other.toList())
+    val other = other as VideoTrackChangedEvent
+    return MessagesPigeonUtils.deepEquals(this.selectedTrackId, other.selectedTrackId)
   }
 
-  override fun hashCode(): Int = toList().hashCode()
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.selectedTrackId)
+    return result
+  }
 }
 
 /**
@@ -324,16 +473,21 @@
   }
 
   override fun equals(other: Any?): Boolean {
-    if (other !is PlatformVideoViewCreationParams) {
+    if (other == null || other.javaClass != javaClass) {
       return false
     }
     if (this === other) {
       return true
     }
-    return MessagesPigeonUtils.deepEquals(toList(), other.toList())
+    val other = other as PlatformVideoViewCreationParams
+    return MessagesPigeonUtils.deepEquals(this.playerId, other.playerId)
   }
 
-  override fun hashCode(): Int = toList().hashCode()
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.playerId)
+    return result
+  }
 }
 
 /** Generated class from Pigeon that represents data sent in messages. */
@@ -341,7 +495,8 @@
     val uri: String,
     val formatHint: PlatformVideoFormat? = null,
     val httpHeaders: Map<String, String>,
-    val userAgent: String? = null
+    val userAgent: String? = null,
+    val backBufferDurationMs: Long? = null
 ) {
   companion object {
     fun fromList(pigeonVar_list: List<Any?>): CreationOptions {
@@ -349,7 +504,8 @@
       val formatHint = pigeonVar_list[1] as PlatformVideoFormat?
       val httpHeaders = pigeonVar_list[2] as Map<String, String>
       val userAgent = pigeonVar_list[3] as String?
-      return CreationOptions(uri, formatHint, httpHeaders, userAgent)
+      val backBufferDurationMs = pigeonVar_list[4] as Long?
+      return CreationOptions(uri, formatHint, httpHeaders, userAgent, backBufferDurationMs)
     }
   }
 
@@ -359,20 +515,34 @@
         formatHint,
         httpHeaders,
         userAgent,
+        backBufferDurationMs,
     )
   }
 
   override fun equals(other: Any?): Boolean {
-    if (other !is CreationOptions) {
+    if (other == null || other.javaClass != javaClass) {
       return false
     }
     if (this === other) {
       return true
     }
-    return MessagesPigeonUtils.deepEquals(toList(), other.toList())
+    val other = other as CreationOptions
+    return MessagesPigeonUtils.deepEquals(this.uri, other.uri) &&
+        MessagesPigeonUtils.deepEquals(this.formatHint, other.formatHint) &&
+        MessagesPigeonUtils.deepEquals(this.httpHeaders, other.httpHeaders) &&
+        MessagesPigeonUtils.deepEquals(this.userAgent, other.userAgent) &&
+        MessagesPigeonUtils.deepEquals(this.backBufferDurationMs, other.backBufferDurationMs)
   }
 
-  override fun hashCode(): Int = toList().hashCode()
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.uri)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.formatHint)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.httpHeaders)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.userAgent)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.backBufferDurationMs)
+    return result
+  }
 }
 
 /** Generated class from Pigeon that represents data sent in messages. */
@@ -393,16 +563,23 @@
   }
 
   override fun equals(other: Any?): Boolean {
-    if (other !is TexturePlayerIds) {
+    if (other == null || other.javaClass != javaClass) {
       return false
     }
     if (this === other) {
       return true
     }
-    return MessagesPigeonUtils.deepEquals(toList(), other.toList())
+    val other = other as TexturePlayerIds
+    return MessagesPigeonUtils.deepEquals(this.playerId, other.playerId) &&
+        MessagesPigeonUtils.deepEquals(this.textureId, other.textureId)
   }
 
-  override fun hashCode(): Int = toList().hashCode()
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.playerId)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.textureId)
+    return result
+  }
 }
 
 /** Generated class from Pigeon that represents data sent in messages. */
@@ -428,16 +605,23 @@
   }
 
   override fun equals(other: Any?): Boolean {
-    if (other !is PlaybackState) {
+    if (other == null || other.javaClass != javaClass) {
       return false
     }
     if (this === other) {
       return true
     }
-    return MessagesPigeonUtils.deepEquals(toList(), other.toList())
+    val other = other as PlaybackState
+    return MessagesPigeonUtils.deepEquals(this.playPosition, other.playPosition) &&
+        MessagesPigeonUtils.deepEquals(this.bufferPosition, other.bufferPosition)
   }
 
-  override fun hashCode(): Int = toList().hashCode()
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.playPosition)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.bufferPosition)
+    return result
+  }
 }
 
 /**
@@ -484,16 +668,35 @@
   }
 
   override fun equals(other: Any?): Boolean {
-    if (other !is AudioTrackMessage) {
+    if (other == null || other.javaClass != javaClass) {
       return false
     }
     if (this === other) {
       return true
     }
-    return MessagesPigeonUtils.deepEquals(toList(), other.toList())
+    val other = other as AudioTrackMessage
+    return MessagesPigeonUtils.deepEquals(this.id, other.id) &&
+        MessagesPigeonUtils.deepEquals(this.label, other.label) &&
+        MessagesPigeonUtils.deepEquals(this.language, other.language) &&
+        MessagesPigeonUtils.deepEquals(this.isSelected, other.isSelected) &&
+        MessagesPigeonUtils.deepEquals(this.bitrate, other.bitrate) &&
+        MessagesPigeonUtils.deepEquals(this.sampleRate, other.sampleRate) &&
+        MessagesPigeonUtils.deepEquals(this.channelCount, other.channelCount) &&
+        MessagesPigeonUtils.deepEquals(this.codec, other.codec)
   }
 
-  override fun hashCode(): Int = toList().hashCode()
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.id)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.label)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.language)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.isSelected)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.bitrate)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.sampleRate)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.channelCount)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.codec)
+    return result
+  }
 }
 
 /**
@@ -551,16 +754,37 @@
   }
 
   override fun equals(other: Any?): Boolean {
-    if (other !is ExoPlayerAudioTrackData) {
+    if (other == null || other.javaClass != javaClass) {
       return false
     }
     if (this === other) {
       return true
     }
-    return MessagesPigeonUtils.deepEquals(toList(), other.toList())
+    val other = other as ExoPlayerAudioTrackData
+    return MessagesPigeonUtils.deepEquals(this.groupIndex, other.groupIndex) &&
+        MessagesPigeonUtils.deepEquals(this.trackIndex, other.trackIndex) &&
+        MessagesPigeonUtils.deepEquals(this.label, other.label) &&
+        MessagesPigeonUtils.deepEquals(this.language, other.language) &&
+        MessagesPigeonUtils.deepEquals(this.isSelected, other.isSelected) &&
+        MessagesPigeonUtils.deepEquals(this.bitrate, other.bitrate) &&
+        MessagesPigeonUtils.deepEquals(this.sampleRate, other.sampleRate) &&
+        MessagesPigeonUtils.deepEquals(this.channelCount, other.channelCount) &&
+        MessagesPigeonUtils.deepEquals(this.codec, other.codec)
   }
 
-  override fun hashCode(): Int = toList().hashCode()
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.groupIndex)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.trackIndex)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.label)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.language)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.isSelected)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.bitrate)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.sampleRate)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.channelCount)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.codec)
+    return result
+  }
 }
 
 /**
@@ -586,16 +810,21 @@
   }
 
   override fun equals(other: Any?): Boolean {
-    if (other !is NativeAudioTrackData) {
+    if (other == null || other.javaClass != javaClass) {
       return false
     }
     if (this === other) {
       return true
     }
-    return MessagesPigeonUtils.deepEquals(toList(), other.toList())
+    val other = other as NativeAudioTrackData
+    return MessagesPigeonUtils.deepEquals(this.exoPlayerTracks, other.exoPlayerTracks)
   }
 
-  override fun hashCode(): Int = toList().hashCode()
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.exoPlayerTracks)
+    return result
+  }
 }
 
 /**
@@ -645,16 +874,37 @@
   }
 
   override fun equals(other: Any?): Boolean {
-    if (other !is ExoPlayerVideoTrackData) {
+    if (other == null || other.javaClass != javaClass) {
       return false
     }
     if (this === other) {
       return true
     }
-    return MessagesPigeonUtils.deepEquals(toList(), other.toList())
+    val other = other as ExoPlayerVideoTrackData
+    return MessagesPigeonUtils.deepEquals(this.groupIndex, other.groupIndex) &&
+        MessagesPigeonUtils.deepEquals(this.trackIndex, other.trackIndex) &&
+        MessagesPigeonUtils.deepEquals(this.label, other.label) &&
+        MessagesPigeonUtils.deepEquals(this.isSelected, other.isSelected) &&
+        MessagesPigeonUtils.deepEquals(this.bitrate, other.bitrate) &&
+        MessagesPigeonUtils.deepEquals(this.width, other.width) &&
+        MessagesPigeonUtils.deepEquals(this.height, other.height) &&
+        MessagesPigeonUtils.deepEquals(this.frameRate, other.frameRate) &&
+        MessagesPigeonUtils.deepEquals(this.codec, other.codec)
   }
 
-  override fun hashCode(): Int = toList().hashCode()
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.groupIndex)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.trackIndex)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.label)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.isSelected)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.bitrate)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.width)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.height)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.frameRate)
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.codec)
+    return result
+  }
 }
 
 /**
@@ -680,16 +930,21 @@
   }
 
   override fun equals(other: Any?): Boolean {
-    if (other !is NativeVideoTrackData) {
+    if (other == null || other.javaClass != javaClass) {
       return false
     }
     if (this === other) {
       return true
     }
-    return MessagesPigeonUtils.deepEquals(toList(), other.toList())
+    val other = other as NativeVideoTrackData
+    return MessagesPigeonUtils.deepEquals(this.exoPlayerTracks, other.exoPlayerTracks)
   }
 
-  override fun hashCode(): Int = toList().hashCode()
+  override fun hashCode(): Int {
+    var result = javaClass.hashCode()
+    result = 31 * result + MessagesPigeonUtils.deepHash(this.exoPlayerTracks)
+    return result
+  }
 }
 
 private open class MessagesPigeonCodec : StandardMessageCodec() {
diff --git a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/PlatformViewVideoPlayerTest.java b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/PlatformViewVideoPlayerTest.java
new file mode 100644
index 0000000..affbb6b
--- /dev/null
+++ b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/PlatformViewVideoPlayerTest.java
@@ -0,0 +1,70 @@
+// Copyright 2013 The Flutter Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.videoplayer;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import androidx.annotation.OptIn;
+import androidx.media3.common.util.UnstableApi;
+import androidx.media3.exoplayer.ExoPlayer;
+import io.flutter.plugins.videoplayer.platformview.PlatformViewVideoPlayer;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class PlatformViewVideoPlayerTest {
+  private static final String FAKE_ASSET_URL = "https://flutter.dev/movie.mp4";
+  private FakeVideoAsset fakeVideoAsset;
+
+  @Mock private VideoPlayerCallbacks mockEvents;
+  @Mock private ExoPlayer mockExoPlayer;
+
+  @Rule public MockitoRule initRule = MockitoJUnit.rule();
+
+  @Before
+  public void setUp() {
+    fakeVideoAsset = new FakeVideoAsset(FAKE_ASSET_URL);
+  }
+
+  @OptIn(markerClass = UnstableApi.class)
+  @Test
+  public void create_withBackBufferDuration_setsLoadControl() {
+    Context mockContext = mock(Context.class);
+    VideoPlayerOptions options = new VideoPlayerOptions();
+    options.backBufferDurationMs = 20000L;
+
+    try (MockedConstruction<ExoPlayer.Builder> mockedBuilder =
+        mockConstruction(
+            ExoPlayer.Builder.class,
+            (mock, context) -> {
+              when(mock.setLoadControl(any())).thenReturn(mock);
+              when(mock.setTrackSelector(any())).thenReturn(mock);
+              when(mock.setMediaSourceFactory(any())).thenReturn(mock);
+              when(mock.build()).thenReturn(mockExoPlayer);
+            })) {
+
+      PlatformViewVideoPlayer player =
+          PlatformViewVideoPlayer.create(mockContext, mockEvents, fakeVideoAsset, options);
+
+      assertEquals(1, mockedBuilder.constructed().size());
+      ExoPlayer.Builder builderMock = mockedBuilder.constructed().get(0);
+      verify(builderMock).setLoadControl(any());
+      player.dispose();
+    }
+  }
+}
diff --git a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/TextureVideoPlayerTest.java b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/TextureVideoPlayerTest.java
index 6631d35..befb227 100644
--- a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/TextureVideoPlayerTest.java
+++ b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/TextureVideoPlayerTest.java
@@ -25,6 +25,7 @@
 import org.mockito.Captor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.MockedConstruction;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
@@ -200,4 +201,30 @@
     inOrder.verify(mockExoPlayer).release();
     inOrder.verify(mockProducer).release();
   }
+
+  @Test
+  public void create_withBackBufferDuration_setsLoadControl() {
+    android.content.Context mockContext = mock(android.content.Context.class);
+    VideoPlayerOptions options = new VideoPlayerOptions();
+    options.backBufferDurationMs = 20000L;
+
+    try (MockedConstruction<ExoPlayer.Builder> mockedBuilder =
+        mockConstruction(
+            ExoPlayer.Builder.class,
+            (mock, context) -> {
+              when(mock.setLoadControl(any())).thenReturn(mock);
+              when(mock.setTrackSelector(any())).thenReturn(mock);
+              when(mock.setMediaSourceFactory(any())).thenReturn(mock);
+              when(mock.build()).thenReturn(mockExoPlayer);
+            })) {
+
+      TextureVideoPlayer player =
+          TextureVideoPlayer.create(mockContext, mockEvents, mockProducer, fakeVideoAsset, options);
+
+      assertEquals(1, mockedBuilder.constructed().size());
+      ExoPlayer.Builder builderMock = mockedBuilder.constructed().get(0);
+      verify(builderMock).setLoadControl(any());
+      player.dispose();
+    }
+  }
 }
diff --git a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerPluginTest.java b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerPluginTest.java
index 6093dc8..9f5e42f 100644
--- a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerPluginTest.java
+++ b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerPluginTest.java
@@ -81,6 +81,7 @@
               "https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4",
               null,
               new HashMap<>(),
+              null,
               null);
 
       final long playerId = plugin.createForPlatformView(options);
@@ -103,6 +104,7 @@
               "https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4",
               null,
               new HashMap<>(),
+              null,
               null);
 
       final TexturePlayerIds ids = plugin.createForTextureView(options);
diff --git a/packages/video_player/video_player_android/lib/src/android_video_player.dart b/packages/video_player/video_player_android/lib/src/android_video_player.dart
index 4d4330e..2024f18 100644
--- a/packages/video_player/video_player_android/lib/src/android_video_player.dart
+++ b/packages/video_player/video_player_android/lib/src/android_video_player.dart
@@ -103,6 +103,7 @@
       httpHeaders: httpHeaders,
       userAgent: userAgent,
       formatHint: formatHint,
+      backBufferDurationMs: options.videoPlayerOptions?.backBufferDurationMs,
     );
 
     final int playerId;
diff --git a/packages/video_player/video_player_android/lib/src/messages.g.dart b/packages/video_player/video_player_android/lib/src/messages.g.dart
index e430203..e51d721 100644
--- a/packages/video_player/video_player_android/lib/src/messages.g.dart
+++ b/packages/video_player/video_player_android/lib/src/messages.g.dart
@@ -1,39 +1,103 @@
 // Copyright 2013 The Flutter Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v26.1.5), do not edit directly.
+// Autogenerated from Pigeon (v26.3.4), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
-// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, omit_obvious_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
+// ignore_for_file: unused_import, unused_shown_name
+// ignore_for_file: type=lint
 
 import 'dart:async';
-import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
+import 'dart:typed_data' show Float64List, Int32List, Int64List;
 
-import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
 import 'package:flutter/services.dart';
+import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
 
-PlatformException _createConnectionError(String channelName) {
-  return PlatformException(
-    code: 'channel-error',
-    message: 'Unable to establish connection on channel: "$channelName".',
-  );
+Object? _extractReplyValueOrThrow(
+  List<Object?>? replyList,
+  String channelName, {
+  required bool isNullValid,
+}) {
+  if (replyList == null) {
+    throw PlatformException(
+      code: 'channel-error',
+      message: 'Unable to establish connection on channel: "$channelName".',
+    );
+  } else if (replyList.length > 1) {
+    throw PlatformException(
+      code: replyList[0]! as String,
+      message: replyList[1] as String?,
+      details: replyList[2],
+    );
+  } else if (!isNullValid && (replyList.isNotEmpty && replyList[0] == null)) {
+    throw PlatformException(
+      code: 'null-error',
+      message: 'Host platform returned null value for non-null return value.',
+    );
+  }
+  return replyList.firstOrNull;
 }
 
 bool _deepEquals(Object? a, Object? b) {
+  if (identical(a, b)) {
+    return true;
+  }
+  if (a is double && b is double) {
+    if (a.isNaN && b.isNaN) {
+      return true;
+    }
+    return a == b;
+  }
   if (a is List && b is List) {
     return a.length == b.length &&
         a.indexed.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
   }
   if (a is Map && b is Map) {
-    return a.length == b.length &&
-        a.entries.every(
-          (MapEntry<Object?, Object?> entry) =>
-              (b as Map<Object?, Object?>).containsKey(entry.key) &&
-              _deepEquals(entry.value, b[entry.key]),
-        );
+    if (a.length != b.length) {
+      return false;
+    }
+    for (final MapEntry<Object?, Object?> entryA in a.entries) {
+      bool found = false;
+      for (final MapEntry<Object?, Object?> entryB in b.entries) {
+        if (_deepEquals(entryA.key, entryB.key)) {
+          if (_deepEquals(entryA.value, entryB.value)) {
+            found = true;
+            break;
+          } else {
+            return false;
+          }
+        }
+      }
+      if (!found) {
+        return false;
+      }
+    }
+    return true;
   }
   return a == b;
 }
 
+int _deepHash(Object? value) {
+  if (value is List) {
+    return Object.hashAll(value.map(_deepHash));
+  }
+  if (value is Map) {
+    int result = 0;
+    for (final MapEntry<Object?, Object?> entry in value.entries) {
+      result += (_deepHash(entry.key) * 31) ^ _deepHash(entry.value);
+    }
+    return result;
+  }
+  if (value is double && value.isNaN) {
+    // Normalize NaN to a consistent hash.
+    return 0x7FF8000000000000.hashCode;
+  }
+  if (value is double && value == 0.0) {
+    // Normalize -0.0 to 0.0 so they have the same hash code.
+    return 0.0.hashCode;
+  }
+  return value.hashCode;
+}
+
 /// Pigeon equivalent of video_platform_interface's VideoFormat.
 enum PlatformVideoFormat { dash, hls, ss }
 
@@ -91,12 +155,15 @@
     if (identical(this, other)) {
       return true;
     }
-    return _deepEquals(encode(), other.encode());
+    return _deepEquals(duration, other.duration) &&
+        _deepEquals(width, other.width) &&
+        _deepEquals(height, other.height) &&
+        _deepEquals(rotationCorrection, other.rotationCorrection);
   }
 
   @override
   // ignore: avoid_equals_and_hash_code_on_mutable_classes
-  int get hashCode => Object.hashAll(_toList());
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
 }
 
 /// Sent when the video state changes.
@@ -129,12 +196,12 @@
     if (identical(this, other)) {
       return true;
     }
-    return _deepEquals(encode(), other.encode());
+    return _deepEquals(state, other.state);
   }
 
   @override
   // ignore: avoid_equals_and_hash_code_on_mutable_classes
-  int get hashCode => Object.hashAll(_toList());
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
 }
 
 /// Sent when the video starts or stops playing.
@@ -167,12 +234,12 @@
     if (identical(this, other)) {
       return true;
     }
-    return _deepEquals(encode(), other.encode());
+    return _deepEquals(isPlaying, other.isPlaying);
   }
 
   @override
   // ignore: avoid_equals_and_hash_code_on_mutable_classes
-  int get hashCode => Object.hashAll(_toList());
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
 }
 
 /// Sent when audio tracks change.
@@ -207,12 +274,12 @@
     if (identical(this, other)) {
       return true;
     }
-    return _deepEquals(encode(), other.encode());
+    return _deepEquals(selectedTrackId, other.selectedTrackId);
   }
 
   @override
   // ignore: avoid_equals_and_hash_code_on_mutable_classes
-  int get hashCode => Object.hashAll(_toList());
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
 }
 
 /// Sent when video tracks change.
@@ -248,12 +315,12 @@
     if (identical(this, other)) {
       return true;
     }
-    return _deepEquals(encode(), other.encode());
+    return _deepEquals(selectedTrackId, other.selectedTrackId);
   }
 
   @override
   // ignore: avoid_equals_and_hash_code_on_mutable_classes
-  int get hashCode => Object.hashAll(_toList());
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
 }
 
 /// Information passed to the platform view creation.
@@ -284,16 +351,22 @@
     if (identical(this, other)) {
       return true;
     }
-    return _deepEquals(encode(), other.encode());
+    return _deepEquals(playerId, other.playerId);
   }
 
   @override
   // ignore: avoid_equals_and_hash_code_on_mutable_classes
-  int get hashCode => Object.hashAll(_toList());
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
 }
 
 class CreationOptions {
-  CreationOptions({required this.uri, this.formatHint, required this.httpHeaders, this.userAgent});
+  CreationOptions({
+    required this.uri,
+    this.formatHint,
+    required this.httpHeaders,
+    this.userAgent,
+    this.backBufferDurationMs,
+  });
 
   String uri;
 
@@ -303,8 +376,10 @@
 
   String? userAgent;
 
+  int? backBufferDurationMs;
+
   List<Object?> _toList() {
-    return <Object?>[uri, formatHint, httpHeaders, userAgent];
+    return <Object?>[uri, formatHint, httpHeaders, userAgent, backBufferDurationMs];
   }
 
   Object encode() {
@@ -316,8 +391,9 @@
     return CreationOptions(
       uri: result[0]! as String,
       formatHint: result[1] as PlatformVideoFormat?,
-      httpHeaders: (result[2] as Map<Object?, Object?>?)!.cast<String, String>(),
+      httpHeaders: (result[2]! as Map<Object?, Object?>).cast<String, String>(),
       userAgent: result[3] as String?,
+      backBufferDurationMs: result[4] as int?,
     );
   }
 
@@ -330,12 +406,16 @@
     if (identical(this, other)) {
       return true;
     }
-    return _deepEquals(encode(), other.encode());
+    return _deepEquals(uri, other.uri) &&
+        _deepEquals(formatHint, other.formatHint) &&
+        _deepEquals(httpHeaders, other.httpHeaders) &&
+        _deepEquals(userAgent, other.userAgent) &&
+        _deepEquals(backBufferDurationMs, other.backBufferDurationMs);
   }
 
   @override
   // ignore: avoid_equals_and_hash_code_on_mutable_classes
-  int get hashCode => Object.hashAll(_toList());
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
 }
 
 class TexturePlayerIds {
@@ -367,12 +447,12 @@
     if (identical(this, other)) {
       return true;
     }
-    return _deepEquals(encode(), other.encode());
+    return _deepEquals(playerId, other.playerId) && _deepEquals(textureId, other.textureId);
   }
 
   @override
   // ignore: avoid_equals_and_hash_code_on_mutable_classes
-  int get hashCode => Object.hashAll(_toList());
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
 }
 
 class PlaybackState {
@@ -406,12 +486,13 @@
     if (identical(this, other)) {
       return true;
     }
-    return _deepEquals(encode(), other.encode());
+    return _deepEquals(playPosition, other.playPosition) &&
+        _deepEquals(bufferPosition, other.bufferPosition);
   }
 
   @override
   // ignore: avoid_equals_and_hash_code_on_mutable_classes
-  int get hashCode => Object.hashAll(_toList());
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
 }
 
 /// Represents an audio track in a video.
@@ -474,12 +555,19 @@
     if (identical(this, other)) {
       return true;
     }
-    return _deepEquals(encode(), other.encode());
+    return _deepEquals(id, other.id) &&
+        _deepEquals(label, other.label) &&
+        _deepEquals(language, other.language) &&
+        _deepEquals(isSelected, other.isSelected) &&
+        _deepEquals(bitrate, other.bitrate) &&
+        _deepEquals(sampleRate, other.sampleRate) &&
+        _deepEquals(channelCount, other.channelCount) &&
+        _deepEquals(codec, other.codec);
   }
 
   @override
   // ignore: avoid_equals_and_hash_code_on_mutable_classes
-  int get hashCode => Object.hashAll(_toList());
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
 }
 
 /// Raw audio track data from ExoPlayer Format objects.
@@ -556,12 +644,20 @@
     if (identical(this, other)) {
       return true;
     }
-    return _deepEquals(encode(), other.encode());
+    return _deepEquals(groupIndex, other.groupIndex) &&
+        _deepEquals(trackIndex, other.trackIndex) &&
+        _deepEquals(label, other.label) &&
+        _deepEquals(language, other.language) &&
+        _deepEquals(isSelected, other.isSelected) &&
+        _deepEquals(bitrate, other.bitrate) &&
+        _deepEquals(sampleRate, other.sampleRate) &&
+        _deepEquals(channelCount, other.channelCount) &&
+        _deepEquals(codec, other.codec);
   }
 
   @override
   // ignore: avoid_equals_and_hash_code_on_mutable_classes
-  int get hashCode => Object.hashAll(_toList());
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
 }
 
 /// Container for raw audio track data from Android ExoPlayer.
@@ -595,12 +691,12 @@
     if (identical(this, other)) {
       return true;
     }
-    return _deepEquals(encode(), other.encode());
+    return _deepEquals(exoPlayerTracks, other.exoPlayerTracks);
   }
 
   @override
   // ignore: avoid_equals_and_hash_code_on_mutable_classes
-  int get hashCode => Object.hashAll(_toList());
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
 }
 
 /// Raw video track data from ExoPlayer Format objects.
@@ -677,12 +773,20 @@
     if (identical(this, other)) {
       return true;
     }
-    return _deepEquals(encode(), other.encode());
+    return _deepEquals(groupIndex, other.groupIndex) &&
+        _deepEquals(trackIndex, other.trackIndex) &&
+        _deepEquals(label, other.label) &&
+        _deepEquals(isSelected, other.isSelected) &&
+        _deepEquals(bitrate, other.bitrate) &&
+        _deepEquals(width, other.width) &&
+        _deepEquals(height, other.height) &&
+        _deepEquals(frameRate, other.frameRate) &&
+        _deepEquals(codec, other.codec);
   }
 
   @override
   // ignore: avoid_equals_and_hash_code_on_mutable_classes
-  int get hashCode => Object.hashAll(_toList());
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
 }
 
 /// Container for raw video track data from Android ExoPlayer.
@@ -716,12 +820,12 @@
     if (identical(this, other)) {
       return true;
     }
-    return _deepEquals(encode(), other.encode());
+    return _deepEquals(exoPlayerTracks, other.exoPlayerTracks);
   }
 
   @override
   // ignore: avoid_equals_and_hash_code_on_mutable_classes
-  int get hashCode => Object.hashAll(_toList());
+  int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
 }
 
 class _PigeonCodec extends StandardMessageCodec {
@@ -854,17 +958,8 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else {
-      return;
-    }
+
+    _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true);
   }
 
   Future<int> createForPlatformView(CreationOptions options) async {
@@ -877,22 +972,13 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[options]);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else if (pigeonVar_replyList[0] == null) {
-      throw PlatformException(
-        code: 'null-error',
-        message: 'Host platform returned null value for non-null return value.',
-      );
-    } else {
-      return (pigeonVar_replyList[0] as int?)!;
-    }
+
+    final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
+      pigeonVar_replyList,
+      pigeonVar_channelName,
+      isNullValid: false,
+    );
+    return pigeonVar_replyValue! as int;
   }
 
   Future<TexturePlayerIds> createForTextureView(CreationOptions options) async {
@@ -905,22 +991,13 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[options]);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else if (pigeonVar_replyList[0] == null) {
-      throw PlatformException(
-        code: 'null-error',
-        message: 'Host platform returned null value for non-null return value.',
-      );
-    } else {
-      return (pigeonVar_replyList[0] as TexturePlayerIds?)!;
-    }
+
+    final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
+      pigeonVar_replyList,
+      pigeonVar_channelName,
+      isNullValid: false,
+    );
+    return pigeonVar_replyValue! as TexturePlayerIds;
   }
 
   Future<void> dispose(int playerId) async {
@@ -933,17 +1010,8 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[playerId]);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else {
-      return;
-    }
+
+    _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true);
   }
 
   Future<void> setMixWithOthers(bool mixWithOthers) async {
@@ -956,17 +1024,8 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[mixWithOthers]);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else {
-      return;
-    }
+
+    _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true);
   }
 
   Future<String> getLookupKeyForAsset(String asset, String? packageName) async {
@@ -982,22 +1041,13 @@
       packageName,
     ]);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else if (pigeonVar_replyList[0] == null) {
-      throw PlatformException(
-        code: 'null-error',
-        message: 'Host platform returned null value for non-null return value.',
-      );
-    } else {
-      return (pigeonVar_replyList[0] as String?)!;
-    }
+
+    final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
+      pigeonVar_replyList,
+      pigeonVar_channelName,
+      isNullValid: false,
+    );
+    return pigeonVar_replyValue! as String;
   }
 }
 
@@ -1027,17 +1077,8 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[looping]);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else {
-      return;
-    }
+
+    _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true);
   }
 
   /// Sets the volume, with 0.0 being muted and 1.0 being full volume.
@@ -1051,17 +1092,8 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[volume]);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else {
-      return;
-    }
+
+    _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true);
   }
 
   /// Sets the playback speed as a multiple of normal speed.
@@ -1075,17 +1107,8 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[speed]);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else {
-      return;
-    }
+
+    _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true);
   }
 
   /// Begins playback if the video is not currently playing.
@@ -1099,17 +1122,8 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else {
-      return;
-    }
+
+    _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true);
   }
 
   /// Pauses playback if the video is currently playing.
@@ -1123,17 +1137,8 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else {
-      return;
-    }
+
+    _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true);
   }
 
   /// Seeks to the given playback position, in milliseconds.
@@ -1147,17 +1152,8 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[position]);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else {
-      return;
-    }
+
+    _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true);
   }
 
   /// Returns the current playback position, in milliseconds.
@@ -1171,22 +1167,13 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else if (pigeonVar_replyList[0] == null) {
-      throw PlatformException(
-        code: 'null-error',
-        message: 'Host platform returned null value for non-null return value.',
-      );
-    } else {
-      return (pigeonVar_replyList[0] as int?)!;
-    }
+
+    final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
+      pigeonVar_replyList,
+      pigeonVar_channelName,
+      isNullValid: false,
+    );
+    return pigeonVar_replyValue! as int;
   }
 
   /// Returns the current buffer position, in milliseconds.
@@ -1200,22 +1187,13 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else if (pigeonVar_replyList[0] == null) {
-      throw PlatformException(
-        code: 'null-error',
-        message: 'Host platform returned null value for non-null return value.',
-      );
-    } else {
-      return (pigeonVar_replyList[0] as int?)!;
-    }
+
+    final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
+      pigeonVar_replyList,
+      pigeonVar_channelName,
+      isNullValid: false,
+    );
+    return pigeonVar_replyValue! as int;
   }
 
   /// Gets the available audio tracks for the video.
@@ -1229,22 +1207,13 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else if (pigeonVar_replyList[0] == null) {
-      throw PlatformException(
-        code: 'null-error',
-        message: 'Host platform returned null value for non-null return value.',
-      );
-    } else {
-      return (pigeonVar_replyList[0] as NativeAudioTrackData?)!;
-    }
+
+    final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
+      pigeonVar_replyList,
+      pigeonVar_channelName,
+      isNullValid: false,
+    );
+    return pigeonVar_replyValue! as NativeAudioTrackData;
   }
 
   /// Selects which audio track is chosen for playback from its [groupIndex] and [trackIndex]
@@ -1261,17 +1230,8 @@
       trackIndex,
     ]);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else {
-      return;
-    }
+
+    _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true);
   }
 
   /// Gets the available video tracks for the video.
@@ -1285,22 +1245,13 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else if (pigeonVar_replyList[0] == null) {
-      throw PlatformException(
-        code: 'null-error',
-        message: 'Host platform returned null value for non-null return value.',
-      );
-    } else {
-      return (pigeonVar_replyList[0] as NativeVideoTrackData?)!;
-    }
+
+    final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
+      pigeonVar_replyList,
+      pigeonVar_channelName,
+      isNullValid: false,
+    );
+    return pigeonVar_replyValue! as NativeVideoTrackData;
   }
 
   /// Selects which video track is chosen for playback from its [groupIndex] and [trackIndex].
@@ -1317,17 +1268,8 @@
       trackIndex,
     ]);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else {
-      return;
-    }
+
+    _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true);
   }
 
   /// Enables automatic video quality selection, allowing the player to adaptively
@@ -1342,17 +1284,8 @@
     );
     final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
     final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
-    if (pigeonVar_replyList == null) {
-      throw _createConnectionError(pigeonVar_channelName);
-    } else if (pigeonVar_replyList.length > 1) {
-      throw PlatformException(
-        code: pigeonVar_replyList[0]! as String,
-        message: pigeonVar_replyList[1] as String?,
-        details: pigeonVar_replyList[2],
-      );
-    } else {
-      return;
-    }
+
+    _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true);
   }
 }
 
diff --git a/packages/video_player/video_player_android/pigeons/messages.dart b/packages/video_player/video_player_android/pigeons/messages.dart
index c6ed0f4..75a82aa 100644
--- a/packages/video_player/video_player_android/pigeons/messages.dart
+++ b/packages/video_player/video_player_android/pigeons/messages.dart
@@ -82,6 +82,7 @@
   PlatformVideoFormat? formatHint;
   Map<String, String> httpHeaders;
   String? userAgent;
+  int? backBufferDurationMs;
 }
 
 class TexturePlayerIds {
diff --git a/packages/video_player/video_player_android/pubspec.yaml b/packages/video_player/video_player_android/pubspec.yaml
index a86cfe5..11e0efc 100644
--- a/packages/video_player/video_player_android/pubspec.yaml
+++ b/packages/video_player/video_player_android/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Android implementation of the video_player plugin.
 repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
-version: 2.10.0
+version: 2.11.0
 
 environment:
   sdk: ^3.12.0
@@ -20,14 +20,15 @@
 dependencies:
   flutter:
     sdk: flutter
-  video_player_platform_interface: ^6.7.0
+  meta: ^1.10.0
+  video_player_platform_interface: ^6.9.0
 
 dev_dependencies:
   build_runner: ^2.3.3
   flutter_test:
     sdk: flutter
   mockito: ^5.4.4
-  pigeon: ^26.1.5
+  pigeon: ^26.3.4
 
 topics:
   - video
diff --git a/packages/video_player/video_player_android/test/android_video_player_test.dart b/packages/video_player/video_player_android/test/android_video_player_test.dart
index 6fe80ac..3a09bb0 100644
--- a/packages/video_player/video_player_android/test/android_video_player_test.dart
+++ b/packages/video_player/video_player_android/test/android_video_player_test.dart
@@ -426,6 +426,47 @@
       );
     });
 
+    test('createWithOptions passes backBufferDurationMs for texture view', () async {
+      final (AndroidVideoPlayer player, MockAndroidVideoPlayerApi api, _) = setUpMockPlayer(
+        playerId: 1,
+        textureId: 100,
+      );
+      when(
+        api.createForTextureView(any),
+      ).thenAnswer((_) async => TexturePlayerIds(playerId: 2, textureId: 100));
+
+      await player.createWithOptions(
+        VideoCreationOptions(
+          dataSource: DataSource(sourceType: DataSourceType.network, uri: 'https://example.com'),
+          viewType: VideoViewType.textureView,
+          videoPlayerOptions: VideoPlayerOptions(backBufferDurationMs: 20000),
+        ),
+      );
+
+      final VerificationResult verification = verify(api.createForTextureView(captureAny));
+      final creationOptions = verification.captured[0] as CreationOptions;
+      expect(creationOptions.backBufferDurationMs, 20000);
+    });
+
+    test('createWithOptions passes backBufferDurationMs for platform view', () async {
+      final (AndroidVideoPlayer player, MockAndroidVideoPlayerApi api, _) = setUpMockPlayer(
+        playerId: 1,
+      );
+      when(api.createForPlatformView(any)).thenAnswer((_) async => 2);
+
+      await player.createWithOptions(
+        VideoCreationOptions(
+          dataSource: DataSource(sourceType: DataSourceType.network, uri: 'https://example.com'),
+          viewType: VideoViewType.platformView,
+          videoPlayerOptions: VideoPlayerOptions(backBufferDurationMs: 20000),
+        ),
+      );
+
+      final VerificationResult verification = verify(api.createForPlatformView(captureAny));
+      final creationOptions = verification.captured[0] as CreationOptions;
+      expect(creationOptions.backBufferDurationMs, 20000);
+    });
+
     test('setLooping', () async {
       final (AndroidVideoPlayer player, _, MockVideoPlayerInstanceApi playerApi) = setUpMockPlayer(
         playerId: 1,