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 * Pauses the current audio player
*/ */
@OnEvent('internal.voice.controls.pause')
pause() { pause() {
this.createAndReturnOrGetAudioPlayer().pause(); this.createAndReturnOrGetAudioPlayer().pause();
this.eventEmitter.emit('playback.state.pause', true); this.eventEmitter.emit('playback.state.pause', true);
@ -124,6 +125,7 @@ export class DiscordVoiceService {
/** /**
* Stops the audio player * Stops the audio player
*/ */
@OnEvent('internal.voice.controls.stop')
stop(force: boolean): boolean { stop(force: boolean): boolean {
const stopped = this.createAndReturnOrGetAudioPlayer().stop(force); const stopped = this.createAndReturnOrGetAudioPlayer().stop(force);
this.eventEmitter.emit('playback.state.stop'); 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. * 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 * @returns The new paused state - true: paused, false: unpaused
*/ */
@OnEvent('internal.voice.controls.togglePause')
togglePaused(): boolean { togglePaused(): boolean {
if (this.isPaused()) { if (this.isPaused()) {
this.unpause(); 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]); 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> { async getRemoteImageById(id: string, limit = 20): Promise<RemoteImageResult> {
const api = this.jellyfinService.getApi(); const api = this.jellyfinService.getApi();
const remoteImageApi = getRemoteImageApi(api); const remoteImageApi = getRemoteImageApi(api);

View File

@ -1,11 +1,13 @@
import { import {
PlaystateCommand, PlaystateCommand,
SessionMessageType, SessionMessageType,
UserItemDataDto,
} from '@jellyfin/sdk/lib/generated-client/models'; } from '@jellyfin/sdk/lib/generated-client/models';
import { Injectable, Logger, OnModuleDestroy } from '@nestjs/common'; import { Injectable, Logger, OnModuleDestroy } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { Cron } from '@nestjs/schedule';
import { Session } from 'inspector';
import { WebSocket } from 'ws'; import { WebSocket } from 'ws';
@ -14,11 +16,9 @@ import {
PlayNowCommand, PlayNowCommand,
SessionApiSendPlaystateCommandRequest, SessionApiSendPlaystateCommandRequest,
} from '../../types/websocket'; } from '../../types/websocket';
import { Track } from '../../models/shared/Track';
import { JellyfinSearchService } from './jellyfin.search.service'; import { JellyfinSearchService } from './jellyfin.search.service';
import { JellyfinService } from './jellyfin.service'; import { JellyfinService } from './jellyfin.service';
import { JellyfinStreamBuilderService } from './jellyfin.stream.builder.service';
@Injectable() @Injectable()
export class JellyfinWebSocketService implements OnModuleDestroy { export class JellyfinWebSocketService implements OnModuleDestroy {
@ -28,9 +28,8 @@ export class JellyfinWebSocketService implements OnModuleDestroy {
constructor( constructor(
private readonly jellyfinService: JellyfinService, private readonly jellyfinService: JellyfinService,
private readonly jellyfinSearchService: JellyfinSearchService,
private readonly playbackService: PlaybackService, private readonly playbackService: PlaybackService,
private readonly jellyfinStreamBuilderService: JellyfinStreamBuilderService, private readonly jellyfinSearchService: JellyfinSearchService,
private readonly eventEmitter: EventEmitter2, private readonly eventEmitter: EventEmitter2,
) {} ) {}
@ -103,14 +102,29 @@ export class JellyfinWebSocketService implements OnModuleDestroy {
data.hasSelection = PlayNowCommand.prototype.hasSelection; data.hasSelection = PlayNowCommand.prototype.hasSelection;
data.getSelection = PlayNowCommand.prototype.getSelection; data.getSelection = PlayNowCommand.prototype.getSelection;
const ids = data.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; break;
case SessionMessageType[SessionMessageType.Playstate]: case SessionMessageType[SessionMessageType.Playstate]:
const sendPlaystateCommandRequest = const sendPlaystateCommandRequest =
msg.Data as SessionApiSendPlaystateCommandRequest; msg.Data as SessionApiSendPlaystateCommandRequest;
this.handleSendPlaystateCommandRequest(sendPlaystateCommandRequest); this.handleSendPlaystateCommandRequest(sendPlaystateCommandRequest);
break; break;
case SessionMessageType[SessionMessageType.UserDataChanged]:
this.logger.debug(`Received update for user session data`);
break;
default: default:
this.logger.warn( this.logger.warn(
`Received a package from the socket of unknown type: ${msg.MessageType}`, `Received a package from the socket of unknown type: ${msg.MessageType}`,
@ -124,13 +138,13 @@ export class JellyfinWebSocketService implements OnModuleDestroy {
) { ) {
switch (request.Command) { switch (request.Command) {
case PlaystateCommand.PlayPause: case PlaystateCommand.PlayPause:
this.eventEmitter.emitAsync('playback.control.togglePause'); this.eventEmitter.emitAsync('internal.voice.controls.togglePause');
break; break;
case PlaystateCommand.Pause: case PlaystateCommand.Pause:
this.eventEmitter.emitAsync('playback.control.pause'); this.eventEmitter.emitAsync('internal.voice.controls.pause');
break; break;
case PlaystateCommand.Stop: case PlaystateCommand.Stop:
this.eventEmitter.emitAsync('playback.control.stop'); this.eventEmitter.emitAsync('internal.voice.controls.stop');
break; break;
default: default:
this.logger.warn( 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'; import { Track } from './Track';

View File

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