Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/video_player/video_player_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since videoAssetProvider is a static field that can be set from any thread (e.g., during background initialization or dependency injection) and read on the main thread, it should be marked as volatile to ensure thread visibility and prevent stale reads.

Suggested change
private static @Nullable VideoAssetProvider videoAssetProvider;
private static volatile @Nullable VideoAssetProvider videoAssetProvider;


/**
* Sets the provider consulted before this plugin's own URI handling, or null to clear it.
*
* <p>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() {}

Expand Down Expand Up @@ -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:")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TextureVideoPlayer> mockedTextureVideoPlayerStatic =
mockStatic(TextureVideoPlayer.class)) {
mockedTextureVideoPlayerStatic
.when(() -> TextureVideoPlayer.create(any(), any(), any(), any(), any()))
.thenReturn(mock(TextureVideoPlayer.class));

plugin.createForTextureView(new CreationOptions(uri, null, new HashMap<>(), null, null));

assertEquals(uri, requestedUri[0]);
mockedTextureVideoPlayerStatic.verify(
() -> TextureVideoPlayer.create(any(), any(), any(), eq(providedAsset), any()));
} finally {
VideoPlayerPlugin.setVideoAssetProvider(null);
}
}

@Test
public void videoAssetProviderReturningNullFallsBackToDefault() throws Exception {
VideoPlayerPlugin.setVideoAssetProvider((context, requested) -> null);

try (MockedStatic<TextureVideoPlayer> mockedTextureVideoPlayerStatic =
mockStatic(TextureVideoPlayer.class)) {
mockedTextureVideoPlayerStatic
.when(() -> TextureVideoPlayer.create(any(), any(), any(), any(), any()))
.thenReturn(mock(TextureVideoPlayer.class));

final TexturePlayerIds ids =
plugin.createForTextureView(
new CreationOptions(
"https://example.com/video.m3u8", null, new HashMap<>(), null, null));

// The default handling still produced a working player.
final LongSparseArray<VideoPlayer> videoPlayers = getVideoPlayers();
assertTrue(videoPlayers.get(ids.getPlayerId()) instanceof TextureVideoPlayer);
} finally {
VideoPlayerPlugin.setVideoAssetProvider(null);
}
}
}
2 changes: 1 addition & 1 deletion packages/video_player/video_player_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.12.0
version: 2.13.0

environment:
sdk: ^3.12.0
Expand Down