Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/CesarArellano/Music-Player-App/llms.txt

Use this file to discover all available pages before exploring further.

Musynth’s MusicOrchestratorService acts as the single public entry point for every screen that wants to start or restore audio playback. Rather than letting each screen know how to resolve a playlist, stop the current track, load a new source list, and enable shuffle, the orchestrator encapsulates that logic and exposes two simple methods: playSong and initSong. Screens depend only on this service — they never touch PlaybackService or any cubit directly. This page covers the service’s constructor dependencies, both public methods, and the PlaylistResolverFactory strategy pattern that powers playlist resolution.

Constructor and Dependencies

MusicOrchestratorService is a plain const constructor whose seven dependencies are all injected by GetIt:
const MusicOrchestratorService({
  required PlaybackService _playbackService,
  required PlaybackStateCubit _playbackStateCubit,
  required AudioControlCubit _audioControlCubit,
  required UICubit _uiCubit,
  required LibraryCubit _libraryCubit,
  required FavoritesCubit _favoritesCubit,
  required PreferencesRepository _preferences,
});

PlaybackService

The abstract audio-playback interface. The orchestrator calls stop, loadPlaylist, play, seek, and setShuffleModeEnabled on it.

PlaybackStateCubit

Holds currentPlaylist and songPlayed. The orchestrator reads both to decide whether a full reload or a simple seek is needed.

AudioControlCubit

Tracks the current track index. Updated whenever playSong resolves a new index inside the playlist.

UICubit

Stores the active hero-animation ID. Both methods call updateCurrentHeroId so the player screen can animate from the correct widget.

LibraryCubit

Provides LibraryState (song list, album/artist/genre/playlist collections, appDirectory). Passed to PlaylistResolverFactory and loadPlaylist.

FavoritesCubit

Provides favoriteList to PlaylistResolverFactory when the PlaylistType.favorites resolver is selected.

PreferencesRepository

Supplies lastSongDuration (as milliseconds) used by initSong to restore the last playback position.
The service is registered in di.dart by the factory function buildMusicOrchestratorService(), which pulls all dependencies from setupLocator:
MusicOrchestratorService buildMusicOrchestratorService() =>
    MusicOrchestratorService(
      playbackService:     setupLocator<PlaybackService>(),
      playbackStateCubit:  setupLocator<PlaybackStateCubit>(),
      audioControlCubit:   setupLocator<AudioControlCubit>(),
      uiCubit:             setupLocator<UICubit>(),
      libraryCubit:        setupLocator<LibraryCubit>(),
      favoritesCubit:      setupLocator<FavoritesCubit>(),
      preferences:         setupLocator<PreferencesRepository>(),
    );

playSong

void playSong(
  SongModel song,
  PlaylistType type, {
  required String heroId,
  int? id,
  bool activateShuffle = false,
})
playSong is used whenever a user taps a song to play it immediately. The flow is:
1

Update the hero ID

_uiCubit.updateCurrentHeroId(heroId) records which widget the player screen should animate from.
2

Resolve the playlist

PlaylistResolverFactory.resolve(type, libraryState, id: id, favoriteList: ...) returns the correct List<SongModel> for the context (all songs, an album, an artist, a genre, a playlist, or favourites).
3

Persist the new playlist

_playbackStateCubit.updateCurrentPlaylist(newPlaylist) stores the resolved list so the queue screen and initSong can reference it later.
4

Load or seek

If the requested song differs from the currently playing one or the playlist length changed, the service calls _playbackService.stop() followed by loadPlaylist(...). If the same song is already playing in the same-length list, it simply calls seek(Duration.zero) — a fast path that avoids reloading the audio source.
5

Start playback

_playbackService.play() begins audio output. If activateShuffle is true, setShuffleModeEnabled(true) is called immediately after.

Example — playing a song from the Albums screen

// Inside AlbumDetailScreen when the user taps a track:
final orchestrator = context.read<MusicOrchestratorService>();

orchestrator.playSong(
  song,
  PlaylistType.album,
  heroId: 'album-art-${album.id}',
  id: album.id,          // passed to _AlbumResolver
  activateShuffle: false,
);
Pass id whenever type is album, artist, genre, or playlist — the corresponding resolver uses it to look up the correct sub-collection in LibraryState. It is ignored for PlaylistType.songs and PlaylistType.favorites.

initSong

void initSong(SongModel song, {required String heroId})
initSong restores the last-played track on app relaunch without starting playback. It is called once from the app startup sequence after the library has been loaded and the last song ID has been resolved from preferences.
void initSong(SongModel song, {required String heroId}) {
  _uiCubit.updateCurrentHeroId(heroId);

  final playlist = _playbackStateCubit.state.currentPlaylist;
  final index = playlist.indexWhere((s) => s.id == song.id);

  _audioControlCubit.updateCurrentIndex(index);

  _playbackService.loadPlaylist(
    songs: playlist,
    initialIndex: index,
    appDirectory: _libraryCubit.state.appDirectory,
    initialPosition: Duration(
      milliseconds: _preferences.lastSongDuration,
    ),
  );
}
The key difference from playSong is that initSong:
  • Does not call stop() before loading (the player is already idle on startup).
  • Passes initialPosition so just_audio seeks to the last known position before the audio source is even buffered.
  • Does not call play() — the track appears paused in the player UI, ready for the user to press play.

PlaylistResolverFactory

PlaylistResolverFactory implements the strategy pattern. Each PlaylistType maps to a dedicated PlaylistResolver implementation; all six are registered in a compile-time constant map:
PlaylistTypeResolver classData source in LibraryState
songs_SongsResolverlibraryState.songList
album_AlbumResolverlibraryState.albumCollection[id]
artist_ArtistResolverlibraryState.artistCollection[id].songs
genre_GenreResolverlibraryState.genreCollection[id]
playlist_PlaylistResolverlibraryState.playlistCollection[id]
favorites_FavoritesResolverfavoriteList (from FavoritesCubit)
static List<SongModel> resolve(
  PlaylistType type,
  LibraryState libraryState, {
  int? id,
  List<SongModel> favoriteList = const [],
}) =>
    (_resolvers[type] ?? const _SongsResolver()).resolve(
      libraryState,
      id: id,
      favoriteList: favoriteList,
    );
Adding a new playlist context requires only one new PlaylistResolver subclass and one entry in _resolvers. MusicOrchestratorService and every screen that calls playSong remain untouched.

Build docs developers (and LLMs) love