From 23e6c0caf03596ef5e8b03eb9ac634e08a117c38 Mon Sep 17 00:00:00 2001
From: austinried <4966622+austinried@users.noreply.github.com>
Date: Wed, 30 Jun 2021 13:07:53 +0900
Subject: [PATCH] artist art pull from last.fm or album covers
---
src/components/common/AlbumArt.tsx | 34 +++
src/components/common/AlbumView.tsx | 58 +-----
src/components/common/ArtistArt.tsx | 193 ++++++++++++++++++
src/components/common/ArtistView.tsx | 55 +++++
src/components/common/Button.tsx | 32 +++
.../common/{AlbumCover.tsx => CoverArt.tsx} | 37 ++--
src/components/common/GradientScrollView.tsx | 15 ++
src/components/library/AlbumsTab.tsx | 4 +-
src/components/library/ArtistsTab.tsx | 61 ++++--
.../navigation/LibraryTopTabNavigator.tsx | 67 +++---
src/hooks/player.ts | 1 +
src/models/music.ts | 10 +-
src/state/music.ts | 50 ++++-
src/subsonic/api.ts | 9 +-
src/subsonic/elements.ts | 47 +++++
src/subsonic/params.ts | 4 +
src/subsonic/responses.ts | 62 ++----
src/util.ts | 11 +
18 files changed, 582 insertions(+), 168 deletions(-)
create mode 100644 src/components/common/AlbumArt.tsx
create mode 100644 src/components/common/ArtistArt.tsx
create mode 100644 src/components/common/ArtistView.tsx
create mode 100644 src/components/common/Button.tsx
rename src/components/common/{AlbumCover.tsx => CoverArt.tsx} (64%)
create mode 100644 src/components/common/GradientScrollView.tsx
create mode 100644 src/util.ts
diff --git a/src/components/common/AlbumArt.tsx b/src/components/common/AlbumArt.tsx
new file mode 100644
index 0000000..f78fecd
--- /dev/null
+++ b/src/components/common/AlbumArt.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import FastImage from 'react-native-fast-image';
+import LinearGradient from 'react-native-linear-gradient';
+import colors from '../../styles/colors';
+import CoverArt from './CoverArt';
+
+const AlbumArt: React.FC<{
+ height: number,
+ width: number,
+ coverArtUri?: string
+}> = ({ height, width, coverArtUri }) => {
+ const Placeholder = () => (
+
+
+
+ );
+
+ return (
+
+ );
+}
+
+export default React.memo(AlbumArt);
diff --git a/src/components/common/AlbumView.tsx b/src/components/common/AlbumView.tsx
index 15c1610..9522379 100644
--- a/src/components/common/AlbumView.tsx
+++ b/src/components/common/AlbumView.tsx
@@ -2,56 +2,13 @@ import { useNavigation } from '@react-navigation/native';
import { useAtomValue } from 'jotai/utils';
import React, { useEffect, useState } from 'react';
import { GestureResponderEvent, Image, Pressable, ScrollView, Text, useWindowDimensions, View } from 'react-native';
-import { TrackPlayerEvents } from 'react-native-track-player';
import { useCurrentTrackId, useSetQueue } from '../../hooks/player';
import { albumAtomFamily } from '../../state/music';
import colors from '../../styles/colors';
import text from '../../styles/text';
-import AlbumCover from './AlbumCover';
-import GradientBackground from './GradientBackground';
-
-function secondsToTime(s: number): string {
- const seconds = s % 60;
- const minutes = Math.floor(s / 60) % 60;
- const hours = Math.floor(s / 60 / 60);
-
- let time = `${minutes.toString().padStart(1, '0')}:${seconds.toString().padStart(2, '0')}`;
- if (hours > 0) {
- time = `${hours}:${time}`;
- }
- return time;
-}
-
-const Button: React.FC<{
- title: string;
- onPress: (event: GestureResponderEvent) => void;
-}> = ({ title, onPress }) => {
- const [opacity, setOpacity] = useState(1);
-
- return (
- setOpacity(0.6)}
- onPressOut={() => setOpacity(1)}
- onLongPress={() => setOpacity(1)}
- style={{
- backgroundColor: colors.accent,
- paddingHorizontal: 24,
- minHeight: 42,
- justifyContent: 'center',
- borderRadius: 1000,
- opacity,
- }}
- >
- {title}
-
- );
-}
-
-const songEvents = [
- TrackPlayerEvents.PLAYBACK_STATE,
- TrackPlayerEvents.PLAYBACK_TRACK_CHANGED,
-]
+import AlbumArt from './AlbumArt';
+import Button from './Button';
+import GradientScrollView from './GradientScrollView';
const SongItem: React.FC<{
id: string;
@@ -134,7 +91,7 @@ const AlbumDetails: React.FC<{
}
return (
-
-
-
-
+
);
}
-
const AlbumView: React.FC<{
id: string,
title: string;
diff --git a/src/components/common/ArtistArt.tsx b/src/components/common/ArtistArt.tsx
new file mode 100644
index 0000000..0f2727f
--- /dev/null
+++ b/src/components/common/ArtistArt.tsx
@@ -0,0 +1,193 @@
+import React from 'react';
+import { View } from 'react-native';
+import FastImage from 'react-native-fast-image';
+import LinearGradient from 'react-native-linear-gradient';
+import colors from '../../styles/colors';
+import CoverArt from './CoverArt';
+
+const PlaceholderContainer: React.FC<{
+ height: number,
+ width: number,
+}> = ({ height, width, children}) => (
+
+ {children}
+
+);
+
+const FourUp: React.FC<{
+ height: number,
+ width: number,
+ coverArtUris: string[];
+}> = ({ height, width, coverArtUris }) => {
+ const halfHeight = height / 2;
+ const halfWidth = width / 2;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const ThreeUp: React.FC<{
+ height: number,
+ width: number,
+ coverArtUris: string[];
+}> = ({ height, width, coverArtUris }) => {
+ const halfHeight = height / 2;
+ const halfWidth = width / 2;
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const TwoUp: React.FC<{
+ height: number,
+ width: number,
+ coverArtUris: string[];
+}> = ({ height, width, coverArtUris }) => {
+ const halfHeight = height / 2;
+
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+const OneUp: React.FC<{
+ height: number,
+ width: number,
+ coverArtUris: string[];
+}> = ({ height, width, coverArtUris }) => {
+ return (
+
+
+
+ );
+};
+
+const NoneUp: React.FC<{
+ height: number,
+ width: number,
+}> = ({ height, width }) => {
+ return (
+
+
+
+ );
+};
+
+const ArtistArt: React.FC<{
+ height: number,
+ width: number,
+ mediumImageUrl?: string;
+ coverArtUris?: string[]
+}> = ({ height, width, mediumImageUrl, coverArtUris }) => {
+ const Placeholder = () => {
+ if (coverArtUris && coverArtUris.length >= 4) {
+ return ;
+ }
+ if (coverArtUris && coverArtUris.length === 3) {
+ return ;
+ }
+ if (coverArtUris && coverArtUris.length === 2) {
+ return ;
+ }
+ if (coverArtUris && coverArtUris.length === 1) {
+ return ;
+ }
+ return ;
+ }
+
+ return (
+
+
+
+ );
+}
+
+export default React.memo(ArtistArt);
diff --git a/src/components/common/ArtistView.tsx b/src/components/common/ArtistView.tsx
new file mode 100644
index 0000000..6ce2de5
--- /dev/null
+++ b/src/components/common/ArtistView.tsx
@@ -0,0 +1,55 @@
+import { useNavigation } from '@react-navigation/native';
+import { useAtomValue } from 'jotai/utils';
+import React, { useEffect } from 'react';
+import { Text } from 'react-native';
+import { artistInfoAtomFamily } from '../../state/music';
+import text from '../../styles/text';
+import ArtistArt from './ArtistArt';
+import GradientScrollView from './GradientScrollView';
+
+const ArtistDetails: React.FC<{ id: string }> = ({ id }) => {
+ const artist = useAtomValue(artistInfoAtomFamily(id));
+
+ if (!artist) {
+ return <>>;
+ }
+
+ return (
+
+ {artist.name}
+
+
+ )
+}
+
+const ArtistView: React.FC<{
+ id: string,
+ title: string;
+}> = ({ id, title }) => {
+ const navigation = useNavigation();
+
+ useEffect(() => {
+ navigation.setOptions({ title });
+ });
+
+ return (
+ Loading...}>
+
+
+ );
+};
+
+export default React.memo(ArtistView);
diff --git a/src/components/common/Button.tsx b/src/components/common/Button.tsx
new file mode 100644
index 0000000..12fae07
--- /dev/null
+++ b/src/components/common/Button.tsx
@@ -0,0 +1,32 @@
+import React, { useState } from 'react';
+import { GestureResponderEvent, Pressable, Text } from 'react-native';
+import colors from '../../styles/colors';
+import text from '../../styles/text';
+
+const Button: React.FC<{
+ title: string;
+ onPress: (event: GestureResponderEvent) => void;
+}> = ({ title, onPress }) => {
+ const [opacity, setOpacity] = useState(1);
+
+ return (
+ setOpacity(0.6)}
+ onPressOut={() => setOpacity(1)}
+ onLongPress={() => setOpacity(1)}
+ style={{
+ backgroundColor: colors.accent,
+ paddingHorizontal: 24,
+ minHeight: 42,
+ justifyContent: 'center',
+ borderRadius: 1000,
+ opacity,
+ }}
+ >
+ {title}
+
+ );
+}
+
+export default Button;
diff --git a/src/components/common/AlbumCover.tsx b/src/components/common/CoverArt.tsx
similarity index 64%
rename from src/components/common/AlbumCover.tsx
rename to src/components/common/CoverArt.tsx
index f5dfe5e..e6a4c74 100644
--- a/src/components/common/AlbumCover.tsx
+++ b/src/components/common/CoverArt.tsx
@@ -1,14 +1,14 @@
import React, { useState } from 'react';
import { ActivityIndicator, View } from 'react-native';
import FastImage from 'react-native-fast-image';
-import LinearGradient from 'react-native-linear-gradient';
import colors from '../../styles/colors';
-const AlbumCover: React.FC<{
+const CoverArt: React.FC<{
+ PlaceholderComponent: () => JSX.Element,
height: number,
width: number,
coverArtUri?: string
-}> = ({ height, width, coverArtUri }) => {
+}> = ({ PlaceholderComponent, height, width, coverArtUri }) => {
const [placeholderVisible, setPlaceholderVisible] = useState(false);
const [loading, setLoading] = useState(true);
@@ -16,23 +16,15 @@ const AlbumCover: React.FC<{
const halfIndicatorHeight = indicatorSize === 'large' ? 18 : 10;
const Placeholder: React.FC<{ visible: boolean }> = ({ visible }) => (
-
-
-
+
+
+
);
const CoverArt = () => (
-
+ <>
setPlaceholderVisible(true)}
+ onError={() => {
+ setLoading(false);
+ setPlaceholderVisible(true);
+ }}
onLoadEnd={() => setLoading(false)}
/>
-
+ >
);
return (
- {!coverArtUri ? : }
+ {!coverArtUri ? : }
);
}
-export default React.memo(AlbumCover);
+export default React.memo(CoverArt);
diff --git a/src/components/common/GradientScrollView.tsx b/src/components/common/GradientScrollView.tsx
new file mode 100644
index 0000000..9c6a6ab
--- /dev/null
+++ b/src/components/common/GradientScrollView.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import { ScrollView, ScrollViewProps } from 'react-native';
+import GradientBackground from './GradientBackground';
+
+const GradientScrollView: React.FC = (props) => (
+
+
+ {props.children}
+
+);
+
+export default GradientScrollView;
diff --git a/src/components/library/AlbumsTab.tsx b/src/components/library/AlbumsTab.tsx
index 2483ec4..1edf13b 100644
--- a/src/components/library/AlbumsTab.tsx
+++ b/src/components/library/AlbumsTab.tsx
@@ -5,7 +5,7 @@ import { Pressable, Text, View } from 'react-native';
import { Album } from '../../models/music';
import { albumsAtom, albumsUpdatingAtom, useUpdateAlbums } from '../../state/music';
import textStyles from '../../styles/text';
-import AlbumCover from '../common/AlbumCover';
+import AlbumArt from '../common/AlbumArt';
import GradientFlatList from '../common/GradientFlatList';
const AlbumItem: React.FC<{
@@ -27,7 +27,7 @@ const AlbumItem: React.FC<{
}}
onPress={() => navigation.navigate('AlbumView', { id, title: name })}
>
- = ({ item }) => (
-
- = ({ item }) => {
+ const navigation = useNavigation();
+ const artistInfo = useAtomValue(artistInfoAtomFamily(item.id));
+
+ return (
+
- {item.name}
-
+ onPress={() => navigation.navigate('ArtistView', { id: item.id, title: item.name })}
+ >
+
+ {/* */}
+ {item.name}
+
+ );
+};
+
+const ArtistItemLoader: React.FC<{ item: Artist }> = (props) => (
+ Loading...}>
+
+
);
const ArtistsList = () => {
@@ -39,7 +62,7 @@ const ArtistsList = () => {
});
const renderItem: React.FC<{ item: Artist }> = ({ item }) => (
-
+
);
return (
diff --git a/src/components/navigation/LibraryTopTabNavigator.tsx b/src/components/navigation/LibraryTopTabNavigator.tsx
index b0bd15f..f8dacce 100644
--- a/src/components/navigation/LibraryTopTabNavigator.tsx
+++ b/src/components/navigation/LibraryTopTabNavigator.tsx
@@ -10,16 +10,12 @@ import { RouteProp } from '@react-navigation/native';
import text from '../../styles/text';
import colors from '../../styles/colors';
import FastImage from 'react-native-fast-image';
+import ArtistView from '../common/ArtistView';
const Tab = createMaterialTopTabNavigator();
const LibraryTopTabNavigator = () => (
(
type LibraryStackParamList = {
LibraryTopTabs: undefined,
AlbumView: { id: string, title: string };
+ ArtistView: { id: string, title: string };
}
type AlbumScreenNavigationProp = StackNavigationProp;
@@ -66,8 +63,41 @@ const AlbumScreen: React.FC = ({ route }) => (
);
+type ArtistScreenNavigationProp = StackNavigationProp;
+type ArtistScreenRouteProp = RouteProp;
+type ArtistScreenProps = {
+ route: ArtistScreenRouteProp,
+ navigation: ArtistScreenNavigationProp,
+};
+
+const ArtistScreen: React.FC = ({ route }) => (
+
+);
+
const Stack = createStackNavigator();
+const itemScreenOptions = {
+ title: '',
+ headerStyle: {
+ height: 50,
+ backgroundColor: colors.gradient.high,
+ },
+ headerTitleContainerStyle: {
+ marginLeft: -14,
+ },
+ headerLeftContainerStyle: {
+ marginLeft: 8,
+ },
+ headerTitleStyle: {
+ ...text.header,
+ },
+ headerBackImage: () => ,
+}
+
const LibraryStackNavigator = () => (
@@ -79,27 +109,12 @@ const LibraryStackNavigator = () => (
,
- }}
+ options={itemScreenOptions}
+ />
+
diff --git a/src/hooks/player.ts b/src/hooks/player.ts
index ca8feea..9f2b256 100644
--- a/src/hooks/player.ts
+++ b/src/hooks/player.ts
@@ -9,6 +9,7 @@ function mapSongToTrack(song: Song): Track {
artist: song.artist || 'Unknown Artist',
url: song.streamUri,
artwork: song.coverArtUri,
+ duration: song.duration,
}
}
diff --git a/src/models/music.ts b/src/models/music.ts
index 6d70bf2..f846f9a 100644
--- a/src/models/music.ts
+++ b/src/models/music.ts
@@ -2,8 +2,14 @@ export interface Artist {
id: string;
name: string;
starred?: Date;
- coverArt?: string;
- coverArtUri?: string,
+}
+
+export interface ArtistInfo extends Artist {
+ albums: Album[];
+
+ mediumImageUrl?: string;
+ largeImageUrl?: string;
+ coverArtUris: string[];
}
export interface Album {
diff --git a/src/state/music.ts b/src/state/music.ts
index 09ec7c8..d3a2f82 100644
--- a/src/state/music.ts
+++ b/src/state/music.ts
@@ -1,8 +1,9 @@
import { atom, useAtom } from 'jotai';
import { atomFamily, useAtomValue, useUpdateAtom } from 'jotai/utils';
-import { Album as Album, AlbumWithSongs, Artist, Song } from '../models/music';
+import { Album as Album, AlbumWithSongs, Artist, ArtistInfo, Song } from '../models/music';
import { SubsonicApiClient } from '../subsonic/api';
-import { AlbumID3Element, ChildElement } from '../subsonic/elements';
+import { AlbumID3Element, ArtistID3Element, ArtistInfo2Element, ChildElement } from '../subsonic/elements';
+import { GetArtistResponse } from '../subsonic/responses';
import { activeServerAtom } from './settings';
export const artistsAtom = atom([]);
@@ -30,8 +31,6 @@ export const useUpdateArtists = () => {
id: x.id,
name: x.name,
starred: x.starred,
- coverArt: x.coverArt,
- coverArtUri: x.coverArt ? client.getCoverArtUri({ id: x.coverArt }) : undefined,
})));
setUpdating(false);
}
@@ -74,6 +73,49 @@ export const albumAtomFamily = atomFamily((id: string) => atom atom(async (get) => {
+ const server = get(activeServerAtom);
+ if (!server) {
+ return undefined;
+ }
+
+ const client = new SubsonicApiClient(server);
+ const [artistResponse, artistInfoResponse] = await Promise.all([
+ client.getArtist({ id }),
+ client.getArtistInfo2({ id }),
+ ]);
+ return mapArtistInfo(artistResponse.data, artistInfoResponse.data.artistInfo, client);
+}));
+
+function mapArtistInfo(
+ artistResponse: GetArtistResponse,
+ artistInfo: ArtistInfo2Element,
+ client: SubsonicApiClient
+): ArtistInfo {
+ const info = { ...artistInfo } as any;
+ delete info.similarArtists;
+
+ const { artist, albums } = artistResponse
+
+ const mappedAlbums = albums.map(a => mapAlbumID3(a, client));
+ const coverArtUris = mappedAlbums
+ .sort((a, b) => {
+ if (a.year && b.year) {
+ return a.year - b.year;
+ } else {
+ return a.name.localeCompare(b.name) - 9000;
+ }
+ })
+ .map(a => a.coverArtThumbUri);
+
+ return {
+ ...artist,
+ ...info,
+ albums: mappedAlbums,
+ coverArtUris,
+ }
+}
+
function mapAlbumID3(album: AlbumID3Element, client: SubsonicApiClient): Album {
return {
...album,
diff --git a/src/subsonic/api.ts b/src/subsonic/api.ts
index 1a1b149..d5a7967 100644
--- a/src/subsonic/api.ts
+++ b/src/subsonic/api.ts
@@ -1,7 +1,7 @@
import { DOMParser } from 'xmldom';
import RNFS from 'react-native-fs';
-import { GetAlbumList2Params, GetAlbumListParams, GetAlbumParams, GetArtistInfo2Params, GetArtistInfoParams, GetCoverArtParams, GetIndexesParams, GetMusicDirectoryParams, StreamParams } from './params';
-import { GetAlbumList2Response, GetAlbumListResponse, GetAlbumResponse, GetArtistInfo2Response, GetArtistInfoResponse, GetArtistsResponse, GetIndexesResponse, GetMusicDirectoryResponse, SubsonicResponse } from './responses';
+import { GetAlbumList2Params, GetAlbumListParams, GetAlbumParams, GetArtistInfo2Params, GetArtistInfoParams, GetArtistParams, GetCoverArtParams, GetIndexesParams, GetMusicDirectoryParams, StreamParams } from './params';
+import { GetAlbumList2Response, GetAlbumListResponse, GetAlbumResponse, GetArtistInfo2Response, GetArtistInfoResponse, GetArtistResponse, GetArtistsResponse, GetIndexesResponse, GetMusicDirectoryResponse, SubsonicResponse } from './responses';
import { Server } from '../models/settings';
import paths from '../paths';
@@ -167,6 +167,11 @@ export class SubsonicApiClient {
return new SubsonicResponse(xml, new GetArtistInfo2Response(xml));
}
+ async getArtist(params: GetArtistParams): Promise> {
+ const xml = await this.apiGetXml('getArtist', params);
+ return new SubsonicResponse(xml, new GetArtistResponse(xml));
+ }
+
//
// Album/song lists
//
diff --git a/src/subsonic/elements.ts b/src/subsonic/elements.ts
index 77cf713..a112216 100644
--- a/src/subsonic/elements.ts
+++ b/src/subsonic/elements.ts
@@ -72,6 +72,53 @@ export class ArtistElement extends BaseArtistElement {
}
}
+export class BaseArtistInfoElement {
+ similarArtists: T[] = [];
+ biography?: string;
+ musicBrainzId?: string;
+ lastFmUrl?: string;
+ smallImageUrl?: string;
+ mediumImageUrl?: string;
+ largeImageUrl?: string;
+
+ constructor(e: Element, artistType: new (e: Element) => T) {
+ if (e.getElementsByTagName('biography').length > 0) {
+ this.biography = e.getElementsByTagName('biography')[0].textContent as string;
+ }
+ if (e.getElementsByTagName('musicBrainzId').length > 0) {
+ this.musicBrainzId = e.getElementsByTagName('musicBrainzId')[0].textContent as string;
+ }
+ if (e.getElementsByTagName('lastFmUrl').length > 0) {
+ this.lastFmUrl = e.getElementsByTagName('lastFmUrl')[0].textContent as string;
+ }
+ if (e.getElementsByTagName('smallImageUrl').length > 0) {
+ this.smallImageUrl = e.getElementsByTagName('smallImageUrl')[0].textContent as string;
+ }
+ if (e.getElementsByTagName('mediumImageUrl').length > 0) {
+ this.mediumImageUrl = e.getElementsByTagName('mediumImageUrl')[0].textContent as string;
+ }
+ if (e.getElementsByTagName('largeImageUrl').length > 0) {
+ this.largeImageUrl = e.getElementsByTagName('largeImageUrl')[0].textContent as string;
+ }
+
+ const similarArtistElements = e.getElementsByTagName('similarArtist');
+ for (let i = 0; i < similarArtistElements.length; i++) {
+ this.similarArtists.push(new artistType(similarArtistElements[i]));
+ }
+ }
+}
+
+export class ArtistInfoElement extends BaseArtistInfoElement {
+ constructor(e: Element) {
+ super(e, ArtistElement);
+ }
+}
+export class ArtistInfo2Element extends BaseArtistInfoElement {
+ constructor(e: Element) {
+ super(e, ArtistID3Element);
+ }
+}
+
export class DirectoryElement {
id: string;
parent?: string;
diff --git a/src/subsonic/params.ts b/src/subsonic/params.ts
index 2f3cc3b..688248c 100644
--- a/src/subsonic/params.ts
+++ b/src/subsonic/params.ts
@@ -23,6 +23,10 @@ export type GetAlbumParams = {
id: string;
}
+export type GetArtistParams = {
+ id: string;
+}
+
//
// Album/song lists
diff --git a/src/subsonic/responses.ts b/src/subsonic/responses.ts
index a265a4e..637dc47 100644
--- a/src/subsonic/responses.ts
+++ b/src/subsonic/responses.ts
@@ -1,4 +1,4 @@
-import { AlbumID3Element, ArtistElement, ArtistID3Element, BaseArtistElement, ChildElement, DirectoryElement } from "./elements";
+import { AlbumID3Element, ArtistElement, ArtistID3Element, ArtistInfo2Element, ArtistInfoElement, BaseArtistElement, BaseArtistInfoElement, ChildElement, DirectoryElement } from "./elements";
export type ResponseStatus = 'ok' | 'failed';
@@ -32,6 +32,20 @@ export class GetArtistsResponse {
}
}
+export class GetArtistResponse {
+ artist: ArtistID3Element;
+ albums: AlbumID3Element[] = [];
+
+ constructor(xml: Document) {
+ this.artist = new ArtistID3Element(xml.getElementsByTagName('artist')[0]);
+
+ const albumElements = xml.getElementsByTagName('album');
+ for (let i = 0; i < albumElements.length; i++) {
+ this.albums.push(new AlbumID3Element(albumElements[i]));
+ }
+ }
+}
+
export class GetIndexesResponse {
ignoredArticles: string;
lastModified: number;
@@ -50,51 +64,19 @@ export class GetIndexesResponse {
}
}
-class BaseGetArtistInfoResponse {
- similarArtists: T[] = [];
- biography?: string;
- musicBrainzId?: string;
- lastFmUrl?: string;
- smallImageUrl?: string;
- mediumImageUrl?: string;
- largeImageUrl?: string;
+export class GetArtistInfoResponse {
+ artistInfo: ArtistInfoElement;
- constructor(xml: Document, artistType: new (e: Element) => T) {
- if (xml.getElementsByTagName('biography').length > 0) {
- this.biography = xml.getElementsByTagName('biography')[0].textContent as string;
- }
- if (xml.getElementsByTagName('musicBrainzId').length > 0) {
- this.musicBrainzId = xml.getElementsByTagName('musicBrainzId')[0].textContent as string;
- }
- if (xml.getElementsByTagName('lastFmUrl').length > 0) {
- this.lastFmUrl = xml.getElementsByTagName('lastFmUrl')[0].textContent as string;
- }
- if (xml.getElementsByTagName('smallImageUrl').length > 0) {
- this.smallImageUrl = xml.getElementsByTagName('smallImageUrl')[0].textContent as string;
- }
- if (xml.getElementsByTagName('mediumImageUrl').length > 0) {
- this.mediumImageUrl = xml.getElementsByTagName('mediumImageUrl')[0].textContent as string;
- }
- if (xml.getElementsByTagName('largeImageUrl').length > 0) {
- this.largeImageUrl = xml.getElementsByTagName('largeImageUrl')[0].textContent as string;
- }
-
- const similarArtistElements = xml.getElementsByTagName('similarArtist');
- for (let i = 0; i < similarArtistElements.length; i++) {
- this.similarArtists.push(new artistType(similarArtistElements[i]));
- }
+ constructor(xml: Document) {
+ this.artistInfo = new ArtistInfoElement(xml.getElementsByTagName('artistInfo')[0]);
}
}
-export class GetArtistInfoResponse extends BaseGetArtistInfoResponse {
- constructor(xml: Document) {
- super(xml, ArtistElement);
- }
-}
+export class GetArtistInfo2Response {
+ artistInfo: ArtistInfo2Element;
-export class GetArtistInfo2Response extends BaseGetArtistInfoResponse {
constructor(xml: Document) {
- super(xml, ArtistID3Element);
+ this.artistInfo = new ArtistInfo2Element(xml.getElementsByTagName('artistInfo2')[0]);
}
}
diff --git a/src/util.ts b/src/util.ts
new file mode 100644
index 0000000..72d9897
--- /dev/null
+++ b/src/util.ts
@@ -0,0 +1,11 @@
+export function formatDuration(seconds: number): string {
+ const s = seconds % 60;
+ const m = Math.floor(seconds / 60) % 60;
+ const h = Math.floor(seconds / 60 / 60);
+
+ let time = `${m.toString().padStart(1, '0')}:${s.toString().padStart(2, '0')}`;
+ if (h > 0) {
+ time = `${h}:${time}`;
+ }
+ return time;
+}