diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 46f39a8d9d4a..a173b6308eed 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,6 +1,7 @@ -## 2.11.1 +## 2.12.0 * Optimizes caption retrieval with binary search. +* Adds `VideoPlayerAndroidOptions` to configure Android renderer settings during player creation. ## 2.11.0 diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index 3e66109d7feb..f6be652fa028 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -20,6 +20,7 @@ export 'package:video_player_platform_interface/video_player_platform_interface. DataSourceType, DurationRange, VideoFormat, + VideoPlayerAndroidOptions, VideoPlayerOptions, VideoPlayerWebOptions, VideoPlayerWebOptionsControls, @@ -576,6 +577,7 @@ class VideoPlayerController extends ValueNotifier { final creationOptions = platform_interface.VideoCreationOptions( dataSource: dataSourceDescription, viewType: viewType, + androidOptions: videoPlayerOptions?.androidOptions, ); if (videoPlayerOptions?.mixWithOthers != null) { diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index c55f4e823c4e..0ca9ff395cbf 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for displaying inline video with other Flutter widgets on Android, iOS, macOS and web. repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.11.1 +version: 2.12.0 environment: sdk: ^3.10.0 @@ -26,9 +26,9 @@ dependencies: flutter: sdk: flutter html: ^0.15.0 - video_player_android: ^2.9.1 + video_player_android: ^2.10.0 video_player_avfoundation: ^2.9.0 - video_player_platform_interface: ^6.6.0 + video_player_platform_interface: ^6.7.0 video_player_web: ^2.1.0 dev_dependencies: diff --git a/packages/video_player/video_player/test/video_player_test.dart b/packages/video_player/video_player/test/video_player_test.dart index 77608a45d0ac..6609ca83a27e 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -1976,14 +1976,33 @@ void main() { ); addTearDown(controller.dispose); - await controller.initialize(); - await controller.play(); - verifyPlayStateRespondsToLifecycle( - controller, - shouldPlayInBackground: false, - ); - }); + await controller.initialize(); + await controller.play(); + verifyPlayStateRespondsToLifecycle( + controller, + shouldPlayInBackground: false, + ); + }); + + test('androidOptions are forwarded during player creation', () async { + const androidOptions = VideoPlayerAndroidOptions( + enableDecoderFallback: true, + disableMediaCodecAsyncQueueing: true, + ); + final controller = VideoPlayerController.networkUrl( + _localhostUri, + videoPlayerOptions: VideoPlayerOptions(androidOptions: androidOptions), + ); + addTearDown(controller.dispose); + + await controller.initialize(); + + expect( + fakeVideoPlayerPlatform.creationOptions.single.androidOptions, + androidOptions, + ); }); + }); test('VideoProgressColors', () { const playedColor = Color.fromRGBO(0, 0, 255, 0.75); @@ -2108,6 +2127,7 @@ void main() { class FakeVideoPlayerPlatform extends VideoPlayerPlatform { Completer initialized = Completer(); List calls = []; + List creationOptions = []; List dataSources = []; List viewTypes = []; final Map> streams = @@ -2146,6 +2166,7 @@ class FakeVideoPlayerPlatform extends VideoPlayerPlatform { @override Future createWithOptions(VideoCreationOptions options) async { calls.add('createWithOptions'); + creationOptions.add(options); final stream = StreamController(); streams[nextPlayerId] = stream; if (forceInitError) { diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md index 1d56131e8bb2..d78bdc2a01b6 100644 --- a/packages/video_player/video_player_android/CHANGELOG.md +++ b/packages/video_player/video_player_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.10.0 + +* Adds per-player Android renderer options for decoder fallback and MediaCodec asynchronous queueing. + ## 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..0a20579295e0 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,8 +7,10 @@ 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.annotation.VisibleForTesting; import androidx.media3.common.AudioAttributes; import androidx.media3.common.C; import androidx.media3.common.Format; @@ -18,6 +20,7 @@ import androidx.media3.common.TrackSelectionOverride; import androidx.media3.common.Tracks; import androidx.media3.common.util.UnstableApi; +import androidx.media3.exoplayer.DefaultRenderersFactory; import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; import io.flutter.view.TextureRegistry.SurfaceProducer; @@ -87,6 +90,24 @@ public void setDisposeHandler(@Nullable DisposeHandler handler) { protected abstract ExoPlayerEventListener createExoPlayerEventListener( @NonNull ExoPlayer exoPlayer, @Nullable SurfaceProducer surfaceProducer); + @UnstableApi + protected static DefaultRenderersFactory createRenderersFactory( + @NonNull Context context, @NonNull VideoPlayerOptions options) { + final DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(context); + configureRenderersFactory(renderersFactory, options); + return renderersFactory; + } + + @VisibleForTesting + @UnstableApi + static void configureRenderersFactory( + @NonNull DefaultRenderersFactory renderersFactory, @NonNull VideoPlayerOptions options) { + renderersFactory.setEnableDecoderFallback(options.enableDecoderFallback); + if (options.disableMediaCodecAsyncQueueing) { + renderersFactory.forceDisableMediaCodecAsynchronousQueueing(); + } + } + private static void setAudioAttributes(ExoPlayer exoPlayer, boolean isMixMode) { exoPlayer.setAudioAttributes( new AudioAttributes.Builder().setContentType(C.AUDIO_CONTENT_TYPE_MOVIE).build(), 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 20f7c5d2dbab..3a3186771457 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 @@ -6,4 +6,6 @@ public class VideoPlayerOptions { public boolean mixWithOthers; + public boolean enableDecoderFallback; + public boolean disableMediaCodecAsyncQueueing; } 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 49adaf4b7b33..d5bebf964148 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 @@ -84,6 +84,7 @@ public void initialize() { @Override public long createForPlatformView(@NonNull CreationOptions options) { final VideoAsset videoAsset = videoAssetWithOptions(options); + final VideoPlayerOptions playerOptions = videoPlayerOptionsWithCreationOptions(options); long id = nextPlayerIdentifier++; final String streamInstance = Long.toString(id); @@ -92,7 +93,7 @@ public long createForPlatformView(@NonNull CreationOptions options) { flutterState.applicationContext, VideoPlayerEventCallbacks.bindTo(flutterState.binaryMessenger, streamInstance), videoAsset, - sharedOptions); + playerOptions); registerPlayerInstance(videoPlayer, id); return id; @@ -102,6 +103,7 @@ public long createForPlatformView(@NonNull CreationOptions options) { @Override public @NonNull TexturePlayerIds createForTextureView(@NonNull CreationOptions options) { final VideoAsset videoAsset = videoAssetWithOptions(options); + final VideoPlayerOptions playerOptions = videoPlayerOptionsWithCreationOptions(options); long id = nextPlayerIdentifier++; final String streamInstance = Long.toString(id); @@ -112,7 +114,7 @@ public long createForPlatformView(@NonNull CreationOptions options) { VideoPlayerEventCallbacks.bindTo(flutterState.binaryMessenger, streamInstance), handle, videoAsset, - sharedOptions); + playerOptions); registerPlayerInstance(videoPlayer, id); return new TexturePlayerIds(id, handle.id()); @@ -145,6 +147,15 @@ public long createForPlatformView(@NonNull CreationOptions options) { } } + private @NonNull VideoPlayerOptions videoPlayerOptionsWithCreationOptions( + @NonNull CreationOptions options) { + final VideoPlayerOptions playerOptions = new VideoPlayerOptions(); + playerOptions.mixWithOthers = sharedOptions.mixWithOthers; + playerOptions.enableDecoderFallback = options.getEnableDecoderFallback(); + playerOptions.disableMediaCodecAsyncQueueing = options.getDisableMediaCodecAsyncQueueing(); + return playerOptions; + } + private void registerPlayerInstance(VideoPlayer player, long id) { // Set up the instance-specific API handler, and make sure it is removed when the player is // disposed. 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..2b1905f7fd18 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 @@ -60,6 +60,7 @@ public static PlatformViewVideoPlayer create( new androidx.media3.exoplayer.trackselection.DefaultTrackSelector(context); ExoPlayer.Builder builder = new ExoPlayer.Builder(context) + .setRenderersFactory(createRenderersFactory(context, options)) .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 e482bdd85020..4d5fd7cccf98 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 @@ -59,6 +59,7 @@ public static TextureVideoPlayer create( new androidx.media3.exoplayer.trackselection.DefaultTrackSelector(context); ExoPlayer.Builder builder = new ExoPlayer.Builder(context) + .setRenderersFactory(createRenderersFactory(context, options)) .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 e546c744e561..52e9dc60da45 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 @@ -300,7 +300,9 @@ data class CreationOptions( val uri: String, val formatHint: PlatformVideoFormat? = null, val httpHeaders: Map, - val userAgent: String? = null + val userAgent: String? = null, + val enableDecoderFallback: Boolean, + val disableMediaCodecAsyncQueueing: Boolean ) { companion object { fun fromList(pigeonVar_list: List): CreationOptions { @@ -308,7 +310,15 @@ data class CreationOptions( val formatHint = pigeonVar_list[1] as PlatformVideoFormat? val httpHeaders = pigeonVar_list[2] as Map val userAgent = pigeonVar_list[3] as String? - return CreationOptions(uri, formatHint, httpHeaders, userAgent) + val enableDecoderFallback = pigeonVar_list[4] as Boolean + val disableMediaCodecAsyncQueueing = pigeonVar_list[5] as Boolean + return CreationOptions( + uri, + formatHint, + httpHeaders, + userAgent, + enableDecoderFallback, + disableMediaCodecAsyncQueueing) } } @@ -318,6 +328,8 @@ data class CreationOptions( formatHint, httpHeaders, userAgent, + enableDecoderFallback, + disableMediaCodecAsyncQueueing, ) } 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 6093dc86573c..e82e41f30b71 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,7 +81,9 @@ public void createsPlatformViewVideoPlayer() throws Exception { "https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4", null, new HashMap<>(), - null); + null, + false, + false); final long playerId = plugin.createForPlatformView(options); @@ -103,7 +105,9 @@ public void createsTextureVideoPlayer() throws Exception { "https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4", null, new HashMap<>(), - null); + null, + false, + false); final TexturePlayerIds ids = plugin.createForTextureView(options); @@ -111,4 +115,40 @@ public void createsTextureVideoPlayer() throws Exception { assertTrue(videoPlayers.get(ids.getPlayerId()) instanceof TextureVideoPlayer); } } + + @Test + public void createsTextureVideoPlayerWithCreationScopedAndroidOptions() { + try (MockedStatic mockedTextureVideoPlayerStatic = + mockStatic(TextureVideoPlayer.class)) { + mockedTextureVideoPlayerStatic + .when(() -> TextureVideoPlayer.create(any(), any(), any(), any(), any())) + .thenReturn(mock(TextureVideoPlayer.class)); + + plugin.setMixWithOthers(true); + + final CreationOptions options = + new CreationOptions( + "https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4", + null, + new HashMap<>(), + null, + true, + true); + + plugin.createForTextureView(options); + + mockedTextureVideoPlayerStatic.verify( + () -> + TextureVideoPlayer.create( + any(), + any(), + any(), + any(), + argThat( + (VideoPlayerOptions playerOptions) -> + playerOptions.mixWithOthers + && playerOptions.enableDecoderFallback + && playerOptions.disableMediaCodecAsyncQueueing))); + } + } } diff --git a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java index 92c2ff5f1566..825298426ae6 100644 --- a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java +++ b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java @@ -24,6 +24,7 @@ import androidx.media3.common.TrackGroup; import androidx.media3.common.TrackSelectionOverride; import androidx.media3.common.Tracks; +import androidx.media3.exoplayer.DefaultRenderersFactory; import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; import com.google.common.collect.ImmutableList; @@ -61,6 +62,7 @@ public final class VideoPlayerTest { @Mock private VideoPlayerCallbacks mockEvents; @Mock private ExoPlayer mockExoPlayer; + @Mock private DefaultRenderersFactory mockRenderersFactory; @Captor private ArgumentCaptor attributesCaptor; @Captor private ArgumentCaptor listenerCaptor; @@ -126,6 +128,26 @@ public void loadsAndPreparesProvidedMediaDisablesAudioFocusWhenMixModeSet() { videoPlayer.dispose(); } + @Test + public void configureRenderersFactoryAppliesDefaults() { + VideoPlayer.configureRenderersFactory(mockRenderersFactory, new VideoPlayerOptions()); + + verify(mockRenderersFactory).setEnableDecoderFallback(false); + verify(mockRenderersFactory, never()).forceDisableMediaCodecAsynchronousQueueing(); + } + + @Test + public void configureRenderersFactoryAppliesAndroidOptions() { + VideoPlayerOptions options = new VideoPlayerOptions(); + options.enableDecoderFallback = true; + options.disableMediaCodecAsyncQueueing = true; + + VideoPlayer.configureRenderersFactory(mockRenderersFactory, options); + + verify(mockRenderersFactory).setEnableDecoderFallback(true); + verify(mockRenderersFactory).forceDisableMediaCodecAsynchronousQueueing(); + } + @Test public void playsAndPausesProvidedMedia() { VideoPlayer videoPlayer = createVideoPlayer(); 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..fdd39ca35454 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 @@ -81,6 +81,8 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { @override Future createWithOptions(VideoCreationOptions options) async { final DataSource dataSource = options.dataSource; + final VideoPlayerAndroidOptions androidOptions = + options.androidOptions ?? const VideoPlayerAndroidOptions(); String? uri; PlatformVideoFormat? formatHint; @@ -114,6 +116,9 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { httpHeaders: httpHeaders, userAgent: userAgent, formatHint: formatHint, + enableDecoderFallback: androidOptions.enableDecoderFallback, + disableMediaCodecAsyncQueueing: + androidOptions.disableMediaCodecAsyncQueueing, ); 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 1aca7dc531dd..89de6ecc45a3 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 @@ -261,6 +261,8 @@ class CreationOptions { this.formatHint, required this.httpHeaders, this.userAgent, + required this.enableDecoderFallback, + required this.disableMediaCodecAsyncQueueing, }); String uri; @@ -271,8 +273,19 @@ class CreationOptions { String? userAgent; + bool enableDecoderFallback; + + bool disableMediaCodecAsyncQueueing; + List _toList() { - return [uri, formatHint, httpHeaders, userAgent]; + return [ + uri, + formatHint, + httpHeaders, + userAgent, + enableDecoderFallback, + disableMediaCodecAsyncQueueing, + ]; } Object encode() { @@ -287,6 +300,8 @@ class CreationOptions { httpHeaders: (result[2] as Map?)! .cast(), userAgent: result[3] as String?, + enableDecoderFallback: result[4]! as bool, + disableMediaCodecAsyncQueueing: result[5]! as bool, ); } diff --git a/packages/video_player/video_player_android/pigeons/messages.dart b/packages/video_player/video_player_android/pigeons/messages.dart index 8666b074969a..d77c430e3bef 100644 --- a/packages/video_player/video_player_android/pigeons/messages.dart +++ b/packages/video_player/video_player_android/pigeons/messages.dart @@ -68,11 +68,18 @@ class PlatformVideoViewCreationParams { } class CreationOptions { - CreationOptions({required this.uri, required this.httpHeaders}); + CreationOptions({ + required this.uri, + required this.httpHeaders, + required this.enableDecoderFallback, + required this.disableMediaCodecAsyncQueueing, + }); String uri; PlatformVideoFormat? formatHint; Map httpHeaders; String? userAgent; + bool enableDecoderFallback; + bool disableMediaCodecAsyncQueueing; } class TexturePlayerIds { 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..3233f4c7c8da 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 @@ -156,6 +156,8 @@ void main() { expect(creationOptions.uri, uri); expect(creationOptions.formatHint, PlatformVideoFormat.dash); expect(creationOptions.httpHeaders, {}); + expect(creationOptions.enableDecoderFallback, isFalse); + expect(creationOptions.disableMediaCodecAsyncQueueing, isFalse); expect(playerId, newPlayerId); expect( player.buildViewWithOptions(VideoViewOptions(playerId: playerId!)), @@ -374,6 +376,35 @@ void main() { expect(playerId, newPlayerId); }); + test('createWithOptions forwards android options', () 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, + androidOptions: const VideoPlayerAndroidOptions( + enableDecoderFallback: true, + disableMediaCodecAsyncQueueing: true, + ), + ), + ); + + final VerificationResult verification = verify( + api.createForTextureView(captureAny), + ); + final creationOptions = verification.captured[0] as CreationOptions; + expect(creationOptions.enableDecoderFallback, isTrue); + expect(creationOptions.disableMediaCodecAsyncQueueing, isTrue); + }); + test('createWithOptions with file', () async { final (AndroidVideoPlayer player, MockAndroidVideoPlayerApi api, _) = setUpMockPlayer(playerId: 1, textureId: 100); diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md index 4b44b050047a..4b5518ace22f 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 `VideoPlayerAndroidOptions` and forwards Android-specific creation options through `VideoCreationOptions`. * 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..facb425e64c2 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 @@ -424,6 +424,47 @@ class DurationRange { int get hashCode => Object.hash(start, end); } +/// [VideoPlayerAndroidOptions] can be optionally used to set Android-only +/// player settings. +@immutable +class VideoPlayerAndroidOptions { + /// Set additional Android-only player settings. + const VideoPlayerAndroidOptions({ + this.enableDecoderFallback = false, + this.disableMediaCodecAsyncQueueing = false, + }); + + /// Enables decoder fallback on Android. + /// + /// Ignored on non-Android platforms. + final bool enableDecoderFallback; + + /// Disables MediaCodec asynchronous queueing on Android. + /// + /// Ignored on non-Android platforms. + final bool disableMediaCodecAsyncQueueing; + + @override + String toString() { + return '${objectRuntimeType(this, 'VideoPlayerAndroidOptions')}(' + 'enableDecoderFallback: $enableDecoderFallback, ' + 'disableMediaCodecAsyncQueueing: $disableMediaCodecAsyncQueueing)'; + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is VideoPlayerAndroidOptions && + runtimeType == other.runtimeType && + enableDecoderFallback == other.enableDecoderFallback && + disableMediaCodecAsyncQueueing == + other.disableMediaCodecAsyncQueueing; + + @override + int get hashCode => + Object.hash(enableDecoderFallback, disableMediaCodecAsyncQueueing); +} + /// [VideoPlayerOptions] can be optionally used to set additional player settings @immutable class VideoPlayerOptions { @@ -436,6 +477,7 @@ class VideoPlayerOptions { this.mixWithOthers = false, this.allowBackgroundPlayback = false, this.webOptions, + this.androidOptions, }); /// Set this to true to keep playing video in background, when app goes in background. @@ -451,6 +493,11 @@ class VideoPlayerOptions { /// Additional web controls final VideoPlayerWebOptions? webOptions; + + /// Additional Android-only settings. + /// + /// These settings are silently ignored on non-Android platforms. + final VideoPlayerAndroidOptions? androidOptions; } /// [VideoPlayerWebOptions] can be optionally used to set additional web settings @@ -553,6 +600,7 @@ class VideoCreationOptions { const VideoCreationOptions({ required this.dataSource, required this.viewType, + this.androidOptions, }); /// The data source used to create the player. @@ -560,6 +608,11 @@ class VideoCreationOptions { /// The type of view to be used for displaying the video player final VideoViewType viewType; + + /// Additional Android-only creation options. + /// + /// These settings are silently ignored on non-Android platforms. + final VideoPlayerAndroidOptions? androidOptions; } /// Represents an audio track in a video with its metadata. 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_options_test.dart b/packages/video_player/video_player_platform_interface/test/video_player_options_test.dart index 6af3e57f6c0f..b1895460c73c 100644 --- a/packages/video_player/video_player_platform_interface/test/video_player_options_test.dart +++ b/packages/video_player/video_player_platform_interface/test/video_player_options_test.dart @@ -6,12 +6,73 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; void main() { + test('VideoPlayerAndroidOptions defaults to false', () { + const options = VideoPlayerAndroidOptions(); + expect(options.enableDecoderFallback, false); + expect(options.disableMediaCodecAsyncQueueing, false); + }); + + test( + 'VideoPlayerAndroidOptions equality and toString include both fields', + () { + const options = VideoPlayerAndroidOptions( + enableDecoderFallback: true, + disableMediaCodecAsyncQueueing: true, + ); + + expect( + options, + const VideoPlayerAndroidOptions( + enableDecoderFallback: true, + disableMediaCodecAsyncQueueing: true, + ), + ); + expect( + options, + isNot( + const VideoPlayerAndroidOptions( + enableDecoderFallback: true, + disableMediaCodecAsyncQueueing: false, + ), + ), + ); + expect( + options.toString(), + 'VideoPlayerAndroidOptions(enableDecoderFallback: true, ' + 'disableMediaCodecAsyncQueueing: true)', + ); + }, + ); + test('VideoPlayerOptions allowBackgroundPlayback defaults to false', () { final options = VideoPlayerOptions(); expect(options.allowBackgroundPlayback, false); }); + test('VideoPlayerOptions mixWithOthers defaults to false', () { final options = VideoPlayerOptions(); expect(options.mixWithOthers, false); }); + + test('VideoPlayerOptions androidOptions defaults to null', () { + final options = VideoPlayerOptions(); + expect(options.androidOptions, isNull); + }); + + test('VideoCreationOptions carries androidOptions', () { + const androidOptions = VideoPlayerAndroidOptions( + enableDecoderFallback: true, + disableMediaCodecAsyncQueueing: true, + ); + final options = VideoCreationOptions( + dataSource: DataSource( + sourceType: DataSourceType.network, + uri: 'https://example.com/video.mp4', + ), + viewType: VideoViewType.textureView, + androidOptions: androidOptions, + ); + + expect(options.androidOptions, androidOptions); + }); }