ryanheise / just_audio

Audio Player
1.06k stars 678 forks source link

[Android] cleartextTrafficPermitted on localhost must be enabled when call setUrl with headers on API >= 29 #254

Open odrevet opened 3 years ago

odrevet commented 3 years ago

To Reproduce Steps to reproduce the behavior:

On an android 10 (possibly 9), call to AudioPlayer setUrl function will throw an error if the headers parameter is set, even with an empty map {}.

Error messages

I/ExoPlayerImpl(10242): Init 107a4fa [ExoPlayerLib/2.12.1] [a41, SM-A415F, samsung, 29]
D/skia    (10242): SkJpegCodec::onGetPixels +
D/skia    (10242): SkJpegCodec::onGetPixels -
I/System.out(10242): (HTTPLog)-Static: isSBSettingEnabled false
I/System.out(10242): (HTTPLog)-Static: isSBSettingEnabled false
I/System.out(10242): (HTTPLog)-Static: isSBSettingEnabled false
I/System.out(10242): (HTTPLog)-Static: isSBSettingEnabled false
E/ExoPlayerImplInternal(10242): Playback error
E/ExoPlayerImplInternal(10242):   com.google.android.exoplayer2.ExoPlaybackException: Source error
E/ExoPlayerImplInternal(10242):       at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:554)
E/ExoPlayerImplInternal(10242):       at android.os.Handler.dispatchMessage(Handler.java:103)
E/ExoPlayerImplInternal(10242):       at android.os.Looper.loop(Looper.java:237)
E/ExoPlayerImplInternal(10242):       at android.os.HandlerThread.run(HandlerThread.java:67)
E/ExoPlayerImplInternal(10242):   Caused by: com.google.android.exoplayer2.upstream.HttpDataSource$HttpDataSourceException: Unable to connect
E/ExoPlayerImplInternal(10242):       at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:309)
E/ExoPlayerImplInternal(10242):       at com.google.android.exoplayer2.upstream.DefaultDataSource.open(DefaultDataSource.java:199)
E/ExoPlayerImplInternal(10242):       at com.google.android.exoplayer2.upstream.StatsDataSource.open(StatsDataSource.java:84)
E/ExoPlayerImplInternal(10242):       at com.google.android.exoplayer2.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1013)
E/ExoPlayerImplInternal(10242):       at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:415)
E/ExoPlayerImplInternal(10242):       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
E/ExoPlayerImplInternal(10242):       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
E/ExoPlayerImplInternal(10242):       at java.lang.Thread.run(Thread.java:919)
E/ExoPlayerImplInternal(10242):   Caused by: java.io.IOException: Cleartext HTTP traffic to 127.0.0.1 not permitted
E/ExoPlayerImplInternal(10242):       at com.android.okhttp.HttpHandler$CleartextURLFilter.checkURLPermitted(HttpHandler.java:124)
E/ExoPlayerImplInternal(10242):       at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:480)
E/ExoPlayerImplInternal(10242):       at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:135)
E/ExoPlayerImplInternal(10242):       at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.makeConnection(DefaultHttpDataSource.java:589)
E/ExoPlayerImplInternal(10242):       at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.makeConnection(DefaultHttpDataSource.java:493)
E/ExoPlayerImplInternal(10242):       at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:307)
E/ExoPlayerImplInternal(10242):       ... 7 more
E/AudioPlayer(10242): TYPE_SOURCE: Unable to connect
I/flutter (10242): #0      AudioPlayer._load (package:just_audio/just_audio.dart:498:9)
I/flutter (10242): <asynchronous suspension>
I/flutter (10242): #1      AudioPlayer.load (package:just_audio/just_audio.dart:456:30)
I/flutter (10242): #2      AudioPlayer.setUrl (package:just_audio/just_audio.dart:421:7)
I/flutter (10242): #3      AudioPlayerTask.onPlay (package:bide_et_musique/player.dart:162:26)
I/flutter (10242): <asynchronous suspension>
I/flutter (10242): #4      AudioServiceBackground.run.<anonymous closure> (package:audio_service/audio_service.dart:1229:25)
I/flutter (10242): #5      MethodChannel._handleAsMethodCall (package:flutter/src/services/platform_channel.dart:430:55)
I/flutter (10242): #6      MethodChannel.setMethodCallHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:383:34)
I/flutter (10242): #7      _DefaultBinaryMessenger.handlePlatformMessage (package:flutter/src/services/binding.dart:283:33)
I/flutter (10242): #8      _invoke3.<anonymous closure> (dart:ui/hooks.dart:280:15)
I/flutter (10242): #9      _rootRun (dart:async/zone.dart:1190:13)
I/flutter (10242): #10     _CustomZone.run (dart:async/zone.dart:1093:19)
I/flutter (10242): #11     _CustomZone.runGuarded (dart:async/zone.dart:997:7)
I/flutter (10242): #12     _invoke3 (dart:
E/flutter (10242): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: PlatformException((0) Source error, null, null, null)
E/flutter (10242): #0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:582:7)
E/flutter (10242): #1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:159:18)
E/flutter (10242): <asynchronous suspension>
E/flutter (10242): #2      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:332:12)
E/flutter (10242): #3      AudioService.play (package:audio_service/audio_service.dart:928:20)
E/flutter (10242): #4      _SongPlayerWidgetState.play (package:bide_et_musique/widgets/song_app_bar.dart:404:24)
E/flutter (10242): <asynchronous suspension>
E/flutter (10242): #5      _SongPlayerWidgetState.build.<anonymous closure>.<anonymous closure> (package:bide_et_musique/widgets/song_app_bar.dart:344:34)
E/flutter (10242): #6      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:993:19)
E/flutter (10242): #7      _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:1111:38)
E/flutter (10242): #8      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:183:24)
E/flutter (10242): #9      TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:598:11)
E/flutter (10242): #10     BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:287:5)
E/flutter (10242): #11     BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:222:7)
E/flutter (10242): #12     PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:476:9)
E/flutter (10242): #13     PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:77:12)
E/flutter (10242): #14     PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:122:9)
E/flutter (10242): #15     _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:377:8)
E/flutter (10242): #16     PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:120:18)
E/flutter (10242): #17     PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:106:7)
E/flutter (10242): #18     GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:358:19)
E/flutter (10242): #19     GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:338:22)
E/flutter (10242): #20     RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:267:11)
E/flutter (10242): #21     GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:295:7)
E/flutter (10242): #22     GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:240:7)
E/flutter (10242): #23     GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:213:7)
E/flutter (10242): #24     _rootRunUnary (dart:async/zone.dart:1206:13)
E/flutter (10242): #25     _CustomZone.runUnary (dart:async/zone.dart:1100:19)
E/flutter (10242): #26     _CustomZone.runUnaryGuarded (dart:async/zone.dart:1005:7)
E/flutter (10242): #27     _invoke1 (dart:ui/hooks.dart:265:10)
E/flutter (10242): #28     _dispatchPointerDataPacket (dart:ui/hooks.dart:174:5)
E/flutter (10242): 
E/flutter (10242): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: PlatformException(0, Source error, null, null)

