Reimplement websocket controls

This commit is contained in:
Manuel 2023-03-29 21:57:49 +02:00 committed by Manuel
parent 8c5739a9e5
commit fc74d1b4f3
6 changed files with 68 additions and 10 deletions

View File

@ -116,6 +116,7 @@ export class DiscordVoiceService {
/**
* Pauses the current audio player
*/
@OnEvent('internal.voice.controls.pause')
pause() {
this.createAndReturnOrGetAudioPlayer().pause();
this.eventEmitter.emit('playback.state.pause', true);
@ -124,6 +125,7 @@ export class DiscordVoiceService {
/**
* Stops the audio player
*/
@OnEvent('internal.voice.controls.stop')
stop(force: boolean): boolean {
const stopped = this.createAndReturnOrGetAudioPlayer().stop(force);
this.eventEmitter.emit('playback.state.stop');
@ -161,6 +163,7 @@ export class DiscordVoiceService {
* Checks if the current state is paused or not and toggles the states to the opposite.
* @returns The new paused state - true: paused, false: unpaused
*/
@OnEvent('internal.voice.controls.togglePause')
togglePaused(): boolean {
if (this.isPaused()) {
this.unpause();

View File

@ -71,4 +71,23 @@ export class JellyinPlaystateService {
},
});
}
@OnEvent('playback.state.pause')
private async onPlaybackPause(paused: boolean) {
const track = this.playbackService.getPlaylistOrDefault().getActiveTrack();
if (!track) {
this.logger.error(
`Unable to report changed playstate to Jellyfin because no track was active`,
);
return;
}
this.playstateApi.reportPlaybackProgress({
playbackProgressInfo: {
IsPaused: paused,
ItemId: track.id,
},
});
}
}

View File

@ -130,6 +130,27 @@ export class JellyfinSearchService {
return this.transformToSearchHint(data.Items[0]);
}
async getAllById(
ids: string[],
includeItemTypes: BaseItemKind[] = [BaseItemKind.Audio],
): Promise<SearchHint[]> | undefined {
const api = this.jellyfinService.getApi();
const searchApi = getItemsApi(api);
const { data } = await searchApi.getItems({
ids: ids,
userId: this.jellyfinService.getUserId(),
includeItemTypes: includeItemTypes,
});
if (data.Items.length !== 1) {
this.logger.warn(`Failed to retrieve item via id '${ids}'`);
return null;
}
return data.Items.map((item) => this.transformToSearchHint(item));
}
async getRemoteImageById(id: string, limit = 20): Promise<RemoteImageResult> {
const api = this.jellyfinService.getApi();
const remoteImageApi = getRemoteImageApi(api);

View File

@ -1,11 +1,13 @@
import {
PlaystateCommand,
SessionMessageType,
UserItemDataDto,
} from '@jellyfin/sdk/lib/generated-client/models';
import { Injectable, Logger, OnModuleDestroy } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Cron } from '@nestjs/schedule';
import { Session } from 'inspector';
import { WebSocket } from 'ws';
@ -14,11 +16,9 @@ import {
PlayNowCommand,
SessionApiSendPlaystateCommandRequest,
} from '../../types/websocket';
import { Track } from '../../models/shared/Track';
import { JellyfinSearchService } from './jellyfin.search.service';
import { JellyfinService } from './jellyfin.service';
import { JellyfinStreamBuilderService } from './jellyfin.stream.builder.service';
@Injectable()
export class JellyfinWebSocketService implements OnModuleDestroy {
@ -28,9 +28,8 @@ export class JellyfinWebSocketService implements OnModuleDestroy {
constructor(
private readonly jellyfinService: JellyfinService,
private readonly jellyfinSearchService: JellyfinSearchService,
private readonly playbackService: PlaybackService,
private readonly jellyfinStreamBuilderService: JellyfinStreamBuilderService,
private readonly jellyfinSearchService: JellyfinSearchService,
private readonly eventEmitter: EventEmitter2,
) {}
@ -103,14 +102,29 @@ export class JellyfinWebSocketService implements OnModuleDestroy {
data.hasSelection = PlayNowCommand.prototype.hasSelection;
data.getSelection = PlayNowCommand.prototype.getSelection;
const ids = data.getSelection();
this.logger.log(
`Processing ${ids.length} ids received via websocket and adding them to the queue`,
);
const searchHints = await this.jellyfinSearchService.getAllById(ids);
// TODO: Implement this again
const tracks = await Promise.all(
searchHints.map(async (x) =>
(
await x.toTracks(this.jellyfinSearchService)
).find((x) => x !== null),
),
);
this.playbackService.getPlaylistOrDefault().enqueueTracks(tracks);
break;
case SessionMessageType[SessionMessageType.Playstate]:
const sendPlaystateCommandRequest =
msg.Data as SessionApiSendPlaystateCommandRequest;
this.handleSendPlaystateCommandRequest(sendPlaystateCommandRequest);
break;
case SessionMessageType[SessionMessageType.UserDataChanged]:
this.logger.debug(`Received update for user session data`);
break;
default:
this.logger.warn(
`Received a package from the socket of unknown type: ${msg.MessageType}`,
@ -124,13 +138,13 @@ export class JellyfinWebSocketService implements OnModuleDestroy {
) {
switch (request.Command) {
case PlaystateCommand.PlayPause:
this.eventEmitter.emitAsync('playback.control.togglePause');
this.eventEmitter.emitAsync('internal.voice.controls.togglePause');
break;
case PlaystateCommand.Pause:
this.eventEmitter.emitAsync('playback.control.pause');
this.eventEmitter.emitAsync('internal.voice.controls.pause');
break;
case PlaystateCommand.Stop:
this.eventEmitter.emitAsync('playback.control.stop');
this.eventEmitter.emitAsync('internal.voice.controls.stop');
break;
default:
this.logger.warn(

View File

@ -1,4 +1,4 @@
import { EventEmitter2 } from '@nestjs/event-emitter';
import { EventEmitter2, OnEvent } from '@nestjs/event-emitter';
import { Track } from './Track';

View File

@ -39,6 +39,7 @@ export class Track {
this.name = name;
this.duration = duration;
this.remoteImages = remoteImages;
this.playing = false;
}
getDuration() {