Fliin
On this page

Screens / data-display

Music player

Plays a song from the app's audio player: artwork, title and artist, a position slider read in minutes and seconds, play and pause, next and previous, shuffle, repeat and favorite.

16 of 16 variants pass
Music player, playing state
Platform
Brightness
Text scale
Direction

Import

Music player is part of Fliin Pro. With a license key, sign in once with fliin login, then add it in a Flutter app where fliin init has run:

fliin login
fliin add player

Also installs: alert, app-bar, button, core, empty-state, mini-player, page, result-card, skeleton, slider, spinner, icons

Examples

Playing

A track playing, shuffle on. standalone holds the player over the service you pass; your audio engine implements PlayerService.

Music player, playing state
import 'package:your_app/ui/fliin/screens/player/player_data.dart';
import 'package:your_app/ui/fliin/screens/player/player_screen.dart';
import 'package:flutter/widgets.dart';

class PlayerPlayingExample extends StatelessWidget {
  const PlayerPlayingExample({super.key});

  @override
  Widget build(BuildContext context) {
    return PlayerScreen.standalone(
      service: SamplePlayerService(
        initial: PlaybackReady(
        track: sampleTracks[0],
        position: const Duration(minutes: 1, seconds: 5),
        playing: true,
        shuffle: true,
      ),
        latency: const Duration(milliseconds: 900),
      ),
      onBack: () {},
    );
  }
}

Paused

Paused part-way, repeating the whole queue: the play button and the position say where the track stands.

Music player, paused state
import 'package:your_app/ui/fliin/screens/player/player_data.dart';
import 'package:your_app/ui/fliin/screens/player/player_screen.dart';
import 'package:flutter/widgets.dart';

class PlayerPausedExample extends StatelessWidget {
  const PlayerPausedExample({super.key});

  @override
  Widget build(BuildContext context) {
    return PlayerScreen.standalone(
      service: SamplePlayerService(
        initial: PlaybackReady(track: sampleTracks[1], position: const Duration(seconds: 42), repeat: PlayerRepeat.all),
        latency: const Duration(milliseconds: 900),
      ),
      onBack: () {},
    );
  }
}

Buffering

Playing but waiting for data: buffering shows a spinner on the play button without stopping the clock.

Music player, buffering state
import 'package:your_app/ui/fliin/screens/player/player_data.dart';
import 'package:your_app/ui/fliin/screens/player/player_screen.dart';
import 'package:flutter/widgets.dart';

class PlayerBufferingExample extends StatelessWidget {
  const PlayerBufferingExample({super.key});

  @override
  Widget build(BuildContext context) {
    return PlayerScreen.standalone(
      service: SamplePlayerService(
        initial: PlaybackReady(
        track: sampleTracks[2],
        position: const Duration(minutes: 12, seconds: 40),
        playing: true,
        buffering: true,
        repeat: PlayerRepeat.one,
      ),
        latency: const Duration(milliseconds: 900),
      ),
      onBack: () {},
    );
  }
}

Loading

Before the first track arrives, placeholders hold the artwork, the title and the controls.

Music player, loading state
import 'package:your_app/ui/fliin/screens/player/player_data.dart';
import 'package:your_app/ui/fliin/screens/player/player_screen.dart';
import 'package:flutter/widgets.dart';

class PlayerLoadingExample extends StatelessWidget {
  const PlayerLoadingExample({super.key});

  @override
  Widget build(BuildContext context) {
    return PlayerScreen.standalone(
      service: SamplePlayerService(
        initial: const PlaybackLoading(),
        latency: const Duration(milliseconds: 900),
      ),
      onBack: () {},
    );
  }
}

Error

When a track cannot be played, the screen says so and offers to try again.

Music player, error state
import 'package:your_app/ui/fliin/screens/player/player_data.dart';
import 'package:your_app/ui/fliin/screens/player/player_screen.dart';
import 'package:flutter/widgets.dart';

class PlayerErrorExample extends StatelessWidget {
  const PlayerErrorExample({super.key});

  @override
  Widget build(BuildContext context) {
    return PlayerScreen.standalone(
      service: SamplePlayerService(
        initial: PlaybackFailed(track: sampleTracks[3], position: const Duration(seconds: 20)),
        latency: const Duration(milliseconds: 900),
      ),
      onBack: () {},
    );
  }
}

Offline

Without a connection, the screen asks to reconnect instead of showing an error.

Music player, offline state
import 'package:your_app/ui/fliin/screens/player/player_data.dart';
import 'package:your_app/ui/fliin/screens/player/player_screen.dart';
import 'package:flutter/widgets.dart';

