title: How can I control what is shown in the notification bar in the package audio_service for FLutter
body: <p>I am trying to figure out where can I change what is shown in the notification bar in the audio_service package. More specifically, can you tell me where can I remove the slider (the seek bar) from the notification bar? ( it is not shown until you expand the bar ).
  Or to improve it, by adding the current position and the max duration above it. But I think I can do that if I can find out where can I play around with the code for that.</p>
  <p>My code is from ryanheise's example:</p>
  <pre><code>import 'dart:io';
  import 'dart:math';
  
  import 'package:audio_session/audio_session.dart';
  import 'package:flutter/material.dart';
  import 'dart:async';
  
  import 'package:audio_service/audio_service.dart';
  import 'package:just_audio/just_audio.dart';
  import 'package:rxdart/rxdart.dart';
  
  void main() =&gt; runApp(new MyApp());
  
  class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Audio Service Demo',
        theme: ThemeData(primarySwatch: Colors.blue),
        home: AudioServiceWidget(child: MainScreen()),
      );
    }
  }
  
  class MainScreen extends StatelessWidget {
    /// Tracks the position while the user drags the seek bar.
    final BehaviorSubject&lt;double&gt; _dragPositionSubject =
        BehaviorSubject.seeded(null);
  
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: const Text('Audio Service Demo'),
        ),
        body: Center(
          child: StreamBuilder&lt;ScreenState&gt;(
            stream: _screenStateStream,
            builder: (context, snapshot) {
              final screenState = snapshot.data;
              final queue = screenState?.queue;
              final mediaItem = screenState?.mediaItem;
              
              final state = screenState?.playbackState;
              final processingState =
                  state?.processingState ?? AudioProcessingState.none;
              final playing = state?.playing ?? false;
              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  if (processingState == AudioProcessingState.none) ...[
                    audioPlayerButton(),
                  ] else ...[
                    if (queue != null &amp;&amp; queue.isNotEmpty)
                      Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          IconButton(
                            icon: Icon(Icons.skip_previous),
                            iconSize: 64.0,
                            onPressed: mediaItem == queue.first
                                ? null
                                : AudioService.skipToPrevious,
                          ),
                          IconButton(
                            icon: Icon(Icons.skip_next),
                            iconSize: 64.0,
                            onPressed: mediaItem == queue.last
                                ? null
                                : AudioService.skipToNext,
                          ),
                        ],
                      ),
                    if (mediaItem?.title != null) Text(mediaItem.title),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        if (playing) pauseButton() else playButton(),
                        stopButton(),
                      ],
                    ),
                    positionIndicator(mediaItem, state),
                    Text(&quot;Processing state: &quot; +
                        &quot;$processingState&quot;.replaceAll(RegExp(r'^.*\.'), '')),
                    StreamBuilder(
                      stream: AudioService.customEventStream,
                      builder: (context, snapshot) {
                        return Text(&quot;custom event: ${snapshot.data}&quot;);
                      },
                    ),
                    StreamBuilder&lt;bool&gt;(
                      stream: AudioService.notificationClickEventStream,
                      builder: (context, snapshot) {
                        return Text(
                          'Notification Click Status: ${snapshot.data}',
                        );
                      },
                    ),
                  ],
                ],
              );
            },
          ),
        ),
      );
    }
  
    /// Encapsulate all the different data we're interested in into a single
    /// stream so we don't have to nest StreamBuilders.
    Stream&lt;ScreenState&gt; get _screenStateStream =&gt;
        Rx.combineLatest3&lt;List&lt;MediaItem&gt;, MediaItem, PlaybackState, ScreenState&gt;(
            AudioService.queueStream,
            AudioService.currentMediaItemStream,
            AudioService.playbackStateStream,
            (queue, mediaItem, playbackState) =&gt;
                ScreenState(queue, mediaItem, playbackState));
  
    RaisedButton audioPlayerButton() =&gt; startButton(
          'AudioPlayer',
          () {
            AudioService.start(
              backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint,
              androidNotificationChannelName: 'Audio Service Demo',
              // Enable this if you want the Android service to exit the foreground state on pause.
              //androidStopForegroundOnPause: true,
              androidNotificationColor: 0xFF2196f3,
              androidNotificationIcon: 'mipmap/ic_launcher',
              androidEnableQueue: true,
            );
          },
        );
  
    RaisedButton startButton(String label, VoidCallback onPressed) =&gt;
        RaisedButton(
          child: Text(label),
          onPressed: onPressed,
        );
  
    IconButton playButton() =&gt; IconButton(
          icon: Icon(Icons.play_arrow),
          iconSize: 64.0,
          onPressed: AudioService.play,
        );
  
    IconButton pauseButton() =&gt; IconButton(
          icon: Icon(Icons.pause),
          iconSize: 64.0,
          onPressed: AudioService.pause,
        );
  
    IconButton stopButton() =&gt; IconButton(
          icon: Icon(Icons.stop),
          iconSize: 64.0,
          onPressed: AudioService.stop,
        );
  
    Widget positionIndicator(MediaItem mediaItem, PlaybackState state) {
      double seekPos;
      return StreamBuilder(
        stream: Rx.combineLatest2&lt;double, double, double&gt;(
            _dragPositionSubject.stream,
            Stream.periodic(Duration(milliseconds: 200)),
            (dragPosition, _) =&gt; dragPosition),
        builder: (context, snapshot) {
          double position =
              snapshot.data ?? state.currentPosition.inMilliseconds.toDouble();
          double duration = mediaItem?.duration?.inMilliseconds?.toDouble();
          return Column(
            children: [
              if (duration != null)
                Slider(
                  min: 0.0,
                  max: duration,
                  value: seekPos ?? max(0.0, min(position, duration)),
                  onChanged: (value) {
                    _dragPositionSubject.add(value);
                  },
                  onChangeEnd: (value) {
                    AudioService.seekTo(Duration(milliseconds: value.toInt()));
                    // Due to a delay in platform channel communication, there is
                    // a brief moment after releasing the Slider thumb before the
                    // new position is broadcast from the platform side. This
                    // hack is to hold onto seekPos until the next state update
                    // comes through.
                    // TODO: Improve this code.
                    seekPos = value;
                    _dragPositionSubject.add(null);
                  },
                ),
              Text(&quot;${state.currentPosition}&quot;),
            ],
          );
        },
      );
    }
  }
  
  class ScreenState {
    final List&lt;MediaItem&gt; queue;
    final MediaItem mediaItem;
    final PlaybackState playbackState;
  
    ScreenState(this.queue, this.mediaItem, this.playbackState);
  }
  
  // NOTE: Your entrypoint MUST be a top-level function.
  void _audioPlayerTaskEntrypoint() async {
    AudioServiceBackground.run(() =&gt; AudioPlayerTask());
  }
  
  /// This task defines logic for playing a list of podcast episodes.
  class AudioPlayerTask extends BackgroundAudioTask {
    final _mediaLibrary = MediaLibrary();
    AudioPlayer _player = new AudioPlayer();
    AudioProcessingState _skipState;
    Seeker _seeker;
    StreamSubscription&lt;PlaybackEvent&gt; _eventSubscription;
  
    List&lt;MediaItem&gt; get queue =&gt; _mediaLibrary.items;
    int get index =&gt; _player.currentIndex;
    MediaItem get mediaItem =&gt; index == null ? null : queue[index];
  
    @override
    Future&lt;void&gt; onStart(Map&lt;String, dynamic&gt; params) async {
      // We configure the audio session for speech since we're playing a podcast.
      // You can also put this in your app's initialisation if your app doesn't
      // switch between two types of audio as this example does.
      final session = await AudioSession.instance;
      await session.configure(AudioSessionConfiguration.speech());
      // Broadcast media item changes.
      _player.currentIndexStream.listen((index) {
        if (index != null) AudioServiceBackground.setMediaItem(queue[index]);
      });
      // Propagate all events from the audio player to AudioService clients.
      _eventSubscription = _player.playbackEventStream.listen((event) {
        _broadcastState();
      });
      // Special processing for state transitions.
      _player.processingStateStream.listen((state) {
        switch (state) {
          case ProcessingState.completed:
            // In this example, the service stops when reaching the end.
            onStop();
            break;
          case ProcessingState.ready:
            // If we just came from skipping between tracks, clear the skip
            // state now that we're ready to play.
            _skipState = null;
            break;
          default:
            break;
        }
      });
  
      // Load and broadcast the queue
      AudioServiceBackground.setQueue(queue);
      try {
        await _player.load(ConcatenatingAudioSource(
          children:
              queue.map((item) =&gt; AudioSource.uri(Uri.parse(item.id))).toList(),
        ));
        // In this example, we automatically start playing on start.
        onPlay();
      } catch (e) {
        print(&quot;Error: $e&quot;);
        onStop();
      }
    }
  
    @override
    Future&lt;void&gt; onSkipToQueueItem(String mediaId) async {
      // Then default implementations of onSkipToNext and onSkipToPrevious will
      // delegate to this method.
      final newIndex = queue.indexWhere((item) =&gt; item.id == mediaId);
      if (newIndex == -1) return;
      // During a skip, the player may enter the buffering state. We could just
      // propagate that state directly to AudioService clients but AudioService
      // has some more specific states we could use for skipping to next and
      // previous. This variable holds the preferred state to send instead of
      // buffering during a skip, and it is cleared as soon as the player exits
      // buffering (see the listener in onStart).
      _skipState = newIndex &gt; index
          ? AudioProcessingState.skippingToNext
          : AudioProcessingState.skippingToPrevious;
      // This jumps to the beginning of the queue item at newIndex.
      _player.seek(Duration.zero, index: newIndex);
    }
  
    @override
    Future&lt;void&gt; onPlay() =&gt; _player.play();
  
    @override
    Future&lt;void&gt; onPause() =&gt; _player.pause();
  
    @override
    Future&lt;void&gt; onSeekTo(Duration position) =&gt; _player.seek(position);
  
    @override
    Future&lt;void&gt; onFastForward() =&gt; _seekRelative(fastForwardInterval);
  
    @override
    Future&lt;void&gt; onRewind() =&gt; _seekRelative(-rewindInterval);
  
    @override
    Future&lt;void&gt; onSeekForward(bool begin) async =&gt; _seekContinuously(begin, 1);
  
    @override
    Future&lt;void&gt; onSeekBackward(bool begin) async =&gt; _seekContinuously(begin, -1);
  
    @override
    Future&lt;void&gt; onStop() async {
      await _player.pause();
      await _player.dispose();
      _eventSubscription.cancel();
      // It is important to wait for this state to be broadcast before we shut
      // down the task. If we don't, the background task will be destroyed before
      // the message gets sent to the UI.
      await _broadcastState();
      // Shut down this task
      await super.onStop();
    }
  
    /// Jumps away from the current position by [offset].
    Future&lt;void&gt; _seekRelative(Duration offset) async {
      var newPosition = _player.position + offset;
      // Make sure we don't jump out of bounds.
      if (newPosition &lt; Duration.zero) newPosition = Duration.zero;
      if (newPosition &gt; mediaItem.duration) newPosition = mediaItem.duration;
      // Perform the jump via a seek.
      await _player.seek(newPosition);
    }
  
    /// Begins or stops a continuous seek in [direction]. After it begins it will
    /// continue seeking forward or backward by 10 seconds within the audio, at
    /// intervals of 1 second in app time.
    void _seekContinuously(bool begin, int direction) {
      _seeker?.stop();
      if (begin) {
        _seeker = Seeker(_player, Duration(seconds: 10 * direction),
            Duration(seconds: 1), mediaItem)
          ..start();
      }
    }
  
    /// Broadcasts the current state to all clients.
    Future&lt;void&gt; _broadcastState() async {
      await AudioServiceBackground.setState(
        controls: [
          MediaControl.skipToPrevious,
          if (_player.playing) MediaControl.pause else MediaControl.play,
          MediaControl.stop,
          MediaControl.skipToNext,
        ],
        systemActions: [
          MediaAction.play,
          MediaAction.seekForward,
          MediaAction.seekBackward,
        ],
        processingState: _getProcessingState(),
        playing: _player.playing,
        position: _player.position,
        bufferedPosition: _player.bufferedPosition,
        speed: _player.speed,
      );
    }
  
    /// Maps just_audio's processing state into into audio_service's playing
    /// state. If we are in the middle of a skip, we use [_skipState] instead.
    AudioProcessingState _getProcessingState() {
      if (_skipState != null) return _skipState;
      switch (_player.processingState) {
        case ProcessingState.none:
          return AudioProcessingState.stopped;
        case ProcessingState.loading:
          return AudioProcessingState.connecting;
        case ProcessingState.buffering:
          return AudioProcessingState.buffering;
        case ProcessingState.ready:
          return AudioProcessingState.ready;
        case ProcessingState.completed:
          return AudioProcessingState.completed;
        default:
          throw Exception(&quot;Invalid state: ${_player.processingState}&quot;);
      }
    }
  }
  
  /// Provides access to a library of media items. In your app, this could come
  /// from a database or web service.
  class MediaLibrary {
    final _items = &lt;MediaItem&gt;[
      MediaItem(
        id: &quot;https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3&quot;,
        album: &quot;Science Friday&quot;,
        title: &quot;A Salute To Head-Scratching Science&quot;,
        artist: &quot;Science Friday and WNYC Studios&quot;,
        duration: Duration(milliseconds: 5739820),
        artUri:
            &quot;https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg&quot;,
      ),
      MediaItem(
        id: &quot;https://s3.amazonaws.com/scifri-segments/scifri201711241.mp3&quot;,
        album: &quot;Science Friday&quot;,
        title: &quot;From Cat Rheology To Operatic Incompetence&quot;,
        artist: &quot;Science Friday and WNYC Studios&quot;,
        duration: Duration(milliseconds: 2856950),
        artUri:
            &quot;https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg&quot;,
      ),
    ];
  
    List&lt;MediaItem&gt; get items =&gt; _items;
  }
  
  /// An object that performs interruptable sleep.
  class Sleeper {
    Completer _blockingCompleter;
  
    /// Sleep for a duration. If sleep is interrupted, a
    /// [SleeperInterruptedException] will be thrown.
    Future&lt;void&gt; sleep([Duration duration]) async {
      _blockingCompleter = Completer();
      if (duration != null) {
        await Future.any([Future.delayed(duration), _blockingCompleter.future]);
      } else {
        await _blockingCompleter.future;
      }
      final interrupted = _blockingCompleter.isCompleted;
      _blockingCompleter = null;
      if (interrupted) {
        throw SleeperInterruptedException();
      }
    }
  
    /// Interrupt any sleep that's underway.
    void interrupt() {
      if (_blockingCompleter?.isCompleted == false) {
        _blockingCompleter.complete();
      }
    }
  }
  
  class SleeperInterruptedException {}
  
  /// A wrapper around FlutterTts that makes it easier to wait for speech to
  /// complete.
  
  class Seeker {
    final AudioPlayer player;
    final Duration positionInterval;
    final Duration stepInterval;
    final MediaItem mediaItem;
    bool _running = false;
  
    Seeker(
      this.player,
      this.positionInterval,
      this.stepInterval,
      this.mediaItem,
    );
  
    start() async {
      _running = true;
      while (_running) {
        Duration newPosition = player.position + positionInterval;
        if (newPosition &lt; Duration.zero) newPosition = Duration.zero;
        if (newPosition &gt; mediaItem.duration) newPosition = mediaItem.duration;
        player.seek(newPosition);
        await Future.delayed(stepInterval);
      }
    }
  
    stop() {
      _running = false;
    }
  }
  </code></pre>