2018-08-19 20:37:53 +00:00
|
|
|
import { Injectable, EventEmitter } from '@angular/core';
|
2019-10-08 05:43:49 +00:00
|
|
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
2022-11-14 16:17:51 +00:00
|
|
|
import { Observable, Subscription } from 'rxjs';
|
|
|
|
import { finalize, shareReplay } from 'rxjs/operators';
|
2019-10-08 05:43:49 +00:00
|
|
|
|
2019-11-07 18:02:26 +00:00
|
|
|
import {
|
|
|
|
PluginDescription,
|
|
|
|
State,
|
|
|
|
Slide,
|
|
|
|
ServiceItem,
|
2020-01-23 20:10:11 +00:00
|
|
|
Theme,
|
2019-11-07 18:02:26 +00:00
|
|
|
MainView,
|
|
|
|
SystemInformation,
|
|
|
|
Credentials,
|
|
|
|
AuthToken
|
|
|
|
} from './responses';
|
2018-08-27 21:01:02 +00:00
|
|
|
import { environment } from '../environments/environment';
|
2018-08-19 20:37:53 +00:00
|
|
|
|
2019-11-07 18:02:26 +00:00
|
|
|
|
2019-10-06 00:28:36 +00:00
|
|
|
const deserialize = (json, cls) => {
|
|
|
|
const inst = new cls();
|
|
|
|
for (const p in json) {
|
|
|
|
if (!json.hasOwnProperty(p)) {
|
|
|
|
continue;
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
2019-10-06 00:28:36 +00:00
|
|
|
inst[p] = json[p];
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
2019-10-06 00:28:36 +00:00
|
|
|
return inst;
|
|
|
|
};
|
2018-08-19 20:37:53 +00:00
|
|
|
|
2019-10-08 05:43:49 +00:00
|
|
|
const httpOptions = {
|
2020-07-21 23:26:34 +00:00
|
|
|
headers: new HttpHeaders({
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
'Access-Control-Allow-Origin': '*'
|
|
|
|
})
|
2019-10-08 05:43:49 +00:00
|
|
|
};
|
|
|
|
|
2022-11-14 16:17:51 +00:00
|
|
|
const WEBSOCKET_RECONNECT_TIMEOUT = 5 * 1000;
|
|
|
|
|
|
|
|
export enum WebSocketStatus {
|
|
|
|
Open, Closed
|
|
|
|
}
|
2019-11-07 18:02:26 +00:00
|
|
|
|
2018-08-19 20:37:53 +00:00
|
|
|
@Injectable()
|
|
|
|
export class OpenLPService {
|
2018-08-27 21:01:02 +00:00
|
|
|
private apiURL: string;
|
2022-11-14 16:17:51 +00:00
|
|
|
private host: string;
|
2018-08-19 20:37:53 +00:00
|
|
|
public stateChanged$: EventEmitter<State>;
|
2022-11-14 16:17:51 +00:00
|
|
|
public webSocketStateChanged$: EventEmitter<WebSocketStatus>;
|
2021-02-19 06:16:41 +00:00
|
|
|
private isTwelveHourTime = true;
|
2018-08-19 20:37:53 +00:00
|
|
|
|
2022-11-14 16:17:51 +00:00
|
|
|
private webSocketTimeoutHandle: any = 0;
|
|
|
|
private ws: WebSocket = null;
|
|
|
|
private retrieveSystemInformationSubscription: Subscription;
|
|
|
|
|
2018-08-19 20:37:53 +00:00
|
|
|
constructor(private http: HttpClient) {
|
2019-11-07 18:02:26 +00:00
|
|
|
const host = window.location.hostname;
|
2018-08-27 21:01:02 +00:00
|
|
|
let port: string;
|
|
|
|
if (environment.production) {
|
|
|
|
port = window.location.port;
|
2019-10-06 00:28:36 +00:00
|
|
|
}
|
|
|
|
else {
|
2018-08-27 21:01:02 +00:00
|
|
|
port = '4316';
|
|
|
|
}
|
2019-11-09 16:26:56 +00:00
|
|
|
this.apiURL = `http://${host}:${port}/api/v2`;
|
2022-11-14 16:17:51 +00:00
|
|
|
this.host = host;
|
2018-08-19 20:37:53 +00:00
|
|
|
this.stateChanged$ = new EventEmitter<State>();
|
2022-11-14 16:17:51 +00:00
|
|
|
this.webSocketStateChanged$ = new EventEmitter<WebSocketStatus>();
|
|
|
|
this.createWebSocket();
|
2019-10-08 05:43:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
setAuthToken(token: string): void {
|
|
|
|
httpOptions.headers = httpOptions.headers.set('Authorization', token);
|
|
|
|
}
|
|
|
|
|
2021-02-19 06:09:28 +00:00
|
|
|
getIsTwelveHourTime(): boolean {
|
|
|
|
return this.isTwelveHourTime;
|
|
|
|
}
|
|
|
|
|
2019-10-08 05:43:49 +00:00
|
|
|
retrieveSystemInformation(): Observable<SystemInformation> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doGet<SystemInformation>(`${this.apiURL}/core/system`);
|
2019-10-08 05:43:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getMainImage(): Observable<MainView> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doGet<MainView>(`${this.apiURL}/core/live-image`);
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 07:02:05 +00:00
|
|
|
getSearchablePlugins(): Observable<PluginDescription[]> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doGet<PluginDescription[]>(`${this.apiURL}/core/plugins`);
|
2020-05-09 07:02:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
search(plugin, text): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doGet(`${this.apiURL}/plugins/${plugin}/search?text=${text}`);
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2020-08-27 04:55:11 +00:00
|
|
|
getSearchOptions(plugin): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doGet(`${this.apiURL}/plugins/${plugin}/search-options`);
|
2020-08-27 04:55:11 +00:00
|
|
|
}
|
|
|
|
|
2021-02-02 02:27:51 +00:00
|
|
|
setSearchOption(plugin, option, value): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/plugins/${plugin}/search-options`, {'option': option, 'value': value});
|
2020-08-27 04:55:11 +00:00
|
|
|
}
|
|
|
|
|
2018-08-19 20:37:53 +00:00
|
|
|
getServiceItems(): Observable<ServiceItem[]> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doGet<ServiceItem[]>(`${this.apiURL}/service/items`);
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 07:02:05 +00:00
|
|
|
setServiceItem(id: any): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/service/show`, {'id': id});
|
2020-01-23 20:10:11 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 07:02:05 +00:00
|
|
|
nextItem(): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/service/progress`, {'action': 'next'});
|
2020-01-23 20:10:11 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 07:02:05 +00:00
|
|
|
previousItem(): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/service/progress`, {'action': 'previous'});
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2020-07-21 23:26:34 +00:00
|
|
|
getServiceItem(): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doGet<Slide[]>(`${this.apiURL}/controller/live-items`);
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2020-07-21 23:26:34 +00:00
|
|
|
getNotes(): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doGet(`${this.apiURL}/controller/notes`);
|
2020-07-21 23:26:34 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 07:02:05 +00:00
|
|
|
setSlide(id: any): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/controller/show`, {'id': id});
|
2018-08-20 12:16:15 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 07:02:05 +00:00
|
|
|
nextSlide(): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/controller/progress`, {'action': 'next'});
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 07:02:05 +00:00
|
|
|
previousSlide(): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/controller/progress`, {'action': 'previous'});
|
2020-01-23 20:10:11 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 07:02:05 +00:00
|
|
|
getThemeLevel(): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doGet(`${this.apiURL}/controller/theme-level`);
|
2020-01-23 20:10:11 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 07:02:05 +00:00
|
|
|
getThemes(): Observable<Theme[]> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doGet<Theme[]>(`${this.apiURL}/controller/themes`);
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 07:02:05 +00:00
|
|
|
setThemeLevel(level): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/controller/theme-level`, {'level': level});
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 07:02:05 +00:00
|
|
|
getTheme(): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doGet(`${this.apiURL}/controller/theme`);
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 07:02:05 +00:00
|
|
|
setTheme(theme: string): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/controller/theme`, {'theme': theme});
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2018-08-20 13:00:12 +00:00
|
|
|
blankDisplay(): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/core/display`, {'display': 'blank'});
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2018-08-20 13:00:12 +00:00
|
|
|
themeDisplay(): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/core/display`, {'display': 'theme'});
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2018-08-20 13:00:12 +00:00
|
|
|
desktopDisplay(): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/core/display`, {'display': 'desktop'});
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2018-08-20 13:00:12 +00:00
|
|
|
showDisplay(): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/core/display`, {'display': 'show'});
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2018-08-20 13:00:12 +00:00
|
|
|
showAlert(text): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/plugins/alerts`, {'text': text});
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2018-08-20 13:00:12 +00:00
|
|
|
sendItemLive(plugin, id): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/plugins/${plugin}/live`, {'id': id});
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
|
2018-08-20 13:00:12 +00:00
|
|
|
addItemToService(plugin, id): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost(`${this.apiURL}/plugins/${plugin}/add`, {'id': id});
|
2019-10-08 05:43:49 +00:00
|
|
|
}
|
|
|
|
|
2022-10-19 11:51:03 +00:00
|
|
|
transposeSong(transpose_value): Observable<any> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doGet(`${this.apiURL}/plugins/songs/transpose-live-item/${transpose_value}`);
|
2022-10-19 11:51:03 +00:00
|
|
|
}
|
|
|
|
|
2019-10-08 05:43:49 +00:00
|
|
|
login(credentials: Credentials): Observable<AuthToken> {
|
2022-11-14 16:17:51 +00:00
|
|
|
return this.doPost<AuthToken>(`${this.apiURL}/core/login`, credentials);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected doGet<T>(url: string): Observable<T> {
|
|
|
|
return this.http.get<T>(url, httpOptions);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected doPost<T>(url: string, body: any): Observable<T> {
|
|
|
|
// User is expecting instant response, so we'll accelerate the websocket reconnection process if needed.
|
|
|
|
this.reconnectWebSocketIfNeeded();
|
|
|
|
return this.http.post<T>(url, body, httpOptions);
|
|
|
|
}
|
|
|
|
|
|
|
|
get webSocketStatus(): WebSocketStatus {
|
|
|
|
if (this.ws) {
|
|
|
|
switch (this.ws.readyState) {
|
|
|
|
case WebSocket.OPEN:
|
|
|
|
return WebSocketStatus.Open;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return WebSocketStatus.Closed;
|
|
|
|
}
|
|
|
|
|
|
|
|
reconnectWebSocketIfNeeded() {
|
|
|
|
if (this.webSocketStatus === WebSocketStatus.Closed) {
|
|
|
|
this.createWebSocket();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
createWebSocket() {
|
|
|
|
this.clearWebSocketTimeoutHandle();
|
|
|
|
if (this.retrieveSystemInformationSubscription) {
|
|
|
|
// Cancels ongoing request to avoid connection flooding
|
|
|
|
this.retrieveSystemInformationSubscription.unsubscribe();
|
|
|
|
}
|
|
|
|
this.retrieveSystemInformationSubscription = this.retrieveSystemInformation()
|
|
|
|
.pipe(
|
|
|
|
shareReplay(1),
|
|
|
|
finalize(() => this.retrieveSystemInformationSubscription = null)
|
|
|
|
)
|
|
|
|
.subscribe(info => {
|
|
|
|
if (this.ws) {
|
|
|
|
// Removing listeners to avoid loop
|
|
|
|
this.ws.onmessage = null;
|
|
|
|
this.ws.onclose = null;
|
|
|
|
this.ws.onerror = null;
|
|
|
|
this.ws.close();
|
|
|
|
this.webSocketStateChanged$.emit(WebSocketStatus.Closed);
|
|
|
|
}
|
|
|
|
const ws = this.ws = new WebSocket(`ws://${this.host}:${info.websocket_port}`);
|
|
|
|
ws.onopen = () => {
|
|
|
|
this.webSocketStateChanged$.emit(WebSocketStatus.Open);
|
|
|
|
};
|
|
|
|
ws.onmessage = this.readWebSocketMessage;
|
|
|
|
ws.onerror = this.handleWebSocketError;
|
|
|
|
ws.onclose = () => {
|
|
|
|
this.webSocketStateChanged$.emit(WebSocketStatus.Closed);
|
|
|
|
this.handleWebSocketError();
|
|
|
|
};
|
|
|
|
}, _ => this.handleWebSocketError());
|
|
|
|
}
|
|
|
|
|
|
|
|
private handleWebSocketError = () => {
|
|
|
|
this.clearWebSocketTimeoutHandle();
|
|
|
|
this.webSocketTimeoutHandle = setTimeout(() => {
|
|
|
|
this.createWebSocket();
|
|
|
|
}, WEBSOCKET_RECONNECT_TIMEOUT);
|
|
|
|
}
|
|
|
|
|
|
|
|
private clearWebSocketTimeoutHandle() {
|
|
|
|
if (this.webSocketTimeoutHandle) {
|
|
|
|
clearTimeout(this.webSocketTimeoutHandle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private readWebSocketMessage = (event: MessageEvent<any>) => {
|
|
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = () => {
|
|
|
|
const state = deserialize(JSON.parse(reader.result as string).results, State);
|
|
|
|
this.handleStateChange(state);
|
|
|
|
};
|
|
|
|
reader.readAsText(event.data);
|
|
|
|
}
|
|
|
|
|
|
|
|
handleStateChange(state: State) {
|
|
|
|
this.isTwelveHourTime = state.twelve;
|
|
|
|
this.stateChanged$.emit(state);
|
2018-08-19 20:37:53 +00:00
|
|
|
}
|
|
|
|
}
|