mirror of
https://github.com/informaticker/discord-jellyfin-bot.git
synced 2024-11-25 02:51:57 +01:00
parent
9ecce22f14
commit
1ec65c93a8
@ -10,11 +10,9 @@ import { DiscordConfigService } from './clients/discord/discord.config.service';
|
|||||||
import { DiscordClientModule } from './clients/discord/discord.module';
|
import { DiscordClientModule } from './clients/discord/discord.module';
|
||||||
import { JellyfinClientModule } from './clients/jellyfin/jellyfin.module';
|
import { JellyfinClientModule } from './clients/jellyfin/jellyfin.module';
|
||||||
import { CommandModule } from './commands/command.module';
|
import { CommandModule } from './commands/command.module';
|
||||||
|
import { HealthModule } from './health/health.module';
|
||||||
import { PlaybackModule } from './playback/playback.module';
|
import { PlaybackModule } from './playback/playback.module';
|
||||||
import { UpdatesModule } from './updates/updates.module';
|
import { UpdatesModule } from './updates/updates.module';
|
||||||
import { HealthController } from './health/health.controller';
|
|
||||||
import { HealthModule } from './health/health.module';
|
|
||||||
import { TerminusModule } from '@nestjs/terminus';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
} from '@discordjs/voice';
|
} from '@discordjs/voice';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Logger } from '@nestjs/common/services';
|
import { Logger } from '@nestjs/common/services';
|
||||||
import { OnEvent } from '@nestjs/event-emitter';
|
import { EventEmitter2, OnEvent } from '@nestjs/event-emitter';
|
||||||
import { GuildMember } from 'discord.js';
|
import { GuildMember } from 'discord.js';
|
||||||
import { GenericTryHandler } from '../../models/generic-try-handler';
|
import { GenericTryHandler } from '../../models/generic-try-handler';
|
||||||
import { PlaybackService } from '../../playback/playback.service';
|
import { PlaybackService } from '../../playback/playback.service';
|
||||||
@ -29,6 +29,7 @@ export class DiscordVoiceService {
|
|||||||
private readonly discordMessageService: DiscordMessageService,
|
private readonly discordMessageService: DiscordMessageService,
|
||||||
private readonly playbackService: PlaybackService,
|
private readonly playbackService: PlaybackService,
|
||||||
private readonly jellyfinWebSocketService: JellyfinWebSocketService,
|
private readonly jellyfinWebSocketService: JellyfinWebSocketService,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@OnEvent('playback.newTrack')
|
@OnEvent('playback.newTrack')
|
||||||
@ -95,15 +96,20 @@ export class DiscordVoiceService {
|
|||||||
/**
|
/**
|
||||||
* Pauses the current audio player
|
* Pauses the current audio player
|
||||||
*/
|
*/
|
||||||
|
@OnEvent('playback.control.pause')
|
||||||
pause() {
|
pause() {
|
||||||
this.createAndReturnOrGetAudioPlayer().pause();
|
this.createAndReturnOrGetAudioPlayer().pause();
|
||||||
|
this.eventEmitter.emit('playback.state.pause', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the audio player
|
* Stops the audio player
|
||||||
*/
|
*/
|
||||||
|
@OnEvent('playback.control.stop')
|
||||||
stop(force: boolean): boolean {
|
stop(force: boolean): boolean {
|
||||||
return this.createAndReturnOrGetAudioPlayer().stop(force);
|
const stopped = this.createAndReturnOrGetAudioPlayer().stop(force);
|
||||||
|
this.eventEmitter.emit('playback.state.stop');
|
||||||
|
return stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,6 +117,7 @@ export class DiscordVoiceService {
|
|||||||
*/
|
*/
|
||||||
unpause() {
|
unpause() {
|
||||||
this.createAndReturnOrGetAudioPlayer().unpause();
|
this.createAndReturnOrGetAudioPlayer().unpause();
|
||||||
|
this.eventEmitter.emit('playback.state.pause', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -136,6 +143,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('playback.control.togglePause')
|
||||||
togglePaused(): boolean {
|
togglePaused(): boolean {
|
||||||
if (this.isPaused()) {
|
if (this.isPaused()) {
|
||||||
this.unpause();
|
this.unpause();
|
||||||
|
@ -9,12 +9,17 @@ import {
|
|||||||
} from '@jellyfin/sdk/lib/generated-client/models';
|
} from '@jellyfin/sdk/lib/generated-client/models';
|
||||||
import { getPlaystateApi } from '@jellyfin/sdk/lib/utils/api/playstate-api';
|
import { getPlaystateApi } from '@jellyfin/sdk/lib/utils/api/playstate-api';
|
||||||
import { getSessionApi } from '@jellyfin/sdk/lib/utils/api/session-api';
|
import { getSessionApi } from '@jellyfin/sdk/lib/utils/api/session-api';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
import { Track } from '../../types/track';
|
||||||
|
import { PlaybackService } from '../../playback/playback.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JellyinPlaystateService {
|
export class JellyinPlaystateService {
|
||||||
private playstateApi: PlaystateApi;
|
private playstateApi: PlaystateApi;
|
||||||
private sessionApi: SessionApi;
|
private sessionApi: SessionApi;
|
||||||
|
|
||||||
|
constructor(private readonly playbackService: PlaybackService) {}
|
||||||
|
|
||||||
private readonly logger = new Logger(JellyinPlaystateService.name);
|
private readonly logger = new Logger(JellyinPlaystateService.name);
|
||||||
|
|
||||||
async initializePlayState(api: Api) {
|
async initializePlayState(api: Api) {
|
||||||
@ -32,7 +37,6 @@ export class JellyinPlaystateService {
|
|||||||
playableMediaTypes: [BaseItemKind[BaseItemKind.Audio]],
|
playableMediaTypes: [BaseItemKind[BaseItemKind.Audio]],
|
||||||
supportsMediaControl: true,
|
supportsMediaControl: true,
|
||||||
supportedCommands: [
|
supportedCommands: [
|
||||||
GeneralCommandType.SetRepeatMode,
|
|
||||||
GeneralCommandType.Play,
|
GeneralCommandType.Play,
|
||||||
GeneralCommandType.PlayState,
|
GeneralCommandType.PlayState,
|
||||||
],
|
],
|
||||||
@ -40,4 +44,36 @@ export class JellyinPlaystateService {
|
|||||||
|
|
||||||
this.logger.debug('Reported playback capabilities sucessfully');
|
this.logger.debug('Reported playback capabilities sucessfully');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnEvent('playback.newTrack')
|
||||||
|
private async onPlaybackNewTrack(track: Track) {
|
||||||
|
await this.playstateApi.reportPlaybackStart({
|
||||||
|
playbackStartInfo: {
|
||||||
|
ItemId: track.jellyfinId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnEvent('playback.state.pause')
|
||||||
|
private async onPlaybackPaused(isPaused: boolean) {
|
||||||
|
const activeTrack = this.playbackService.getActiveTrack();
|
||||||
|
|
||||||
|
await this.playstateApi.reportPlaybackProgress({
|
||||||
|
playbackProgressInfo: {
|
||||||
|
ItemId: activeTrack.track.jellyfinId,
|
||||||
|
IsPaused: isPaused,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnEvent('playback.state.stop')
|
||||||
|
private async onPlaybackStopped() {
|
||||||
|
const activeTrack = this.playbackService.getActiveTrack();
|
||||||
|
|
||||||
|
await this.playstateApi.reportPlaybackStopped({
|
||||||
|
playbackStopInfo: {
|
||||||
|
ItemId: activeTrack.track.jellyfinId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,17 @@ import { Injectable, Logger, OnModuleDestroy } from '@nestjs/common';
|
|||||||
import { Cron } from '@nestjs/schedule';
|
import { Cron } from '@nestjs/schedule';
|
||||||
import { JellyfinService } from './jellyfin.service';
|
import { JellyfinService } from './jellyfin.service';
|
||||||
|
|
||||||
import { SessionMessageType } from '@jellyfin/sdk/lib/generated-client/models';
|
import {
|
||||||
|
PlaystateCommand,
|
||||||
|
SessionMessageType,
|
||||||
|
} from '@jellyfin/sdk/lib/generated-client/models';
|
||||||
import { WebSocket } from 'ws';
|
import { WebSocket } from 'ws';
|
||||||
import { PlaybackService } from '../../playback/playback.service';
|
import { PlaybackService } from '../../playback/playback.service';
|
||||||
import { JellyfinSearchService } from './jellyfin.search.service';
|
import { JellyfinSearchService } from './jellyfin.search.service';
|
||||||
import { JellyfinStreamBuilderService } from './jellyfin.stream.builder.service';
|
import { JellyfinStreamBuilderService } from './jellyfin.stream.builder.service';
|
||||||
import { Track } from '../../types/track';
|
import { Track } from '../../types/track';
|
||||||
import { PlayNowCommand } from '../../types/websocket';
|
import { PlayNowCommand, SessionApiSendPlaystateCommandRequest } from '../../types/websocket';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JellyfinWebSocketService implements OnModuleDestroy {
|
export class JellyfinWebSocketService implements OnModuleDestroy {
|
||||||
@ -21,6 +25,7 @@ export class JellyfinWebSocketService implements OnModuleDestroy {
|
|||||||
private readonly jellyfinSearchService: JellyfinSearchService,
|
private readonly jellyfinSearchService: JellyfinSearchService,
|
||||||
private readonly playbackService: PlaybackService,
|
private readonly playbackService: PlaybackService,
|
||||||
private readonly jellyfinStreamBuilderService: JellyfinStreamBuilderService,
|
private readonly jellyfinStreamBuilderService: JellyfinStreamBuilderService,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Cron('*/30 * * * * *')
|
@Cron('*/30 * * * * *')
|
||||||
@ -130,6 +135,11 @@ export class JellyfinWebSocketService implements OnModuleDestroy {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case SessionMessageType[SessionMessageType.Playstate]:
|
||||||
|
const sendPlaystateCommandRequest =
|
||||||
|
msg.Data as SessionApiSendPlaystateCommandRequest;
|
||||||
|
this.handleSendPlaystateCommandRequest(sendPlaystateCommandRequest);
|
||||||
|
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}`,
|
||||||
@ -138,6 +148,27 @@ export class JellyfinWebSocketService implements OnModuleDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async handleSendPlaystateCommandRequest(
|
||||||
|
request: SessionApiSendPlaystateCommandRequest,
|
||||||
|
) {
|
||||||
|
switch (request.Command) {
|
||||||
|
case PlaystateCommand.PlayPause:
|
||||||
|
this.eventEmitter.emitAsync('playback.control.togglePause');
|
||||||
|
break;
|
||||||
|
case PlaystateCommand.Pause:
|
||||||
|
this.eventEmitter.emitAsync('playback.control.pause');
|
||||||
|
break;
|
||||||
|
case PlaystateCommand.Stop:
|
||||||
|
this.eventEmitter.emitAsync('playback.control.stop');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.logger.warn(
|
||||||
|
`Unable to process incoming playstate command request: ${request.Command}`,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bindWebSocketEvents() {
|
private bindWebSocketEvents() {
|
||||||
this.webSocket.on('message', this.messageHandler.bind(this));
|
this.webSocket.on('message', this.messageHandler.bind(this));
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { PlaystateCommand } from '@jellyfin/sdk/lib/generated-client/models';
|
||||||
|
|
||||||
export class PlayNowCommand {
|
export class PlayNowCommand {
|
||||||
/**
|
/**
|
||||||
* A list of all items available in the parent element.
|
* A list of all items available in the parent element.
|
||||||
@ -35,3 +37,24 @@ export class PlayNowCommand {
|
|||||||
return this.ItemIds;
|
return this.ItemIds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SessionApiSendPlaystateCommandRequest {
|
||||||
|
/**
|
||||||
|
* The MediaBrowser.Model.Session.PlaystateCommand.
|
||||||
|
* @type {PlaystateCommand}
|
||||||
|
* @memberof SessionApiSendPlaystateCommand
|
||||||
|
*/
|
||||||
|
readonly Command: PlaystateCommand;
|
||||||
|
/**
|
||||||
|
* The optional position ticks.
|
||||||
|
* @type {number}
|
||||||
|
* @memberof SessionApiSendPlaystateCommand
|
||||||
|
*/
|
||||||
|
readonly SeekPositionTicks?: number;
|
||||||
|
/**
|
||||||
|
* The optional controlling user id.
|
||||||
|
* @type {string}
|
||||||
|
* @memberof SessionApiSendPlaystateCommand
|
||||||
|
*/
|
||||||
|
readonly ControllingUserId?: string;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user