mirror of
https://github.com/informaticker/discord-jellyfin-bot.git
synced 2024-11-23 18:21:55 +01:00
✨ Add on track change event and automatic track waterfall
This commit is contained in:
parent
feeb09a17d
commit
60df58959a
@ -1,11 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { OnModuleDestroy } from '@nestjs/common/interfaces/hooks';
|
||||
import { PlaybackModule } from '../../playback/playback.module';
|
||||
import { DiscordConfigService } from './discord.config.service';
|
||||
import { DiscordMessageService } from './discord.message.service';
|
||||
import { DiscordVoiceService } from './discord.voice.service';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
imports: [PlaybackModule],
|
||||
controllers: [],
|
||||
providers: [DiscordConfigService, DiscordVoiceService, DiscordMessageService],
|
||||
exports: [DiscordConfigService, DiscordVoiceService, DiscordMessageService],
|
||||
|
@ -1,9 +1,9 @@
|
||||
import {
|
||||
AudioPlayer,
|
||||
AudioPlayerPausedState,
|
||||
AudioPlayerStatus,
|
||||
AudioResource,
|
||||
createAudioPlayer,
|
||||
createAudioResource,
|
||||
getVoiceConnection,
|
||||
getVoiceConnections,
|
||||
joinVoiceChannel,
|
||||
@ -11,8 +11,11 @@ import {
|
||||
} from '@discordjs/voice';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Logger } from '@nestjs/common/services';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { GuildMember } from 'discord.js';
|
||||
import { GenericTryHandler } from '../../models/generic-try-handler';
|
||||
import { PlaybackService } from '../../playback/playback.service';
|
||||
import { Track } from '../../types/track';
|
||||
import { DiscordMessageService } from './discord.message.service';
|
||||
|
||||
@Injectable()
|
||||
@ -21,7 +24,16 @@ export class DiscordVoiceService {
|
||||
private audioPlayer: AudioPlayer;
|
||||
private voiceConnection: VoiceConnection;
|
||||
|
||||
constructor(private readonly discordMessageService: DiscordMessageService) {}
|
||||
constructor(
|
||||
private readonly discordMessageService: DiscordMessageService,
|
||||
private readonly playbackService: PlaybackService,
|
||||
) {}
|
||||
|
||||
@OnEvent('playback.newTrack')
|
||||
handleOnNewTrack(newTrack: Track) {
|
||||
const resource = createAudioResource(newTrack.streamUrl);
|
||||
this.playResource(resource);
|
||||
}
|
||||
|
||||
tryJoinChannelAndEstablishVoiceConnection(
|
||||
member: GuildMember,
|
||||
@ -156,6 +168,17 @@ export class DiscordVoiceService {
|
||||
this.audioPlayer.on('error', (message) => {
|
||||
this.logger.error(message);
|
||||
});
|
||||
this.audioPlayer.on('stateChange', (statusChange) => {
|
||||
if (statusChange.status !== AudioPlayerStatus.AutoPaused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.playbackService.hasNextTrack()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.playbackService.nextTrack();
|
||||
});
|
||||
this.voiceConnection.subscribe(this.audioPlayer);
|
||||
return this.audioPlayer;
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import { DiscordVoiceService } from '../clients/discord/discord.voice.service';
|
||||
import { JellyfinStreamBuilderService } from '../clients/jellyfin/jellyfin.stream.builder.service';
|
||||
import { PlaybackService } from '../playback/playback.service';
|
||||
import { Constants } from '../utils/constants';
|
||||
import { trimStringToFixedLength } from '../utils/stringUtils';
|
||||
|
||||
@Command({
|
||||
name: 'play',
|
||||
@ -69,9 +70,9 @@ export class PlayItemCommand
|
||||
|
||||
const lines: string[] = firstItems.map(
|
||||
(item) =>
|
||||
`:white_small_square: ${this.markSearchTermOverlap(
|
||||
item.Name,
|
||||
dto.search,
|
||||
`:white_small_square: ${trimStringToFixedLength(
|
||||
this.markSearchTermOverlap(item.Name, dto.search),
|
||||
30,
|
||||
)} *(${item.Type})*`,
|
||||
);
|
||||
|
||||
@ -104,7 +105,8 @@ export class PlayItemCommand
|
||||
return {
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: '',
|
||||
title: 'a',
|
||||
description: description,
|
||||
mixin(embedBuilder) {
|
||||
return embedBuilder.setAuthor({
|
||||
name: 'Jellyfin Search Results',
|
||||
@ -158,12 +160,6 @@ export class PlayItemCommand
|
||||
|
||||
const artists = item.Artists.join(', ');
|
||||
|
||||
const addedIndex = this.playbackService.eneuqueTrack({
|
||||
jellyfinId: item.Id,
|
||||
name: item.Name,
|
||||
durationInMilliseconds: milliseconds,
|
||||
});
|
||||
|
||||
const guildMember = interaction.member as GuildMember;
|
||||
const bitrate = guildMember.voice.channel.bitrate;
|
||||
|
||||
@ -171,11 +167,16 @@ export class PlayItemCommand
|
||||
guildMember,
|
||||
);
|
||||
|
||||
this.jellyfinStreamBuilder
|
||||
.buildStreamUrl(item.Id, bitrate)
|
||||
.then((stream) => {
|
||||
const resource = createAudioResource(stream);
|
||||
this.discordVoiceService.playResource(resource);
|
||||
const stream = await this.jellyfinStreamBuilder.buildStreamUrl(
|
||||
item.Id,
|
||||
bitrate,
|
||||
);
|
||||
|
||||
const addedIndex = this.playbackService.eneuqueTrack({
|
||||
jellyfinId: item.Id,
|
||||
name: item.Name,
|
||||
durationInMilliseconds: milliseconds,
|
||||
streamUrl: stream,
|
||||
});
|
||||
|
||||
await interaction.update({
|
||||
|
@ -3,6 +3,7 @@ import { Playlist } from '../types/playlist';
|
||||
import { Track } from '../types/track';
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
|
||||
@Injectable()
|
||||
export class PlaybackService {
|
||||
@ -11,6 +12,8 @@ export class PlaybackService {
|
||||
activeTrack: null,
|
||||
};
|
||||
|
||||
constructor(private readonly eventEmitter: EventEmitter2) {}
|
||||
|
||||
getActiveTrack() {
|
||||
return this.getTrackById(this.playlist.activeTrack);
|
||||
}
|
||||
@ -38,6 +41,7 @@ export class PlaybackService {
|
||||
|
||||
const newKey = keys[index + 1];
|
||||
this.setActiveTrack(newKey);
|
||||
this.controlAudioPlayer();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -51,6 +55,7 @@ export class PlaybackService {
|
||||
const keys = this.getTrackIds();
|
||||
const newKey = keys[index - 1];
|
||||
this.setActiveTrack(newKey);
|
||||
this.controlAudioPlayer();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -66,6 +71,7 @@ export class PlaybackService {
|
||||
|
||||
if (emptyBefore) {
|
||||
this.setActiveTrack(this.playlist.tracks.find((x) => x.id === uuid).id);
|
||||
this.controlAudioPlayer();
|
||||
}
|
||||
|
||||
return this.playlist.tracks.findIndex((x) => x.id === uuid);
|
||||
@ -82,6 +88,10 @@ export class PlaybackService {
|
||||
this.playlist.tracks = [];
|
||||
}
|
||||
|
||||
hasNextTrack() {
|
||||
return this.getActiveIndex() + 1 < this.getTrackIds().length;
|
||||
}
|
||||
|
||||
hasActiveTrack() {
|
||||
return this.playlist.activeTrack !== null;
|
||||
}
|
||||
@ -101,4 +111,11 @@ export class PlaybackService {
|
||||
private getActiveIndex() {
|
||||
return this.getTrackIds().indexOf(this.playlist.activeTrack);
|
||||
}
|
||||
|
||||
private controlAudioPlayer() {
|
||||
const activeTrack = this.getActiveTrack();
|
||||
console.log('received track change');
|
||||
console.log(activeTrack.track);
|
||||
this.eventEmitter.emit('playback.newTrack', activeTrack.track);
|
||||
}
|
||||
}
|
||||
|
@ -2,4 +2,5 @@ export interface Track {
|
||||
jellyfinId: string;
|
||||
name: string;
|
||||
durationInMilliseconds: number;
|
||||
streamUrl: string;
|
||||
}
|
||||
|
9
src/utils/stringUtils.ts
Normal file
9
src/utils/stringUtils.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export const trimStringToFixedLength = (value: string, maxLength: number) => {
|
||||
if (maxLength < 1) {
|
||||
throw new Error('max length must be positive');
|
||||
}
|
||||
|
||||
return value.length > maxLength
|
||||
? value.substring(0, maxLength - 3) + '...'
|
||||
: value;
|
||||
};
|
Loading…
Reference in New Issue
Block a user