ryanheise / just_audio

Audio Player
1.05k stars 677 forks source link

Adding Headers to setUrl #99

Closed Jeremiah104 closed 4 years ago

Jeremiah104 commented 4 years ago

Hi I am new to flutter and I am trying to stream an audio file from amazon s3, this package seems to be able to do everything I need when I use a public link but I need to access a link that uses headers for authentication and I can't find where to add headers.

I'm not sure if there is a simple solution to this that I am unaware of, or if it is a functionality that would need to be added.

shipofsea commented 4 years ago

Hello, can you let me know the audio file url for test? thanks

shipofsea commented 4 years ago

I have read your post on upwork. so I send this message to you here

Jeremiah104 commented 4 years ago

Hi thanks for responding, The url is https://moc-app-test.s3.amazonaws.com/audios/test.mp3.

The audio file URL is inaccessible because you need the authentication headers. This is the goal of my question here - I need to be able to add S3 authentication headers to this package so that I can access and stream a non public file. (I am using https://github.com/diagnosia/flutter_aws_s3_client to generate the header info)

shipofsea commented 4 years ago

Hi, can you provide AWS S3 key for me to test?

Jeremiah104 commented 4 years ago

Yeah I just don't want to post that publicly, what is your Upwork name/id? We can talk there and I can get you the keys.

shipofsea commented 4 years ago

ok, then let's discuss via skype my skype id is live:fc722aec9c3a7bb4 please let you send message with secure rar file via skype.

ryanheise commented 4 years ago

Hi @Jeremiah104 and @shipofsea

Is there a reason why the issue is now closed?

This is a useful feature of benefit to the larger user base and I was going to work on it, but I wasn't sure if @shipofsea had started working on it and didn't want to interfere with that particularly if it was being done through upwork or some other arrangement.

Note that there is now a pull request that implements this feature by @raveesh-me here: #107 , which provides guidance on how such a feature could be added.

Since @Jeremiah104 closed this issue without a solution being added to the repo, please track the new open issue #105 (which is essentially the same) if you are still interested in this feature, and I will do an implementation based on @raveesh-me 's suggestions.

raveesh-me commented 4 years ago

Hi @ryanheise This was done by Cookytech team on Upwork. @Jeremiah104 is using a fork of our fork of your project, and we decided to send a pull request to share the results with you.

Jeremiah104 commented 4 years ago

Hi @ryanheise, Sorry about that lack of communication from me. @raveesh-me has solved the problem for me and I have been using their fork of the repository and it has been working great.

BartusZak commented 3 years ago

@Jeremiah104 Have you guys figured out how to generate and pass headers dynamically? I'm working on HLS Audio player and need to create header (SigV4) per file from S3.

ryanheise commented 3 years ago

@BartusZak

I've written API documentation for every method and constructor in this plugin and I'm sure that if you read the API documentation for whichever method you're using, you will either find the answer to your question, or you will at least be in a position to submit a documentation request so that I can clear up any confusing documentation.

defsub commented 3 years ago

AudioSource.uri allows you to pass headers. I stream from s3 doing something similar. In my case I need to pass a cookie to the server and if the cookie is valid, the server will redirect to a presigned time-based s3 url to stream the media. From the UI side you can use MediaItem extras to pass along header information and within your player task extract headers from extras and use with AudioSource.uri. Last I looked I think setUrl was a shortcut for AudioSource.uri and setUrl also allows you to pass headers.

ryanheise commented 3 years ago

Well that's certainly a much more helpful response than mine :-) But in my defence, I am more concerned with using GitHub for project development and using StackOverflow for community support. It's a bit overwhelming to manage otherwise.

To that end, let's keep these GitHub pages focused on the improvement and development of the plugin. In what ways can the documentation be improved (@BartusZak ?)

As for the question, it has actually been asked and answered on StackOverflow, but I am concerned that if someone really needs to ask that question, I can do a better job of the documentation.

BartusZak commented 3 years ago

Hey @defsub and @ryanheise

So sorry for late reply. We had a pause on that requirement.

What I ends up doing (trying):

TLDR: Public Access works perfectly on WEB but produces some errors/warnings on Andorid. Private S3 Bucket does not work either on WEB or Android.

Public Access

(works on WEB and Android - producess warnings? )

  ConcatenatingAudioSource _playlist = ConcatenatingAudioSource(children: [
    AudioSource.uri(
      Uri.parse(
          "https://bartuszak-full-public.s3-eu-west-1.amazonaws.com/boska-komedia-czysciec_001_czysciec-piesn-pierwsza.mp3"),
      tag: AudioMetadata(
          title: "Public access TEST",
          artwork:
              "https://zak-dev-public-access-bucket.s3-eu-west-1.amazonaws.com/audiobooks/Boska_Komedia/boska-komedia-icon.jpg"),
    ),
  ]);
Android console ``` D/CCodecConfig( 5927): c2::u32 input.delay.value = 0 D/CCodecConfig( 5927): string input.media-type.value = "audio/mpeg" D/CCodecConfig( 5927): string output.media-type.value = "audio/raw" D/CCodecConfig( 5927): c2::u32 raw.channel-count.value = 2 D/CCodecConfig( 5927): c2::u32 raw.sample-rate.value = 44100 D/CCodecConfig( 5927): } D/CCodec ( 5927): [c2.android.mp3.decoder] buffers are bound to CCodec for this session D/CCodecConfig( 5927): no c2 equivalents for flags D/CCodecConfig( 5927): config failed => CORRUPTED D/CCodecConfig( 5927): c2 config diff is c2::u32 raw.channel-count.value = 1 W/Codec2Client( 5927): query -- param skipped: index = 1107298332. D/CCodec ( 5927): client requested max input size 4096, which is smaller than what component recommended (8192); overriding with component recommendation. W/CCodec ( 5927): This behavior is subject to change. It is recommended that app developers double check whether the requested max input size is in reasonable range. D/CCodec ( 5927): setup formats input: AMessage(what = 0x00000000) = { D/CCodec ( 5927): int32_t channel-count = 1 D/CCodec ( 5927): int32_t max-input-size = 8192 D/CCodec ( 5927): string mime = "audio/mpeg" D/CCodec ( 5927): int32_t sample-rate = 44100 D/CCodec ( 5927): } and output: AMessage(what = 0x00000000) = { D/CCodec ( 5927): int32_t channel-count = 1 D/CCodec ( 5927): string mime = "audio/raw" D/CCodec ( 5927): int32_t sample-rate = 44100 D/CCodec ( 5927): } W/Codec2Client( 5927): query -- param skipped: index = 1342179345. W/Codec2Client( 5927): query -- param skipped: index = 2415921170. 2 E/FMQ ( 5927): grantorIdx must be less than 3 D/CCodecBufferChannel( 5927): [c2.android.mp3.decoder#735] Created input block pool with allocatorID 16 => poolID 17 - OK (0) I/CCodecBufferChannel( 5927): [c2.android.mp3.decoder#735] Created output block pool with allocatorID 16 => poolID 31 - OK D/CCodecBufferChannel( 5927): [c2.android.mp3.decoder#735] Configured output block pool ids 31 => OK E/ion ( 5927): ioctl c0044901 failed with code -1: Inappropriate ioctl for device 2 E/FMQ ( 5927): grantorIdx must be less than 3 D/BufferPoolAccessor2.0( 5927): bufferpool2 0xaffe1ad8 : 5(40960 size) total buffers - 4(32768 size) used buffers - 0/5 (recycle/alloc) - 4/38 (fetch/transfer) ```

Private S3

(sigV4 headers required)

  _playlist.add(AudioSource.uri(
        Uri.parse(
            "https://zak-dev-user-access-bucket/all-access/audiobooks/Boska_Komedia/boska-komedia-czysciec_001_czysciec-piesn-pierwsza.mp3"),
        headers: _s3Repository.generateSignedHeaders(
            "https://zak-dev-user-access-bucket/all-access/audiobooks/Boska_Komedia/boska-komedia-czysciec_001_czysciec-piesn-pierwsza.mp3"),
        tag: AudioMetadata(
            title: widget.audiobook.content[0].title,
            artwork:
                "https://zak-dev-public-access-bucket.s3-eu-west-1.amazonaws.com/audiobooks/Boska_Komedia/boska-komedia-icon.jpg"),
      ));

WEB

(not supported) - ok, can we support it?

WEB console ``` TypeError: Cannot read property 'addUriAudioSource' of null at just_audio.ProgressiveAudioSource.new._setup (http://localhost:58146/packages/just_audio/just_audio.dart.lib.js:2091:47) at _setup.next () at http://localhost:58146/dart_sdk.js:39029:33 at _RootZone.runUnary (http://localhost:58146/dart_sdk.js:38886:58) at _FutureListener.thenAwait.handleValue (http://localhost:58146/dart_sdk.js:33872:29) at handleValueCallback (http://localhost:58146/dart_sdk.js:34432:49) at Function._propagateToListeners (http://localhost:58146/dart_sdk.js:34470:17) at _Future.new.[_completeWithValue] (http://localhost:58146/dart_sdk.js:34312:23) at async._AsyncCallbackEntry.new.callback (http://localhost:58146/dart_sdk.js:34335:35) at Object._microtaskLoop (http://localhost:58146/dart_sdk.js:39173:13) at _startMicrotaskLoop (http://localhost:58146/dart_sdk.js:39179:13) at http://localhost:58146/dart_sdk.js:34686:9 ```

Android

Android console ``` I/TetheringManager( 5542): registerTetheringEventCallback: W/( 5542): Accessing hidden method Landroid/media/AudioTrack;->getLatency()I (greylist, reflection, allowed) I/ExoPlayerImpl( 5542): Init dfdccc9 [ExoPlayerLib/2.13.1] [generic_x86_arm, sdk_gphone_x86_arm, Google, 30] I/VideoCapabilities( 5542): Unsupported profile 4 for video/mp4v-es D/CCodec ( 5542): allocate(c2.android.mp3.decoder) I/Codec2Client( 5542): Available Codec2 services: "software" I/CCodec ( 5542): Created component [c2.android.mp3.decoder] D/CCodecConfig( 5542): read media type: audio/mpeg D/ReflectedParamUpdater( 5542): extent() != 1 for single value type: algo.buffers.max-count.values D/ReflectedParamUpdater( 5542): extent() != 1 for single value type: output.subscribed-indices.values D/ReflectedParamUpdater( 5542): extent() != 1 for single value type: input.buffers.allocator-ids.values D/ReflectedParamUpdater( 5542): extent() != 1 for single value type: output.buffers.allocator-ids.values D/ReflectedParamUpdater( 5542): extent() != 1 for single value type: algo.buffers.allocator-ids.values D/ReflectedParamUpdater( 5542): extent() != 1 for single value type: output.buffers.pool-ids.values D/ReflectedParamUpdater( 5542): extent() != 1 for single value type: algo.buffers.pool-ids.values I/CCodecConfig( 5542): query failed after returning 7 values (BAD_INDEX) D/CCodecConfig( 5542): c2 config diff is Dict { D/CCodecConfig( 5542): c2::u32 coded.bitrate.value = 64000 D/CCodecConfig( 5542): c2::u32 input.buffers.max-size.value = 8192 D/CCodecConfig( 5542): c2::u32 input.delay.value = 0 D/CCodecConfig( 5542): string input.media-type.value = "audio/mpeg" D/CCodecConfig( 5542): string output.media-type.value = "audio/raw" D/CCodecConfig( 5542): c2::u32 raw.channel-count.value = 2 ```
Android - more logs ``` E/ExoPlayerImplInternal( 5927): at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:415) E/ExoPlayerImplInternal( 5927): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) E/ExoPlayerImplInternal( 5927): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) E/ExoPlayerImplInternal( 5927): at java.lang.Thread.run(Thread.java:923) E/ExoPlayerImplInternal( 5927): Caused by: java.io.IOException: Cleartext HTTP traffic to 127.0.0.1 not permitted E/ExoPlayerImplInternal( 5927): at com.android.okhttp.HttpHandler$CleartextURLFilter.checkURLPermitted(HttpHandler.java:127) E/ExoPlayerImplInternal( 5927): at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:462) E/ExoPlayerImplInternal( 5927): at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:131) E/ExoPlayerImplInternal( 5927): at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.makeConnection(DefaultHttpDataSource.java:641) E/ExoPlayerImplInternal( 5927): at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.makeConnection(DefaultHttpDataSource.java:543) E/ExoPlayerImplInternal( 5927): at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:349) E/ExoPlayerImplInternal( 5927): ... 7 more E/AudioPlayer( 5927): TYPE_SOURCE: Cleartext HTTP traffic not permitted. See https://exoplayer.dev/issues/cleartext-not-permitted E/flutter ( 5927): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: PlatformException(0, Source error, null, null) E/flutter ( 5927): E/ExoPlayerImplInternal( 5927): Playback error E/ExoPlayerImplInternal( 5927): com.google.android.exoplayer2.ExoPlaybackException: Source error E/ExoPlayerImplInternal( 5927): at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:579) E/ExoPlayerImplInternal( 5927): at android.os.Handler.dispatchMessage(Handler.java:102) E/ExoPlayerImplInternal( 5927): at android.os.Looper.loop(Looper.java:223) E/ExoPlayerImplInternal( 5927): at android.os.HandlerThread.run(HandlerThread.java:67) E/ExoPlayerImplInternal( 5927): Caused by: com.google.android.exoplayer2.upstream.HttpDataSource$CleartextNotPermittedException: Cleartext HTTP traffic not permitted. See https://exoplayer.dev/issues/cleartext-not-permitted E/ExoPlayerImplInternal( 5927): at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:354) E/ExoPlayerImplInternal( 5927): at com.google.android.exoplayer2.upstream.DefaultDataSource.open(DefaultDataSource.java:201) E/ExoPlayerImplInternal( 5927): at com.google.android.exoplayer2.upstream.StatsDataSource.open(StatsDataSource.java:84) E/ExoPlayerImplInternal( 5927): at com.google.android.exoplayer2.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1015) E/ExoPlayerImplInternal( 5927): at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:415) E/ExoPlayerImplInternal( 5927): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) E/ExoPlayerImplInternal( 5927): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) E/ExoPlayerImplInternal( 5927): at java.lang.Thread.run(Thread.java:923) E/ExoPlayerImplInternal( 5927): Caused by: java.io.IOException: Cleartext HTTP traffic to 127.0.0.1 not permitted E/ExoPlayerImplInternal( 5927): at com.android.okhttp.HttpHandler$CleartextURLFilter.checkURLPermitted(HttpHandler.java:127) E/ExoPlayerImplInternal( 5927): at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:462) E/ExoPlayerImplInternal( 5927): at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:131) E/ExoPlayerImplInternal( 5927): at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.makeConnection(DefaultHttpDataSource.java:641) E/ExoPlayerImplInternal( 5927): at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.makeConnection(DefaultHttpDataSource.java:543) E/ExoPlayerImplInternal( 5927): at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:349) E/ExoPlayerImplInternal( 5927): ... 7 more E/AudioPlayer( 5927): TYPE_SOURCE: Cleartext HTTP traffic not permitted. See https://exoplayer.dev/issues/cleartext-not-permitted E/flutter ( 5927): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: PlatformException(0, Source error, null, null) E/flutter ( 5927): ```
Flutter 1.26.0-17.6.pre • channel beta • https://github.com/flutter/flutter.git
Framework • revision a29104a69b (11 days ago) • 2021-02-16 09:26:56 -0800      
Engine • revision 21fa8bb99e
Tools • Dart 2.12.0 (build 2.12.0-259.12.beta)
 just_audio: ^0.6.14+1
Full dart code ```dart import 'dart:math'; import 'package:audio_session/audio_session.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:just_audio/just_audio.dart'; import 'package:multibook/features/audiobook/data/models/audiobook/AudiobookModel.dart'; import 'package:multibook/repositories/s3/S3Repository.dart'; import 'package:rxdart/rxdart.dart'; class AudiobookAudioPlayer extends StatefulWidget { const AudiobookAudioPlayer({@required this.audiobook}); final AudiobookModel audiobook; @override _AudiobookAudioPlayerState createState() => _AudiobookAudioPlayerState(); } class _AudiobookAudioPlayerState extends State { AudioPlayer _player; S3Repository _s3Repository; int _addedCount = 0; ConcatenatingAudioSource _playlist; //PUBLIC // ConcatenatingAudioSource _playlist = ConcatenatingAudioSource(children: [ // AudioSource.uri( // Uri.parse( // "https://bartuszak-full-public.s3-eu-west-1.amazonaws.com/boska-komedia-czysciec_001_czysciec-piesn-pierwsza.mp3"), // tag: AudioMetadata( // title: "TEST", // artwork: // "https://zak-dev-public-access-bucket.s3-eu-west-1.amazonaws.com/audiobooks/Boska_Komedia/boska-komedia-icon.jpg"), // ), // ]); @override void initState() { super.initState(); _s3Repository = RepositoryProvider.of(context); _player = AudioPlayer(); SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( statusBarColor: Colors.black, )); //PRIVATE _playlist = ConcatenatingAudioSource( children: widget.audiobook.content .map( (content) => AudioSource.uri( Uri.parse(widget.audiobook.url + content.file), headers: _s3Repository .generateSignedHeaders(widget.audiobook.url + content.file), tag: AudioMetadata( title: content.title, artwork: "https://zak-dev-public-access-bucket.s3-eu-west-1.amazonaws.com/audiobooks/Boska_Komedia/boska-komedia-icon.jpg"), ), ) .toList()); _init(); } _init() async { final session = await AudioSession.instance; await session.configure(AudioSessionConfiguration.speech()); try { await _player.setAudioSource(_playlist); // _playlist.add(AudioSource.uri( // Uri.parse( // "https://zak-dev-user-access-bucket/all-access/audiobooks/Boska_Komedia/boska-komedia-czysciec_001_czysciec-piesn-pierwsza.mp3"), // headers: _s3Repository.generateSignedHeaders( // "https://zak-dev-user-access-bucket/all-access/audiobooks/Boska_Komedia/boska-komedia-czysciec_001_czysciec-piesn-pierwsza.mp3"), // tag: AudioMetadata( // title: widget.audiobook.content[0].title, // artwork: // "https://zak-dev-public-access-bucket.s3-eu-west-1.amazonaws.com/audiobooks/Boska_Komedia/boska-komedia-icon.jpg"), // )); } catch (e) { // catch load errors: 404, invalid url ... print("An error occured $e"); } } @override void dispose() { _player.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: StreamBuilder( stream: _player.sequenceStateStream, builder: (context, snapshot) { final state = snapshot.data; if (state?.sequence?.isEmpty ?? true) return SizedBox(); final metadata = state.currentSource.tag as AudioMetadata; return Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: Center(child: Image.network(metadata.artwork)), ), ), Text(metadata.album ?? '', style: Theme.of(context).textTheme.headline6), Text(metadata.title ?? ''), ], ); }, ), ), ControlButtons(_player), StreamBuilder( stream: _player.durationStream, builder: (context, snapshot) { final duration = snapshot.data ?? Duration.zero; return StreamBuilder( stream: Rx.combineLatest2( _player.positionStream, _player.bufferedPositionStream, (position, bufferedPosition) => PositionData(position, bufferedPosition)), builder: (context, snapshot) { final positionData = snapshot.data ?? PositionData(Duration.zero, Duration.zero); var position = positionData.position ?? Duration.zero; if (position > duration) { position = duration; } var bufferedPosition = positionData.bufferedPosition ?? Duration.zero; if (bufferedPosition > duration) { bufferedPosition = duration; } return SeekBar( duration: duration, position: position, bufferedPosition: bufferedPosition, onChangeEnd: (newPosition) { _player.seek(newPosition); }, ); }, ); }, ), SizedBox(height: 8.0), Row( children: [ StreamBuilder( stream: _player.loopModeStream, builder: (context, snapshot) { final loopMode = snapshot.data ?? LoopMode.off; const icons = [ Icon(Icons.repeat, color: Colors.grey), Icon(Icons.repeat, color: Colors.orange), Icon(Icons.repeat_one, color: Colors.orange), ]; const cycleModes = [ LoopMode.off, LoopMode.all, LoopMode.one, ]; final index = cycleModes.indexOf(loopMode); return IconButton( icon: icons[index], onPressed: () { _player.setLoopMode(cycleModes[ (cycleModes.indexOf(loopMode) + 1) % cycleModes.length]); }, ); }, ), Expanded( child: Text( "Playlist", style: Theme.of(context).textTheme.headline6, textAlign: TextAlign.center, ), ), StreamBuilder( stream: _player.shuffleModeEnabledStream, builder: (context, snapshot) { final shuffleModeEnabled = snapshot.data ?? false; return IconButton( icon: shuffleModeEnabled ? Icon(Icons.shuffle, color: Colors.orange) : Icon(Icons.shuffle, color: Colors.grey), onPressed: () async { final enable = !shuffleModeEnabled; if (enable) { await _player.shuffle(); } await _player.setShuffleModeEnabled(enable); }, ); }, ), ], ), // Container( // height: 240.0, // child: StreamBuilder( // stream: _player.sequenceStateStream, // builder: (context, snapshot) { // final state = snapshot.data; // final sequence = state?.sequence ?? []; // return ReorderableListView( // onReorder: (int oldIndex, int newIndex) { // if (oldIndex < newIndex) newIndex--; // _playlist.move(oldIndex, newIndex); // }, // children: [ // for (var i = 0; i < sequence.length; i++) // Dismissible( // key: ValueKey(sequence[i]), // background: Container( // color: Colors.redAccent, // alignment: Alignment.centerRight, // child: Padding( // padding: const EdgeInsets.only(right: 8.0), // child: Icon(Icons.delete, color: Colors.white), // ), // ), // onDismissed: (dismissDirection) { // _playlist.removeAt(i); // }, // child: Material( // color: i == state.currentIndex // ? Colors.grey.shade300 // : null, // child: ListTile( // title: Text(sequence[i].tag.title), // onTap: () { // _player.seek(Duration.zero, index: i); // }, // ), // ), // ), // ], // ); // }, // ), // ), ], ), ); } } class ControlButtons extends StatelessWidget { final AudioPlayer player; ControlButtons(this.player); @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: Icon(Icons.volume_up), onPressed: () { _showSliderDialog( context: context, title: "Adjust volume", divisions: 10, min: 0.0, max: 1.0, stream: player.volumeStream, onChanged: player.setVolume, ); }, ), StreamBuilder( stream: player.sequenceStateStream, builder: (context, snapshot) => IconButton( icon: Icon(Icons.skip_previous), onPressed: player.hasPrevious ? player.seekToPrevious : null, ), ), StreamBuilder( stream: player.playerStateStream, builder: (context, snapshot) { final playerState = snapshot.data; final processingState = playerState?.processingState; final playing = playerState?.playing; if (processingState == ProcessingState.loading || processingState == ProcessingState.buffering) { return Container( margin: EdgeInsets.all(8.0), width: 64.0, height: 64.0, child: CircularProgressIndicator(), ); } else if (playing != true) { return IconButton( icon: Icon(Icons.play_arrow), iconSize: 64.0, onPressed: player.play, ); } else if (processingState != ProcessingState.completed) { return IconButton( icon: Icon(Icons.pause), iconSize: 64.0, onPressed: player.pause, ); } else { return IconButton( icon: Icon(Icons.replay), iconSize: 64.0, onPressed: () => player.seek(Duration.zero, index: player.effectiveIndices.first), ); } }, ), StreamBuilder( stream: player.sequenceStateStream, builder: (context, snapshot) => IconButton( icon: Icon(Icons.skip_next), onPressed: player.hasNext ? player.seekToNext : null, ), ), StreamBuilder( stream: player.speedStream, builder: (context, snapshot) => IconButton( icon: Text("${snapshot.data?.toStringAsFixed(1)}x", style: TextStyle(fontWeight: FontWeight.bold)), onPressed: () { _showSliderDialog( context: context, title: "Adjust speed", divisions: 10, min: 0.5, max: 1.5, stream: player.speedStream, onChanged: player.setSpeed, ); }, ), ), ], ); } } class SeekBar extends StatefulWidget { final Duration duration; final Duration position; final Duration bufferedPosition; final ValueChanged onChanged; final ValueChanged onChangeEnd; SeekBar({ @required this.duration, @required this.position, @required this.bufferedPosition, this.onChanged, this.onChangeEnd, }); @override _SeekBarState createState() => _SeekBarState(); } class _SeekBarState extends State { double _dragValue; SliderThemeData _sliderThemeData; @override void didChangeDependencies() { super.didChangeDependencies(); _sliderThemeData = SliderTheme.of(context).copyWith( trackHeight: 2.0, ); } @override Widget build(BuildContext context) { return Stack( children: [ SliderTheme( data: _sliderThemeData.copyWith( thumbShape: HiddenThumbComponentShape(), activeTrackColor: Colors.blue.shade100, inactiveTrackColor: Colors.grey.shade300, ), child: ExcludeSemantics( child: Slider( min: 0.0, max: widget.duration.inMilliseconds.toDouble(), value: widget.bufferedPosition.inMilliseconds.toDouble(), onChanged: (value) { setState(() { _dragValue = value; }); if (widget.onChanged != null) { widget.onChanged(Duration(milliseconds: value.round())); } }, onChangeEnd: (value) { if (widget.onChangeEnd != null) { widget.onChangeEnd(Duration(milliseconds: value.round())); } _dragValue = null; }, ), ), ), SliderTheme( data: _sliderThemeData.copyWith( inactiveTrackColor: Colors.transparent, ), child: Slider( min: 0.0, max: widget.duration.inMilliseconds.toDouble(), value: min(_dragValue ?? widget.position.inMilliseconds.toDouble(), widget.duration.inMilliseconds.toDouble()), onChanged: (value) { setState(() { _dragValue = value; }); if (widget.onChanged != null) { widget.onChanged(Duration(milliseconds: value.round())); } }, onChangeEnd: (value) { if (widget.onChangeEnd != null) { widget.onChangeEnd(Duration(milliseconds: value.round())); } _dragValue = null; }, ), ), Positioned( right: 16.0, bottom: 0.0, child: Text( RegExp(r'((^0*[1-9]\d*:)?\d{2}:\d{2})\.\d+$') .firstMatch("$_remaining") ?.group(1) ?? '$_remaining', style: Theme.of(context).textTheme.caption), ), ], ); } Duration get _remaining => widget.duration - widget.position; } _showSliderDialog({ BuildContext context, String title, int divisions, double min, double max, String valueSuffix = '', Stream stream, ValueChanged onChanged, }) { showDialog( context: context, builder: (context) => AlertDialog( title: Text(title, textAlign: TextAlign.center), content: StreamBuilder( stream: stream, builder: (context, snapshot) => Container( height: 100.0, child: Column( children: [ Text('${snapshot.data?.toStringAsFixed(1)}$valueSuffix', style: TextStyle( fontFamily: 'Fixed', fontWeight: FontWeight.bold, fontSize: 24.0)), Slider( divisions: divisions, min: min, max: max, value: snapshot.data ?? 1.0, onChanged: onChanged, ), ], ), ), ), ), ); } class AudioMetadata { final String album; final String title; final String artwork; AudioMetadata({this.album, this.title, this.artwork}); } class HiddenThumbComponentShape extends SliderComponentShape { @override Size getPreferredSize(bool isEnabled, bool isDiscrete) => Size.zero; @override void paint( PaintingContext context, Offset center, { Animation activationAnimation, Animation enableAnimation, bool isDiscrete, TextPainter labelPainter, RenderBox parentBox, SliderThemeData sliderTheme, TextDirection textDirection, double value, double textScaleFactor, Size sizeWithOverflow, }) {} } class PositionData { final Duration position; final Duration bufferedPosition; PositionData(this.position, this.bufferedPosition); } ```
ryanheise commented 3 years ago

I/CCodecConfig( 5542): query failed after returning 7 values (BAD_INDEX)

Sounds like an Exoplayer issue. After searching for the error message I found this:

https://github.com/google/ExoPlayer/issues/6397

Note the comments about the encoding (if it is within your control, you could try a different encoding)

Cleartext HTTP traffic to 127.0.0.1 not permitted

This is because headers are supported through the use of a clear text proxy running on the device. In recent versions of Android, it is now required to add the cleartext option to your manifest (search the README for cleartext). But see also this discussion if you want to secure the proxy. (If you need to do that, there is now a way to hook into the proxy using StreamAudioSource).

BartusZak commented 3 years ago

Hey @ryanheise

Adding that to AndroidManifest.xml removed Cleartext HTTP traffic to 127.0.0.1 not permitted error.

 <application ... android:usesCleartextTraffic="true">

What left

Android console ``` I/ExoPlayerImpl( 4718): Init e8d5442 [ExoPlayerLib/2.13.1] [generic_x86_arm, sdk_gphone_x86_arm, Google, 30] I/VideoCapabilities( 4718): Unsupported profile 4 for video/mp4v-es D/CCodec ( 4718): allocate(c2.android.mp3.decoder) I/Codec2Client( 4718): Available Codec2 services: "software" I/CCodec ( 4718): Created component [c2.android.mp3.decoder] D/CCodecConfig( 4718): read media type: audio/mpeg D/ReflectedParamUpdater( 4718): extent() != 1 for single value type: algo.buffers.max-count.values D/ReflectedParamUpdater( 4718): extent() != 1 for single value type: output.subscribed-indices.values D/ReflectedParamUpdater( 4718): extent() != 1 for single value type: input.buffers.allocator-ids.values D/ReflectedParamUpdater( 4718): extent() != 1 for single value type: output.buffers.allocator-ids.values D/ReflectedParamUpdater( 4718): extent() != 1 for single value type: algo.buffers.allocator-ids.values D/ReflectedParamUpdater( 4718): extent() != 1 for single value type: output.buffers.pool-ids.values D/ReflectedParamUpdater( 4718): extent() != 1 for single value type: algo.buffers.pool-ids.values I/CCodecConfig( 4718): query failed after returning 7 values (BAD_INDEX) D/CCodecConfig( 4718): c2 config diff is Dict { D/CCodecConfig( 4718): c2::u32 coded.bitrate.value = 64000 D/CCodecConfig( 4718): c2::u32 input.buffers.max-size.value = 8192 D/CCodecConfig( 4718): c2::u32 input.delay.value = 0 D/CCodecConfig( 4718): string input.media-type.value = "audio/mpeg" D/CCodecConfig( 4718): string output.media-type.value = "audio/raw" D/CCodecConfig( 4718): c2::u32 raw.channel-count.value = 2 D/CCodecConfig( 4718): c2::u32 raw.sample-rate.value = 44100 D/CCodecConfig( 4718): } D/CCodec ( 4718): [c2.android.mp3.decoder] buffers are bound to CCodec for this session D/CCodecConfig( 4718): no c2 equivalents for flags D/CCodecConfig( 4718): config failed => CORRUPTED D/CCodecConfig( 4718): c2 config diff is c2::u32 raw.channel-count.value = 1 W/Codec2Client( 4718): query -- param skipped: index = 1107298332. ```

Player works! It loooks like those are info logs and ExoPlayer logs level is preatty high. I don't know If I should leave it for now.

What can I do for you to help with WEB support for generated headers?

For WEB it gives: An error occured NoSuchMethodError: invalid member on null: 'addUriAudioSource'

ryanheise commented 3 years ago

For web, what's required is service workers support in Flutter. There is the service_worker package but that doesn't appear to have Flutter support. That would require integration with Flutter's build process, so support would need to be built into Flutter itself.

github-actions[bot] commented 3 years ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs, or use StackOverflow if you need help with just_audio.