mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00

* feat: add connect server support * feat: add ability discover and connect to same network Spotube(s) and sync queue * feat(connect): add player controls, shuffle, loop, progress bar and queue support * feat: make control page adaptive * feat: add volume control support * cd: upgrade macos runner version * chore: upgrade inappwebview version to 6 * feat: customized devices button * feat: add user icon next to devices button * feat: add play in remote device support * feat: show alert when new client connects * fix: ignore the device itself from broadcast list * fix: volume control not working * feat: add ability to select current device's output speaker
375 lines
8.7 KiB
Dart
375 lines
8.7 KiB
Dart
part of 'connect.dart';
|
|
|
|
enum WsEvent {
|
|
error,
|
|
volume,
|
|
removeTrack,
|
|
addTrack,
|
|
reorder,
|
|
shuffle,
|
|
loop,
|
|
seek,
|
|
duration,
|
|
queue,
|
|
position,
|
|
playing,
|
|
resume,
|
|
pause,
|
|
load,
|
|
next,
|
|
previous,
|
|
jump,
|
|
stop;
|
|
|
|
static WsEvent fromString(String value) {
|
|
return WsEvent.values.firstWhere((e) => e.name == value);
|
|
}
|
|
}
|
|
|
|
typedef EventCallback<T> = FutureOr<void> Function(T event);
|
|
|
|
class WebSocketEvent<T> {
|
|
final WsEvent type;
|
|
final T data;
|
|
|
|
WebSocketEvent(this.type, this.data);
|
|
|
|
factory WebSocketEvent.fromJson(
|
|
Map<String, dynamic> json,
|
|
T Function(dynamic) fromJson,
|
|
) {
|
|
return WebSocketEvent(
|
|
WsEvent.fromString(json["type"]),
|
|
fromJson(json["data"]),
|
|
);
|
|
}
|
|
|
|
String toJson() {
|
|
return jsonEncode({
|
|
"type": type.name,
|
|
"data": data,
|
|
});
|
|
}
|
|
|
|
Future<void> onPosition(
|
|
EventCallback<WebSocketPositionEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.position) {
|
|
await callback(WebSocketPositionEvent.fromJson({"data": data}));
|
|
}
|
|
}
|
|
|
|
Future<void> onPlaying(
|
|
EventCallback<WebSocketPlayingEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.playing) {
|
|
await callback(WebSocketPlayingEvent(data as bool));
|
|
}
|
|
}
|
|
|
|
Future<void> onResume(
|
|
EventCallback<WebSocketResumeEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.resume) {
|
|
await callback(WebSocketResumeEvent());
|
|
}
|
|
}
|
|
|
|
Future<void> onPause(
|
|
EventCallback<WebSocketPauseEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.pause) {
|
|
await callback(WebSocketPauseEvent());
|
|
}
|
|
}
|
|
|
|
Future<void> onStop(
|
|
EventCallback<WebSocketStopEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.stop) {
|
|
await callback(WebSocketStopEvent());
|
|
}
|
|
}
|
|
|
|
Future<void> onLoad(
|
|
EventCallback<WebSocketLoadEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.load) {
|
|
await callback(
|
|
WebSocketLoadEvent(
|
|
WebSocketLoadEventData.fromJson(data as Map<String, dynamic>),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> onNext(
|
|
EventCallback<WebSocketNextEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.next) {
|
|
await callback(WebSocketNextEvent());
|
|
}
|
|
}
|
|
|
|
Future<void> onPrevious(
|
|
EventCallback<WebSocketPreviousEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.previous) {
|
|
await callback(WebSocketPreviousEvent());
|
|
}
|
|
}
|
|
|
|
Future<void> onJump(
|
|
EventCallback<WebSocketJumpEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.jump) {
|
|
await callback(WebSocketJumpEvent(data as int));
|
|
}
|
|
}
|
|
|
|
Future<void> onError(
|
|
EventCallback<WebSocketErrorEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.error) {
|
|
await callback(WebSocketErrorEvent(data as String));
|
|
}
|
|
}
|
|
|
|
Future<void> onQueue(
|
|
EventCallback<WebSocketQueueEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.queue) {
|
|
await callback(
|
|
WebSocketQueueEvent.fromJson(data as Map<String, dynamic>),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> onDuration(
|
|
EventCallback<WebSocketDurationEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.duration) {
|
|
await callback(
|
|
WebSocketDurationEvent(
|
|
Duration(seconds: data as int),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> onSeek(
|
|
EventCallback<WebSocketSeekEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.seek) {
|
|
await callback(
|
|
WebSocketSeekEvent(
|
|
Duration(seconds: data as int),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> onShuffle(
|
|
EventCallback<WebSocketShuffleEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.shuffle) {
|
|
await callback(WebSocketShuffleEvent(data as bool));
|
|
}
|
|
}
|
|
|
|
Future<void> onLoop(
|
|
EventCallback<WebSocketLoopEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.loop) {
|
|
await callback(
|
|
WebSocketLoopEvent(
|
|
PlaybackLoopMode.fromString(data as String),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> onRemoveTrack(
|
|
EventCallback<WebSocketRemoveTrackEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.removeTrack) {
|
|
await callback(WebSocketRemoveTrackEvent(data as String));
|
|
}
|
|
}
|
|
|
|
Future<void> onAddTrack(
|
|
EventCallback<WebSocketAddTrackEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.addTrack) {
|
|
await callback(
|
|
WebSocketAddTrackEvent.fromJson(data as Map<String, dynamic>));
|
|
}
|
|
}
|
|
|
|
Future<void> onReorder(
|
|
EventCallback<WebSocketReorderEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.reorder) {
|
|
await callback(
|
|
WebSocketReorderEvent.fromJson(data as Map<String, dynamic>));
|
|
}
|
|
}
|
|
|
|
Future<void> onVolume(
|
|
EventCallback<WebSocketVolumeEvent> callback,
|
|
) async {
|
|
if (type == WsEvent.volume) {
|
|
await callback(WebSocketVolumeEvent(data as double));
|
|
}
|
|
}
|
|
}
|
|
|
|
class WebSocketLoopEvent extends WebSocketEvent<PlaybackLoopMode> {
|
|
WebSocketLoopEvent(PlaybackLoopMode data) : super(WsEvent.loop, data);
|
|
|
|
WebSocketLoopEvent.fromJson(Map<String, dynamic> json)
|
|
: super(
|
|
WsEvent.loop, PlaybackLoopMode.fromString(json["data"] as String));
|
|
|
|
@override
|
|
String toJson() {
|
|
return jsonEncode({
|
|
"type": type.name,
|
|
"data": data.name,
|
|
});
|
|
}
|
|
}
|
|
|
|
class WebSocketPositionEvent extends WebSocketEvent<Duration> {
|
|
WebSocketPositionEvent(Duration data) : super(WsEvent.position, data);
|
|
|
|
WebSocketPositionEvent.fromJson(Map<String, dynamic> json)
|
|
: super(WsEvent.position, Duration(seconds: json["data"] as int));
|
|
|
|
@override
|
|
String toJson() {
|
|
return jsonEncode({
|
|
"type": type.name,
|
|
"data": data.inSeconds,
|
|
});
|
|
}
|
|
}
|
|
|
|
class WebSocketDurationEvent extends WebSocketEvent<Duration> {
|
|
WebSocketDurationEvent(Duration data) : super(WsEvent.duration, data);
|
|
|
|
WebSocketDurationEvent.fromJson(Map<String, dynamic> json)
|
|
: super(WsEvent.duration, Duration(seconds: json["data"] as int));
|
|
|
|
@override
|
|
String toJson() {
|
|
return jsonEncode({
|
|
"type": type.name,
|
|
"data": data.inSeconds,
|
|
});
|
|
}
|
|
}
|
|
|
|
class WebSocketSeekEvent extends WebSocketEvent<Duration> {
|
|
WebSocketSeekEvent(Duration data) : super(WsEvent.seek, data);
|
|
|
|
WebSocketSeekEvent.fromJson(Map<String, dynamic> json)
|
|
: super(WsEvent.seek, Duration(seconds: json["data"] as int));
|
|
|
|
@override
|
|
String toJson() {
|
|
return jsonEncode({
|
|
"type": type.name,
|
|
"data": data.inSeconds,
|
|
});
|
|
}
|
|
}
|
|
|
|
class WebSocketShuffleEvent extends WebSocketEvent<bool> {
|
|
WebSocketShuffleEvent(bool data) : super(WsEvent.shuffle, data);
|
|
}
|
|
|
|
class WebSocketPlayingEvent extends WebSocketEvent<bool> {
|
|
WebSocketPlayingEvent(bool data) : super(WsEvent.playing, data);
|
|
}
|
|
|
|
class WebSocketResumeEvent extends WebSocketEvent<void> {
|
|
WebSocketResumeEvent() : super(WsEvent.resume, null);
|
|
}
|
|
|
|
class WebSocketPauseEvent extends WebSocketEvent<void> {
|
|
WebSocketPauseEvent() : super(WsEvent.pause, null);
|
|
}
|
|
|
|
class WebSocketStopEvent extends WebSocketEvent<void> {
|
|
WebSocketStopEvent() : super(WsEvent.stop, null);
|
|
}
|
|
|
|
class WebSocketNextEvent extends WebSocketEvent<void> {
|
|
WebSocketNextEvent() : super(WsEvent.next, null);
|
|
}
|
|
|
|
class WebSocketPreviousEvent extends WebSocketEvent<void> {
|
|
WebSocketPreviousEvent() : super(WsEvent.previous, null);
|
|
}
|
|
|
|
class WebSocketJumpEvent extends WebSocketEvent<int> {
|
|
WebSocketJumpEvent(int data) : super(WsEvent.jump, data);
|
|
}
|
|
|
|
class WebSocketErrorEvent extends WebSocketEvent<String> {
|
|
WebSocketErrorEvent(String data) : super(WsEvent.error, data);
|
|
}
|
|
|
|
class WebSocketQueueEvent extends WebSocketEvent<ProxyPlaylist> {
|
|
WebSocketQueueEvent(ProxyPlaylist data) : super(WsEvent.queue, data);
|
|
|
|
factory WebSocketQueueEvent.fromJson(Map<String, dynamic> json) =>
|
|
WebSocketQueueEvent(
|
|
ProxyPlaylist.fromJsonRaw(json),
|
|
);
|
|
}
|
|
|
|
class WebSocketRemoveTrackEvent extends WebSocketEvent<String> {
|
|
WebSocketRemoveTrackEvent(String data) : super(WsEvent.removeTrack, data);
|
|
}
|
|
|
|
class WebSocketAddTrackEvent extends WebSocketEvent<Track> {
|
|
WebSocketAddTrackEvent(Track data) : super(WsEvent.addTrack, data);
|
|
|
|
WebSocketAddTrackEvent.fromJson(Map<String, dynamic> json)
|
|
: super(
|
|
WsEvent.addTrack,
|
|
Track.fromJson(json["data"] as Map<String, dynamic>),
|
|
);
|
|
}
|
|
|
|
typedef ReorderData = ({int oldIndex, int newIndex});
|
|
|
|
class WebSocketReorderEvent extends WebSocketEvent<ReorderData> {
|
|
WebSocketReorderEvent(ReorderData data) : super(WsEvent.reorder, data);
|
|
|
|
factory WebSocketReorderEvent.fromJson(Map<String, dynamic> json) =>
|
|
WebSocketReorderEvent(
|
|
(
|
|
oldIndex: json["oldIndex"] as int,
|
|
newIndex: json["newIndex"] as int,
|
|
),
|
|
);
|
|
|
|
@override
|
|
String toJson() {
|
|
return jsonEncode({
|
|
"type": type.name,
|
|
"data": {
|
|
"oldIndex": data.oldIndex,
|
|
"newIndex": data.newIndex,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
class WebSocketVolumeEvent extends WebSocketEvent<double> {
|
|
WebSocketVolumeEvent(double data) : super(WsEvent.volume, data);
|
|
}
|