Add on track change event and automatic track waterfall

This commit is contained in:
Manuel Ruwe 2022-12-17 20:19:25 +01:00
parent feeb09a17d
commit 60df58959a
6 changed files with 71 additions and 19 deletions

View File

@ -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],

View File

@ -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;
}

View File

@ -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,12 +167,17 @@ 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({
embeds: [

View File

@ -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);
}
}

View File

@ -2,4 +2,5 @@ export interface Track {
jellyfinId: string;
name: string;
durationInMilliseconds: number;
streamUrl: string;
}

9
src/utils/stringUtils.ts Normal file
View 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;
};