[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,