mirror of
https://github.com/johrpan/musicus_mobile.git
synced 2025-10-26 10:47:25 +01:00
Functional program screen
This implements playlist updates and skipping functionality and updates the program screen accordingly.
This commit is contained in:
parent
5344f16f53
commit
4c0bcb9d26
2 changed files with 125 additions and 2 deletions
|
|
@ -23,6 +23,16 @@ class Player {
|
||||||
/// service is ready to play.
|
/// service is ready to play.
|
||||||
final active = BehaviorSubject.seeded(false);
|
final active = BehaviorSubject.seeded(false);
|
||||||
|
|
||||||
|
/// The current playlist.
|
||||||
|
///
|
||||||
|
/// If the player is not active, this will be an empty list.
|
||||||
|
final playlist = BehaviorSubject.seeded(<InternalTrack>[]);
|
||||||
|
|
||||||
|
/// Index of the currently played (or paused) track within the playlist.
|
||||||
|
///
|
||||||
|
/// This will be zero, if the player is not active!
|
||||||
|
final currentTrack = BehaviorSubject.seeded(0);
|
||||||
|
|
||||||
/// Whether we are currently playing or not.
|
/// Whether we are currently playing or not.
|
||||||
///
|
///
|
||||||
/// This will be false, if the player is not active.
|
/// This will be false, if the player is not active.
|
||||||
|
|
@ -52,6 +62,8 @@ class Player {
|
||||||
/// Set everything to its default because the playback service was stopped.
|
/// Set everything to its default because the playback service was stopped.
|
||||||
void _stop() {
|
void _stop() {
|
||||||
active.add(false);
|
active.add(false);
|
||||||
|
playlist.add([]);
|
||||||
|
currentTrack.add(0);
|
||||||
playing.add(false);
|
playing.add(false);
|
||||||
position.add(const Duration());
|
position.add(const Duration());
|
||||||
duration.add(const Duration(seconds: 1));
|
duration.add(const Duration(seconds: 1));
|
||||||
|
|
@ -87,6 +99,8 @@ class Player {
|
||||||
final state = msg as PlaybackServiceState;
|
final state = msg as PlaybackServiceState;
|
||||||
|
|
||||||
// TODO: Consider checking, whether values have actually changed.
|
// TODO: Consider checking, whether values have actually changed.
|
||||||
|
playlist.add(state.playlist);
|
||||||
|
currentTrack.add(state.currentTrack);
|
||||||
playing.add(state.playing);
|
playing.add(state.playing);
|
||||||
position.add(Duration(milliseconds: state.positionMs));
|
position.add(Duration(milliseconds: state.positionMs));
|
||||||
duration.add(Duration(milliseconds: state.durationMs));
|
duration.add(Duration(milliseconds: state.durationMs));
|
||||||
|
|
@ -138,6 +152,34 @@ class Player {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Play the previous track in the playlist.
|
||||||
|
///
|
||||||
|
/// If the player is not active or there is no previous track, this will do
|
||||||
|
/// nothing.
|
||||||
|
Future<void> skipToNext() async {
|
||||||
|
if (AudioService.running) {
|
||||||
|
await AudioService.skipToNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Skip to the next track in the playlist.
|
||||||
|
///
|
||||||
|
/// If the player is not active or there is no next track, this will do
|
||||||
|
/// nothing. If more than five seconds of the current track have been played,
|
||||||
|
/// this will go back to its beginning instead.
|
||||||
|
Future<void> skipToPrevious() async {
|
||||||
|
if (AudioService.running) {
|
||||||
|
await AudioService.skipToPrevious();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Switch to the track with the index [index] in the playlist.
|
||||||
|
Future<void> skipTo(int index) async {
|
||||||
|
if (AudioService.running) {
|
||||||
|
await AudioService.customAction('skipTo', index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Tidy up.
|
/// Tidy up.
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_playbackServiceStateSubscription.cancel();
|
_playbackServiceStateSubscription.cancel();
|
||||||
|
|
@ -288,6 +330,7 @@ class _PlaybackService extends BackgroundAudioTask {
|
||||||
super.onCustomAction(name, arguments);
|
super.onCustomAction(name, arguments);
|
||||||
|
|
||||||
// addTracks expects a List<Map<String, dynamic>> as its argument.
|
// addTracks expects a List<Map<String, dynamic>> as its argument.
|
||||||
|
// skipTo expects an integer as its argument.
|
||||||
if (name == 'addTracks') {
|
if (name == 'addTracks') {
|
||||||
final tracksJson = jsonDecode(arguments);
|
final tracksJson = jsonDecode(arguments);
|
||||||
final List<InternalTrack> tracks = List.castFrom(
|
final List<InternalTrack> tracks = List.castFrom(
|
||||||
|
|
@ -297,6 +340,15 @@ class _PlaybackService extends BackgroundAudioTask {
|
||||||
_durationMs = newDurationMs;
|
_durationMs = newDurationMs;
|
||||||
_setState();
|
_setState();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
if (name == 'skipTo') {
|
||||||
|
final index = arguments as int;
|
||||||
|
|
||||||
|
if (index >= 0 && index < _playlist.length) {
|
||||||
|
_currentTrack = index;
|
||||||
|
_player.setUri(_playlist[index].uri);
|
||||||
|
_setState();
|
||||||
|
}
|
||||||
} else if (name == 'sendState') {
|
} else if (name == 'sendState') {
|
||||||
// Send the current state to the main isolate.
|
// Send the current state to the main isolate.
|
||||||
_setState();
|
_setState();
|
||||||
|
|
@ -331,6 +383,33 @@ class _PlaybackService extends BackgroundAudioTask {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onSkipToNext() {
|
||||||
|
super.onSkipToNext();
|
||||||
|
|
||||||
|
if (_playlist.length > 1 && _currentTrack < _playlist.length - 1) {
|
||||||
|
_currentTrack++;
|
||||||
|
_player.setUri(_playlist[_currentTrack].uri);
|
||||||
|
_setState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onSkipToPrevious() async {
|
||||||
|
super.onSkipToPrevious();
|
||||||
|
|
||||||
|
// If more than five seconds of the current track have been played, go back
|
||||||
|
// to its beginning, else, switch to the previous track.
|
||||||
|
if (await _player.getPosition() > 5000) {
|
||||||
|
await _player.setUri(_playlist[_currentTrack].uri);
|
||||||
|
_setState();
|
||||||
|
} else if (_playlist.length > 1 && _currentTrack > 0) {
|
||||||
|
_currentTrack--;
|
||||||
|
_player.setUri(_playlist[_currentTrack].uri);
|
||||||
|
_setState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onStop() {
|
void onStop() {
|
||||||
_player.stop();
|
_player.stop();
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
import '../backend.dart';
|
||||||
|
import '../music_library.dart';
|
||||||
import '../widgets/play_pause_button.dart';
|
import '../widgets/play_pause_button.dart';
|
||||||
|
|
||||||
class ProgramScreen extends StatefulWidget {
|
class ProgramScreen extends StatefulWidget {
|
||||||
|
|
@ -45,6 +46,45 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
||||||
),
|
),
|
||||||
title: Text('Program'),
|
title: Text('Program'),
|
||||||
),
|
),
|
||||||
|
body: StreamBuilder<List<InternalTrack>>(
|
||||||
|
stream: backend.player.playlist,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final playlist = snapshot.data;
|
||||||
|
|
||||||
|
if (playlist != null && playlist.isNotEmpty) {
|
||||||
|
return StreamBuilder<int>(
|
||||||
|
stream: backend.player.currentTrack,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: playlist.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final track = playlist[index];
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
leading: index == snapshot.data
|
||||||
|
? const Icon(Icons.play_arrow)
|
||||||
|
: SizedBox(
|
||||||
|
width: 24.0,
|
||||||
|
height: 24.0,
|
||||||
|
),
|
||||||
|
title: Text(track.track.fileName),
|
||||||
|
onTap: () {
|
||||||
|
backend.player.skipTo(index);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
bottomNavigationBar: BottomAppBar(
|
bottomNavigationBar: BottomAppBar(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|
@ -82,12 +122,16 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
||||||
Spacer(),
|
Spacer(),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.skip_previous),
|
icon: const Icon(Icons.skip_previous),
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
backend.player.skipToPrevious();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
PlayPauseButton(),
|
PlayPauseButton(),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.skip_next),
|
icon: const Icon(Icons.skip_next),
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
backend.player.skipToNext();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
Padding(
|
Padding(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue