Skip to content

Commit 731067a

Browse files
authored
Merge pull request #19 from byrdsandbytes/feature/sc-2035-add-client-mutation-functions
add client mutation functions [sc-2035]
2 parents 9b1e958 + 5ffe32d commit 731067a

File tree

3 files changed

+145
-11
lines changed

3 files changed

+145
-11
lines changed

src/app/pages/clients/client-details/client-details.page.html

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,53 @@
1414
<ion-list *ngIf="serverState | async as state">
1515
<ion-item>
1616
<ion-label>
17-
<h2>Client ID: {{ id }}</h2>
17+
<h2>Connection Status</h2>
18+
<p>Last Seen: {{ client.lastSeen.sec * 1000 | date: 'long' }}</p>
19+
<p>Status: {{ client.connected ? 'Connected' : 'Disconnected' }}</p>
20+
<p>Id: {{ client.id }}</p>
21+
22+
<ng-container *ngIf="state.server.groups">
23+
<ng-container *ngFor="let group of state.server.groups">
24+
<ng-container *ngFor="let client of group.clients">
25+
<p *ngIf="client.id === id">Group: {{ group.name }}</p>
26+
</ng-container>
27+
</ng-container>
28+
29+
30+
</ng-container>
1831
</ion-label>
32+
<ion-button fill="outline" slot="end" (click)="refreshClient()">
33+
<ion-icon name="refresh"></ion-icon>
34+
Refresh
35+
</ion-button>
36+
</ion-item>
37+
38+
<ion-item>
39+
<ion-input label="Name" labelPlacement="floating" type="text" placeholder="Enter client name"
40+
[(ngModel)]="client.config.name" (ngModelChange)="setClientName()"></ion-input>
41+
</ion-item>
42+
<ion-item>
43+
<ion-input label="Latency (in ms)" labelPlacement="floating" type="number" placeholder="Enter latency in ms"
44+
[(ngModel)]="client.config.latency" (ngModelChange)="setClientLatency()"></ion-input>
1945
</ion-item>
20-
<ion-item >
21-
<ion-input type="text" placeholder="Enter client name" [(ngModel)]="client.config.name" (ngModelChange)="setClientName()"></ion-input>
46+
<ion-item lines="none">
47+
<ion-input label="Volume (0-100%)" labelPlacement="floating" type="number" placeholder="Enter volume percentage"
48+
[(ngModel)]="client.config.volume.percent" (ngModelChange)="setClientVolume()"></ion-input>
2249
</ion-item>
50+
<ion-item>
51+
<!-- <ion-label position="floating">Volume</ion-label> -->
52+
<ion-range [(ngModel)]="client.config.volume.percent" (ngModelChange)="setClientVolume()">
53+
<!-- <ion-label slot="start">0%</ion-label>
54+
<ion-label slot="end">100%</ion-label> -->
55+
<ion-icon slot="start" name="volume-low"></ion-icon>
56+
<ion-icon slot="end" name="volume-high"></ion-icon>
57+
</ion-range>
58+
</ion-item>
59+
60+
<!-- group selector -->
61+
2362

24-
<ng-container *ngIf="state.server.groups">
63+
<!-- <ng-container *ngIf="state.server.groups">
2564
<ng-container *ngFor="let group of state.server.groups">
2665
<ng-container *ngFor="let client of group.clients">
2766
<ion-item *ngIf="client.id === id">
@@ -31,19 +70,20 @@ <h2>{{ client.id }}</h2>
3170
<p>Volume: {{ client.config.volume.percent || 'N/A' }}%</p>
3271
<p>Last Seen: {{ client.lastSeen.sec * 1000 | date }}</p>
3372
<p>Connected: {{ client.connected }}</p>
73+
<p>Latency: {{ client.config.latency }} ms</p>
3474
</ion-label>
3575
</ion-item>
3676
</ng-container>
3777
</ng-container>
38-
</ng-container>
78+
</ng-container> -->
3979
</ion-list>
4080

4181
<ion-accordion-group *ngIf="client">
4282
<ion-accordion value="{{ client.id }}">
4383
<ion-item slot="header">
4484
<ion-label>
4585
<h2>{{ client.id }}</h2>
46-
<p>{{ client.connected}}</p>
86+
<p>Raw json</p>
4787
</ion-label>
4888
</ion-item>
4989
<ion-item slot="content" lines="none" color="dark">

src/app/pages/clients/client-details/client-details.page.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export class ClientDetailsPage implements OnInit {
1616
id?: string;
1717

1818
serverState?: Observable<SnapCastServerStatusResponse>;
19-
client?: Client;
19+
public client?: Client;
2020

2121

2222
constructor(
@@ -66,5 +66,52 @@ export class ClientDetailsPage implements OnInit {
6666
}
6767

6868

69+
setClientLatency() {
70+
if (!this.client || !this.client.id) {
71+
console.error('ClientDetailsPage: No client or client ID available to set latency');
72+
return;
73+
}
74+
this.snapcastService.setClientLatency(this.client.id, this.client.config.latency).subscribe({
75+
next: () => {
76+
console.log(`ClientDetailsPage: Successfully set latency for client ${this.client.id} to ${this.client.config.latency}`);
77+
},
78+
error: (err) => {
79+
console.error(`ClientDetailsPage: Failed to set latency for client ${this.client.id}`, err);
80+
}
81+
});
82+
}
83+
84+
setClientVolume() {
85+
if (!this.client || !this.client.id) {
86+
console.error('ClientDetailsPage: No client or client ID available to set volume');
87+
return;
88+
}
89+
this.snapcastService.setClientVolumePercent(this.client.id, this.client.config.volume.percent).subscribe({
90+
next: () => {
91+
console.log(`ClientDetailsPage: Successfully set volume for client ${this.client.id} to ${this.client.config.volume.percent}`);
92+
},
93+
error: (err) => {
94+
console.error(`ClientDetailsPage: Failed to set volume for client ${this.client.id}`, err);
95+
}
96+
});
97+
}
98+
99+
refreshClient() {
100+
if (!this.id) {
101+
console.error('ClientDetailsPage: No ID available to refresh client');
102+
return;
103+
}
104+
this.snapcastService.getClientStatus(this.id).subscribe({
105+
next: () => {
106+
console.log(`ClientDetailsPage: Successfully refreshed client ${this.id}`);
107+
this.snapcastService.refreshState(); // Refresh the server state to get the latest data
108+
},
109+
error: (err) => {
110+
console.error(`ClientDetailsPage: Failed to refresh client ${this.id}`, err);
111+
}
112+
});
113+
}
114+
115+
69116

70117
}

src/app/services/snapcast.service.ts

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,27 @@ export class SnapcastService implements OnDestroy {
205205
console.warn(`SnapcastService: Client disconnected but not found in current state: ${disconnectParams.id}`);
206206
}
207207
break;
208+
case 'Client.OnNameChanged':
209+
const nameParams = notification.params as { id: string; name: string };
210+
const clientName = server.groups.flatMap(g => g.clients).find(c => c.id === nameParams.id);
211+
if (clientName) {
212+
clientName.config.name = nameParams.name;
213+
console.log(`SnapcastService: Client ${clientName.id} name changed to ${nameParams.name}`);
214+
} else {
215+
console.warn(`SnapcastService: Client name change for unknown client ID: ${nameParams.id}`);
216+
}
217+
break;
218+
219+
case 'Client.OnLatencyChanged':
220+
const latencyParams = notification.params as { id: string; latency: number };
221+
const clientLatency = server.groups.flatMap(g => g.clients).find(c => c.id === latencyParams.id);
222+
if (clientLatency) {
223+
clientLatency.config.latency = latencyParams.latency;
224+
console.log(`SnapcastService: Client ${clientLatency.id} latency changed to ${latencyParams.latency}`);
225+
} else {
226+
console.warn(`SnapcastService: Client latency change for unknown client ID: ${latencyParams.id}`);
227+
}
228+
break;
208229

209230

210231
case 'Group.OnMute':
@@ -299,6 +320,8 @@ export class SnapcastService implements OnDestroy {
299320

300321
// CLIENT ACTIONS
301322

323+
324+
302325
public setClientVolumePercent(clientId: string, percent: number): Observable<void> {
303326
if (percent < 0 || percent > 100) return throwError(() => new Error('Volume percentage must be between 0 and 100.'));
304327

@@ -333,7 +356,31 @@ export class SnapcastService implements OnDestroy {
333356
);
334357
}
335358

336-
359+
setClientLatency(clientId: string, latency: number): Observable<void> {
360+
if (latency < 0) return throwError(() => new Error('Latency must be a non-negative number.'));
361+
return this.rpc('Client.SetLatency', { id: clientId, latency }).pipe(
362+
map((): void => void 0),
363+
catchError(err => {
364+
console.error(`SnapcastService: Failed to set latency for client ${clientId}`, err);
365+
return throwError(() => err);
366+
})
367+
);
368+
}
369+
370+
// Function added just for completeness, not used in the app yet.
371+
getClientStatus(id: string): Observable<Client | undefined> {
372+
return this.rpc('Client.GetStatus', { id }).pipe(
373+
map(response => response.result as Client | undefined),
374+
catchError(err => {
375+
console.error(`SnapcastService: Failed to get status for client ${id}`, err);
376+
return throwError(() => err);
377+
})
378+
);
379+
}
380+
381+
382+
383+
337384
setGroupName(groupId: string, name: string): Observable<void> {
338385
return this.rpc('Group.SetName', { id: groupId, name }).pipe(
339386
map((): void => void 0),
@@ -362,7 +409,7 @@ export class SnapcastService implements OnDestroy {
362409

363410
}
364411

365-
412+
366413

367414

368415

@@ -373,8 +420,8 @@ export class SnapcastService implements OnDestroy {
373420
}
374421

375422
public mockServerState(): void {
376-
const url = "assets/mock/json/server-state.json"
377-
this.http.get<SnapCastServerStatusResponse>(url).subscribe({
423+
const url = "assets/mock/json/server-state.json"
424+
this.http.get<SnapCastServerStatusResponse>(url).subscribe({
378425
next: (data) => {
379426
console.log('Mock server state loaded:', data);
380427
this.stateSubject$.next(data);

0 commit comments

Comments
 (0)