92 lines
3 KiB
Dart
92 lines
3 KiB
Dart
import 'package:just_audio/just_audio.dart' show LoopMode;
|
|
|
|
import '../playback/playback_engine.dart';
|
|
import '../subsonic/models.dart';
|
|
import 'messages.dart';
|
|
import 'remote_session.dart';
|
|
|
|
/// Map a wire [StateSnapshot] back into the app's [PlaybackState] so the UI can
|
|
/// render a remote device's playback exactly as it renders local playback.
|
|
PlaybackState playbackStateFromSnapshot(StateSnapshot s) => PlaybackState(
|
|
queue: s.queue,
|
|
currentIndex: s.currentIndex,
|
|
playing: s.playing,
|
|
position: Duration(milliseconds: s.positionMs),
|
|
duration: Duration(milliseconds: s.durationMs),
|
|
shuffle: s.shuffle,
|
|
loop: LoopMode.values.firstWhere(
|
|
(m) => m.name == s.loop,
|
|
orElse: () => LoopMode.off,
|
|
),
|
|
supported: true,
|
|
);
|
|
|
|
/// The command half of the facade while attached to another device: every
|
|
/// [PlaybackCommands] call is serialized into a [RemoteCommand] and sent to the
|
|
/// host, which applies it to *its* engine. Playback state comes back the other
|
|
/// way as snapshots (see `remote_providers.dart`), so this proxy holds none.
|
|
///
|
|
/// Commands are fire-and-forget: LAN round-trips are sub-frame, and the host's
|
|
/// next snapshot is the source of truth, so there is no optimistic local edit
|
|
/// to reconcile.
|
|
class RemotePlaybackProxy implements PlaybackCommands {
|
|
RemotePlaybackProxy(this._session);
|
|
|
|
final RemoteSession _session;
|
|
|
|
@override
|
|
Future<void> playSongs(List<Song> songs, {int startIndex = 0}) async =>
|
|
_session.send(RemoteCommand.playSongs(songs, startIndex: startIndex));
|
|
|
|
@override
|
|
Future<void> jumpTo(int index) async =>
|
|
_session.send(RemoteCommand.jumpTo(index));
|
|
|
|
@override
|
|
Future<void> playNext(Song song) async =>
|
|
_session.send(RemoteCommand.playNext(song));
|
|
|
|
@override
|
|
Future<void> addToQueue(Song song) async =>
|
|
_session.send(RemoteCommand.addToQueue(song));
|
|
|
|
@override
|
|
Future<void> removeAt(int index) async =>
|
|
_session.send(RemoteCommand.removeAt(index));
|
|
|
|
@override
|
|
Future<void> reorderQueue(int oldIndex, int newIndex) async =>
|
|
_session.send(RemoteCommand.reorder(oldIndex, newIndex));
|
|
|
|
@override
|
|
Future<void> togglePlayPause() async =>
|
|
_session.send(const RemoteCommand.playPause());
|
|
|
|
@override
|
|
Future<void> next() async => _session.send(const RemoteCommand.next());
|
|
|
|
@override
|
|
Future<void> previous() async =>
|
|
_session.send(const RemoteCommand.previous());
|
|
|
|
@override
|
|
Future<void> seek(Duration position) async =>
|
|
_session.send(RemoteCommand.seek(position.inMilliseconds));
|
|
|
|
@override
|
|
Future<void> toggleShuffle() async =>
|
|
_session.send(const RemoteCommand.toggleShuffle());
|
|
|
|
@override
|
|
Future<void> cycleLoop() async =>
|
|
_session.send(const RemoteCommand.cycleLoop());
|
|
|
|
@override
|
|
Future<void> retry() async => _session.send(const RemoteCommand.retry());
|
|
|
|
@override
|
|
void resyncFromPlayer() {
|
|
// No-op: the remote device owns the player; we receive its state pushed to
|
|
// us and have no local playhead to re-read.
|
|
}
|
|
}
|