network addition

This commit is contained in:
Forrest 2026-08-04 16:39:18 -04:00
parent 3bd713d667
commit 2099d3d64d
27 changed files with 2237 additions and 40 deletions

View file

@ -0,0 +1,92 @@
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.
}
}