Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[video_player_avfoundation] Platform view support #8237

Open
wants to merge 133 commits into
base: main
Choose a base branch
from

Conversation

FirentisTFW
Copy link
Contributor

@FirentisTFW FirentisTFW commented Dec 6, 2024

This PR adds support for platform views on iOS as a way of displaying a video. When creating a video, it's now possible to choose between texture view approach (rendered using Texture widget on the Flutter side) and platform view approach (rendered on the native side, using AVPlayerLayer).

FVPVideoPlayer class now has nothing to do with texture. The texture-related code was moved from it to FVPVideoPlayerTextureApproach - a subclass of FVPVideoPlayer that adds texture functionality. In the plugin class (createWithOptions method) we create either the basic version (for platform view) or the texture subclass (in case of texture approach) based on the parameter passed in from Flutter side.

Platform view is only supported on iOS, no MacOS implementation is added in this PR. The functionality is not yet exposed in the app-facing package (only in example app) - it will be done later, once we add the Android implementation. The PR does not introduce breaking changes, I followed the rule "non-breaking changes, even at the expense of a less-clean API" (buildViewWithOptions and createWithOptions methods).

Up to this point, the variable naming relied heavily on the texture (we had a lot of textureId variables and properties). Since now you can use a platform view instead of a texture view, these variables and parameters will be renamed to just playerId. This will be done in a separate PR to keep git diff for this one clean.

I left some comments in the PR to clarify/discuss some choices.

Resolves #86613 (not fully though, as it is not yet available in the app-facing package).

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@ditman ditman removed their request for review January 7, 2025 21:39
@ditman
Copy link
Member

ditman commented Jan 7, 2025

I don't own these packages so removing myself, but this seems like a good entrypoint for an incoming web PR adding a "texture"-like rendering for the video player there. PR (it got closed though, so I'm not sure if it'll ever get updated to leverage this).

Future<int?> create(DataSource dataSource) {
throw UnimplementedError('create() has not been implemented.');
}

/// Creates an instance of a video player based on creation options
/// and returns its playerId. If view type is [VideoViewType.textureView],
/// the playerId is also the texture id.
Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at just the platform interface part again, I don't think we should encode this into the interface, as it will make it harder for the higher-level code to untangle the now-ideally-abstract player ID from the texture ID, which has a very specific meaning.

There's also a design issue here where both createWithOptions and buildViewWithOptions are being passed a view type, but once createWithOptions has been called, there is only one valid type that can be passed to buildViewWithOptions for that player ID, so the calling code is being forced to maintain view type state.

Couldn't VideoViewOptions only contain the player ID for now, and the state of how to map player ID to implementation details like the texture ID (if any) be maintained in the platform implementation, which is already stateful?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did as suggested. Now the responsibility of maintaining view type state for a given player relies on the platform implementation.

The player ID as viewed by the app facing package should now more abstract, but currently we ended up in a very ugly state where most of the methods still use textureId for parameter names (which now should be renamed to playerId). I suppose that we could fix it all up when creating platform interface PR?

In case of texture view, the player ID will still be the same as texture ID, we would just not explicitly reveal that. If we wanted to change this and make the player ID fully decoupled from the texture ID, that would probably require some significant changes - either moving the player ID generation process to the Dart side or changing the pigeon interface so that it returns player ID as well as texture ID (which can be null).

Copy link
Contributor

Choose a reason for hiding this comment

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

In case of texture view, the player ID will still be the same as texture ID, we would just not explicitly reveal that.

That's fine for now as an implementation detail, I just don't want that to become something we are stuck with indefinitely because it's part of the API contract of the new methods.

Comment on lines +208 to +223
test('createWithOptions with incorrect asset throws exception', () async {
try {
await player.createWithOptions(
VideoCreationOptions(
dataSource: DataSource(
sourceType: DataSourceType.asset,
asset: '/path/to/incorrect_asset',
),
viewType: VideoViewType.textureView,
),
);
fail('should throw PlatformException');
} catch (e) {
expect(e, isException);
}
});
Copy link
Contributor Author

@FirentisTFW FirentisTFW Jan 15, 2025

Choose a reason for hiding this comment

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

I copied this test from the one above (for create method) which was added in this PR, but I don't think it checks anything.

I assume the call to createWithOptions (or create in the original test) should fail, then we should catch it, and verify the exception. But looks like what actually happens is that the fail call throws, we just catch it and verify that it is exception (which it is because it's a TestFailure).

Is this test needed? What value does it bring?

Copy link
Contributor

Choose a reason for hiding this comment

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

But looks like what actually happens is that the fail call throws, we just catch it and verify that it is exception (which it is because it's a TestFailure).

Sounds like the test is broken; the preferred way to write it would be with a throwsA matcher on the call, instead of a manual try/catch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Does this test make sense here in the first place? The exception it is supposed to check comes from the native side.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ugh, I see; sorry, I hadn't looked at the initial PR. You are correct, this test makes no sense here. It should have been either a native unit test, or a Dart integration test, since a Dart unit test can't possibly exercise that code. Unfortunately that was missed in review.

@stuartmorgan stuartmorgan added the triage-ios Should be looked at in iOS triage label Jan 15, 2025
Copy link
Contributor

@stuartmorgan stuartmorgan left a comment

Choose a reason for hiding this comment

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

I didn't fully re-review, but I looked at the platform interface part and skimmed how that affected the implementation. It looks like this approach does what we need, and will be clearly applicable to other platforms (and if ever necessary, added view types) so I think we're good to split that piece out to its own PR now, so that it can be landed and unblock Android work, while the iOS review proceeds.

/// A map that associates player ID with a view type.
/// This is used to determine which view type to use when building a view.
@visibleForTesting
final Map<int, VideoViewType> playerViewTypes = <int, VideoViewType>{};
Copy link
Contributor

Choose a reason for hiding this comment

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

In the interest of making this less confusing to disentangle later, let's instead make the value here a sealed base type called something like _VideoPlayerState, with subtypes for the two video types. The texture view subtype can contain a textureId field, and that can be used on line 186 so that we aren't subtly relying on player ID and texture ID being the same, such that if we change that later in one place, we have to know to change it in a totally different method elsewhere in the file.

(In the short term it'll be redundant in practice because the player ID and the texture ID will be the same, but this way we're tracking the different uses explicitly from the start.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants