diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 4e4c13b0a0f2..f1e1b26392c5 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,5 +1,8 @@ -## NEXT +## 2.12.0 +* Adds `VideoPlayerAndroidOptions.enableDecoderFallback` to allow Android's + ExoPlayer implementation to fall back to another decoder if decoder + initialization fails. * Updates minimum supported SDK version to Flutter 3.38/Dart 3.10. ## 2.11.1 diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index b287a39fed24..f312e730c683 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, @@ -560,6 +561,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..f4ea96251aa7 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.8.0 video_player_web: ^2.1.0 dev_dependencies: diff --git a/packages/video_player/video_player/test/video_player_initialization_test.dart b/packages/video_player/video_player/test/video_player_initialization_test.dart index 4524e8aca167..d0b57e5c47b7 100644 --- a/packages/video_player/video_player/test/video_player_initialization_test.dart +++ b/packages/video_player/video_player/test/video_player_initialization_test.dart @@ -73,4 +73,20 @@ void main() { reason: 'view type must be passed to the platform', ); }); + + test('Android options are applied', () async { + const expected = VideoPlayerAndroidOptions(enableDecoderFallback: true); + + final controller = VideoPlayerController.networkUrl( + Uri.parse('https://127.0.0.1'), + videoPlayerOptions: VideoPlayerOptions(androidOptions: expected), + ); + await controller.initialize(); + + expect( + fakeVideoPlayerPlatform.androidOptions[controller.playerId], + expected, + reason: 'Android options must be passed to the platform', + ); + }); } 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 56cd402f1228..760297b386dc 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -1027,13 +1027,11 @@ void main() { } expect(isSorted, false, reason: 'Expected captions to be unsorted'); - expect(captions.map((Caption c) => c.text).toList(), [ - 'one', - 'two', - 'three', - 'five', - 'four', - ], reason: 'Captions should be in original unsorted order'); + expect( + captions.map((Caption c) => c.text).toList(), + ['one', 'two', 'three', 'five', 'four'], + reason: 'Captions should be in original unsorted order', + ); }); test('works when seeking, includes all captions', () async { @@ -1899,6 +1897,7 @@ class FakeVideoPlayerPlatform extends VideoPlayerPlatform { List calls = []; List dataSources = []; List viewTypes = []; + List androidOptions = []; final Map> streams = >{}; bool forceInitError = false; int nextPlayerId = 0; @@ -1943,6 +1942,7 @@ class FakeVideoPlayerPlatform extends VideoPlayerPlatform { } dataSources.add(options.dataSource); viewTypes.add(options.viewType); + androidOptions.add(options.androidOptions); return nextPlayerId++; } diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md index 99cda569f5c2..9a5e327bc6e0 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 support for enabling ExoPlayer decoder fallback. + ## 2.9.6 * Migrates to Built-in Kotlin to support AGP 9. 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..fb466296eb4a 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,5 @@ public class VideoPlayerOptions { public boolean mixWithOthers; + public boolean enableDecoderFallback; } 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..977ce252232a 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 @@ -92,7 +92,7 @@ public long createForPlatformView(@NonNull CreationOptions options) { flutterState.applicationContext, VideoPlayerEventCallbacks.bindTo(flutterState.binaryMessenger, streamInstance), videoAsset, - sharedOptions); + optionsFromCreationOptions(options)); registerPlayerInstance(videoPlayer, id); return id; @@ -112,7 +112,7 @@ public long createForPlatformView(@NonNull CreationOptions options) { VideoPlayerEventCallbacks.bindTo(flutterState.binaryMessenger, streamInstance), handle, videoAsset, - sharedOptions); + optionsFromCreationOptions(options)); registerPlayerInstance(videoPlayer, id); return new TexturePlayerIds(id, handle.id()); @@ -145,6 +145,13 @@ public long createForPlatformView(@NonNull CreationOptions options) { } } + private @NonNull VideoPlayerOptions optionsFromCreationOptions(@NonNull CreationOptions options) { + VideoPlayerOptions videoPlayerOptions = new VideoPlayerOptions(); + videoPlayerOptions.mixWithOthers = sharedOptions.mixWithOthers; + videoPlayerOptions.enableDecoderFallback = options.getEnableDecoderFallback(); + return videoPlayerOptions; + } + 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..c8a0c559f23c 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewVideoPlayer.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewVideoPlayer.java @@ -10,6 +10,7 @@ import androidx.annotation.VisibleForTesting; import androidx.media3.common.MediaItem; import androidx.media3.common.util.UnstableApi; +import androidx.media3.exoplayer.DefaultRenderersFactory; import androidx.media3.exoplayer.ExoPlayer; import io.flutter.plugins.videoplayer.ExoPlayerEventListener; import io.flutter.plugins.videoplayer.VideoAsset; @@ -55,15 +56,26 @@ public static PlatformViewVideoPlayer create( 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(); - }); + () -> createExoPlayer(context, asset, options, new DefaultRenderersFactory(context))); + } + + // TODO: Migrate to stable API, see https://github.com/flutter/flutter/issues/147039. + @UnstableApi + @VisibleForTesting + @NonNull + public static ExoPlayer createExoPlayer( + @NonNull Context context, + @NonNull VideoAsset asset, + @NonNull VideoPlayerOptions options, + @NonNull DefaultRenderersFactory renderersFactory) { + androidx.media3.exoplayer.trackselection.DefaultTrackSelector trackSelector = + new androidx.media3.exoplayer.trackselection.DefaultTrackSelector(context); + renderersFactory.setEnableDecoderFallback(options.enableDecoderFallback); + ExoPlayer.Builder builder = + new ExoPlayer.Builder(context, renderersFactory) + .setTrackSelector(trackSelector) + .setMediaSourceFactory(asset.getMediaSourceFactory(context)); + return builder.build(); } @NonNull 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 d623ddc88608..6f8c771c8b81 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureVideoPlayer.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureVideoPlayer.java @@ -12,6 +12,7 @@ import androidx.annotation.VisibleForTesting; import androidx.media3.common.MediaItem; import androidx.media3.common.util.UnstableApi; +import androidx.media3.exoplayer.DefaultRenderersFactory; import androidx.media3.exoplayer.ExoPlayer; import io.flutter.plugins.videoplayer.ExoPlayerEventListener; import io.flutter.plugins.videoplayer.VideoAsset; @@ -55,15 +56,26 @@ public static TextureVideoPlayer create( 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(); - }); + () -> createExoPlayer(context, asset, options, new DefaultRenderersFactory(context))); + } + + // TODO: Migrate to stable API, see https://github.com/flutter/flutter/issues/147039. + @UnstableApi + @VisibleForTesting + @NonNull + public static ExoPlayer createExoPlayer( + @NonNull Context context, + @NonNull VideoAsset asset, + @NonNull VideoPlayerOptions options, + @NonNull DefaultRenderersFactory renderersFactory) { + androidx.media3.exoplayer.trackselection.DefaultTrackSelector trackSelector = + new androidx.media3.exoplayer.trackselection.DefaultTrackSelector(context); + renderersFactory.setEnableDecoderFallback(options.enableDecoderFallback); + ExoPlayer.Builder builder = + new ExoPlayer.Builder(context, renderersFactory) + .setTrackSelector(trackSelector) + .setMediaSourceFactory(asset.getMediaSourceFactory(context)); + return builder.build(); } // 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..a54d4b6a4753 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,8 @@ data class CreationOptions( val uri: String, val formatHint: PlatformVideoFormat? = null, val httpHeaders: Map, - val userAgent: String? = null + val userAgent: String? = null, + val enableDecoderFallback: Boolean ) { companion object { fun fromList(pigeonVar_list: List): CreationOptions { @@ -308,7 +309,8 @@ 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 + return CreationOptions(uri, formatHint, httpHeaders, userAgent, enableDecoderFallback) } } @@ -318,6 +320,7 @@ data class CreationOptions( formatHint, httpHeaders, userAgent, + enableDecoderFallback, ) } diff --git a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/PlatformViewVideoPlayerTest.java b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/PlatformViewVideoPlayerTest.java new file mode 100644 index 000000000000..085f41bc5d05 --- /dev/null +++ b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/PlatformViewVideoPlayerTest.java @@ -0,0 +1,55 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.videoplayer; + +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import androidx.annotation.OptIn; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.exoplayer.DefaultRenderersFactory; +import androidx.media3.exoplayer.ExoPlayer; +import androidx.test.core.app.ApplicationProvider; +import io.flutter.plugins.videoplayer.platformview.PlatformViewVideoPlayer; +import java.lang.reflect.Field; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Unit tests for {@link PlatformViewVideoPlayer}. */ +@RunWith(RobolectricTestRunner.class) +public final class PlatformViewVideoPlayerTest { + private static final String FAKE_ASSET_URL = "https://flutter.dev/movie.mp4"; + private FakeVideoAsset fakeVideoAsset; + + @Before + public void setUp() { + fakeVideoAsset = new FakeVideoAsset(FAKE_ASSET_URL); + } + + private boolean getEnableDecoderFallback(DefaultRenderersFactory renderersFactory) + throws Exception { + final Field field = DefaultRenderersFactory.class.getDeclaredField("enableDecoderFallback"); + field.setAccessible(true); + return field.getBoolean(renderersFactory); + } + + @OptIn(markerClass = UnstableApi.class) + @Test + public void createExoPlayerEnablesDecoderFallbackWhenSet() throws Exception { + final Context context = ApplicationProvider.getApplicationContext(); + final DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(context); + final VideoPlayerOptions options = new VideoPlayerOptions(); + options.enableDecoderFallback = true; + + final ExoPlayer exoPlayer = + PlatformViewVideoPlayer.createExoPlayer(context, fakeVideoAsset, options, renderersFactory); + + assertTrue(getEnableDecoderFallback(renderersFactory)); + + exoPlayer.release(); + } +} diff --git a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/TextureVideoPlayerTest.java b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/TextureVideoPlayerTest.java index 6631d35a899f..a52c6ffc240c 100644 --- a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/TextureVideoPlayerTest.java +++ b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/TextureVideoPlayerTest.java @@ -5,18 +5,25 @@ package io.flutter.plugins.videoplayer; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +import android.content.Context; import android.view.Surface; +import androidx.annotation.OptIn; import androidx.media3.common.AudioAttributes; import androidx.media3.common.C; import androidx.media3.common.PlaybackParameters; import androidx.media3.common.Player; import androidx.media3.common.VideoSize; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.exoplayer.DefaultRenderersFactory; import androidx.media3.exoplayer.ExoPlayer; +import androidx.test.core.app.ApplicationProvider; import io.flutter.plugins.videoplayer.texture.TextureVideoPlayer; import io.flutter.view.TextureRegistry; +import java.lang.reflect.Field; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -67,6 +74,29 @@ private TextureVideoPlayer createVideoPlayer(VideoPlayerOptions options) { mockEvents, mockProducer, fakeVideoAsset.getMediaItem(), options, () -> mockExoPlayer); } + private boolean getEnableDecoderFallback(DefaultRenderersFactory renderersFactory) + throws Exception { + final Field field = DefaultRenderersFactory.class.getDeclaredField("enableDecoderFallback"); + field.setAccessible(true); + return field.getBoolean(renderersFactory); + } + + @OptIn(markerClass = UnstableApi.class) + @Test + public void createExoPlayerEnablesDecoderFallbackWhenSet() throws Exception { + final Context context = ApplicationProvider.getApplicationContext(); + final DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(context); + final VideoPlayerOptions options = new VideoPlayerOptions(); + options.enableDecoderFallback = true; + + final ExoPlayer exoPlayer = + TextureVideoPlayer.createExoPlayer(context, fakeVideoAsset, options, renderersFactory); + + assertTrue(getEnableDecoderFallback(renderersFactory)); + + exoPlayer.release(); + } + @Test public void loadsAndPreparesProvidedMediaEnablesAudioFocusByDefault() { VideoPlayer videoPlayer = createVideoPlayer(); 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..3f3b4cee91e7 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,8 @@ public void createsPlatformViewVideoPlayer() throws Exception { "https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4", null, new HashMap<>(), - null); + null, + false); final long playerId = plugin.createForPlatformView(options); @@ -103,7 +104,8 @@ public void createsTextureVideoPlayer() throws Exception { "https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4", null, new HashMap<>(), - null); + null, + false); final TexturePlayerIds ids = plugin.createForTextureView(options); @@ -111,4 +113,54 @@ public void createsTextureVideoPlayer() throws Exception { assertTrue(videoPlayers.get(ids.getPlayerId()) instanceof TextureVideoPlayer); } } + + @Test + public void passesDecoderFallbackOptionToPlatformViewVideoPlayer() { + try (MockedStatic mockedPlatformViewVideoPlayerStatic = + mockStatic(PlatformViewVideoPlayer.class)) { + mockedPlatformViewVideoPlayerStatic + .when(() -> PlatformViewVideoPlayer.create(any(), any(), any(), any())) + .thenAnswer( + invocation -> { + final VideoPlayerOptions playerOptions = invocation.getArgument(3); + assertTrue(playerOptions.enableDecoderFallback); + return mock(PlatformViewVideoPlayer.class); + }); + + final CreationOptions options = + new CreationOptions( + "https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4", + null, + new HashMap<>(), + null, + true); + + plugin.createForPlatformView(options); + } + } + + @Test + public void passesDecoderFallbackOptionToTextureVideoPlayer() { + try (MockedStatic mockedTextureVideoPlayerStatic = + mockStatic(TextureVideoPlayer.class)) { + mockedTextureVideoPlayerStatic + .when(() -> TextureVideoPlayer.create(any(), any(), any(), any(), any())) + .thenAnswer( + invocation -> { + final VideoPlayerOptions playerOptions = invocation.getArgument(4); + assertTrue(playerOptions.enableDecoderFallback); + return mock(TextureVideoPlayer.class); + }); + + final CreationOptions options = + new CreationOptions( + "https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4", + null, + new HashMap<>(), + null, + true); + + plugin.createForTextureView(options); + } + } } diff --git a/packages/video_player/video_player_android/lib/src/android_video_player.dart b/packages/video_player/video_player_android/lib/src/android_video_player.dart index 5ecf673a7aec..1be9a523a988 100644 --- a/packages/video_player/video_player_android/lib/src/android_video_player.dart +++ b/packages/video_player/video_player_android/lib/src/android_video_player.dart @@ -103,6 +103,7 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { httpHeaders: httpHeaders, userAgent: userAgent, formatHint: formatHint, + enableDecoderFallback: options.androidOptions?.enableDecoderFallback ?? false, ); 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 2782e80e8c14..a08d95b2c7e8 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 @@ -252,7 +252,13 @@ class PlatformVideoViewCreationParams { } class CreationOptions { - CreationOptions({required this.uri, this.formatHint, required this.httpHeaders, this.userAgent}); + CreationOptions({ + required this.uri, + this.formatHint, + required this.httpHeaders, + this.userAgent, + required this.enableDecoderFallback, + }); String uri; @@ -262,8 +268,10 @@ class CreationOptions { String? userAgent; + bool enableDecoderFallback; + List _toList() { - return [uri, formatHint, httpHeaders, userAgent]; + return [uri, formatHint, httpHeaders, userAgent, enableDecoderFallback]; } Object encode() { @@ -277,6 +285,7 @@ class CreationOptions { formatHint: result[1] as PlatformVideoFormat?, httpHeaders: (result[2] as Map?)!.cast(), userAgent: result[3] as String?, + enableDecoderFallback: result[4]! 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 5b67adb40fad..ceaea0f664cc 100644 --- a/packages/video_player/video_player_android/pigeons/messages.dart +++ b/packages/video_player/video_player_android/pigeons/messages.dart @@ -67,11 +67,16 @@ class PlatformVideoViewCreationParams { } class CreationOptions { - CreationOptions({required this.uri, required this.httpHeaders}); + CreationOptions({ + required this.uri, + required this.httpHeaders, + required this.enableDecoderFallback, + }); String uri; PlatformVideoFormat? formatHint; Map httpHeaders; String? userAgent; + bool enableDecoderFallback; } class TexturePlayerIds { diff --git a/packages/video_player/video_player_android/pubspec.yaml b/packages/video_player/video_player_android/pubspec.yaml index d2cdfc394bc2..fa3b9bd3f2d7 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.6 +version: 2.10.0 environment: sdk: ^3.12.0 @@ -20,7 +20,7 @@ flutter: dependencies: flutter: sdk: flutter - video_player_platform_interface: ^6.6.0 + video_player_platform_interface: ^6.8.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 84239afc78c5..f336b965777b 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 @@ -307,10 +307,34 @@ void main() { expect(creationOptions.uri, uri); expect(creationOptions.formatHint, PlatformVideoFormat.dash); expect(creationOptions.httpHeaders, {}); + expect(creationOptions.enableDecoderFallback, false); expect(playerId, newPlayerId); expect(player.buildViewWithOptions(VideoViewOptions(playerId: playerId!)), isA()); }); + test('createWithOptions passes Android decoder fallback option', () async { + final (AndroidVideoPlayer player, MockAndroidVideoPlayerApi api, _) = setUpMockPlayer( + playerId: 1, + textureId: 100, + ); + const newPlayerId = 2; + when( + api.createForTextureView(any), + ).thenAnswer((_) async => TexturePlayerIds(playerId: newPlayerId, textureId: 100)); + + await player.createWithOptions( + VideoCreationOptions( + dataSource: DataSource(sourceType: DataSourceType.network, uri: 'https://example.com'), + viewType: VideoViewType.textureView, + androidOptions: const VideoPlayerAndroidOptions(enableDecoderFallback: true), + ), + ); + + final VerificationResult verification = verify(api.createForTextureView(captureAny)); + final creationOptions = verification.captured[0] as CreationOptions; + expect(creationOptions.enableDecoderFallback, true); + }); + test('createWithOptions with network passes headers', () async { final (AndroidVideoPlayer player, MockAndroidVideoPlayerApi api, _) = setUpMockPlayer( playerId: 1, diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md index 203f913e937c..8bfb3c6906d3 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.8.0 +* Adds `VideoPlayerAndroidOptions` for Android-specific player configuration. * Updates minimum supported SDK version to Flutter 3.38/Dart 3.10. ## 6.7.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 16c83ed35018..39142efbc9f0 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 @@ -461,6 +461,7 @@ class VideoPlayerOptions { VideoPlayerOptions({ this.mixWithOthers = false, this.allowBackgroundPlayback = false, + this.androidOptions, this.webOptions, }); @@ -475,10 +476,29 @@ class VideoPlayerOptions { /// currently no way to implement this feature in this platform). final bool mixWithOthers; + /// Android-specific player options. + final VideoPlayerAndroidOptions? androidOptions; + /// Additional web controls final VideoPlayerWebOptions? webOptions; } +/// [VideoPlayerAndroidOptions] can be optionally used to set additional +/// Android settings. +@immutable +class VideoPlayerAndroidOptions { + /// [VideoPlayerAndroidOptions] can be optionally used to set additional + /// Android settings. + const VideoPlayerAndroidOptions({this.enableDecoderFallback = false}); + + /// Whether ExoPlayer should try other decoders if the first decoder fails to + /// initialize. + /// + /// This does not force software decoding. ExoPlayer will still use the + /// default decoder selection unless decoder initialization fails. + final bool enableDecoderFallback; +} + /// [VideoPlayerWebOptions] can be optionally used to set additional web settings @immutable class VideoPlayerWebOptions { @@ -576,13 +596,20 @@ class VideoViewOptions { @immutable class VideoCreationOptions { /// Constructs an instance of [VideoCreationOptions]. - const VideoCreationOptions({required this.dataSource, required this.viewType}); + const VideoCreationOptions({ + required this.dataSource, + required this.viewType, + this.androidOptions, + }); /// The data source used to create the player. final DataSource dataSource; /// The type of view to be used for displaying the video player final VideoViewType viewType; + + /// Android-specific player options. + 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 1742dec5a53c..c8d03f31704d 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.7.0 +version: 6.8.0 environment: sdk: ^3.10.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..6a8fd3c351a6 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 @@ -14,4 +14,12 @@ void main() { final options = VideoPlayerOptions(); expect(options.mixWithOthers, false); }); + test('VideoPlayerOptions androidOptions defaults to null', () { + final options = VideoPlayerOptions(); + expect(options.androidOptions, isNull); + }); + test('VideoPlayerAndroidOptions enableDecoderFallback defaults to false', () { + const options = VideoPlayerAndroidOptions(); + expect(options.enableDecoderFallback, false); + }); }