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.
|
||||
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.
|
||||
///
|
||||
/// 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.
|
||||
void _stop() {
|
||||
active.add(false);
|
||||
playlist.add([]);
|
||||
currentTrack.add(0);
|
||||
playing.add(false);
|
||||
position.add(const Duration());
|
||||
duration.add(const Duration(seconds: 1));
|
||||
|
|
@ -87,6 +99,8 @@ class Player {
|
|||
final state = msg as PlaybackServiceState;
|
||||
|
||||
// TODO: Consider checking, whether values have actually changed.
|
||||
playlist.add(state.playlist);
|
||||
currentTrack.add(state.currentTrack);
|
||||
playing.add(state.playing);
|
||||
position.add(Duration(milliseconds: state.positionMs));
|
||||
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.
|
||||
void dispose() {
|
||||
_playbackServiceStateSubscription.cancel();
|
||||
|
|
@ -288,6 +330,7 @@ class _PlaybackService extends BackgroundAudioTask {
|
|||
super.onCustomAction(name, arguments);
|
||||
|
||||
// addTracks expects a List<Map<String, dynamic>> as its argument.
|
||||
// skipTo expects an integer as its argument.
|
||||
if (name == 'addTracks') {
|
||||
final tracksJson = jsonDecode(arguments);
|
||||
final List<InternalTrack> tracks = List.castFrom(
|
||||
|
|
@ -297,6 +340,15 @@ class _PlaybackService extends BackgroundAudioTask {
|
|||
_durationMs = newDurationMs;
|
||||
_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') {
|
||||
// Send the current state to the main isolate.
|
||||
_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
|
||||
void onStop() {
|
||||
_player.stop();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../backend.dart';
|
||||
import '../music_library.dart';
|
||||
import '../widgets/play_pause_button.dart';
|
||||
|
||||
class ProgramScreen extends StatefulWidget {
|
||||
|
|
@ -45,6 +46,45 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
|||
),
|
||||
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(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
@ -82,12 +122,16 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
|||
Spacer(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.skip_previous),
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
backend.player.skipToPrevious();
|
||||
},
|
||||
),
|
||||
PlayPauseButton(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.skip_next),
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
backend.player.skipToNext();
|
||||
},
|
||||
),
|
||||
Spacer(),
|
||||
Padding(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue