diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md index 26f60a5c2b71..8f5b9397e9e9 100644 --- a/packages/video_player/video_player_android/CHANGELOG.md +++ b/packages/video_player/video_player_android/CHANGELOG.md @@ -1,3 +1,11 @@ +## 2.13.0 + +* Adds `VideoAssetProvider` and `VideoPlayerPlugin.setVideoAssetProvider`, letting another + component supply the `VideoAsset` used for a URI. This is the extension point for playback + this plugin cannot express itself, such as reading from a download or HTTP cache, applying a + custom `DataSource.Factory`, or resolving an unknown scheme. Behavior is unchanged when no + provider is registered. + ## 2.12.0 * Fixes a [bug](https://github.com/flutter/flutter/issues/176575) where some videos report an incorrect duration when initialized without a video duration. diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoAssetProvider.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoAssetProvider.java new file mode 100644 index 000000000000..6cf4fd572a61 --- /dev/null +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoAssetProvider.java @@ -0,0 +1,37 @@ +// 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 android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Supplies a {@link VideoAsset} for a URI, overriding this plugin's default handling. + * + *
This is the extension point for playback that the plugin cannot express on its own, such as + * reading from a download or HTTP cache, applying a custom {@code DataSource.Factory}, or resolving + * a scheme this plugin does not know about. Because {@link VideoAsset} already exposes the media + * item and media source factory, an implementation has full control over how a URI is played + * without this plugin needing to know why. + * + *
Register with {@link VideoPlayerPlugin#setVideoAssetProvider}. At most one provider is active + * at a time; the last one registered wins. + */ +public interface VideoAssetProvider { + /** + * Returns the asset to play for {@code uri}, or null to use this plugin's default handling. + * + *
Called on the main thread each time a player is created, before the URI is inspected for a + * known scheme, so a provider may override any URI including {@code asset:} and {@code rtsp:} + * ones. + * + * @param context application context. + * @param uri the URI the player was created with. + * @return the asset to play, or null to fall through to the default. + */ + @Nullable + VideoAsset getAsset(@NonNull Context context, @NonNull String uri); +} 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 5d520ac1f827..71a19c824067 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 @@ -27,6 +27,21 @@ public class VideoPlayerPlugin implements FlutterPlugin, AndroidVideoPlayerApi { private final VideoPlayerOptions sharedOptions = new VideoPlayerOptions(); private long nextPlayerIdentifier = 1; + private static @Nullable VideoAssetProvider videoAssetProvider; + + /** + * Sets the provider consulted before this plugin's own URI handling, or null to clear it. + * + *
Lets another component take over how a URI is played; see {@link VideoAssetProvider}. This
+ * is process-wide rather than per-plugin-instance because it is typically registered once at
+ * startup, before any player exists.
+ *
+ * @param provider the provider to consult, or null for default handling only.
+ */
+ public static void setVideoAssetProvider(@Nullable VideoAssetProvider provider) {
+ videoAssetProvider = provider;
+ }
+
/** Register this with the v2 embedding for the plugin to respond to lifecycle callbacks. */
public VideoPlayerPlugin() {}
@@ -126,6 +141,15 @@ public long createForPlatformView(@NonNull CreationOptions options) {
private @NonNull VideoAsset videoAssetWithOptions(@NonNull CreationOptions options) {
final @NonNull String uri = options.getUri();
+
+ VideoAssetProvider provider = videoAssetProvider;
+ if (provider != null) {
+ VideoAsset providedAsset = provider.getAsset(flutterState.applicationContext, uri);
+ if (providedAsset != null) {
+ return providedAsset;
+ }
+ }
+
if (uri.startsWith("asset:")) {
return VideoAsset.fromAssetUrl(uri);
} else if (uri.startsWith("rtsp:")) {
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 9f5e42fa66d3..17cc60d5696d 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
@@ -113,4 +113,55 @@ public void createsTextureVideoPlayer() throws Exception {
assertTrue(videoPlayers.get(ids.getPlayerId()) instanceof TextureVideoPlayer);
}
}
+
+ @Test
+ public void videoAssetProviderOverridesDefaultAsset() throws Exception {
+ final VideoAsset providedAsset = mock(VideoAsset.class);
+ final String uri = "https://example.com/video.m3u8";
+ final String[] requestedUri = new String[1];
+
+ VideoPlayerPlugin.setVideoAssetProvider(
+ (context, requested) -> {
+ requestedUri[0] = requested;
+ return providedAsset;
+ });
+
+ try (MockedStatic