androidx / media

Jetpack Media3 support libraries for media use cases, including ExoPlayer, an extensible media player for Android
https://developer.android.com/media/media3
Apache License 2.0
1.61k stars 380 forks source link

Bug between Flutter and ExoPlayer - URLs with included BasicAuth do not work #1627

Closed poisonnuke closed 1 month ago

poisonnuke commented 1 month ago

Hi,

Im just an affected user of how ExoPlayer and Flutter work together. When media is stored on a server that requires BasicAuth, this information is stored in the URL in the scheme:

https://username:password@domain.tld

When using the demo-app of ExoPlayer this works without issues but I can see that there is some processing of the URL before setting up the player (the demo-app is quite complex tbh, isnt there a simpler version possible, especially Flutter has a completely different way to use ExoPlayer) From what I can see, when Flutter is using ExoPlayer its just handing the URL to ExoPlayer and then ExoPlayer stops with an exception (as it cannot process this URL in this way)

But because the iOS version of Flutter works I suspect the issue somewhere near the ExoPlayer. As the code of Flutter and ExoPlayer is extremly complex and almost impossible to debug due to the async nature, I'm hoping a dev could at least point out to me what the ExoPlayer app is doing that BasicAuth is working so I can investigate further and then help yall to pinpoint the exact line of code wether its flutter or exoplayer.

oceanjules commented 1 month ago

Do you know if Flutter is using RTSP? If so, I think the URL would need to be of the form: https://developer.android.com/media/media3/exoplayer/rtsp#authentication

The relevant places would be: https://github.com/androidx/media/blob/b01c6ffcb3fca3d038476dab5d3bc9c9f2010781/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMessageUtil.java#L70-L82

https://github.com/androidx/media/blob/b01c6ffcb3fca3d038476dab5d3bc9c9f2010781/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspClient.java#L465-L485

https://github.com/androidx/media/blob/b01c6ffcb3fca3d038476dab5d3bc9c9f2010781/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspAuthenticationInfo.java#L118-L124

poisonnuke commented 1 month ago

It is using DefaultHttpDataSource as MediaSource Factory as far as I can see. Does that help. I cannot find RTSP anywhere in the android implementation of flutter.

icbaker commented 1 month ago

I can see that there is some processing of the URL before setting up the player (the demo-app is quite complex tbh, isnt there a simpler version possible, especially Flutter has a completely different way to use ExoPlayer)

I'm not sure what you mean by this. Most of the complexity in the demo app is not directly related to ExoPlayer and is instead involved in:

  1. Reading a list of samples+metadata from JSON.
  2. Passing info about a MediaItem in an Intent.

Once you get into PlayerActivity (the receiver of the Intent from above), the logic is relatively simple:

  1. Build an ExoPlayer instance, with a customized DefaultMediaSourceFactory to support local and server-side ad insertion, and to use Cronet/HttpEngine instead of HttpUrlConnection.
  2. Pass the MediaItem to this instance using player.setMediaItem.

https://github.com/androidx/media/blob/b01c6ffcb3fca3d038476dab5d3bc9c9f2010781/demos/main/src/main/java/androidx/media3/demo/main/PlayerActivity.java#L261-L299

Specifically I'm not sure what URL processing you are referring to - do you have a link to the code in question? The media URL is taken pretty much verbatim from the JSON file or intent, and passed to MediaItem.Builder.setUri here:

https://github.com/androidx/media/blob/b01c6ffcb3fca3d038476dab5d3bc9c9f2010781/demos/main/src/main/java/androidx/media3/demo/main/IntentUtil.java#L147-L148

Have you filed an issue against Flutter? If this issue only reproduces with Flutter and not the ExoPlayer demo app, then it's likely related to how Flutter is using the ExoPlayer library, and the best people to discuss that with us is probably the Flutter devs.

poisonnuke commented 1 month ago

As written, flutter uses DefaultHttpDataSource, so the Android stack.

From my point of view inside setURI and other methods there is still a lot happening, the URL is disected in lots of components and I could not figure out where this is happening exactly but it looks very much different from what the URL handling in flutter/exoplayer looks like.

Yes Ive filed an issue with flutter but they insist its an issue with ExoPlayer. So Im currently the messenger going back and forth and trying to debug everything myself but Im just not able to fathom the architecture of flutter and exoplayer and therefore I have no idea what code Im stepping into whilst debugging.

At some point the URL is disected into the different strings for host, userinfo and many more and I would like to understand is that something that the DemoApp is doing differently? Where could that happen? And is that really the important part we have to look into to understand the issue (that is, BasicAuth is not working in some scenarios)

FongMi commented 1 month ago

You need to add Header Authorization by yourself.

icbaker commented 1 month ago

As written, flutter uses DefaultHttpDataSource, so the Android stack.

Ah yes, sorry I missed that.

What happens if you change the demo app to use DefaultHttpDataSource instead of Cronet? You can do this by setting this constant to false and rebuilding:

https://github.com/androidx/media/blob/51622b6d80f5e73734d43b4c0b4fd63902ddb0ff/demos/main/src/main/java/androidx/media3/demo/main/DemoUtil.java#L61

Untested hypothesis that this would verify: Cronet has some magic support for basic authentication, while HttpUrlConnection does not (and hence @FongMi's suggestion to add the header yourself).

If this causes the demo app to also be unable to play the content, then the easiest approach is probably to use CronetDataSource in your Flutter integration (see my link about network stacks above for how to do this).

If Flutter doesn't let you modify the DataSource used for playback with ExoPlayer then I'm not sure how you can resolve this - since that is the replaceable component that allows customization of this part of the playback process.

If Flutter does let you modify the DataSource, but can't just switch to using CronetDataSource, you could use a ResolvingDataSource wrapped around DefaultHttpDataSource. The Resolver would parse the username+password from the DataSpec.uri field and add the Authorization header to DataSpec.httpRequestHeaders.

poisonnuke commented 1 month ago

Thank you very much, that is indeed the issue. When using DefaultHttpDataSource it is not working in the Demo either.

Even though by default "HttpEngineDataSource" was used because SDK is 34, therefore the CRONET switch is without function.

What is different in Cronet and HttpEngineDataSource, that they "understand" BasicAuth. Is that something the ExoPlayer team has influence on?

I will address the flutter team as well regarding this issue but I would also like to understand the underlying issue of DefaultHttpDataSource.

icbaker commented 1 month ago

What is different in Cronet and HttpEngineDataSource, that they "understand" BasicAuth. Is that something the ExoPlayer team has influence on?

AFAICT ExoPlayer doesn't manually set the "Authorization" header anywhere outside of the RTSP module: https://github.com/search?q=repo%3Aandroidx%2Fmedia+authorization+-path%3Artsp&type=code

We don't have control over the behaviour of the Cronet library, or the HttpEngine or HttpUrlConnection platform APIs.

We could add custom logic to DefaultHttpDataSource to try and detect this case and add the header, but since it's easy for an app to do this themselves using ResolvingDataSource, I'm not sure there's much benefit to this (and it risks breaking other existing uses of the class which aren't expecting this magic).

I'm going to close this issue because I don't think there's an issue in ExoPlayer here, just a difference in behaviour between the HTTP stacks used underneath the library.