diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md index 1d56131e8bb2..470b40a07766 100644 --- a/packages/video_player/video_player_android/CHANGELOG.md +++ b/packages/video_player/video_player_android/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.10.0 + +* Adds adaptive bitrate streaming support with `setBandwidthLimit` for HLS/DASH content. +* Configures ExoPlayer with `AdaptiveTrackSelection.Factory` and `DefaultBandwidthMeter` for optimized ABR. +* Uses `TextureView` in platform view mode for seamless resolution switching during quality transitions. + ## 2.9.4 * Updates `androidx.media3` to 1.9.2. diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java index 7cfb5c1c13be..bc6f2d114012 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java @@ -7,6 +7,7 @@ import static androidx.media3.common.Player.REPEAT_MODE_ALL; import static androidx.media3.common.Player.REPEAT_MODE_OFF; +import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.media3.common.AudioAttributes; @@ -19,7 +20,9 @@ import androidx.media3.common.Tracks; import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.exoplayer.trackselection.AdaptiveTrackSelection; import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; +import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter; import io.flutter.view.TextureRegistry.SurfaceProducer; import java.util.ArrayList; import java.util.List; @@ -93,6 +96,45 @@ private static void setAudioAttributes(ExoPlayer exoPlayer, boolean isMixMode) { !isMixMode); } + /** + * Creates an {@link ExoPlayer} configured for adaptive bitrate streaming. + * + *

Configures {@link AdaptiveTrackSelection.Factory} and {@link DefaultBandwidthMeter} so + * ExoPlayer selects video tracks based on measured network bandwidth. + * + * @param context application context. + * @param asset video asset providing the media source factory. + * @return a configured ExoPlayer instance. + */ + // TODO: Migrate to stable API, see https://github.com/flutter/flutter/issues/147039. + @UnstableApi + @NonNull + public static ExoPlayer createAbrExoPlayer( + @NonNull Context context, @NonNull VideoAsset asset) { + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(context) + .setInitialBitrateEstimate(1_000_000) + .build(); + + DefaultTrackSelector trackSelector = + new DefaultTrackSelector(context, new AdaptiveTrackSelection.Factory()); + + trackSelector.setParameters( + trackSelector + .buildUponParameters() + .setAllowVideoNonSeamlessAdaptiveness(true) + .setAllowVideoMixedDecoderSupportAdaptiveness(true) + .setForceLowestBitrate(false) + .setForceHighestSupportedBitrate(false) + .build()); + + return new ExoPlayer.Builder(context) + .setTrackSelector(trackSelector) + .setBandwidthMeter(bandwidthMeter) + .setMediaSourceFactory(asset.getMediaSourceFactory(context)) + .build(); + } + @Override public void play() { exoPlayer.play(); @@ -233,6 +275,28 @@ public void selectAudioTrack(long groupIndex, long trackIndex) { trackSelector.buildUponParameters().setOverrideForType(override).build()); } + // TODO: Migrate to stable API, see https://github.com/flutter/flutter/issues/147039. + @UnstableApi + @Override + public void setBandwidthLimit(long maxBandwidthBps) { + if (trackSelector == null) { + return; + } + + DefaultTrackSelector.Parameters.Builder parametersBuilder = + trackSelector.buildUponParameters(); + + if (maxBandwidthBps <= 0) { + parametersBuilder + .setMaxVideoBitrate(Integer.MAX_VALUE); + } else { + int maxBitrate = maxBandwidthBps > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) maxBandwidthBps; + parametersBuilder.setMaxVideoBitrate(maxBitrate); + } + + trackSelector.setParameters(parametersBuilder.build()); + } + public void dispose() { if (disposeHandler != null) { disposeHandler.onDispose(); diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformVideoView.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformVideoView.java index 35fd66283231..4fbfc21f9d70 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformVideoView.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformVideoView.java @@ -5,22 +5,27 @@ package io.flutter.plugins.videoplayer.platformview; import android.content.Context; -import android.os.Build; -import android.view.SurfaceHolder; -import android.view.SurfaceView; +import android.graphics.SurfaceTexture; +import android.view.Surface; +import android.view.TextureView; import android.view.View; import androidx.annotation.NonNull; -import androidx.annotation.OptIn; import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.ExoPlayer; import io.flutter.plugin.platform.PlatformView; /** - * A class used to create a native video view that can be embedded in a Flutter app. It wraps an - * {@link ExoPlayer} instance and displays its video content. + * A {@link PlatformView} that displays video content using a {@link TextureView}. + * + *

TextureView is used instead of SurfaceView to support seamless resolution changes during + * adaptive bitrate streaming (HLS/DASH). SurfaceView operates on a separate window layer which + * can cause visual artifacts when the video resolution changes mid-playback. */ +@UnstableApi public final class PlatformVideoView implements PlatformView { - @NonNull private final SurfaceView surfaceView; + @NonNull private final TextureView textureView; + @NonNull private final ExoPlayer exoPlayer; + private Surface surface; /** * Constructs a new PlatformVideoView. @@ -28,67 +33,62 @@ public final class PlatformVideoView implements PlatformView { * @param context The context in which the view is running. * @param exoPlayer The ExoPlayer instance used to play the video. */ - @OptIn(markerClass = UnstableApi.class) public PlatformVideoView(@NonNull Context context, @NonNull ExoPlayer exoPlayer) { - surfaceView = new SurfaceView(context); + this.exoPlayer = exoPlayer; + this.textureView = new TextureView(context); - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) { - // Workaround for rendering issues on Android 9 (API 28). - // On Android 9, using setVideoSurfaceView seems to lead to issues where the first frame is - // not displayed if the video is paused initially. - // To ensure the first frame is visible, the surface is directly set using holder.getSurface() - // when the surface is created, and ExoPlayer seeks to a position to force rendering of the - // first frame. - setupSurfaceWithCallback(exoPlayer); - } else { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) { - // Avoid blank space instead of a video on Android versions below 8 by adjusting video's - // z-layer within the Android view hierarchy: - surfaceView.setZOrderMediaOverlay(true); - } - exoPlayer.setVideoSurfaceView(surfaceView); - } - } + textureView.setSurfaceTextureListener( + new TextureView.SurfaceTextureListener() { + @Override + public void onSurfaceTextureAvailable( + @NonNull SurfaceTexture surfaceTexture, int width, int height) { + surface = new Surface(surfaceTexture); + exoPlayer.setVideoSurface(surface); + } - private void setupSurfaceWithCallback(@NonNull ExoPlayer exoPlayer) { - surfaceView - .getHolder() - .addCallback( - new SurfaceHolder.Callback() { - @Override - public void surfaceCreated(@NonNull SurfaceHolder holder) { - exoPlayer.setVideoSurface(holder.getSurface()); - // Force first frame rendering: - exoPlayer.seekTo(1); - } + @Override + public void onSurfaceTextureSizeChanged( + @NonNull SurfaceTexture surfaceTexture, int width, int height) { + // No-op: ExoPlayer handles resolution changes during adaptive bitrate streaming. + // The MediaCodec decoder seamlessly adapts to the new resolution. + } - @Override - public void surfaceChanged( - @NonNull SurfaceHolder holder, int format, int width, int height) { - // No implementation needed. - } + @Override + public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) { + exoPlayer.setVideoSurface(null); + if (surface != null) { + surface.release(); + surface = null; + } + return true; + } - @Override - public void surfaceDestroyed(@NonNull SurfaceHolder holder) { - exoPlayer.setVideoSurface(null); - } - }); + @Override + public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) { + // No-op. + } + }); } /** * Returns the view associated with this PlatformView. * - * @return The SurfaceView used to display the video. + * @return The TextureView used to display the video. */ @NonNull @Override public View getView() { - return surfaceView; + return textureView; } /** Disposes of the resources used by this PlatformView. */ @Override public void dispose() { - surfaceView.getHolder().getSurface().release(); + textureView.setSurfaceTextureListener(null); + exoPlayer.setVideoSurface(null); + if (surface != null) { + surface.release(); + surface = null; + } } } diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewExoPlayerEventListener.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewExoPlayerEventListener.java index 699eab6eb0c1..389a98669d4d 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewExoPlayerEventListener.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewExoPlayerEventListener.java @@ -7,13 +7,14 @@ import androidx.annotation.NonNull; import androidx.annotation.OptIn; import androidx.media3.common.Format; +import androidx.media3.common.VideoSize; import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.ExoPlayer; import io.flutter.plugins.videoplayer.ExoPlayerEventListener; import io.flutter.plugins.videoplayer.VideoPlayerCallbacks; -import java.util.Objects; public final class PlatformViewExoPlayerEventListener extends ExoPlayerEventListener { + public PlatformViewExoPlayerEventListener( @NonNull ExoPlayer exoPlayer, @NonNull VideoPlayerCallbacks events) { super(exoPlayer, events); @@ -25,8 +26,15 @@ protected void sendInitialized() { // We can't rely on VideoSize here, because at this point it is not available - the platform // view was not created yet. We use the video format instead. Format videoFormat = exoPlayer.getVideoFormat(); + if (videoFormat == null) { + // Format not yet available; report 0×0 so the player can still initialize. + // The native view will display at the correct size once layout occurs. + events.onInitialized(0, 0, exoPlayer.getDuration(), 0); + return; + } + RotationDegrees rotationCorrection = - RotationDegrees.fromDegrees(Objects.requireNonNull(videoFormat).rotationDegrees); + RotationDegrees.fromDegrees(videoFormat.rotationDegrees); int width = videoFormat.width; int height = videoFormat.height; @@ -36,10 +44,16 @@ protected void sendInitialized() { || rotationCorrection == RotationDegrees.ROTATE_270) { width = videoFormat.height; height = videoFormat.width; - rotationCorrection = RotationDegrees.fromDegrees(0); } events.onInitialized(width, height, exoPlayer.getDuration(), rotationCorrection.getDegrees()); } + + @Override + public void onVideoSizeChanged(@NonNull VideoSize videoSize) { + // No-op: during adaptive bitrate quality transitions the resolution changes + // but we should not re-report initialization to Flutter. The native view + // and ExoPlayer handle the resize seamlessly. + } } 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 a7c079773b58..a50ce15bfeba 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 @@ -51,19 +51,14 @@ public static PlatformViewVideoPlayer create( @NonNull VideoPlayerCallbacks events, @NonNull VideoAsset asset, @NonNull VideoPlayerOptions options) { - return new PlatformViewVideoPlayer( + + PlatformViewVideoPlayer player = new PlatformViewVideoPlayer( events, asset.getMediaItem(), options, - () -> { - 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)); - return builder.build(); - }); + () -> VideoPlayer.createAbrExoPlayer(context, asset)); + + return player; } @NonNull diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureExoPlayerEventListener.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureExoPlayerEventListener.java index bcc901b7218f..1dd22f6104c5 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureExoPlayerEventListener.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureExoPlayerEventListener.java @@ -8,6 +8,7 @@ import androidx.annotation.OptIn; import androidx.media3.common.Format; import androidx.media3.common.VideoSize; +import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.ExoPlayer; import io.flutter.plugins.videoplayer.ExoPlayerEventListener; import io.flutter.plugins.videoplayer.VideoPlayerCallbacks; @@ -51,7 +52,14 @@ protected void sendInitialized() { events.onInitialized(width, height, exoPlayer.getDuration(), rotationCorrection.getDegrees()); } - @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) + @Override + public void onVideoSizeChanged(@NonNull VideoSize videoSize) { + // No-op: during adaptive bitrate quality transitions the resolution changes + // but we should not re-report initialization to Flutter. The texture surface + // automatically adapts to new dimensions. + } + + @OptIn(markerClass = UnstableApi.class) // A video's Format and its rotation degrees are unstable because they are not guaranteed // the same implementation across API versions. It is possible that this logic may need // revisiting should the implementation change across versions of the Exoplayer API. 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 e482bdd85020..e431653c4386 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 @@ -30,6 +30,7 @@ public final class TextureVideoPlayer extends VideoPlayer implements SurfaceProducer.Callback { // True when the ExoPlayer instance has a null surface. private boolean needsSurface = true; + /** * Creates a texture video player. * @@ -49,20 +50,15 @@ public static TextureVideoPlayer create( @NonNull SurfaceProducer surfaceProducer, @NonNull VideoAsset asset, @NonNull VideoPlayerOptions options) { - return new TextureVideoPlayer( + + TextureVideoPlayer player = new TextureVideoPlayer( events, surfaceProducer, asset.getMediaItem(), options, - () -> { - 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)); - return builder.build(); - }); + () -> VideoPlayer.createAbrExoPlayer(context, asset)); + + return player; } // TODO: Migrate to stable API, see https://github.com/flutter/flutter/issues/147039. 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 e546c744e561..0f393df6351d 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.1.7), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") @@ -12,11 +12,10 @@ import io.flutter.plugin.common.BasicMessageChannel import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MessageCodec -import io.flutter.plugin.common.StandardMessageCodec import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec import java.io.ByteArrayOutputStream import java.nio.ByteBuffer - private object MessagesPigeonUtils { fun wrapResult(result: Any?): List { @@ -25,53 +24,61 @@ private object MessagesPigeonUtils { fun wrapError(exception: Throwable): List { return if (exception is FlutterError) { - listOf(exception.code, exception.message, exception.details) + listOf( + exception.code, + exception.message, + exception.details + ) } else { listOf( - exception.javaClass.simpleName, - exception.toString(), - "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)) + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) } } - fun deepEquals(a: Any?, b: Any?): Boolean { if (a is ByteArray && b is ByteArray) { - return a.contentEquals(b) + return a.contentEquals(b) } if (a is IntArray && b is IntArray) { - return a.contentEquals(b) + return a.contentEquals(b) } if (a is LongArray && b is LongArray) { - return a.contentEquals(b) + return a.contentEquals(b) } if (a is DoubleArray && b is DoubleArray) { - return a.contentEquals(b) + return a.contentEquals(b) } if (a is Array<*> && b is Array<*>) { - return a.size == b.size && a.indices.all { deepEquals(a[it], b[it]) } + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } } if (a is List<*> && b is List<*>) { - return a.size == b.size && a.indices.all { deepEquals(a[it], b[it]) } + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } } if (a is Map<*, *> && b is Map<*, *>) { - return a.size == b.size && - a.all { (b as Map).contains(it.key) && deepEquals(it.value, b[it.key]) } + return a.size == b.size && a.all { + (b as Map).contains(it.key) && + deepEquals(it.value, b[it.key]) + } } return a == b } + } /** * Error class for passing custom error details to Flutter via a thrown PlatformException. - * * @property code The error code. * @property message The error message. * @property details The error details. Must be a datatype supported by the api codec. */ -class FlutterError( - val code: String, - override val message: String? = null, - val details: Any? = null +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null ) : Throwable() /** Pigeon equivalent of video_platform_interface's VideoFormat. */ @@ -106,25 +113,26 @@ enum class PlatformPlaybackState(val raw: Int) { } /** - * Generated class from Pigeon that represents data sent in messages. This class should not be - * extended by any user class outside of the generated file. + * Generated class from Pigeon that represents data sent in messages. + * This class should not be extended by any user class outside of the generated file. */ -sealed class PlatformVideoEvent +sealed class PlatformVideoEvent /** * Sent when the video is initialized and ready to play. * * Generated class from Pigeon that represents data sent in messages. */ -data class InitializationEvent( - /** The video duration in milliseconds. */ - val duration: Long, - /** The width of the video in pixels. */ - val width: Long, - /** The height of the video in pixels. */ - val height: Long, - /** The rotation that should be applied during playback. */ - val rotationCorrection: Long -) : PlatformVideoEvent() { +data class InitializationEvent ( + /** The video duration in milliseconds. */ + val duration: Long, + /** The width of the video in pixels. */ + val width: Long, + /** The height of the video in pixels. */ + val height: Long, + /** The rotation that should be applied during playback. */ + val rotationCorrection: Long +) : PlatformVideoEvent() + { companion object { fun fromList(pigeonVar_list: List): InitializationEvent { val duration = pigeonVar_list[0] as Long @@ -134,16 +142,14 @@ data class InitializationEvent( return InitializationEvent(duration, width, height, rotationCorrection) } } - fun toList(): List { return listOf( - duration, - width, - height, - rotationCorrection, + duration, + width, + height, + rotationCorrection, ) } - override fun equals(other: Any?): Boolean { if (other !is InitializationEvent) { return false @@ -151,8 +157,7 @@ data class InitializationEvent( if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } @@ -164,20 +169,21 @@ data class InitializationEvent( * * Generated class from Pigeon that represents data sent in messages. */ -data class PlaybackStateChangeEvent(val state: PlatformPlaybackState) : PlatformVideoEvent() { +data class PlaybackStateChangeEvent ( + val state: PlatformPlaybackState +) : PlatformVideoEvent() + { companion object { fun fromList(pigeonVar_list: List): PlaybackStateChangeEvent { val state = pigeonVar_list[0] as PlatformPlaybackState return PlaybackStateChangeEvent(state) } } - fun toList(): List { return listOf( - state, + state, ) } - override fun equals(other: Any?): Boolean { if (other !is PlaybackStateChangeEvent) { return false @@ -185,8 +191,7 @@ data class PlaybackStateChangeEvent(val state: PlatformPlaybackState) : Platform if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } @@ -198,20 +203,21 @@ data class PlaybackStateChangeEvent(val state: PlatformPlaybackState) : Platform * * Generated class from Pigeon that represents data sent in messages. */ -data class IsPlayingStateEvent(val isPlaying: Boolean) : PlatformVideoEvent() { +data class IsPlayingStateEvent ( + val isPlaying: Boolean +) : PlatformVideoEvent() + { companion object { fun fromList(pigeonVar_list: List): IsPlayingStateEvent { val isPlaying = pigeonVar_list[0] as Boolean return IsPlayingStateEvent(isPlaying) } } - fun toList(): List { return listOf( - isPlaying, + isPlaying, ) } - override fun equals(other: Any?): Boolean { if (other !is IsPlayingStateEvent) { return false @@ -219,8 +225,7 @@ data class IsPlayingStateEvent(val isPlaying: Boolean) : PlatformVideoEvent() { if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } @@ -228,28 +233,27 @@ data class IsPlayingStateEvent(val isPlaying: Boolean) : PlatformVideoEvent() { /** * Sent when audio tracks change. * - * This includes when the selected audio track changes after calling selectAudioTrack. Corresponds - * to ExoPlayer's onTracksChanged. + * This includes when the selected audio track changes after calling selectAudioTrack. + * Corresponds to ExoPlayer's onTracksChanged. * * Generated class from Pigeon that represents data sent in messages. */ -data class AudioTrackChangedEvent( - /** The ID of the newly selected audio track, if any. */ - val selectedTrackId: String? = null -) : PlatformVideoEvent() { +data class AudioTrackChangedEvent ( + /** The ID of the newly selected audio track, if any. */ + val selectedTrackId: String? = null +) : PlatformVideoEvent() + { companion object { fun fromList(pigeonVar_list: List): AudioTrackChangedEvent { val selectedTrackId = pigeonVar_list[0] as String? return AudioTrackChangedEvent(selectedTrackId) } } - fun toList(): List { return listOf( - selectedTrackId, + selectedTrackId, ) } - override fun equals(other: Any?): Boolean { if (other !is AudioTrackChangedEvent) { return false @@ -257,8 +261,7 @@ data class AudioTrackChangedEvent( if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } @@ -268,20 +271,21 @@ data class AudioTrackChangedEvent( * * Generated class from Pigeon that represents data sent in messages. */ -data class PlatformVideoViewCreationParams(val playerId: Long) { +data class PlatformVideoViewCreationParams ( + val playerId: Long +) + { companion object { fun fromList(pigeonVar_list: List): PlatformVideoViewCreationParams { val playerId = pigeonVar_list[0] as Long return PlatformVideoViewCreationParams(playerId) } } - fun toList(): List { return listOf( - playerId, + playerId, ) } - override fun equals(other: Any?): Boolean { if (other !is PlatformVideoViewCreationParams) { return false @@ -289,19 +293,19 @@ data class PlatformVideoViewCreationParams(val playerId: Long) { if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } /** Generated class from Pigeon that represents data sent in messages. */ -data class CreationOptions( - val uri: String, - val formatHint: PlatformVideoFormat? = null, - val httpHeaders: Map, - val userAgent: String? = null -) { +data class CreationOptions ( + val uri: String, + val formatHint: PlatformVideoFormat? = null, + val httpHeaders: Map, + val userAgent: String? = null +) + { companion object { fun fromList(pigeonVar_list: List): CreationOptions { val uri = pigeonVar_list[0] as String @@ -311,16 +315,14 @@ data class CreationOptions( return CreationOptions(uri, formatHint, httpHeaders, userAgent) } } - fun toList(): List { return listOf( - uri, - formatHint, - httpHeaders, - userAgent, + uri, + formatHint, + httpHeaders, + userAgent, ) } - override fun equals(other: Any?): Boolean { if (other !is CreationOptions) { return false @@ -328,14 +330,17 @@ data class CreationOptions( if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } /** Generated class from Pigeon that represents data sent in messages. */ -data class TexturePlayerIds(val playerId: Long, val textureId: Long) { +data class TexturePlayerIds ( + val playerId: Long, + val textureId: Long +) + { companion object { fun fromList(pigeonVar_list: List): TexturePlayerIds { val playerId = pigeonVar_list[0] as Long @@ -343,14 +348,12 @@ data class TexturePlayerIds(val playerId: Long, val textureId: Long) { return TexturePlayerIds(playerId, textureId) } } - fun toList(): List { return listOf( - playerId, - textureId, + playerId, + textureId, ) } - override fun equals(other: Any?): Boolean { if (other !is TexturePlayerIds) { return false @@ -358,19 +361,19 @@ data class TexturePlayerIds(val playerId: Long, val textureId: Long) { if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } /** Generated class from Pigeon that represents data sent in messages. */ -data class PlaybackState( - /** The current playback position, in milliseconds. */ - val playPosition: Long, - /** The current buffer position, in milliseconds. */ - val bufferPosition: Long -) { +data class PlaybackState ( + /** The current playback position, in milliseconds. */ + val playPosition: Long, + /** The current buffer position, in milliseconds. */ + val bufferPosition: Long +) + { companion object { fun fromList(pigeonVar_list: List): PlaybackState { val playPosition = pigeonVar_list[0] as Long @@ -378,14 +381,12 @@ data class PlaybackState( return PlaybackState(playPosition, bufferPosition) } } - fun toList(): List { return listOf( - playPosition, - bufferPosition, + playPosition, + bufferPosition, ) } - override fun equals(other: Any?): Boolean { if (other !is PlaybackState) { return false @@ -393,8 +394,7 @@ data class PlaybackState( if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } @@ -404,16 +404,17 @@ data class PlaybackState( * * Generated class from Pigeon that represents data sent in messages. */ -data class AudioTrackMessage( - val id: String, - val label: String, - val language: String, - val isSelected: Boolean, - val bitrate: Long? = null, - val sampleRate: Long? = null, - val channelCount: Long? = null, - val codec: String? = null -) { +data class AudioTrackMessage ( + val id: String, + val label: String, + val language: String, + val isSelected: Boolean, + val bitrate: Long? = null, + val sampleRate: Long? = null, + val channelCount: Long? = null, + val codec: String? = null +) + { companion object { fun fromList(pigeonVar_list: List): AudioTrackMessage { val id = pigeonVar_list[0] as String @@ -424,24 +425,21 @@ data class AudioTrackMessage( val sampleRate = pigeonVar_list[5] as Long? val channelCount = pigeonVar_list[6] as Long? val codec = pigeonVar_list[7] as String? - return AudioTrackMessage( - id, label, language, isSelected, bitrate, sampleRate, channelCount, codec) + return AudioTrackMessage(id, label, language, isSelected, bitrate, sampleRate, channelCount, codec) } } - fun toList(): List { return listOf( - id, - label, - language, - isSelected, - bitrate, - sampleRate, - channelCount, - codec, + id, + label, + language, + isSelected, + bitrate, + sampleRate, + channelCount, + codec, ) } - override fun equals(other: Any?): Boolean { if (other !is AudioTrackMessage) { return false @@ -449,8 +447,7 @@ data class AudioTrackMessage( if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } @@ -460,17 +457,18 @@ data class AudioTrackMessage( * * Generated class from Pigeon that represents data sent in messages. */ -data class ExoPlayerAudioTrackData( - val groupIndex: Long, - val trackIndex: Long, - val label: String? = null, - val language: String? = null, - val isSelected: Boolean, - val bitrate: Long? = null, - val sampleRate: Long? = null, - val channelCount: Long? = null, - val codec: String? = null -) { +data class ExoPlayerAudioTrackData ( + val groupIndex: Long, + val trackIndex: Long, + val label: String? = null, + val language: String? = null, + val isSelected: Boolean, + val bitrate: Long? = null, + val sampleRate: Long? = null, + val channelCount: Long? = null, + val codec: String? = null +) + { companion object { fun fromList(pigeonVar_list: List): ExoPlayerAudioTrackData { val groupIndex = pigeonVar_list[0] as Long @@ -482,33 +480,22 @@ data class ExoPlayerAudioTrackData( val sampleRate = pigeonVar_list[6] as Long? val channelCount = pigeonVar_list[7] as Long? val codec = pigeonVar_list[8] as String? - return ExoPlayerAudioTrackData( - groupIndex, - trackIndex, - label, - language, - isSelected, - bitrate, - sampleRate, - channelCount, - codec) + return ExoPlayerAudioTrackData(groupIndex, trackIndex, label, language, isSelected, bitrate, sampleRate, channelCount, codec) } } - fun toList(): List { return listOf( - groupIndex, - trackIndex, - label, - language, - isSelected, - bitrate, - sampleRate, - channelCount, - codec, + groupIndex, + trackIndex, + label, + language, + isSelected, + bitrate, + sampleRate, + channelCount, + codec, ) } - override fun equals(other: Any?): Boolean { if (other !is ExoPlayerAudioTrackData) { return false @@ -516,8 +503,7 @@ data class ExoPlayerAudioTrackData( if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } @@ -527,23 +513,22 @@ data class ExoPlayerAudioTrackData( * * Generated class from Pigeon that represents data sent in messages. */ -data class NativeAudioTrackData( - /** ExoPlayer-based tracks */ - val exoPlayerTracks: List? = null -) { +data class NativeAudioTrackData ( + /** ExoPlayer-based tracks */ + val exoPlayerTracks: List? = null +) + { companion object { fun fromList(pigeonVar_list: List): NativeAudioTrackData { val exoPlayerTracks = pigeonVar_list[0] as List? return NativeAudioTrackData(exoPlayerTracks) } } - fun toList(): List { return listOf( - exoPlayerTracks, + exoPlayerTracks, ) } - override fun equals(other: Any?): Boolean { if (other !is NativeAudioTrackData) { return false @@ -551,32 +536,42 @@ data class NativeAudioTrackData( if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } - private open class MessagesPigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { return when (type) { 129.toByte() -> { - return (readValue(buffer) as Long?)?.let { PlatformVideoFormat.ofRaw(it.toInt()) } + return (readValue(buffer) as Long?)?.let { + PlatformVideoFormat.ofRaw(it.toInt()) + } } 130.toByte() -> { - return (readValue(buffer) as Long?)?.let { PlatformPlaybackState.ofRaw(it.toInt()) } + return (readValue(buffer) as Long?)?.let { + PlatformPlaybackState.ofRaw(it.toInt()) + } } 131.toByte() -> { - return (readValue(buffer) as? List)?.let { InitializationEvent.fromList(it) } + return (readValue(buffer) as? List)?.let { + InitializationEvent.fromList(it) + } } 132.toByte() -> { - return (readValue(buffer) as? List)?.let { PlaybackStateChangeEvent.fromList(it) } + return (readValue(buffer) as? List)?.let { + PlaybackStateChangeEvent.fromList(it) + } } 133.toByte() -> { - return (readValue(buffer) as? List)?.let { IsPlayingStateEvent.fromList(it) } + return (readValue(buffer) as? List)?.let { + IsPlayingStateEvent.fromList(it) + } } 134.toByte() -> { - return (readValue(buffer) as? List)?.let { AudioTrackChangedEvent.fromList(it) } + return (readValue(buffer) as? List)?.let { + AudioTrackChangedEvent.fromList(it) + } } 135.toByte() -> { return (readValue(buffer) as? List)?.let { @@ -584,28 +579,39 @@ private open class MessagesPigeonCodec : StandardMessageCodec() { } } 136.toByte() -> { - return (readValue(buffer) as? List)?.let { CreationOptions.fromList(it) } + return (readValue(buffer) as? List)?.let { + CreationOptions.fromList(it) + } } 137.toByte() -> { - return (readValue(buffer) as? List)?.let { TexturePlayerIds.fromList(it) } + return (readValue(buffer) as? List)?.let { + TexturePlayerIds.fromList(it) + } } 138.toByte() -> { - return (readValue(buffer) as? List)?.let { PlaybackState.fromList(it) } + return (readValue(buffer) as? List)?.let { + PlaybackState.fromList(it) + } } 139.toByte() -> { - return (readValue(buffer) as? List)?.let { AudioTrackMessage.fromList(it) } + return (readValue(buffer) as? List)?.let { + AudioTrackMessage.fromList(it) + } } 140.toByte() -> { - return (readValue(buffer) as? List)?.let { ExoPlayerAudioTrackData.fromList(it) } + return (readValue(buffer) as? List)?.let { + ExoPlayerAudioTrackData.fromList(it) + } } 141.toByte() -> { - return (readValue(buffer) as? List)?.let { NativeAudioTrackData.fromList(it) } + return (readValue(buffer) as? List)?.let { + NativeAudioTrackData.fromList(it) + } } else -> super.readValueOfType(type, buffer) } } - - override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { when (value) { is PlatformVideoFormat -> { stream.write(129) @@ -669,47 +675,31 @@ val MessagesPigeonMethodCodec = StandardMethodCodec(MessagesPigeonCodec()) /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ interface AndroidVideoPlayerApi { fun initialize() - fun createForPlatformView(options: CreationOptions): Long - fun createForTextureView(options: CreationOptions): TexturePlayerIds - fun dispose(playerId: Long) - fun setMixWithOthers(mixWithOthers: Boolean) - fun getLookupKeyForAsset(asset: String, packageName: String?): String companion object { /** The codec used by AndroidVideoPlayerApi. */ - val codec: MessageCodec by lazy { MessagesPigeonCodec() } - /** - * Sets up an instance of `AndroidVideoPlayerApi` to handle messages through the - * `binaryMessenger`. - */ + val codec: MessageCodec by lazy { + MessagesPigeonCodec() + } + /** Sets up an instance of `AndroidVideoPlayerApi` to handle messages through the `binaryMessenger`. */ @JvmOverloads - fun setUp( - binaryMessenger: BinaryMessenger, - api: AndroidVideoPlayerApi?, - messageChannelSuffix: String = "" - ) { - val separatedMessageChannelSuffix = - if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + fun setUp(binaryMessenger: BinaryMessenger, api: AndroidVideoPlayerApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.initialize$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.initialize$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = - try { - api.initialize() - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.initialize() + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -717,21 +707,16 @@ interface AndroidVideoPlayerApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.createForPlatformView$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.createForPlatformView$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val optionsArg = args[0] as CreationOptions - val wrapped: List = - try { - listOf(api.createForPlatformView(optionsArg)) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + listOf(api.createForPlatformView(optionsArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -739,21 +724,16 @@ interface AndroidVideoPlayerApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.createForTextureView$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.createForTextureView$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val optionsArg = args[0] as CreationOptions - val wrapped: List = - try { - listOf(api.createForTextureView(optionsArg)) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + listOf(api.createForTextureView(optionsArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -761,22 +741,17 @@ interface AndroidVideoPlayerApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.dispose$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.dispose$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val playerIdArg = args[0] as Long - val wrapped: List = - try { - api.dispose(playerIdArg) - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.dispose(playerIdArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -784,22 +759,17 @@ interface AndroidVideoPlayerApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setMixWithOthers$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setMixWithOthers$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val mixWithOthersArg = args[0] as Boolean - val wrapped: List = - try { - api.setMixWithOthers(mixWithOthersArg) - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.setMixWithOthers(mixWithOthersArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -807,22 +777,17 @@ interface AndroidVideoPlayerApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.getLookupKeyForAsset$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.getLookupKeyForAsset$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val assetArg = args[0] as String val packageNameArg = args[1] as String? - val wrapped: List = - try { - listOf(api.getLookupKeyForAsset(assetArg, packageNameArg)) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + listOf(api.getLookupKeyForAsset(assetArg, packageNameArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -854,39 +819,41 @@ interface VideoPlayerInstanceApi { fun getAudioTracks(): NativeAudioTrackData /** Selects which audio track is chosen for playback from its [groupIndex] and [trackIndex] */ fun selectAudioTrack(groupIndex: Long, trackIndex: Long) + /** + * Sets the maximum bandwidth limit in bits per second for HLS adaptive bitrate streaming. + * Pass 0 to remove any bandwidth limit and allow the player to select quality freely. + * Common values: + * - 360p: 500000 bps (500 kbps) + * - 480p: 800000 bps (800 kbps) + * - 720p: 1200000 bps (1.2 Mbps) + * - 1080p: 2500000 bps (2.5 Mbps) + * + * Note: This helps ExoPlayer's track selector choose appropriate quality variants + * for HLS streams. The player will attempt to stay within this bandwidth estimate. + */ + fun setBandwidthLimit(maxBandwidthBps: Long) companion object { /** The codec used by VideoPlayerInstanceApi. */ - val codec: MessageCodec by lazy { MessagesPigeonCodec() } - /** - * Sets up an instance of `VideoPlayerInstanceApi` to handle messages through the - * `binaryMessenger`. - */ + val codec: MessageCodec by lazy { + MessagesPigeonCodec() + } + /** Sets up an instance of `VideoPlayerInstanceApi` to handle messages through the `binaryMessenger`. */ @JvmOverloads - fun setUp( - binaryMessenger: BinaryMessenger, - api: VideoPlayerInstanceApi?, - messageChannelSuffix: String = "" - ) { - val separatedMessageChannelSuffix = - if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + fun setUp(binaryMessenger: BinaryMessenger, api: VideoPlayerInstanceApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setLooping$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setLooping$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val loopingArg = args[0] as Boolean - val wrapped: List = - try { - api.setLooping(loopingArg) - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.setLooping(loopingArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -894,22 +861,17 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setVolume$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setVolume$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val volumeArg = args[0] as Double - val wrapped: List = - try { - api.setVolume(volumeArg) - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.setVolume(volumeArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -917,22 +879,17 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setPlaybackSpeed$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setPlaybackSpeed$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val speedArg = args[0] as Double - val wrapped: List = - try { - api.setPlaybackSpeed(speedArg) - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.setPlaybackSpeed(speedArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -940,20 +897,15 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.play$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.play$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = - try { - api.play() - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.play() + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -961,20 +913,15 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.pause$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.pause$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = - try { - api.pause() - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.pause() + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -982,22 +929,17 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.seekTo$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.seekTo$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val positionArg = args[0] as Long - val wrapped: List = - try { - api.seekTo(positionArg) - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.seekTo(positionArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1005,19 +947,14 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getCurrentPosition$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getCurrentPosition$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = - try { - listOf(api.getCurrentPosition()) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + listOf(api.getCurrentPosition()) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1025,19 +962,14 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getBufferedPosition$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getBufferedPosition$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = - try { - listOf(api.getBufferedPosition()) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + listOf(api.getBufferedPosition()) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1045,19 +977,14 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getAudioTracks$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getAudioTracks$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = - try { - listOf(api.getAudioTracks()) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + listOf(api.getAudioTracks()) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1065,23 +992,36 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.selectAudioTrack$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.selectAudioTrack$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val groupIndexArg = args[0] as Long val trackIndexArg = args[1] as Long - val wrapped: List = - try { - api.selectAudioTrack(groupIndexArg, trackIndexArg) - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.selectAudioTrack(groupIndexArg, trackIndexArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setBandwidthLimit$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val maxBandwidthBpsArg = args[0] as Long + val wrapped: List = try { + api.setBandwidthLimit(maxBandwidthBpsArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1092,8 +1032,9 @@ interface VideoPlayerInstanceApi { } } -private class MessagesPigeonStreamHandler(val wrapper: MessagesPigeonEventChannelWrapper) : - EventChannel.StreamHandler { +private class MessagesPigeonStreamHandler( + val wrapper: MessagesPigeonEventChannelWrapper +) : EventChannel.StreamHandler { var pigeonSink: PigeonEventSink? = null override fun onListen(p0: Any?, sink: EventChannel.EventSink) { @@ -1126,26 +1067,21 @@ class PigeonEventSink(private val sink: EventChannel.EventSink) { sink.endOfStream() } } - + abstract class VideoEventsStreamHandler : MessagesPigeonEventChannelWrapper { companion object { - fun register( - messenger: BinaryMessenger, - streamHandler: VideoEventsStreamHandler, - instanceName: String = "" - ) { - var channelName: String = - "dev.flutter.pigeon.video_player_android.VideoEventChannel.videoEvents" + fun register(messenger: BinaryMessenger, streamHandler: VideoEventsStreamHandler, instanceName: String = "") { + var channelName: String = "dev.flutter.pigeon.video_player_android.VideoEventChannel.videoEvents" if (instanceName.isNotEmpty()) { channelName += ".$instanceName" } val internalStreamHandler = MessagesPigeonStreamHandler(streamHandler) - EventChannel(messenger, channelName, MessagesPigeonMethodCodec) - .setStreamHandler(internalStreamHandler) + EventChannel(messenger, channelName, MessagesPigeonMethodCodec).setStreamHandler(internalStreamHandler) } } - // Implement methods from MessagesPigeonEventChannelWrapper - override fun onListen(p0: Any?, sink: PigeonEventSink) {} +// Implement methods from MessagesPigeonEventChannelWrapper +override fun onListen(p0: Any?, sink: PigeonEventSink) {} - override fun onCancel(p0: Any?) {} +override fun onCancel(p0: Any?) {} } + 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 84249bd41afd..b27b38b8bfb2 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 @@ -266,6 +266,17 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { return true; } + @override + Future setBandwidthLimit(int playerId, int maxBandwidthBps) { + return _playerWith(id: playerId).setBandwidthLimit(maxBandwidthBps); + } + + @override + bool isBandwidthLimitSupportAvailable() { + // Android with ExoPlayer supports adaptive bitrate bandwidth limiting. + return true; + } + _PlayerInstance _playerWith({required int id}) { final _PlayerInstance? player = _players[id]; return player ?? (throw StateError('No active player with ID $id.')); @@ -384,6 +395,10 @@ class _PlayerInstance { } } + Future setBandwidthLimit(int maxBandwidthBps) { + return _api.setBandwidthLimit(maxBandwidthBps); + } + Future dispose() async { _isDisposed = true; _bufferPollingTimer?.cancel(); 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 1aca7dc531dd..93186880ca7c 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,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.1.7), 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 @@ -1142,6 +1142,41 @@ class VideoPlayerInstanceApi { return; } } + + /// Sets the maximum bandwidth limit in bits per second for HLS adaptive bitrate streaming. + /// Pass 0 to remove any bandwidth limit and allow the player to select quality freely. + /// Common values: + /// - 360p: 500000 bps (500 kbps) + /// - 480p: 800000 bps (800 kbps) + /// - 720p: 1200000 bps (1.2 Mbps) + /// - 1080p: 2500000 bps (2.5 Mbps) + /// + /// Note: This helps ExoPlayer's track selector choose appropriate quality variants + /// for HLS streams. The player will attempt to stay within this bandwidth estimate. + Future setBandwidthLimit(int maxBandwidthBps) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setBandwidthLimit$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [maxBandwidthBps], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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; + } + } } Stream videoEvents({String instanceName = ''}) { diff --git a/packages/video_player/video_player_android/pigeons/messages.dart b/packages/video_player/video_player_android/pigeons/messages.dart index 8666b074969a..24a6446f7c3e 100644 --- a/packages/video_player/video_player_android/pigeons/messages.dart +++ b/packages/video_player/video_player_android/pigeons/messages.dart @@ -192,6 +192,18 @@ abstract class VideoPlayerInstanceApi { /// Selects which audio track is chosen for playback from its [groupIndex] and [trackIndex] void selectAudioTrack(int groupIndex, int trackIndex); + + /// Sets the maximum bandwidth limit in bits per second for HLS adaptive bitrate streaming. + /// Pass 0 to remove any bandwidth limit and allow the player to select quality freely. + /// Common values: + /// - 360p: 500000 bps (500 kbps) + /// - 480p: 800000 bps (800 kbps) + /// - 720p: 1200000 bps (1.2 Mbps) + /// - 1080p: 2500000 bps (2.5 Mbps) + /// + /// Note: This helps ExoPlayer's track selector choose appropriate quality variants + /// for HLS streams. The player will attempt to stay within this bandwidth estimate. + void setBandwidthLimit(int maxBandwidthBps); } @EventChannelApi() diff --git a/packages/video_player/video_player_android/pubspec.yaml b/packages/video_player/video_player_android/pubspec.yaml index 359ba7466e27..2ada2a2cc9e0 100644 --- a/packages/video_player/video_player_android/pubspec.yaml +++ b/packages/video_player/video_player_android/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player_android 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.9.4 +version: 2.10.0 environment: sdk: ^3.9.0 @@ -20,7 +20,7 @@ flutter: dependencies: flutter: sdk: flutter - video_player_platform_interface: ^6.6.0 + video_player_platform_interface: ^6.7.0 dev_dependencies: build_runner: ^2.3.3 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 810a815fddf5..ec8e933f9bfc 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 @@ -950,5 +950,19 @@ void main() { verify(api.selectAudioTrack(0, 1)); }); }); + + test('setBandwidthLimit', () async { + final ( + AndroidVideoPlayer player, + _, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer( + playerId: 1, + ); + + await player.setBandwidthLimit(1, 5000000); + + verify(playerApi.setBandwidthLimit(5000000)); + }); }); } diff --git a/packages/video_player/video_player_android/test/android_video_player_test.mocks.dart b/packages/video_player/video_player_android/test/android_video_player_test.mocks.dart index 212c9bde40c1..ad489d80e753 100644 --- a/packages/video_player/video_player_android/test/android_video_player_test.mocks.dart +++ b/packages/video_player/video_player_android/test/android_video_player_test.mocks.dart @@ -22,6 +22,7 @@ import 'package:video_player_android/src/messages.g.dart' as _i2; // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class +// ignore_for_file: invalid_use_of_internal_member class _FakeTexturePlayerIds_0 extends _i1.SmartFake implements _i2.TexturePlayerIds { @@ -252,4 +253,13 @@ class MockVideoPlayerInstanceApi extends _i1.Mock returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); + + @override + _i4.Future setBandwidthLimit(int? maxBandwidthBps) => + (super.noSuchMethod( + Invocation.method(#setBandwidthLimit, [maxBandwidthBps]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); } diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md index 4b44b050047a..19cab4d95813 100644 --- a/packages/video_player/video_player_platform_interface/CHANGELOG.md +++ b/packages/video_player/video_player_platform_interface/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 6.7.0 +* Adds `setBandwidthLimit` and `isBandwidthLimitSupportAvailable` methods for adaptive bitrate streaming control. * Updates minimum supported SDK version to Flutter 3.35/Dart 3.9. ## 6.6.0 diff --git a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart index 1cec5f42c218..6ff574d72037 100644 --- a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart +++ b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart @@ -153,6 +153,45 @@ abstract class VideoPlayerPlatform extends PlatformInterface { bool isAudioTrackSupportAvailable() { return false; } + + /// Sets the maximum bandwidth limit for adaptive bitrate streaming. + /// + /// This method controls which HLS/DASH variant streams are selected by + /// limiting the maximum video bitrate the player will choose. + /// + /// [playerId] identifies the video player instance. + /// [maxBandwidthBps] is the maximum bandwidth in bits per second. + /// Pass 0 or a negative value to remove the limit and allow the player + /// to select quality freely. + /// + /// Common bandwidth values: + /// - 360p: 500000 bps (500 kbps) + /// - 480p: 800000 bps (800 kbps) + /// - 720p: 1200000 bps (1.2 Mbps) + /// - 1080p: 2500000 bps (2.5 Mbps) + /// + /// Platform-specific behavior: + /// - **Android**: Uses ExoPlayer's `DefaultTrackSelector.setMaxVideoBitrate()`. + /// - **iOS/macOS**: Sets `AVPlayerItem.preferredPeakBitRate`. + /// - **Web**: Not implemented (throws [UnimplementedError]). + Future setBandwidthLimit(int playerId, int maxBandwidthBps) { + throw UnimplementedError('setBandwidthLimit() has not been implemented.'); + } + + /// Returns whether bandwidth limit setting is supported on this platform. + /// + /// This method allows developers to query at runtime whether the current + /// platform supports setting a bandwidth limit for adaptive bitrate + /// streaming. This follows the same pattern as + /// [isAudioTrackSupportAvailable]. + /// + /// Returns `true` if [setBandwidthLimit] is supported, `false` otherwise. + /// + /// The default implementation returns `false`. Platform implementations + /// should override this to return `true` if they support bandwidth limiting. + bool isBandwidthLimitSupportAvailable() { + return false; + } } class _PlaceholderImplementation extends VideoPlayerPlatform {} diff --git a/packages/video_player/video_player_platform_interface/pubspec.yaml b/packages/video_player/video_player_platform_interface/pubspec.yaml index b39acce19665..373ddb91f1fb 100644 --- a/packages/video_player/video_player_platform_interface/pubspec.yaml +++ b/packages/video_player/video_player_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/video_player/ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 6.6.0 +version: 6.7.0 environment: sdk: ^3.9.0 diff --git a/packages/video_player/video_player_platform_interface/test/video_player_platform_interface_test.dart b/packages/video_player/video_player_platform_interface/test/video_player_platform_interface_test.dart index 2d920161ec9e..4e5105a43333 100644 --- a/packages/video_player/video_player_platform_interface/test/video_player_platform_interface_test.dart +++ b/packages/video_player/video_player_platform_interface/test/video_player_platform_interface_test.dart @@ -40,4 +40,21 @@ void main() { test('default implementation isAudioTrackSupportAvailable returns false', () { expect(initialInstance.isAudioTrackSupportAvailable(), false); }); + + test( + 'default implementation setBandwidthLimit throws unimplemented', + () async { + await expectLater( + () => initialInstance.setBandwidthLimit(1, 5000000), + throwsUnimplementedError, + ); + }, + ); + + test( + 'default implementation isBandwidthLimitSupportAvailable returns false', + () { + expect(initialInstance.isBandwidthLimitSupportAvailable(), false); + }, + ); }