2020-04-18 13:50:38 +02:00
|
|
|
import 'dart:async';
|
2020-04-21 17:37:01 +02:00
|
|
|
import 'dart:convert';
|
2020-04-21 19:50:18 +02:00
|
|
|
import 'dart:isolate';
|
|
|
|
|
import 'dart:ui';
|
2020-04-18 13:50:38 +02:00
|
|
|
|
|
|
|
|
import 'package:audio_service/audio_service.dart';
|
2020-05-04 09:23:49 +02:00
|
|
|
import 'package:moor/isolate.dart';
|
|
|
|
|
import 'package:musicus_database/musicus_database.dart';
|
2020-05-04 21:49:44 +02:00
|
|
|
import 'package:musicus_common/musicus_common.dart';
|
2020-04-21 17:37:01 +02:00
|
|
|
import 'package:musicus_player/musicus_player.dart';
|
|
|
|
|
|
2020-04-21 19:50:18 +02:00
|
|
|
const _portName = 'playbackService';
|
|
|
|
|
|
2020-04-18 13:50:38 +02:00
|
|
|
/// Entrypoint for the playback service.
|
|
|
|
|
void _playbackServiceEntrypoint() {
|
|
|
|
|
AudioServiceBackground.run(() => _PlaybackService());
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-04 21:49:44 +02:00
|
|
|
class Playback extends MusicusPlayback {
|
2020-04-21 19:50:18 +02:00
|
|
|
StreamSubscription _playbackServiceStateSubscription;
|
2020-04-18 13:50:38 +02:00
|
|
|
|
|
|
|
|
/// Start playback service.
|
2020-05-04 21:49:44 +02:00
|
|
|
Future<void> _start() async {
|
2020-04-18 13:50:38 +02:00
|
|
|
if (!AudioService.running) {
|
|
|
|
|
await AudioService.start(
|
|
|
|
|
backgroundTaskEntrypoint: _playbackServiceEntrypoint,
|
|
|
|
|
androidNotificationChannelName: 'Musicus playback',
|
|
|
|
|
androidNotificationChannelDescription:
|
|
|
|
|
'Keeps Musicus playing in the background',
|
|
|
|
|
androidNotificationIcon: 'drawable/ic_notification',
|
|
|
|
|
);
|
|
|
|
|
|
2020-04-21 19:50:18 +02:00
|
|
|
active.add(true);
|
2020-04-18 13:50:38 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-24 19:50:03 +02:00
|
|
|
/// Update [position] and [normalizedPosition].
|
2020-04-24 21:49:14 +02:00
|
|
|
///
|
2020-04-24 19:50:03 +02:00
|
|
|
/// Requires [duration] to be up to date
|
|
|
|
|
void _updatePosition(int positionMs) {
|
|
|
|
|
position.add(Duration(milliseconds: positionMs));
|
|
|
|
|
normalizedPosition.add(positionMs / duration.value.inMilliseconds);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Update [position], [duration] and [normalizedPosition].
|
|
|
|
|
void _updateDuration(int positionMs, int durationMs) {
|
|
|
|
|
position.add(Duration(milliseconds: positionMs));
|
|
|
|
|
duration.add(Duration(milliseconds: durationMs));
|
|
|
|
|
normalizedPosition.add(positionMs / durationMs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Update [currentIndex] and [currentTrack].
|
2020-04-24 21:49:14 +02:00
|
|
|
///
|
2020-04-24 19:50:03 +02:00
|
|
|
/// Requires [playlist] to be up to date.
|
|
|
|
|
void _updateCurrentTrack(int index) {
|
|
|
|
|
currentIndex.add(index);
|
|
|
|
|
currentTrack.add(playlist.value[index]);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-04 21:49:44 +02:00
|
|
|
@override
|
|
|
|
|
Future<void> setup() async {
|
2020-05-03 22:45:28 +02:00
|
|
|
if (_playbackServiceStateSubscription != null) {
|
|
|
|
|
_playbackServiceStateSubscription.cancel();
|
2020-04-21 19:50:18 +02:00
|
|
|
}
|
2020-04-18 13:50:38 +02:00
|
|
|
|
2020-05-03 22:45:28 +02:00
|
|
|
// We will receive updated state information from the playback service,
|
|
|
|
|
// which runs in its own isolate, through this port.
|
|
|
|
|
final receivePort = ReceivePort();
|
|
|
|
|
receivePort.asBroadcastStream(
|
|
|
|
|
onListen: (subscription) {
|
|
|
|
|
_playbackServiceStateSubscription = subscription;
|
|
|
|
|
},
|
|
|
|
|
).listen((msg) {
|
|
|
|
|
// If state is null, the background audio service has stopped.
|
|
|
|
|
if (msg == null) {
|
2020-05-04 21:49:44 +02:00
|
|
|
dispose();
|
2020-05-03 22:45:28 +02:00
|
|
|
} else {
|
2020-05-04 21:49:44 +02:00
|
|
|
if (!active.value) {
|
|
|
|
|
active.add(true);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-03 22:45:28 +02:00
|
|
|
if (msg is _StatusMessage) {
|
|
|
|
|
playing.add(msg.playing);
|
|
|
|
|
} else if (msg is _PositionMessage) {
|
|
|
|
|
_updatePosition(msg.positionMs);
|
|
|
|
|
} else if (msg is _TrackMessage) {
|
|
|
|
|
_updateCurrentTrack(msg.currentTrack);
|
|
|
|
|
_updateDuration(msg.positionMs, msg.durationMs);
|
|
|
|
|
} else if (msg is _PlaylistMessage) {
|
|
|
|
|
playlist.add(msg.playlist);
|
|
|
|
|
_updateCurrentTrack(msg.currentTrack);
|
|
|
|
|
_updateDuration(msg.positionMs, msg.durationMs);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2020-05-04 09:23:49 +02:00
|
|
|
|
2020-05-03 22:45:28 +02:00
|
|
|
IsolateNameServer.removePortNameMapping(_portName);
|
|
|
|
|
IsolateNameServer.registerPortWithName(receivePort.sendPort, _portName);
|
|
|
|
|
|
2020-04-21 19:50:18 +02:00
|
|
|
if (AudioService.running) {
|
|
|
|
|
active.add(true);
|
|
|
|
|
|
|
|
|
|
// Instruct the background service to send its current state. This will
|
|
|
|
|
// by handled in the listeners, that were already set in the constructor.
|
|
|
|
|
AudioService.customAction('sendState');
|
2020-04-18 13:50:38 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-04 21:49:44 +02:00
|
|
|
@override
|
2020-04-21 17:37:01 +02:00
|
|
|
Future<void> addTracks(List<InternalTrack> tracks) async {
|
|
|
|
|
if (!AudioService.running) {
|
2020-05-04 21:49:44 +02:00
|
|
|
await _start();
|
2020-04-21 17:37:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await AudioService.customAction('addTracks', jsonEncode(tracks));
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-04 21:49:44 +02:00
|
|
|
@override
|
2020-04-26 18:54:49 +02:00
|
|
|
Future<void> removeTrack(int index) async {
|
|
|
|
|
if (AudioService.running) {
|
|
|
|
|
await AudioService.customAction('removeTrack', index);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-04 21:49:44 +02:00
|
|
|
@override
|
|
|
|
|
Future<void> playPause() async {
|
|
|
|
|
if (active.value) {
|
|
|
|
|
if (playing.value) {
|
|
|
|
|
await AudioService.pause();
|
|
|
|
|
} else {
|
|
|
|
|
await AudioService.play();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
2020-04-18 13:50:38 +02:00
|
|
|
Future<void> seekTo(double pos) async {
|
|
|
|
|
if (active.value && pos >= 0.0 && pos <= 1.0) {
|
|
|
|
|
final durationMs = duration.value.inMilliseconds;
|
|
|
|
|
await AudioService.seekTo((pos * durationMs).floor());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-04 21:49:44 +02:00
|
|
|
@override
|
|
|
|
|
Future<void> skipToPrevious() async {
|
2020-04-22 10:01:50 +02:00
|
|
|
if (AudioService.running) {
|
2020-05-04 21:49:44 +02:00
|
|
|
await AudioService.skipToPrevious();
|
2020-04-22 10:01:50 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-04 21:49:44 +02:00
|
|
|
@override
|
|
|
|
|
Future<void> skipToNext() async {
|
2020-04-22 10:01:50 +02:00
|
|
|
if (AudioService.running) {
|
2020-05-04 21:49:44 +02:00
|
|
|
await AudioService.skipToNext();
|
2020-04-22 10:01:50 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-04 21:49:44 +02:00
|
|
|
@override
|
2020-04-22 10:01:50 +02:00
|
|
|
Future<void> skipTo(int index) async {
|
|
|
|
|
if (AudioService.running) {
|
|
|
|
|
await AudioService.customAction('skipTo', index);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-04 21:49:44 +02:00
|
|
|
@override
|
2020-04-18 13:50:38 +02:00
|
|
|
void dispose() {
|
2020-05-04 21:49:44 +02:00
|
|
|
super.dispose();
|
2020-04-21 19:50:18 +02:00
|
|
|
_playbackServiceStateSubscription.cancel();
|
2020-04-18 13:50:38 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-24 19:50:03 +02:00
|
|
|
/// A message from the playback service to the UI.
|
|
|
|
|
abstract class _Message {}
|
2020-04-21 19:50:18 +02:00
|
|
|
|
2020-04-24 19:50:03 +02:00
|
|
|
/// Playback status update.
|
|
|
|
|
class _StatusMessage extends _Message {
|
2020-04-21 19:50:18 +02:00
|
|
|
/// Whether the player is playing (or paused).
|
|
|
|
|
final bool playing;
|
|
|
|
|
|
2020-04-24 21:49:14 +02:00
|
|
|
/// Playback position in milliseconds.
|
|
|
|
|
final int positionMs;
|
|
|
|
|
|
2020-04-24 19:50:03 +02:00
|
|
|
_StatusMessage({
|
|
|
|
|
this.playing,
|
2020-04-24 21:49:14 +02:00
|
|
|
this.positionMs,
|
2020-04-24 19:50:03 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The playback position has changed.
|
|
|
|
|
///
|
|
|
|
|
/// This could be due to seeking or because time progressed.
|
|
|
|
|
class _PositionMessage extends _Message {
|
|
|
|
|
/// Playback position in milliseconds.
|
2020-04-21 19:50:18 +02:00
|
|
|
final int positionMs;
|
|
|
|
|
|
2020-04-24 19:50:03 +02:00
|
|
|
_PositionMessage({
|
|
|
|
|
this.positionMs,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The current track has changed.
|
|
|
|
|
///
|
|
|
|
|
/// This also notifies about the playback position, as the old position could be
|
|
|
|
|
/// behind the new duration.
|
|
|
|
|
class _TrackMessage extends _Message {
|
|
|
|
|
/// Index of the new track within the playlist.
|
|
|
|
|
final int currentTrack;
|
|
|
|
|
|
|
|
|
|
/// Duration of the new track in milliseconds.
|
2020-04-21 19:50:18 +02:00
|
|
|
final int durationMs;
|
|
|
|
|
|
2020-04-24 19:50:03 +02:00
|
|
|
/// Playback position in milliseconds.
|
|
|
|
|
final int positionMs;
|
|
|
|
|
|
|
|
|
|
_TrackMessage({
|
2020-04-21 19:50:18 +02:00
|
|
|
this.currentTrack,
|
|
|
|
|
this.durationMs,
|
2020-04-24 19:50:03 +02:00
|
|
|
this.positionMs,
|
2020-04-21 19:50:18 +02:00
|
|
|
});
|
2020-04-24 19:50:03 +02:00
|
|
|
}
|
2020-04-21 19:50:18 +02:00
|
|
|
|
2020-04-24 19:50:03 +02:00
|
|
|
/// The playlist was changed.
|
|
|
|
|
///
|
|
|
|
|
/// This also notifies about the current track, as the old index could be out of
|
|
|
|
|
/// range in the new playlist.
|
|
|
|
|
class _PlaylistMessage extends _Message {
|
|
|
|
|
/// The new playlist.
|
|
|
|
|
final List<InternalTrack> playlist;
|
2020-04-21 19:50:18 +02:00
|
|
|
|
2020-04-24 19:50:03 +02:00
|
|
|
/// The current track.
|
|
|
|
|
final int currentTrack;
|
|
|
|
|
|
|
|
|
|
/// Duration of the current track in milliseconds.
|
|
|
|
|
final int durationMs;
|
|
|
|
|
|
|
|
|
|
/// Playback position in milliseconds.
|
|
|
|
|
final int positionMs;
|
|
|
|
|
|
|
|
|
|
_PlaylistMessage({
|
|
|
|
|
this.playlist,
|
|
|
|
|
this.currentTrack,
|
|
|
|
|
this.durationMs,
|
|
|
|
|
this.positionMs,
|
|
|
|
|
});
|
2020-04-21 19:50:18 +02:00
|
|
|
}
|
|
|
|
|
|
2020-04-18 13:50:38 +02:00
|
|
|
class _PlaybackService extends BackgroundAudioTask {
|
2020-04-21 19:50:18 +02:00
|
|
|
/// The interval between playback position updates in milliseconds.
|
|
|
|
|
static const positionUpdateInterval = 250;
|
|
|
|
|
|
2020-04-18 13:50:38 +02:00
|
|
|
static const playControl = MediaControl(
|
|
|
|
|
androidIcon: 'drawable/ic_play',
|
|
|
|
|
label: 'Play',
|
|
|
|
|
action: MediaAction.play,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
static const pauseControl = MediaControl(
|
|
|
|
|
androidIcon: 'drawable/ic_pause',
|
|
|
|
|
label: 'Pause',
|
|
|
|
|
action: MediaAction.pause,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
static const stopControl = MediaControl(
|
|
|
|
|
androidIcon: 'drawable/ic_stop',
|
|
|
|
|
label: 'Stop',
|
|
|
|
|
action: MediaAction.stop,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
final _completer = Completer();
|
2020-05-04 09:23:49 +02:00
|
|
|
final _loading = Completer();
|
2020-04-21 17:37:01 +02:00
|
|
|
final List<InternalTrack> _playlist = [];
|
2020-04-18 13:50:38 +02:00
|
|
|
|
2020-05-04 09:23:49 +02:00
|
|
|
Database db;
|
2020-04-21 17:37:01 +02:00
|
|
|
MusicusPlayer _player;
|
|
|
|
|
int _currentTrack = 0;
|
2020-04-18 13:50:38 +02:00
|
|
|
bool _playing = false;
|
2020-04-21 19:50:18 +02:00
|
|
|
int _durationMs = 1000;
|
2020-04-18 13:50:38 +02:00
|
|
|
|
2020-04-21 17:37:01 +02:00
|
|
|
_PlaybackService() {
|
2020-04-24 19:50:03 +02:00
|
|
|
_player = MusicusPlayer(onComplete: () async {
|
|
|
|
|
if (_currentTrack < _playlist.length - 1) {
|
|
|
|
|
await _setCurrentTrack(_currentTrack + 1);
|
|
|
|
|
_sendTrack();
|
|
|
|
|
} else {
|
|
|
|
|
_playing = false;
|
|
|
|
|
_sendStatus();
|
|
|
|
|
_setState();
|
|
|
|
|
}
|
2020-04-21 17:37:01 +02:00
|
|
|
});
|
2020-05-04 09:23:49 +02:00
|
|
|
|
|
|
|
|
_load();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Initialize database.
|
|
|
|
|
Future<void> _load() async {
|
2020-05-04 21:49:44 +02:00
|
|
|
final moorPort = IsolateNameServer.lookupPortByName('moor');
|
2020-05-04 09:23:49 +02:00
|
|
|
final moorIsolate = MoorIsolate.fromConnectPort(moorPort);
|
|
|
|
|
db = Database.connect(await moorIsolate.connect());
|
|
|
|
|
_loading.complete();
|
2020-04-21 17:37:01 +02:00
|
|
|
}
|
|
|
|
|
|
2020-04-24 19:50:03 +02:00
|
|
|
/// Update the audio service status for the system.
|
2020-04-21 19:50:18 +02:00
|
|
|
Future<void> _setState() async {
|
|
|
|
|
final positionMs = await _player.getPosition() ?? 0;
|
|
|
|
|
final updateTime = DateTime.now().millisecondsSinceEpoch;
|
|
|
|
|
|
2020-04-18 13:50:38 +02:00
|
|
|
AudioServiceBackground.setState(
|
|
|
|
|
controls:
|
|
|
|
|
_playing ? [pauseControl, stopControl] : [playControl, stopControl],
|
|
|
|
|
basicState:
|
|
|
|
|
_playing ? BasicPlaybackState.playing : BasicPlaybackState.paused,
|
2020-04-21 19:50:18 +02:00
|
|
|
position: positionMs,
|
|
|
|
|
updateTime: updateTime,
|
2020-04-18 13:50:38 +02:00
|
|
|
);
|
|
|
|
|
|
2020-05-04 09:23:49 +02:00
|
|
|
if (_playlist.isNotEmpty) {
|
|
|
|
|
await _loading.future;
|
|
|
|
|
|
|
|
|
|
final track = _playlist[_currentTrack];
|
|
|
|
|
final recordingInfo = await db.getRecording(track.track.recordingId);
|
|
|
|
|
final workInfo = await db.getWork(recordingInfo.recording.work);
|
|
|
|
|
|
|
|
|
|
final composers = workInfo.composers
|
|
|
|
|
.map((p) => '${p.firstName} ${p.lastName}')
|
|
|
|
|
.join(', ');
|
|
|
|
|
|
|
|
|
|
final title = workInfo.work.title;
|
|
|
|
|
|
|
|
|
|
AudioServiceBackground.setMediaItem(MediaItem(
|
2020-05-04 21:49:44 +02:00
|
|
|
id: track.identifier,
|
2020-05-04 09:23:49 +02:00
|
|
|
album: composers,
|
|
|
|
|
title: title,
|
|
|
|
|
));
|
|
|
|
|
}
|
2020-04-24 19:50:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Send a message to the UI.
|
|
|
|
|
void _sendMsg(_Message msg) {
|
|
|
|
|
final sendPort = IsolateNameServer.lookupPortByName(_portName);
|
|
|
|
|
sendPort?.send(msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Notify the UI about the current playback status.
|
2020-04-24 21:49:14 +02:00
|
|
|
Future<void> _sendStatus() async {
|
2020-04-24 19:50:03 +02:00
|
|
|
_sendMsg(_StatusMessage(
|
|
|
|
|
playing: _playing,
|
2020-04-24 21:49:14 +02:00
|
|
|
positionMs: await _player.getPosition(),
|
2020-04-24 19:50:03 +02:00
|
|
|
));
|
|
|
|
|
}
|
2020-04-21 19:50:18 +02:00
|
|
|
|
2020-04-24 19:50:03 +02:00
|
|
|
/// Notify the UI about the current playback position.
|
|
|
|
|
Future<void> _sendPosition() async {
|
|
|
|
|
_sendMsg(_PositionMessage(
|
|
|
|
|
positionMs: await _player.getPosition(),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Notify the UI about the current track.
|
|
|
|
|
Future<void> _sendTrack() async {
|
|
|
|
|
_sendMsg(_TrackMessage(
|
|
|
|
|
currentTrack: _currentTrack,
|
|
|
|
|
durationMs: _durationMs,
|
|
|
|
|
positionMs: await _player.getPosition(),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Notify the UI about the current playlist.
|
|
|
|
|
Future<void> _sendPlaylist() async {
|
|
|
|
|
_sendMsg(_PlaylistMessage(
|
2020-04-21 19:50:18 +02:00
|
|
|
playlist: _playlist,
|
|
|
|
|
currentTrack: _currentTrack,
|
|
|
|
|
durationMs: _durationMs,
|
2020-04-24 19:50:03 +02:00
|
|
|
positionMs: await _player.getPosition(),
|
2020-04-21 19:50:18 +02:00
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-24 19:50:03 +02:00
|
|
|
/// Notify the UI of the new playback position periodically.
|
2020-04-21 19:50:18 +02:00
|
|
|
Future<void> _updatePosition() async {
|
|
|
|
|
while (_playing) {
|
|
|
|
|
await Future.delayed(
|
|
|
|
|
const Duration(milliseconds: positionUpdateInterval));
|
2020-04-24 19:50:03 +02:00
|
|
|
_sendPosition();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-24 21:49:14 +02:00
|
|
|
/// Set the current track, update the player and notify the system.
|
2020-04-24 19:50:03 +02:00
|
|
|
Future<void> _setCurrentTrack(int index) async {
|
|
|
|
|
_currentTrack = index;
|
2020-05-04 21:49:44 +02:00
|
|
|
_durationMs = await _player.setUri(_playlist[_currentTrack].identifier);
|
2020-04-24 19:50:03 +02:00
|
|
|
_setState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Add [tracks] to the playlist.
|
|
|
|
|
Future<void> _addTracks(List<InternalTrack> tracks) async {
|
|
|
|
|
final play = _playlist.isEmpty;
|
2020-04-21 19:50:18 +02:00
|
|
|
|
2020-04-24 19:50:03 +02:00
|
|
|
_playlist.addAll(tracks);
|
|
|
|
|
if (play) {
|
|
|
|
|
await _setCurrentTrack(0);
|
2020-04-21 19:50:18 +02:00
|
|
|
}
|
2020-04-24 19:50:03 +02:00
|
|
|
|
|
|
|
|
_sendPlaylist();
|
2020-04-18 13:50:38 +02:00
|
|
|
}
|
|
|
|
|
|
2020-04-26 18:54:49 +02:00
|
|
|
/// Remove the track at [index] from the playlist.
|
|
|
|
|
///
|
|
|
|
|
/// If it was the current track, the next track will be played.
|
|
|
|
|
Future<void> _removeTrack(int index) async {
|
|
|
|
|
if (index >= 0 && index < _playlist.length) {
|
|
|
|
|
_playlist.removeAt(index);
|
|
|
|
|
|
|
|
|
|
if (_playlist.isEmpty) {
|
|
|
|
|
onStop();
|
|
|
|
|
} else {
|
|
|
|
|
if (_currentTrack == index) {
|
|
|
|
|
await _setCurrentTrack(index);
|
|
|
|
|
} else if (_currentTrack > index) {
|
|
|
|
|
_currentTrack--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_sendPlaylist();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-24 21:49:14 +02:00
|
|
|
/// Jump to the beginning of the track with the index [index].
|
|
|
|
|
Future<void> _skipTo(int index) async {
|
|
|
|
|
if (index >= 0 && index < _playlist.length) {
|
|
|
|
|
await _setCurrentTrack(index);
|
|
|
|
|
_sendTrack();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-18 13:50:38 +02:00
|
|
|
@override
|
|
|
|
|
Future<void> onStart() async {
|
|
|
|
|
_setState();
|
|
|
|
|
await _completer.future;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 17:37:01 +02:00
|
|
|
@override
|
2020-05-04 21:49:44 +02:00
|
|
|
Future<void> onCustomAction(String name, dynamic arguments) async {
|
2020-04-21 17:37:01 +02:00
|
|
|
super.onCustomAction(name, arguments);
|
|
|
|
|
|
|
|
|
|
// addTracks expects a List<Map<String, dynamic>> as its argument.
|
2020-04-26 18:54:49 +02:00
|
|
|
// skipTo and removeTrack expect an integer as their argument.
|
2020-04-21 17:37:01 +02:00
|
|
|
if (name == 'addTracks') {
|
|
|
|
|
final tracksJson = jsonDecode(arguments);
|
|
|
|
|
final List<InternalTrack> tracks = List.castFrom(
|
|
|
|
|
tracksJson.map((j) => InternalTrack.fromJson(j)).toList());
|
2020-04-24 19:50:03 +02:00
|
|
|
|
|
|
|
|
_addTracks(tracks);
|
2020-04-26 18:54:49 +02:00
|
|
|
} else if (name == 'removeTrack') {
|
|
|
|
|
final index = arguments as int;
|
|
|
|
|
_removeTrack(index);
|
|
|
|
|
} else if (name == 'skipTo') {
|
2020-04-22 10:01:50 +02:00
|
|
|
final index = arguments as int;
|
2020-04-24 21:49:14 +02:00
|
|
|
_skipTo(index);
|
2020-04-21 19:50:18 +02:00
|
|
|
} else if (name == 'sendState') {
|
2020-04-24 19:50:03 +02:00
|
|
|
_sendPlaylist();
|
|
|
|
|
_sendStatus();
|
2020-04-21 17:37:01 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-18 13:50:38 +02:00
|
|
|
@override
|
|
|
|
|
void onPlay() {
|
|
|
|
|
super.onPlay();
|
|
|
|
|
|
2020-04-21 17:37:01 +02:00
|
|
|
_player.play();
|
2020-04-18 13:50:38 +02:00
|
|
|
_playing = true;
|
2020-04-24 19:50:03 +02:00
|
|
|
|
|
|
|
|
_sendStatus();
|
2020-04-21 19:50:18 +02:00
|
|
|
_updatePosition();
|
2020-04-18 13:50:38 +02:00
|
|
|
_setState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void onPause() {
|
|
|
|
|
super.onPause();
|
|
|
|
|
|
2020-04-21 17:37:01 +02:00
|
|
|
_player.pause();
|
2020-04-18 13:50:38 +02:00
|
|
|
_playing = false;
|
2020-04-24 19:50:03 +02:00
|
|
|
|
|
|
|
|
_sendStatus();
|
2020-04-18 13:50:38 +02:00
|
|
|
_setState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
2020-04-24 19:50:03 +02:00
|
|
|
Future<void> onSeekTo(int position) async {
|
2020-04-18 13:50:38 +02:00
|
|
|
super.onSeekTo(position);
|
|
|
|
|
|
2020-04-24 19:50:03 +02:00
|
|
|
await _player.seekTo(position);
|
|
|
|
|
|
|
|
|
|
_sendPosition();
|
|
|
|
|
_setState();
|
2020-04-18 13:50:38 +02:00
|
|
|
}
|
|
|
|
|
|
2020-04-22 10:01:50 +02:00
|
|
|
@override
|
2020-04-24 19:50:03 +02:00
|
|
|
Future<void> onSkipToNext() async {
|
2020-04-22 10:01:50 +02:00
|
|
|
super.onSkipToNext();
|
|
|
|
|
|
|
|
|
|
if (_playlist.length > 1 && _currentTrack < _playlist.length - 1) {
|
2020-04-24 19:50:03 +02:00
|
|
|
await _setCurrentTrack(_currentTrack + 1);
|
|
|
|
|
_sendTrack();
|
2020-04-22 10:01:50 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
2020-04-24 19:50:03 +02:00
|
|
|
Future<void> onSkipToPrevious() async {
|
2020-04-22 10:01:50 +02:00
|
|
|
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) {
|
2020-04-24 19:50:03 +02:00
|
|
|
await _setCurrentTrack(_currentTrack);
|
|
|
|
|
_sendTrack();
|
2020-04-22 10:01:50 +02:00
|
|
|
} else if (_playlist.length > 1 && _currentTrack > 0) {
|
2020-04-24 19:50:03 +02:00
|
|
|
await _setCurrentTrack(_currentTrack - 1);
|
|
|
|
|
_sendTrack();
|
2020-04-22 10:01:50 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-18 13:50:38 +02:00
|
|
|
@override
|
|
|
|
|
void onStop() {
|
2020-04-21 17:37:01 +02:00
|
|
|
_player.stop();
|
|
|
|
|
|
2020-04-18 13:50:38 +02:00
|
|
|
AudioServiceBackground.setState(
|
|
|
|
|
controls: [],
|
|
|
|
|
basicState: BasicPlaybackState.stopped,
|
|
|
|
|
);
|
|
|
|
|
|
2020-04-21 19:50:18 +02:00
|
|
|
_sendMsg(null);
|
|
|
|
|
|
2020-04-18 13:50:38 +02:00
|
|
|
// This will end onStart.
|
|
|
|
|
_completer.complete();
|
|
|
|
|
}
|
|
|
|
|
}
|