class PlayerOfflineExample extends StatelessWidget {
  const PlayerOfflineExample({super.key});

  @override
  Widget build(BuildContext context) {
    return PlayerScreen.standalone(
      service: SamplePlayerService(
        initial: PlaybackFailed(track: sampleTracks[4], offline: true),
        latency: const Duration(milliseconds: 900),
      ),
      onBack: () {},
    );
  }
}

The player with your own row under the commands: footer takes any widget, here the lyrics and what plays next.

Music player, footer state
import 'package:your_app/ui/fliin/components/button/fl_button.dart';
import 'package:your_app/ui/fliin/core/fliin.dart';
import 'package:your_app/ui/fliin/screens/player/player_data.dart';
import 'package:your_app/ui/fliin/screens/player/player_screen.dart';
import 'package:flutter/widgets.dart';

class PlayerFooterExample extends StatelessWidget {
  const PlayerFooterExample({super.key});

  @override
  Widget build(BuildContext context) {
    final space = FlScope.of(context).space;
    return PlayerScreen.standalone(
      service: SamplePlayerService(
        initial: PlaybackReady(track: sampleTracks[0], position: const Duration(minutes: 1, seconds: 5), playing: true),
        latency: const Duration(milliseconds: 900),
      ),
      onBack: () {},
      onMore: () {},
      footer: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        spacing: space.sm,
        children: [
          FlButton(label: 'Lyrics', size: FlButtonSize.sm, variant: FlButtonVariant.tertiary, onPressed: () {}),
          FlButton(label: 'Up next', size: FlButtonSize.sm, variant: FlButtonVariant.tertiary, onPressed: () {}),
        ],
      ),
    );
  }
}

Routing

fliin add player prints the route for the router it finds in your pubspec, a file next to the page that wires its navigation. Each of them:

go_router

// lib/player_route.dart, next to player_page.dart.
import 'package:go_router/go_router.dart';

import 'player_page.dart';

/// Add playerRoute to the routes of your GoRouter.
final playerRoute = GoRoute(
  path: '/player',
  builder: (context, state) => PlayerPage(
    // Alone in the stack (deep link, go): your app's home.
    onBack: () => context.canPop() ? context.pop() : context.go('/'),
  ),
);

navigator

// lib/player_route.dart, next to player_page.dart.
import 'package:flutter/material.dart';

import 'player_page.dart';

/// Opens “Music player”: `Navigator.of(context).push(playerRoute())`.
Route<void> playerRoute() => MaterialPageRoute<void>(
  builder: (context) => PlayerPage(
    onBack: () => Navigator.of(context).maybePop(),
  ),
);

State

The controller is a ChangeNotifier from widgets.dart, with no package. fliin add player prints the page that holds it with the state management it finds, its imports written for your package; below they read your_app. Your backend implements the Service interface of the screen's _data.dart file:

none

// lib/player_page.dart
// Your backend: implement PlayerService (player_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/player/player_data.dart';
import 'package:your_app/ui/fliin/screens/player/player_screen.dart';
import 'package:flutter/widgets.dart';

// The app's player lives above the routes: the library's mini player
// and this screen share its state.
final samplePlayer = SamplePlayerService();

class PlayerPage extends StatelessWidget {
  const PlayerPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context) => PlayerScreen.standalone(
    service: samplePlayer,
    onBack: onBack,
  );
}

provider

// lib/player_page.dart
// Your backend: implement PlayerService (player_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/player/player_controller.dart';
import 'package:your_app/ui/fliin/screens/player/player_data.dart';
import 'package:your_app/ui/fliin/screens/player/player_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';

// The app's player lives above the routes: the library's mini player
// and this screen share its state.
final samplePlayer = SamplePlayerService();

class PlayerPage extends StatelessWidget {
  const PlayerPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context) => ChangeNotifierProvider(
    create: (_) => PlayerController(service: samplePlayer),
    child: Builder(
      builder: (context) => PlayerScreen(
        controller: context.read<PlayerController>(),
        onBack: onBack,
      ),
    ),
  );
}

riverpod

// lib/player_page.dart
// Your backend: implement PlayerService (player_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/player/player_controller.dart';
import 'package:your_app/ui/fliin/screens/player/player_data.dart';
import 'package:your_app/ui/fliin/screens/player/player_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// The app's player lives above the routes: the library's mini player
// and this screen share its state.
final samplePlayer = SamplePlayerService();

final playerControllerProvider = Provider.autoDispose<PlayerController>((ref) {
  final controller = PlayerController(service: samplePlayer);
  ref.onDispose(controller.dispose);
  return controller;
});

class PlayerPage extends ConsumerWidget {
  const PlayerPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context, WidgetRef ref) => PlayerScreen(
    controller: ref.watch(playerControllerProvider),
    onBack: onBack,
  );
}

bloc

// lib/player_page.dart
// Your backend: implement PlayerService (player_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/player/player_controller.dart';
import 'package:your_app/ui/fliin/screens/player/player_data.dart';
import 'package:your_app/ui/fliin/screens/player/player_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

// The app's player lives above the routes: the library's mini player
// and this screen share its state.
final samplePlayer = SamplePlayerService();

/// The screen's controller as a bloc, for your BlocListener and BlocBuilder:
/// one emission per controller notification. `version` tells them apart
/// when `state` does not change (mode, filters, text…, read them on
/// `controller`). The screen listens to the controller itself.
class PlayerCubit extends Cubit<({Playback state, int version})> {
  PlayerCubit(this.controller) : super((state: controller.state, version: 0)) {
    controller.addListener(_changed);
  }

  final PlayerController controller;

  void _changed() => emit((state: controller.state, version: state.version + 1));

  @override
  Future<void> close() {
    controller
      ..removeListener(_changed)
      ..dispose();
    return super.close();
  }
}

class PlayerPage extends StatelessWidget {
  const PlayerPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context) => BlocProvider(
    create: (_) => PlayerCubit(PlayerController(service: samplePlayer)),
    child: Builder(
      builder: (context) => PlayerScreen(
        controller: context.read<PlayerCubit>().controller,
        onBack: onBack,
      ),
    ),
  );
}

API

