Add album playback

This commit is contained in:
Manuel Ruwe 2022-12-18 17:46:31 +01:00
parent c218c1273d
commit 1a1e12506f
4 changed files with 108 additions and 9 deletions

View File

@ -1,12 +1,17 @@
import { Injectable } from '@nestjs/common';
import { JellyfinService } from './jellyfin.service';
import { SearchHint } from '@jellyfin/sdk/lib/generated-client/models';
import {
BaseItemDto,
BaseItemKind,
SearchHint,
SearchHintResult,
} from '@jellyfin/sdk/lib/generated-client/models';
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api';
import { getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api/playlists-api';
import { getSearchApi } from '@jellyfin/sdk/lib/utils/api/search-api';
import { Logger } from '@nestjs/common/services';
import { JellyfinAudioPlaylist } from '../../models/jellyfinAudioItems';
import { JellyfinAudioPlaylist, JellyfinMusicAlbum } from '../../models/jellyfinAudioItems';
@Injectable()
export class JellyfinSearchService {
@ -25,9 +30,15 @@ export class JellyfinSearchService {
status,
} = await searchApi.get({
searchTerm: searchTerm,
mediaTypes: ['Audio', 'MusicAlbum', 'Playlist'],
includeItemTypes: [
BaseItemKind.Audio,
BaseItemKind.MusicAlbum,
BaseItemKind.Playlist,
],
});
console.log(SearchHints);
if (status !== 200) {
this.logger.error(`Jellyfin Search failed with status code ${status}`);
return [];
@ -48,13 +59,35 @@ export class JellyfinSearchService {
});
if (axiosResponse.status !== 200) {
this.logger.error(`Jellyfin Search failed with status code ${status}`);
this.logger.error(
`Jellyfin Search failed with status code ${axiosResponse.status}`,
);
return new JellyfinAudioPlaylist();
}
return axiosResponse.data as JellyfinAudioPlaylist;
}
async getItemsByAlbum(albumId: string): Promise<JellyfinMusicAlbum> {
const api = this.jellyfinService.getApi();
const searchApi = getSearchApi(api);
const axiosResponse = await searchApi.get({
parentId: albumId,
userId: this.jellyfinService.getUserId(),
mediaTypes: [BaseItemKind[BaseItemKind.Audio]],
searchTerm: '%',
});
if (axiosResponse.status !== 200) {
this.logger.error(
`Jellyfin Search failed with status code ${axiosResponse.status}`,
);
return new JellyfinMusicAlbum();
}
return axiosResponse.data as JellyfinMusicAlbum;
}
async getById(id: string): Promise<SearchHint> {
const api = this.jellyfinService.getApi();

View File

@ -190,6 +190,21 @@ export class PlayItemCommand
components: [],
});
break;
case 'album':
const album = await this.jellyfinSearchService.getItemsByAlbum(id);
console.log(album);
album.SearchHints.forEach((item) => {
this.enqueueSingleTrack(item as BaseJellyfinAudioPlayable, bitrate);
});
interaction.update({
embeds: [
this.discordMessageService.buildMessage({
title: `Added ${album.TotalRecordCount} items from your album`,
}),
],
components: [],
});
break;
case 'playlist':
const playlist = await this.jellyfinSearchService.getPlaylistById(id);
playlist.Items.forEach((item) => {
@ -209,7 +224,7 @@ export class PlayItemCommand
embeds: [
this.discordMessageService.buildErrorMessage({
title: 'Unable to process your selection',
description: `Sorry. I don't know the type you selected: \`\`${type}\`\`. Please report this bug to the developers.\n\nDebug Information:\`\`${interaction.values.join(
description: `Sorry. I don't know the type you selected: \`\`${type}\`\`. Please report this bug to the developers.\n\nDebug Information: \`\`${interaction.values.join(
', ',
)}\`\``,
}),

View File

@ -56,7 +56,7 @@ export class PlaylistCommand implements DiscordCommand {
return point;
})
.join(',\n');
.join('\n');
return {
embeds: [

View File

@ -1,4 +1,7 @@
import { SearchHint } from '@jellyfin/sdk/lib/generated-client/models';
import {
BaseItemKind,
SearchHint,
} from '@jellyfin/sdk/lib/generated-client/models';
import { JellyfinStreamBuilderService } from '../clients/jellyfin/jellyfin.stream.builder.service';
import { Track } from '../types/track';
import { trimStringToFixedLength } from '../utils/stringUtils';
@ -161,22 +164,70 @@ export class JellyfinAudioPlaylist implements BaseJellyfinAudioPlayable {
TotalRecordCount: number;
}
export class JellyfinMusicAlbum implements BaseJellyfinAudioPlayable {
Id: string;
Name: string;
RunTimeTicks: number;
SearchHints: JellyfinAudioItem[];
TotalRecordCount: number;
async fromSearchHint(
jellyfinSearchService: JellyfinSearchService,
searchHint: SearchHint,
): Promise<JellyfinMusicAlbum> {
this.Id = searchHint.Id;
this.Name = searchHint.Name;
this.RunTimeTicks = searchHint.RunTimeTicks;
const album = await jellyfinSearchService.getItemsByAlbum(searchHint.Id);
this.SearchHints = album.SearchHints;
this.TotalRecordCount = album.TotalRecordCount;
return this;
}
fetchTracks(
jellyfinStreamBuilder: JellyfinStreamBuilderService,
bitrate: number,
): Track[] {
return this.SearchHints.flatMap((item) =>
item.fetchTracks(jellyfinStreamBuilder, bitrate),
);
}
prettyPrint(search: string): string {
return `${markSearchTermOverlap(this.Name, search)} (${
this.TotalRecordCount
} items) (Album)`;
}
getId(): string {
return this.Id;
}
getValueId(): string {
return `album_${this.getId()}`;
}
getEmoji(): string {
return '📀';
}
}
export const searchResultAsJellyfinAudio = async (
logger: Logger,
jellyfinSearchService: JellyfinSearchService,
searchHint: SearchHint,
) => {
switch (searchHint.Type) {
case 'Audio':
case BaseItemKind[BaseItemKind.Audio]:
return await new JellyfinAudioItem().fromSearchHint(
jellyfinSearchService,
searchHint,
);
case 'Playlist':
case BaseItemKind[BaseItemKind.Playlist]:
return await new JellyfinAudioPlaylist().fromSearchHint(
jellyfinSearchService,
searchHint,
);
case BaseItemKind[BaseItemKind.MusicAlbum]:
return await new JellyfinMusicAlbum().fromSearchHint(
jellyfinSearchService,
searchHint,
);
default:
logger.error(
`Failed to parse Jellyfin response for item type ${searchHint.Type}`,