Skip to content

Commit

Permalink
Merge pull request #1 from mvolpato/feature/add-playlist
Browse files Browse the repository at this point in the history
Feature/add playlist
  • Loading branch information
mvolpato authored Mar 8, 2021
2 parents 85f26f1 + 53a0994 commit 414f7dc
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 20 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.0.2] - add playlist
### Added
- this changelog
- a playlist to the player

## [0.0.1] - add basic buttons
### Added
- a row of buttons to play, skip, shuffle, and loop.

[0.0.2]: https://github.com/mvolpato/the-player/releases/tag/0.0.2
[0.0.1]: https://github.com/mvolpato/the-player/releases/tag/0.0.1
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
# music_player
# Music Player: create a simple Flutter music player app

A new Flutter project.
This is a [Flutter](https://flutter.dev) project used during a [series](https://ishouldgotosleep.com/tutorials/music-app/simple-flutter-music-player-app/) of articles
on [I should go to sleep](https://ishouldgotosleep.com).

## Getting Started

This project is a starting point for a Flutter application.
## Articles

A few resources to get you started if this is your first Flutter project:
### Part 1: create the project and add basic buttons

- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
In the [first part](https://ishouldgotosleep.com/tutorials/music-app/simple-flutter-music-player-app/) we
create the Flutter project and play some music from the Internet by using
the package [just_audio](https://pub.dev/packages/just_audio).

For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
<img alt="The UI at the end of the first part" src="https://ishouldgotosleep.com/assets/images/blog/music-app/more-buttons.png" width="200" height="433">

### Part 2: Improve repository and add a playlist

TODO
19 changes: 19 additions & 0 deletions lib/domain/audio_metadata.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* File: audio_metadata.dart
* Project: Flutter music player
* Created Date: Thursday February 18th 2021
* Author: Michele Volpato
* -----
* Copyright (c) 2021 Michele Volpato
*/

/// Represents information about an audio source.
class AudioMetadata {
/// The name of the song/show/recording.
final String title;

/// URL to an image representing this audio source.
final String artwork;

AudioMetadata({this.title, this.artwork});
}
19 changes: 19 additions & 0 deletions lib/screens/commons/player_buttons.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';

/// A `Row` of buttons that interact with audio.
///
/// The order is: shuffle, previous, play/pause/restart, next, repeat.
class PlayerButtons extends StatelessWidget {
const PlayerButtons(this._audioPlayer, {Key key}) : super(key: key);

Expand All @@ -20,31 +23,36 @@ class PlayerButtons extends StatelessWidget {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
// Shuffle
StreamBuilder<bool>(
stream: _audioPlayer.shuffleModeEnabledStream,
builder: (context, snapshot) {
return _shuffleButton(context, snapshot.data ?? false);
},
),
// Previous
StreamBuilder<SequenceState>(
stream: _audioPlayer.sequenceStateStream,
builder: (_, __) {
return _previousButton();
},
),
// Play/pause/restart
StreamBuilder<PlayerState>(
stream: _audioPlayer.playerStateStream,
builder: (_, snapshot) {
final playerState = snapshot.data;
return _playPauseButton(playerState);
},
),
// Next
StreamBuilder<SequenceState>(
stream: _audioPlayer.sequenceStateStream,
builder: (_, __) {
return _nextButton();
},
),
// Repeat
StreamBuilder<LoopMode>(
stream: _audioPlayer.loopModeStream,
builder: (context, snapshot) {
Expand All @@ -55,6 +63,12 @@ class PlayerButtons extends StatelessWidget {
);
}

/// A button that play or pause the audio.
///
/// If the audio is playing, a pause button is shown.
/// If the audio has finished playing, a restart button is shown.
/// If the audio is paused, or not started yet, a play button is shown.
/// If the audio is loading, a progress indicator is shown.
Widget _playPauseButton(PlayerState playerState) {
final processingState = playerState?.processingState;
if (processingState == ProcessingState.loading ||
Expand Down Expand Up @@ -87,6 +101,7 @@ class PlayerButtons extends StatelessWidget {
}
}

/// A shuffle button. Tapping it will either enabled or disable shuffle mode.
Widget _shuffleButton(BuildContext context, bool isEnabled) {
return IconButton(
icon: isEnabled
Expand All @@ -102,20 +117,24 @@ class PlayerButtons extends StatelessWidget {
);
}

/// A previous button. Tapping it will seek to the previous audio in the list.
Widget _previousButton() {
return IconButton(
icon: Icon(Icons.skip_previous),
onPressed: _audioPlayer.hasPrevious ? _audioPlayer.seekToPrevious : null,
);
}

/// A next button. Tapping it will seek to the next audio in the list.
Widget _nextButton() {
return IconButton(
icon: Icon(Icons.skip_next),
onPressed: _audioPlayer.hasNext ? _audioPlayer.seekToNext : null,
);
}

/// A repeat button. Tapping it will cycle through not repeating, repeating
/// the entire list, or repeat the current audio.
Widget _repeatButton(BuildContext context, LoopMode loopMode) {
final icons = [
Icon(Icons.repeat),
Expand Down
44 changes: 44 additions & 0 deletions lib/screens/commons/playlist.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* File: playlist.dart
* Project: Flutter music player
* Created Date: Thursday February 18th 2021
* Author: Michele Volpato
* -----
* Copyright (c) 2021 Michele Volpato
*/

import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';

/// A list of tiles showing all the audio sources added to the audio player.
///
/// Audio sources are displayed with a `ListTile` with a leading image (the
/// artwork), and the title of the audio source.
class Playlist extends StatelessWidget {
const Playlist(this._audioPlayer, {Key key}) : super(key: key);

final AudioPlayer _audioPlayer;

Widget build(BuildContext context) {
return StreamBuilder<SequenceState>(
stream: _audioPlayer.sequenceStateStream,
builder: (context, snapshot) {
final state = snapshot.data;
final sequence = state?.sequence ?? [];
return ListView(
children: [
for (var i = 0; i < sequence.length; i++)
ListTile(
selected: i == state.currentIndex,
leading: Image.network(sequence[i].tag.artwork),
title: Text(sequence[i].tag.title),
onTap: () {
_audioPlayer.seek(Duration.zero, index: i);
},
),
],
);
},
);
}
}
58 changes: 49 additions & 9 deletions lib/screens/player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@

import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
import 'package:music_player/domain/audio_metadata.dart';
import 'package:music_player/screens/commons/player_buttons.dart';
import 'package:music_player/screens/commons/playlist.dart';

/// An audio player.
///
/// At the bottom of the page there is [PlayerButtons], while the rest of the
/// page is filled with a [PLaylist] widget.
class Player extends StatefulWidget {
@override
_PlayerState createState() => _PlayerState();
Expand All @@ -24,15 +30,42 @@ class _PlayerState extends State<Player> {
super.initState();
_audioPlayer = AudioPlayer();

// Hardcoded audio sources
// TODO: Get sources with a network call, or at least move to a separated file.
_audioPlayer
.setAudioSource(ConcatenatingAudioSource(children: [
AudioSource.uri(Uri.parse(
"https://archive.org/download/IGM-V7/IGM%20-%20Vol.%207/25%20Diablo%20-%20Tristram%20%28Blizzard%29.mp3")),
AudioSource.uri(Uri.parse(
"https://archive.org/download/igm-v8_202101/IGM%20-%20Vol.%208/15%20Pokemon%20Red%20-%20Cerulean%20City%20%28Game%20Freak%29.mp3")),
AudioSource.uri(Uri.parse(
"https://scummbar.com/mi2/MI1-CD/01%20-%20Opening%20Themes%20-%20Introduction.mp3")),
]))
.setAudioSource(
ConcatenatingAudioSource(
children: [
AudioSource.uri(
Uri.parse(
"https://archive.org/download/IGM-V7/IGM%20-%20Vol.%207/25%20Diablo%20-%20Tristram%20%28Blizzard%29.mp3"),
tag: AudioMetadata(
title: "Tristram",
artwork:
"https://upload.wikimedia.org/wikipedia/en/3/3a/Diablo_Coverart.png",
),
),
AudioSource.uri(
Uri.parse(
"https://archive.org/download/igm-v8_202101/IGM%20-%20Vol.%208/15%20Pokemon%20Red%20-%20Cerulean%20City%20%28Game%20Freak%29.mp3"),
tag: AudioMetadata(
title: "Cerulean City",
artwork:
"https://upload.wikimedia.org/wikipedia/en/f/f1/Bulbasaur_pokemon_red.png",
),
),
AudioSource.uri(
Uri.parse(
"https://scummbar.com/mi2/MI1-CD/01%20-%20Opening%20Themes%20-%20Introduction.mp3"),
tag: AudioMetadata(
title: "The secret of Monkey Island - Introduction",
artwork:
"https://upload.wikimedia.org/wikipedia/en/a/a8/The_Secret_of_Monkey_Island_artwork.jpg",
),
),
],
),
)
.catchError((error) {
// catch load errors: 404, invalid url ...
print("An error occured $error");
Expand All @@ -49,7 +82,14 @@ class _PlayerState extends State<Player> {
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: PlayerButtons(_audioPlayer),
child: SafeArea(
child: Column(
children: [
Expanded(child: Playlist(_audioPlayer)),
PlayerButtons(_audioPlayer),
],
),
),
),
);
}
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.0.1+1
version: 0.0.2+1

environment:
sdk: ">=2.7.0 <3.0.0"
Expand Down

0 comments on commit 414f7dc

Please sign in to comment.