Skip to content

Commit 81195f8

Browse files
committed
camilla dsp service
1 parent 1e394d4 commit 81195f8

File tree

3 files changed

+129
-0
lines changed

3 files changed

+129
-0
lines changed

src/app/app-routing.module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ const routes: Routes = [
7474
path: 'setup-server',
7575
loadChildren: () => import('./pages/setup/setup-server/setup-server.module').then( m => m.SetupServerPageModule)
7676
},
77+
{
78+
path: 'camilla-dsp',
79+
loadChildren: () => import('./pages/camilla-dsp/camilla-dsp.module').then( m => m.CamillaDspPageModule)
80+
},
7781

7882

7983
];
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { TestBed } from '@angular/core/testing';
2+
3+
import { CamillaDspService } from './camilla-dsp.service';
4+
5+
describe('CamillaDspService', () => {
6+
let service: CamillaDspService;
7+
8+
beforeEach(() => {
9+
TestBed.configureTestingModule({});
10+
service = TestBed.inject(CamillaDspService);
11+
});
12+
13+
it('should be created', () => {
14+
expect(service).toBeTruthy();
15+
});
16+
});
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { Injectable } from '@angular/core';
2+
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
3+
import { Subject, Observable, BehaviorSubject, timer, of } from 'rxjs';
4+
import { retryWhen, switchMap, tap, delayWhen } from 'rxjs/operators';
5+
6+
// Defines the possible connection states
7+
export type ConnectionStatus = 'Connected' | 'Connecting' | 'Disconnected' | 'Error';
8+
9+
// A simple type for the JSON commands CamillaDSP expects
10+
// Example: { "command": "GetState", "params": null }
11+
interface CamillaDspCommand {
12+
[command: string]: any;
13+
}
14+
15+
@Injectable({
16+
providedIn: 'root',
17+
})
18+
export class CamillaDspService {
19+
private socket$!: WebSocketSubject<any>;
20+
private messagesSubject = new Subject<any>();
21+
private connectionStatusSubject = new BehaviorSubject<ConnectionStatus>('Disconnected');
22+
private readonly RECONNECT_INTERVAL_MS = 5000;
23+
24+
// Public observables for components to subscribe to
25+
public messages$: Observable<any> = this.messagesSubject.asObservable();
26+
public connectionStatus$: Observable<ConnectionStatus> = this.connectionStatusSubject.asObservable();
27+
28+
constructor() {}
29+
30+
/**
31+
* Establishes a connection to the CamillaDSP WebSocket server.
32+
* @param url The full WebSocket URL (e.g., 'ws://beatnik-client-amp.local:1234')
33+
*/
34+
public connect(url: string): void {
35+
if (this.socket$ && !this.socket$.closed) {
36+
console.log('Already connected.');
37+
return;
38+
}
39+
40+
this.connectionStatusSubject.next('Connecting');
41+
console.log(`Connecting to ${url}...`);
42+
43+
this.socket$ = webSocket({
44+
url: url,
45+
openObserver: {
46+
next: () => {
47+
console.log('WebSocket connection established successfully! 🎉');
48+
this.connectionStatusSubject.next('Connected');
49+
},
50+
},
51+
closeObserver: {
52+
next: () => {
53+
console.log('WebSocket connection closed.');
54+
this.connectionStatusSubject.next('Disconnected');
55+
},
56+
},
57+
});
58+
59+
this.socket$
60+
.pipe(
61+
// The retryWhen operator handles reconnection logic
62+
retryWhen(errors =>
63+
errors.pipe(
64+
tap(err => {
65+
console.error(`Connection error: ${err}. Retrying in ${this.RECONNECT_INTERVAL_MS / 1000}s...`);
66+
this.connectionStatusSubject.next('Error');
67+
}),
68+
// Wait for the specified interval before trying to reconnect
69+
delayWhen(() => timer(this.RECONNECT_INTERVAL_MS))
70+
)
71+
)
72+
)
73+
.subscribe({
74+
next: msg => this.messagesSubject.next(msg), // Forward messages to our subject
75+
error: err => {
76+
// This block is less likely to be hit due to retryWhen, but good for unrecoverable errors
77+
console.error('WebSocket unrecoverable error:', err);
78+
this.connectionStatusSubject.next('Error');
79+
},
80+
});
81+
}
82+
83+
/**
84+
* Sends a command to the CamillaDSP server.
85+
* @param command The command name (e.g., 'GetState', 'SetConfigJson').
86+
* @param params Optional parameters for the command.
87+
*/
88+
public sendCommand(command: string, params: any = null): void {
89+
if (this.connectionStatusSubject.value !== 'Connected') {
90+
console.warn('Cannot send command while not connected.');
91+
return;
92+
}
93+
94+
// CamillaDSP expects a JSON object where the key is the command name
95+
const commandToSend: CamillaDspCommand = { [command]: params };
96+
97+
console.log('Sending command:', commandToSend);
98+
this.socket$.next(commandToSend);
99+
}
100+
101+
/**
102+
* Closes the WebSocket connection gracefully.
103+
*/
104+
public disconnect(): void {
105+
if (this.socket$) {
106+
this.socket$.complete(); // This will trigger the closeObserver
107+
}
108+
}
109+
}

0 commit comments

Comments
 (0)