PlayerScreen: class extends StatefulWidget
PlayerScreen.new: const new({super.key, required PlayerController controller, VoidCallback? onBack, VoidCallback? onMore, Widget? footer})
PlayerScreen.standalone: const new({super.key, required PlayerService service, VoidCallback? onBack, VoidCallback? onMore, Widget? footer})
PlayerScreen.controller: get PlayerController?
PlayerScreen.service: get PlayerService?
PlayerScreen.onBack: get VoidCallback?
PlayerScreen.onMore: get VoidCallback?
PlayerScreen.footer: get Widget?
PlayerScreen.createState: State<PlayerScreen>()
PlayerNotice: enum {favoriteFailed, favoriteOffline}
PlayerNotice.favoriteFailed: static final PlayerNotice
PlayerNotice.favoriteOffline: static final PlayerNotice
PlayerController: class extends ChangeNotifier
PlayerController.new: new({required PlayerService service})
PlayerController.service: get PlayerService
PlayerController.state: get Playback
PlayerController.notice: get PlayerNotice?
PlayerController.togglePlay: void()
PlayerController.seek: Future<void>(Duration position)
PlayerController.previous: void()
PlayerController.next: void()
PlayerController.toggleShuffle: void()
PlayerController.cycleRepeat: void()
PlayerController.retry: void()
PlayerController.toggleFavorite: bool()
PlayerController.dispose: void()
PlayerTrack: final class
PlayerTrack.new: const new({required String id, required String title, required String artist, required Duration duration, String? album, ImageProvider? artwork, bool favorite})
PlayerTrack.id: get String
PlayerTrack.title: get String
PlayerTrack.artist: get String
PlayerTrack.album: get String?
PlayerTrack.duration: get Duration
PlayerTrack.artwork: get ImageProvider?
PlayerTrack.favorite: get bool
PlayerTrack.withFavorite: PlayerTrack(bool favorite)
PlayerRepeat: enum {off, all, one}
PlayerRepeat.off: static final PlayerRepeat
PlayerRepeat.all: static final PlayerRepeat
PlayerRepeat.one: static final PlayerRepeat
Playback: sealed class permits {PlaybackFailed, PlaybackLoading, PlaybackReady}
Playback.new: const new()
PlaybackLoading: final class extends Playback
PlaybackLoading.new: const new()
PlaybackReady: final class extends Playback
PlaybackReady.new: const new({required PlayerTrack track, Duration position, bool playing, bool buffering, bool shuffle, PlayerRepeat repeat, bool hasPrevious, bool hasNext})
PlaybackReady.track: get PlayerTrack
PlaybackReady.position: get Duration
PlaybackReady.playing: get bool
PlaybackReady.buffering: get bool
PlaybackReady.shuffle: get bool
PlaybackReady.repeat: get PlayerRepeat
PlaybackReady.hasPrevious: get bool
PlaybackReady.hasNext: get bool
PlaybackReady.copyWith: PlaybackReady({PlayerTrack? track, Duration? position, bool? playing, bool? buffering, bool? shuffle, PlayerRepeat? repeat, bool? hasPrevious, bool? hasNext})
PlaybackFailed: final class extends Playback
PlaybackFailed.new: const new({required PlayerTrack track, Duration position, bool offline})
PlaybackFailed.track: get PlayerTrack
PlaybackFailed.position: get Duration
PlaybackFailed.offline: get bool
PlayerNoNetwork: final class implements Exception
PlayerNoNetwork.new: const new()
PlayerService: abstract interface class
PlayerService.new: new()
PlayerService.playback: abstract get ValueListenable<Playback>
PlayerService.play: abstract void()
PlayerService.pause: abstract void()
PlayerService.seek: abstract Future<void>(Duration position)
PlayerService.previous: abstract void()
PlayerService.next: abstract void()
PlayerService.setShuffle: abstract void(bool shuffle)
PlayerService.setRepeat: abstract void(PlayerRepeat repeat)
PlayerService.retry: abstract void()
PlayerService.setFavorite: abstract Future<void>(String trackId, bool favorite)
sampleTracks: get List<PlayerTrack>
SamplePlayerService: final class implements PlayerService
SamplePlayerService.new: new({List<PlayerTrack>? queue, Playback? initial, Duration latency, bool offline, math.Random? random})
SamplePlayerService.queue: get List<PlayerTrack>
SamplePlayerService.latency: get Duration
SamplePlayerService.offline: get bool
SamplePlayerService.offline=: set bool
SamplePlayerService.favorites: get List<(String, bool)>
SamplePlayerService.seeks: get List<Duration>
SamplePlayerService.playback: get ValueListenable<Playback>
SamplePlayerService.play: void()
SamplePlayerService.pause: void()
SamplePlayerService.playPause: void()
SamplePlayerService.seek: Future<void>(Duration position)
SamplePlayerService.previous: void()
SamplePlayerService.next: void()
SamplePlayerService.setShuffle: void(bool shuffle)
SamplePlayerService.setRepeat: void(PlayerRepeat repeat)
SamplePlayerService.retry: void()
SamplePlayerService.fail: void({bool offline})
SamplePlayerService.playTrack: void(PlayerTrack track)
SamplePlayerService.setFavorite: Future<void>(String trackId, bool favorite)
playerL10n: const Map<String, Map<String, String>>
PlayerStrings: final class
PlayerStrings.new: const new(String language)
PlayerStrings.of: factory new(BuildContext context)
PlayerStrings.language: get String
PlayerStrings.digits: String(int n)
PlayerStrings.clock: String(Duration time, {required Duration total})
PlayerStrings.spoken: String(Duration time)
PlayerStrings.position: String(Duration elapsed, Duration total)
PlayerStrings.title: get String
PlayerStrings.more: get String
PlayerStrings.byline: String(String artist, String album)
PlayerStrings.remaining: String(String time)
PlayerStrings.back: get String
PlayerStrings.loading: get String
PlayerStrings.favorite: get String
PlayerStrings.favoriteChanged: String(bool favorite)
PlayerStrings.favoriteFailed: String({required bool offline})
PlayerStrings.positionLabel: get String
PlayerStrings.play: get String
PlayerStrings.pause: get String
PlayerStrings.previous: get String
PlayerStrings.next: get String
PlayerStrings.shuffle: get String
PlayerStrings.shuffleChanged: String(bool shuffle)
PlayerStrings.repeat: String(PlayerRepeat repeat)
PlayerStrings.buffering: get String
PlayerStrings.errorTitle: get String
PlayerStrings.errorMessage: get String
PlayerStrings.offlineTitle: get String
PlayerStrings.offlineMessage: get String
PlayerStrings.retry: get String

Strings

Every label in English, French and Arabic. The screen follows the closest Localizations; the same table is in the manifest, ready for your ARB files.

Keyenfrar
playerTitleNow playingLecture en coursقيد التشغيل
playerBackBackRetourرجوع
playerMoreMore actionsPlus d’actionsمزيد من الإجراءات
playerByline{artist} · {album}{artist} · {album}{artist} · {album}
playerRemaining−{time}−{time}−{time}
playerLoadingLoading the trackChargement du morceauجارٍ تحميل المقطع
playerFavoriteFavoriteFavoriالمفضلة
playerFavoriteAddedAdded to favoritesAjouté aux favorisأُضيف إلى المفضلة
playerFavoriteRemovedRemoved from favoritesRetiré des favorisأُزيل من المفضلة
playerFavoriteFailedYour favorite could not be saved.Le favori n’a pas pu être enregistré.تعذّر حفظ المفضلة.
playerFavoriteOfflineYou are offline. Your favorite was not saved.Vous êtes hors ligne. Le favori n’a pas été enregistré.أنت غير متصل. لم تُحفظ المفضلة.
playerPositionPosition in the trackPosition dans le morceauموضع التشغيل
playerPositionValue{elapsed} of {total}{elapsed} sur {total}{elapsed} من {total}
playerZero0 seconds0 seconde٠ ثانية
playerHoursOne1 hour1 heureساعة واحدة
playerHoursTwo2 hours2 heuresساعتان
playerHours{count} hours{count} heures{count} ساعات
playerHoursMany{count} hours{count} heures{count} ساعة
playerHoursOther{count} hours{count} heures{count} ساعة
playerMinutesOne1 minute1 minuteدقيقة واحدة
playerMinutesTwo2 minutes2 minutesدقيقتان
playerMinutes{count} minutes{count} minutes{count} دقائق
playerMinutesMany{count} minutes{count} minutes{count} دقيقة
playerMinutesOther{count} minutes{count} minutes{count} دقيقة
playerSecondsOne1 second1 secondeثانية واحدة
playerSecondsTwo2 seconds2 secondesثانيتان
playerSeconds{count} seconds{count} secondes{count} ثوانٍ
playerSecondsMany{count} seconds{count} secondes{count} ثانية
playerSecondsOther{count} seconds{count} secondes{count} ثانية
playerPlayPlayLireتشغيل
playerPausePauseMettre en pauseإيقاف مؤقت
playerPreviousPrevious trackMorceau précédentالمقطع السابق
playerNextNext trackMorceau suivantالمقطع التالي
playerShuffleShuffleLecture aléatoireتشغيل عشوائي
playerShuffleOnShuffle onLecture aléatoire activéeالتشغيل العشوائي مفعّل
playerShuffleOffShuffle offLecture aléatoire désactivéeالتشغيل العشوائي متوقف
playerRepeatOffRepeat: offRépétition : désactivéeالتكرار: متوقف
playerRepeatAllRepeat: all tracksRépétition : toute la fileالتكرار: كل المقاطع
playerRepeatOneRepeat: this trackRépétition : ce morceauالتكرار: هذا المقطع
playerBufferingBuffering…Mise en mémoire tampon…جارٍ التخزين المؤقت…
playerErrorTitleThis track could not playImpossible de lire ce morceauتعذّر تشغيل هذا المقطع
playerErrorMessageTry again in a moment.Réessayez dans un instant.أعد المحاولة بعد قليل.
playerOfflineTitleYou are offlineVous êtes hors ligneأنت غير متصل
playerOfflineMessageConnect to keep listening.Connectez-vous pour continuer l’écoute.اتصل لمواصلة الاستماع.
playerRetryTry againRéessayerأعد المحاولة

Reference renders

The 16 images published with version 0.2.5, rendered by the shared harness on macOS with Flutter 3.47.7. Each shows every state side by side, at the size of an iPhone (390 × 844) or of an Android phone (412 × 915). fliin verify player compares your copy to them.

iOS · Light
Music player, iOS, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Music player, iOS, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Music player, iOS, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Music player, iOS, Light, text 2.0×, Right to left
Text 2.0× · Right to left
iOS · Dark
Music player, iOS, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Music player, iOS, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Music player, iOS, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Music player, iOS, Dark, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Light
Music player, Android, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Music player, Android, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Music player, Android, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Music player, Android, Light, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Dark
Music player, Android, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Music player, Android, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Music player, Android, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Music player, Android, Dark, text 2.0×, Right to left
Text 2.0× · Right to left

Use it to

  • toggle play and pause of the song being heard
  • show the artwork, title and artist of the current song
  • move to another part of the song being heard
  • switch to the next or previous song
  • turn on shuffle or repeat for a playlist
  • add the current song to favorites
Not for
  • a list of tracks, albums or playlists: use the music library (library)
  • setting a continuous value: use the slider (slider)
States
  • Playing
  • Paused
  • Buffering
  • Loading
  • Error
  • Offline

Arrows to move, Enter to open, Escape to close.