Smartphone (please complete the following information):

Flutter SDK version

[✓] Flutter (Channel stable, 1.22.4, on Linux, locale en_US.UTF-8)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)

Additional context There where no issu on Android 8. So I believe this error is thrown due to new security restrictions. I fixed the problem by adding 127.0.0.1 in a network_security_config.xml as explained at https://stackoverflow.com/questions/45940861/android-8-cleartext-http-traffic-not-permitted Please check how just_audio use the localhost adress to prevent this exception on modern android devices.

BR

defsub commented 3 years ago

I used the android:usesCleartextTraffic="true" manifest approach to solve this issue on Android 11. This isn't really a just_audio bug but related to how it cleverly creates an internal proxy to send headers and the proxy itself is using http not https.

ryanheise commented 3 years ago

Oh, that's interesting. My assumption was that using HTTP within the same device would be safe from snooping, and I guess that's true on an unrooted phone.

After checking the SDK, there is an API to create an HTTPS server (SecureServerSocket) which I should probably use.

ryanheise commented 3 years ago

There is also HttpServer.bindSecure. I think the issue here will be that iOS doesn't work well with self-signed server certificates:

https://stackoverflow.com/questions/38217551/how-can-i-use-an-avplayer-with-https-and-self-signed-server-certificates

For apps where security is a relevant concern, I may look into another solution for headers described here:

https://exceptionshub.com/send-headers-with-avplayer-request-in-ios.html

I have avoided using the private API since that may get your app rejected. The two alternatives proposed were to use a proxy (the current solution adopted by just_audio) or a resourceLoader delegate, which may be the right direction to go in.