-
-
Notifications
You must be signed in to change notification settings - Fork 504
Description
Describe the bug
Im really triggered about this, 1 week with this bug.
The player starts, the Queue is set and everything goes fine in the background BUT the StreamBuilder in the UI cant see what is happening, all the values returned are null or blank;
Minimal reproduction project
Screenshots to see part of the codes.
I really cant share the project, its for a big company and the proj. is pretty large. I tried to reproduce the bug in two ways:
- Making some changes that my project have in the exemple project: No bug.
- Copy and Pasting the exemple project code into my project: The bug persisted.
The way i can help whit some kind of repro is pasting the relevant parts of the code, i guess. Let me know if this could help.
I have JSON files with the streams.
In OnStart
:
- The app loads the user language(
prefs
) and then it loads the respective file; - With the file in hands i parse the JSON and create the
MediaItem
List; - The app set the Queue with this list, set the current
MediaItem
and the player set the URL.
Its starts playing all fine.
The StreamBuilder
in UI reads the ScreenState
, but all the values returns null, for exemple screenState?.queue
always return []
, pre and post play.
I found a way to go through this:
I load the playlist 2 times:
- In the
BackgroundAudioTask
so it can set the queue to the player; - In the UI so i can build the screen with the file info.
The BackgroundAudioTask
sends a port message with the MediaItem
Index of the List, the UI receives it and it shows the current MediaItem being played.
To Reproduce
Could not reproduce in exemple, cant share the company files.
Error messages
I got one error that disappeared when importing the exemple code to my project:
E/flutter (30225): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: Failed assertion: boolean expression must not be null
E/flutter (30225): #0 AudioPlayerTask.getControls
package:flutter_radio_nt/main.dart:427
E/flutter (30225): #1 AudioPlayerTask._setState
package:flutter_radio_nt/main.dart:360
E/flutter (30225): #2 AudioPlayerTask.onStart.<anonymous closure>
Expected behavior
null
Screenshots
The Main App is a stateful widget, i call the AudioServiceWidget before everything:
I read the stream inside an AnimatedBuilder (flutter_sequence_animation):
That middle print tells me that everything is null is blank.
Now the Background:
This i can share:
class AudioPlayerTask extends BackgroundAudioTask {
AudioPlayer player = new AudioPlayer();
List<MediaItem> radioStations = new List<MediaItem>();
bool playing;
bool _interrupted = false;
int currentIndex = 0;
bool get hasNext => currentIndex + 1 < radioStations.length;
AudioProcessingState _skipState;
StreamSubscription<AudioPlaybackState> _playerStateSubscription;
StreamSubscription<AudioPlaybackEvent> _eventSubscription;
@override
void onTaskRemoved() {
// TODO: implement onTaskRemoved
onStop();
super.onTaskRemoved();
}
@override
Future<void> onStart(Map<String, dynamic> params) async {
_playerStateSubscription = player.playbackStateStream
.where((state) => state == AudioPlaybackState.completed)
.listen((state) {
_handlePlaybackCompleted();
});
_eventSubscription = player.playbackEventStream.listen((event) {
final bufferingState =
event.buffering ? AudioProcessingState.buffering : null;
switch (event.state) {
case AudioPlaybackState.paused:
_setState(
processingState: AudioProcessingState.connecting,
position: event.position,
);
break;
case AudioPlaybackState.playing:
_setState(
processingState: bufferingState != null
? AudioProcessingState.connecting
: AudioProcessingState.ready,
position: event.position,
);
break;
case AudioPlaybackState.connecting:
_setState(
processingState: _skipState ?? AudioProcessingState.connecting,
position: event.position,
);
break;
case AudioPlaybackState.stopped:
_setState(
processingState: _skipState ?? AudioProcessingState.stopped,
position: event.position,
);
break;
default:
break;
}
});
final prefs = await SharedPreferences.getInstance();
final lang = prefs.getString('language') ?? 'pt';
currentIndex = prefs.getInt('selectedIndex') ?? 0;
await loadStations(lang).then((onValue) {
radioStations = onValue;
AudioServiceBackground.setQueue(radioStations);
});
if (playing == null) {
await AudioServiceBackground.setMediaItem(radioStations[currentIndex]);
await player.setUrl(radioStations[currentIndex].id);
onPlay();
}
print('OnStart Called');
}
void _handlePlaybackCompleted() {
if (hasNext) {
onSkipToNext();
} else {
onStop();
}
}
@override
Future<void> onSkipToNext() => _skip(1);
@override
Future<void> onSkipToPrevious() => _skip(-1);
@override
Future<void> onSkipToQueueItem(String item) async {
for (var i = 0; i < radioStations.length; i++) {
if (radioStations[i].id == item) {
currentIndex = i;
break;
}
}
await player.stop();
AudioServiceBackground.setQueue(radioStations);
AudioServiceBackground.setMediaItem(radioStations[currentIndex]);
await player.setUrl(radioStations[currentIndex].id);
_skipState = null;
onPlay();
//print('skipped to ' + radioStations[currentIndex].id);
//print('queue' + AudioService.currentMediaItem.toString() ?? "nao");
AudioServiceBackground.sendCustomEvent(currentIndex);
}
Future<void> _skip(int offset) async {
final newPos = currentIndex + offset;
print('gone to pos ' + newPos.toString());
if (!(newPos >= 0 && newPos < radioStations.length)) return;
if (playing == null) {
// First time, we want to start playing
playing = true;
} else if (playing) {
// Stop current item
await player.stop();
}
// Load next item
currentIndex = newPos;
AudioServiceBackground.setMediaItem(radioStations[currentIndex]);
_skipState = offset > 0
? AudioProcessingState.skippingToNext
: AudioProcessingState.skippingToPrevious;
await player.setUrl(radioStations[currentIndex].id);
_skipState = null;
// Resume playback if we were playing
if (playing) {
onPlay();
} else {
_setState(processingState: AudioProcessingState.ready);
}
AudioServiceBackground.sendCustomEvent(currentIndex);
}
@override
void onClick(MediaButton button) {
playPause();
}
void playPause() {
if (AudioServiceBackground.state.playing)
onStop();
else
onPlay();
}
@override
void onPlay() {
if (_skipState == null) {
playing = true;
player.play();
}
}
@override
Future<void> onStop() async {
AudioServiceBackground.sendCustomEvent('stop');
await player.stop();
await player.dispose();
playing = false;
_playerStateSubscription.cancel();
_eventSubscription.cancel();
await _setState(processingState: AudioProcessingState.stopped);
await super.onStop();
}
Future<void> _setState({
AudioProcessingState processingState,
Duration position,
Duration bufferedPosition,
}) async {
if (position == null) {
position = player.playbackEvent.position;
}
await AudioServiceBackground.setState(
controls: getControls(),
systemActions: [MediaAction.seekTo],
processingState:
processingState ?? AudioServiceBackground.state.processingState,
playing: playing,
position: position,
bufferedPosition: bufferedPosition ?? position,
speed: player.speed,
);
}
/* Handling Audio Focus */
@override
void onAudioFocusLost(AudioInterruption interruption) {
print(interruption.index);
if (playing) _interrupted = true;
switch (interruption) {
case AudioInterruption.pause:
onStop();
break;
case AudioInterruption.temporaryPause:
player.setVolume(0.0);
break;
case AudioInterruption.unknownPause:
player.setVolume(0.0);
break;
case AudioInterruption.temporaryDuck:
player.setVolume(0.5);
break;
}
}
@override
void onAudioFocusGained(AudioInterruption interruption) {
switch (interruption) {
case AudioInterruption.pause:
onStop();
break;
case AudioInterruption.temporaryPause:
player.setVolume(1.0);
break;
case AudioInterruption.unknownPause:
player.setVolume(1.0);
break;
case AudioInterruption.temporaryDuck:
player.setVolume(1.0);
break;
}
_interrupted = false;
}
@override
void onAudioBecomingNoisy() {
onStop();
}
List<MediaControl> getControls() {
if (playing) {
return [skipToPreviousControl, stopControl, skipToNextControl];
} else {
return [skipToPreviousControl, stopControl, skipToNextControl];
}
}
}
Future<List<MediaItem>> loadStations(String lang) async {
final response =
await rootBundle.loadString('assets/data/radiosList$lang.json');
return compute(parseRadios, response);
}
List<MediaItem> parseRadios(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<MediaItem>((json) => MediaItem.fromJson(json)).toList();
}
Runtime Environment (please complete the following information if relevant):
Android
Flutter SDK version
[√] Flutter (Channel stable, v1.17.4, on Microsoft Windows [Version 10.0.18362.900], locale en-US)
[√] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[√] Android Studio (version 3.5)
[√] VS Code (version 1.46.1)
[√] Connected device (1 available)
Additional context
If asked i can give, dunno what to say.