Skip to content

StreamBuilder receiving only null values: no Queue, no MediaItem, no ProssessingState #377

@renanmgs

Description

@renanmgs

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:

image

I read the stream inside an AnimatedBuilder (flutter_sequence_animation):
image
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.

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions