fix: retrieve codec metadata and lyrics asynchronously
This commit is contained in:
@@ -1,17 +1,13 @@
|
|||||||
import React, {useCallback, useMemo, useRef, useState} from 'react';
|
import React, {useCallback, useMemo, useRef, useState} from 'react';
|
||||||
import { LayoutChangeEvent, LayoutRectangle, StyleSheet, View } from 'react-native';
|
import { LayoutChangeEvent, LayoutRectangle, StyleSheet, View } from 'react-native';
|
||||||
import Animated from 'react-native-reanimated';
|
import Animated from 'react-native-reanimated';
|
||||||
import { Lyrics } from '@/utility/JellyfinApi/lyrics';
|
|
||||||
import { useProgress } from 'react-native-track-player';
|
import { useProgress } from 'react-native-track-player';
|
||||||
import useCurrentTrack from '@/utility/useCurrentTrack';
|
import useCurrentTrack from '@/utility/useCurrentTrack';
|
||||||
import LyricsLine from './LyricsLine';
|
import LyricsLine from './LyricsLine';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { useTypedSelector } from '@/store';
|
|
||||||
import { NOW_PLAYING_POPOVER_HEIGHT } from '@/screens/Music/overlays/NowPlaying';
|
import { NOW_PLAYING_POPOVER_HEIGHT } from '@/screens/Music/overlays/NowPlaying';
|
||||||
import LyricsProgress, { LyricsProgressProps } from './LyricsProgress';
|
import LyricsProgress, { LyricsProgressProps } from './LyricsProgress';
|
||||||
|
|
||||||
type LyricsLine = Lyrics['Lyrics'][number];
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
lyricsContainerFull: {
|
lyricsContainerFull: {
|
||||||
padding: 40,
|
padding: 40,
|
||||||
@@ -42,9 +38,7 @@ export default function LyricsRenderer({ size = 'full' }: LyricsRendererProps) {
|
|||||||
const scrollViewRef = useRef<Animated.ScrollView>(null);
|
const scrollViewRef = useRef<Animated.ScrollView>(null);
|
||||||
const lineLayoutsRef = useRef(new Map<number, LayoutRectangle>());
|
const lineLayoutsRef = useRef(new Map<number, LayoutRectangle>());
|
||||||
const { position } = useProgress(100);
|
const { position } = useProgress(100);
|
||||||
const { track: trackPlayerTrack } = useCurrentTrack();
|
const { track, albumTrack } = useCurrentTrack();
|
||||||
const tracks = useTypedSelector((state) => state.music.tracks.entities);
|
|
||||||
const track = useMemo(() => tracks[trackPlayerTrack?.backendId], [trackPlayerTrack?.backendId, tracks]);
|
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
|
||||||
// We will be using isUserScrolling to prevent lyrics controller scroll lyrics view
|
// We will be using isUserScrolling to prevent lyrics controller scroll lyrics view
|
||||||
@@ -83,12 +77,12 @@ export default function LyricsRenderer({ size = 'full' }: LyricsRendererProps) {
|
|||||||
const handleScrollBeginDrag = useCallback(() => isUserScrolling.current = true, []);
|
const handleScrollBeginDrag = useCallback(() => isUserScrolling.current = true, []);
|
||||||
const handleScrollEndDrag = useCallback(() => isUserScrolling.current = false, []);
|
const handleScrollEndDrag = useCallback(() => isUserScrolling.current = false, []);
|
||||||
|
|
||||||
if (!track) {
|
if (!track || !albumTrack) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GUARD: If the track has no lyrics, close the modal
|
// GUARD: If the track has no lyrics, close the modal
|
||||||
if (!track.HasLyrics || !track.Lyrics) {
|
if (!albumTrack.HasLyrics || !albumTrack.Lyrics) {
|
||||||
navigation.goBack();
|
navigation.goBack();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -107,18 +101,18 @@ export default function LyricsRenderer({ size = 'full' }: LyricsRendererProps) {
|
|||||||
>
|
>
|
||||||
<LyricsProgress
|
<LyricsProgress
|
||||||
start={0}
|
start={0}
|
||||||
end={track.Lyrics.Lyrics[0].Start - TIME_OFFSET}
|
end={albumTrack.Lyrics.Lyrics[0].Start - TIME_OFFSET}
|
||||||
position={currentTime}
|
position={currentTime}
|
||||||
index={-1}
|
index={-1}
|
||||||
onActive={handleActive}
|
onActive={handleActive}
|
||||||
onLayout={handleLayoutChange}
|
onLayout={handleLayoutChange}
|
||||||
/>
|
/>
|
||||||
{track.Lyrics.Lyrics.map((lyrics, i) => {
|
{albumTrack.Lyrics.Lyrics.map((lyrics, i) => {
|
||||||
const props: LyricsProgressProps = {
|
const props: LyricsProgressProps = {
|
||||||
start: lyrics.Start - TIME_OFFSET,
|
start: lyrics.Start - TIME_OFFSET,
|
||||||
end: track.Lyrics!.Lyrics.length === i + 1
|
end: albumTrack.Lyrics!.Lyrics.length === i + 1
|
||||||
? track.RunTimeTicks
|
? track.RunTimeTicks
|
||||||
: track.Lyrics!.Lyrics[i + 1]?.Start - TIME_OFFSET
|
: albumTrack.Lyrics!.Lyrics[i + 1]?.Start - TIME_OFFSET
|
||||||
,
|
,
|
||||||
position: currentTime,
|
position: currentTime,
|
||||||
onLayout: handleLayoutChange,
|
onLayout: handleLayoutChange,
|
||||||
|
|||||||
@@ -52,18 +52,18 @@ export default function MediaInformation() {
|
|||||||
<WaveformIcon fill={styles.icon.color} height={16} width={16} />
|
<WaveformIcon fill={styles.icon.color} height={16} width={16} />
|
||||||
<Info>
|
<Info>
|
||||||
<Label numberOfLines={1} overflow>
|
<Label numberOfLines={1} overflow>
|
||||||
{track.isDirectPlay ? t('direct-play') : t('transcoded')}
|
{albumTrack.Codec?.isDirectPlay ? t('direct-play') : t('transcoded')}
|
||||||
</Label>
|
</Label>
|
||||||
<Label numberOfLines={1}>
|
<Label numberOfLines={1}>
|
||||||
{track.isDirectPlay
|
{albumTrack.Codec?.isDirectPlay
|
||||||
? mediaStream?.Codec.toUpperCase()
|
? mediaStream?.Codec.toUpperCase()
|
||||||
: track.contentType?.replace('audio/', '').toUpperCase()
|
: albumTrack.Codec?.contentType?.replace('audio/', '').toUpperCase()
|
||||||
}
|
}
|
||||||
</Label>
|
</Label>
|
||||||
{mediaStream && (
|
{mediaStream && (
|
||||||
<>
|
<>
|
||||||
<Label numberOfLines={1}>
|
<Label numberOfLines={1}>
|
||||||
{((track.isDirectPlay ? mediaStream.BitRate : track.bitRate) / 1000)
|
{((albumTrack.Codec?.isDirectPlay ? mediaStream.BitRate : track.bitRate) / 1000)
|
||||||
.toFixed(0)}
|
.toFixed(0)}
|
||||||
{t('kbps')}
|
{t('kbps')}
|
||||||
</Label>
|
</Label>
|
||||||
|
|||||||
@@ -1,15 +1,61 @@
|
|||||||
import { createAsyncThunk, createEntityAdapter } from '@reduxjs/toolkit';
|
import { AsyncThunkPayloadCreator, createAsyncThunk, createEntityAdapter } from '@reduxjs/toolkit';
|
||||||
import { Album, AlbumTrack, Playlist } from './types';
|
import { Album, AlbumTrack, CodecMetadata, Lyrics, Playlist } from './types';
|
||||||
import { AsyncThunkAPI } from '..';
|
import type { AsyncThunkAPI } from '..';
|
||||||
import { retrieveAllAlbums, retrieveRecentAlbums, retrieveAlbumTracks, retrieveAlbum, retrieveSimilarAlbums } from '@/utility/JellyfinApi/album';
|
import { retrieveAllAlbums, retrieveRecentAlbums, retrieveAlbumTracks, retrieveAlbum, retrieveSimilarAlbums } from '@/utility/JellyfinApi/album';
|
||||||
import { retrieveAllPlaylists, retrievePlaylistTracks } from '@/utility/JellyfinApi/playlist';
|
import { retrieveAllPlaylists, retrievePlaylistTracks } from '@/utility/JellyfinApi/playlist';
|
||||||
import { searchItem } from '@/utility/JellyfinApi/search';
|
import { searchItem } from '@/utility/JellyfinApi/search';
|
||||||
|
import { retrieveTrackLyrics } from '@/utility/JellyfinApi/lyrics';
|
||||||
|
import { retrieveTrackCodecMetadata } from '@/utility/JellyfinApi/track';
|
||||||
|
|
||||||
export const albumAdapter = createEntityAdapter<Album, string>({
|
export const albumAdapter = createEntityAdapter<Album, string>({
|
||||||
selectId: album => album.Id,
|
selectId: album => album.Id,
|
||||||
sortComparer: (a, b) => a.Name.localeCompare(b.Name),
|
sortComparer: (a, b) => a.Name.localeCompare(b.Name),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch lyrics for a given track
|
||||||
|
*/
|
||||||
|
export const fetchLyricsByTrack = createAsyncThunk<Lyrics, string, AsyncThunkAPI>(
|
||||||
|
'/track/lyrics',
|
||||||
|
retrieveTrackLyrics,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch codec metadata for a given track
|
||||||
|
*/
|
||||||
|
export const fetchCodecMetadataByTrack = createAsyncThunk<CodecMetadata, string, AsyncThunkAPI>(
|
||||||
|
'/track/codecMetadata',
|
||||||
|
retrieveTrackCodecMetadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
/** A generic type for any action that retrieves tracks */
|
||||||
|
type AlbumTrackPayloadCreator = AsyncThunkPayloadCreator<AlbumTrack[], string, AsyncThunkAPI>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a wrapper that postprocesses any tracks, so that we can also support
|
||||||
|
* lyrics, codec metadata and potential other applications.
|
||||||
|
*/
|
||||||
|
export const postProcessTracks = function(creator: AlbumTrackPayloadCreator): AlbumTrackPayloadCreator {
|
||||||
|
// Return a new payload creator
|
||||||
|
return async (args, thunkAPI) => {
|
||||||
|
// Retrieve the tracks using the original creator
|
||||||
|
const tracks = await creator(args, thunkAPI);
|
||||||
|
|
||||||
|
// GUARD: Check if we've retrieved any tracks
|
||||||
|
if (Array.isArray(tracks)) {
|
||||||
|
// If so, attempt to retrieve lyrics for the tracks that have them
|
||||||
|
tracks.filter((t) => t.HasLyrics)
|
||||||
|
.forEach((t) => thunkAPI.dispatch(fetchLyricsByTrack(t.Id)));
|
||||||
|
|
||||||
|
// Also, retrieve codec metadata
|
||||||
|
tracks.forEach((t) => thunkAPI.dispatch(fetchCodecMetadataByTrack(t.Id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracks;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch all albums available on the jellyfin server
|
* Fetch all albums available on the jellyfin server
|
||||||
*/
|
*/
|
||||||
@@ -36,7 +82,7 @@ export const trackAdapter = createEntityAdapter<AlbumTrack, string>({
|
|||||||
*/
|
*/
|
||||||
export const fetchTracksByAlbum = createAsyncThunk<AlbumTrack[], string, AsyncThunkAPI>(
|
export const fetchTracksByAlbum = createAsyncThunk<AlbumTrack[], string, AsyncThunkAPI>(
|
||||||
'/tracks/byAlbum',
|
'/tracks/byAlbum',
|
||||||
retrieveAlbumTracks,
|
postProcessTracks(retrieveAlbumTracks),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const fetchAlbum = createAsyncThunk<Album, string, AsyncThunkAPI>(
|
export const fetchAlbum = createAsyncThunk<Album, string, AsyncThunkAPI>(
|
||||||
@@ -100,5 +146,5 @@ export const fetchAllPlaylists = createAsyncThunk<Playlist[], undefined, AsyncTh
|
|||||||
*/
|
*/
|
||||||
export const fetchTracksByPlaylist = createAsyncThunk<AlbumTrack[], string, AsyncThunkAPI>(
|
export const fetchTracksByPlaylist = createAsyncThunk<AlbumTrack[], string, AsyncThunkAPI>(
|
||||||
'/tracks/byPlaylist',
|
'/tracks/byPlaylist',
|
||||||
retrievePlaylistTracks,
|
postProcessTracks(retrievePlaylistTracks),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import {
|
|||||||
fetchAllPlaylists,
|
fetchAllPlaylists,
|
||||||
fetchTracksByPlaylist,
|
fetchTracksByPlaylist,
|
||||||
fetchAlbum,
|
fetchAlbum,
|
||||||
fetchSimilarAlbums
|
fetchSimilarAlbums,
|
||||||
|
fetchCodecMetadataByTrack,
|
||||||
|
fetchLyricsByTrack
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import { Album, AlbumTrack, Playlist } from './types';
|
import { Album, AlbumTrack, Playlist } from './types';
|
||||||
@@ -162,6 +164,16 @@ const music = createSlice({
|
|||||||
|
|
||||||
// Reset any caches we have when a new server is set
|
// Reset any caches we have when a new server is set
|
||||||
builder.addCase(setJellyfinCredentials, () => initialState);
|
builder.addCase(setJellyfinCredentials, () => initialState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch track metadata
|
||||||
|
*/
|
||||||
|
builder.addCase(fetchCodecMetadataByTrack.fulfilled, (state, { payload, meta }) => {
|
||||||
|
state.tracks.entities[meta.arg].Codec = payload;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchLyricsByTrack.fulfilled, (state, { payload, meta }) => {
|
||||||
|
state.tracks.entities[meta.arg].Lyrics = payload;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import {Lyrics} from '@/utility/JellyfinApi/lyrics.ts';
|
|
||||||
|
|
||||||
export interface UserData {
|
export interface UserData {
|
||||||
PlaybackPositionTicks: number;
|
PlaybackPositionTicks: number;
|
||||||
PlayCount: number;
|
PlayCount: number;
|
||||||
@@ -72,6 +70,34 @@ export interface Album {
|
|||||||
PrimaryImageItemId?: string;
|
PrimaryImageItemId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CodecMetadata {
|
||||||
|
contentType?: string;
|
||||||
|
isDirectPlay: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LyricMetadata {
|
||||||
|
Artist: string
|
||||||
|
Album: string
|
||||||
|
Title: string
|
||||||
|
Author: string
|
||||||
|
Length: number
|
||||||
|
By: string
|
||||||
|
Offset: number
|
||||||
|
Creator: string
|
||||||
|
Version: string
|
||||||
|
IsSynced: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LyricData {
|
||||||
|
Text: string
|
||||||
|
Start: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Lyrics {
|
||||||
|
Metadata: LyricMetadata;
|
||||||
|
Lyrics: LyricData[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface AlbumTrack {
|
export interface AlbumTrack {
|
||||||
Name: string;
|
Name: string;
|
||||||
ServerId: string;
|
ServerId: string;
|
||||||
@@ -95,7 +121,8 @@ export interface AlbumTrack {
|
|||||||
LocationType: string;
|
LocationType: string;
|
||||||
MediaType: string;
|
MediaType: string;
|
||||||
HasLyrics: boolean;
|
HasLyrics: boolean;
|
||||||
Lyrics: Lyrics | null;
|
Lyrics?: Lyrics;
|
||||||
|
Codec?: CodecMetadata;
|
||||||
MediaStreams: MediaStream[];
|
MediaStreams: MediaStream[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Album, AlbumTrack } from '@/store/music/types';
|
import { Album, AlbumTrack } from '@/store/music/types';
|
||||||
import { fetchApi } from './lib';
|
import { fetchApi } from './lib';
|
||||||
import {retrieveAndInjectLyricsToTracks} from '@/utility/JellyfinApi/lyrics.ts';
|
|
||||||
|
|
||||||
const albumOptions = {
|
const albumOptions = {
|
||||||
SortBy: 'AlbumArtist,SortName',
|
SortBy: 'AlbumArtist,SortName',
|
||||||
@@ -73,5 +72,5 @@ export async function retrieveAlbumTracks(ItemId: string) {
|
|||||||
const singleAlbumParams = new URLSearchParams(singleAlbumOptions).toString();
|
const singleAlbumParams = new URLSearchParams(singleAlbumOptions).toString();
|
||||||
|
|
||||||
return fetchApi<{ Items: AlbumTrack[] }>(({ user_id }) => `/Users/${user_id}/Items?${singleAlbumParams}`)
|
return fetchApi<{ Items: AlbumTrack[] }>(({ user_id }) => `/Users/${user_id}/Items?${singleAlbumParams}`)
|
||||||
.then((data) => retrieveAndInjectLyricsToTracks(data.Items));
|
.then((d) => d.Items);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,6 @@
|
|||||||
|
import { Lyrics } from '@/store/music/types';
|
||||||
import { fetchApi } from './lib';
|
import { fetchApi } from './lib';
|
||||||
import {AlbumTrack} from '@/store/music/types.ts';
|
|
||||||
|
|
||||||
interface Metadata {
|
export async function retrieveTrackLyrics(trackId: string): Promise<Lyrics> {
|
||||||
Artist: string
|
return fetchApi<Lyrics>(`/Audio/${trackId}/Lyrics`);
|
||||||
Album: string
|
|
||||||
Title: string
|
|
||||||
Author: string
|
|
||||||
Length: number
|
|
||||||
By: string
|
|
||||||
Offset: number
|
|
||||||
Creator: string
|
|
||||||
Version: string
|
|
||||||
IsSynced: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LyricData {
|
|
||||||
Text: string
|
|
||||||
Start: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Lyrics {
|
|
||||||
Metadata: Metadata
|
|
||||||
Lyrics: LyricData[]
|
|
||||||
}
|
|
||||||
|
|
||||||
async function retrieveTrackLyrics(trackId: string): Promise<Lyrics | null> {
|
|
||||||
return fetchApi<Lyrics>(`/Audio/${trackId}/Lyrics`)
|
|
||||||
.catch((e) => {
|
|
||||||
console.error('Error on fetching track lyrics: ', e);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export async function retrieveAndInjectLyricsToTracks(tracks: AlbumTrack[]): Promise<AlbumTrack[]> {
|
|
||||||
return Promise.all(tracks.map(async (track) => {
|
|
||||||
if (!track.HasLyrics) {
|
|
||||||
track.Lyrics = null;
|
|
||||||
return track;
|
|
||||||
}
|
|
||||||
|
|
||||||
track.Lyrics = await retrieveTrackLyrics(track.Id);
|
|
||||||
|
|
||||||
return track;
|
|
||||||
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { AlbumTrack, Playlist } from '@/store/music/types';
|
import { AlbumTrack, Playlist } from '@/store/music/types';
|
||||||
import { asyncFetchStore, fetchApi } from './lib';
|
import { asyncFetchStore, fetchApi } from './lib';
|
||||||
import {retrieveAndInjectLyricsToTracks} from '@/utility/JellyfinApi/lyrics.ts';
|
|
||||||
|
|
||||||
const playlistOptions = {
|
const playlistOptions = {
|
||||||
SortBy: 'SortName',
|
SortBy: 'SortName',
|
||||||
@@ -35,5 +34,5 @@ export async function retrievePlaylistTracks(ItemId: string) {
|
|||||||
const singlePlaylistParams = new URLSearchParams(singlePlaylistOptions).toString();
|
const singlePlaylistParams = new URLSearchParams(singlePlaylistOptions).toString();
|
||||||
|
|
||||||
return fetchApi<{ Items: AlbumTrack[] }>(`/Playlists/${ItemId}/Items?${singlePlaylistParams}`)
|
return fetchApi<{ Items: AlbumTrack[] }>(`/Playlists/${ItemId}/Items?${singlePlaylistParams}`)
|
||||||
.then((d) => retrieveAndInjectLyricsToTracks(d.Items));
|
.then((d) => d.Items);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { AlbumTrack } from '@/store/music/types';
|
import { AlbumTrack, CodecMetadata } from '@/store/music/types';
|
||||||
import { Platform } from 'react-native';
|
import { Platform } from 'react-native';
|
||||||
import { Track } from 'react-native-track-player';
|
import { Track } from 'react-native-track-player';
|
||||||
import { fetchApi, getImage } from './lib';
|
import { asyncFetchStore, fetchApi, getImage } from './lib';
|
||||||
import store from '@/store';
|
|
||||||
import {retrieveAndInjectLyricsToTracks} from '@/utility/JellyfinApi/lyrics';
|
|
||||||
|
|
||||||
const trackOptionsOsOverrides: Record<typeof Platform.OS, Record<string, string>> = {
|
const trackOptionsOsOverrides: Record<typeof Platform.OS, Record<string, string>> = {
|
||||||
ios: {
|
ios: {
|
||||||
@@ -30,7 +28,7 @@ const baseTrackOptions: Record<string, string> = {
|
|||||||
* Generate the track streaming url from the trackId
|
* Generate the track streaming url from the trackId
|
||||||
*/
|
*/
|
||||||
export function generateTrackUrl(trackId: string) {
|
export function generateTrackUrl(trackId: string) {
|
||||||
const credentials = store.getState().settings.credentials;
|
const credentials = asyncFetchStore().getState().settings.credentials;
|
||||||
const trackOptions = {
|
const trackOptions = {
|
||||||
...baseTrackOptions,
|
...baseTrackOptions,
|
||||||
UserId: credentials?.user_id || '',
|
UserId: credentials?.user_id || '',
|
||||||
@@ -52,8 +50,6 @@ export async function generateTrack(track: AlbumTrack): Promise<Track> {
|
|||||||
// Also construct the URL for the stream
|
// Also construct the URL for the stream
|
||||||
const url = generateTrackUrl(track.Id);
|
const url = generateTrackUrl(track.Id);
|
||||||
|
|
||||||
const response = await fetch(url, { method: 'HEAD' });
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url,
|
url,
|
||||||
backendId: track.Id,
|
backendId: track.Id,
|
||||||
@@ -62,10 +58,6 @@ export async function generateTrack(track: AlbumTrack): Promise<Track> {
|
|||||||
album: track.Album,
|
album: track.Album,
|
||||||
duration: track.RunTimeTicks,
|
duration: track.RunTimeTicks,
|
||||||
artwork: getImage(track.Id),
|
artwork: getImage(track.Id),
|
||||||
hasLyrics: track.HasLyrics,
|
|
||||||
lyrics: track.Lyrics,
|
|
||||||
contentType: response.headers.get('Content-Type') || undefined,
|
|
||||||
isDirectPlay: response.headers.has('Content-Length'),
|
|
||||||
bitRate: baseTrackOptions.audioBitRate,
|
bitRate: baseTrackOptions.audioBitRate,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -84,5 +76,15 @@ const trackParams = {
|
|||||||
*/
|
*/
|
||||||
export async function retrieveAllTracks() {
|
export async function retrieveAllTracks() {
|
||||||
return fetchApi<{ Items: AlbumTrack[] }>(({ user_id }) => `/Users/${user_id}/Items?${trackParams}`)
|
return fetchApi<{ Items: AlbumTrack[] }>(({ user_id }) => `/Users/${user_id}/Items?${trackParams}`)
|
||||||
.then((d) => retrieveAndInjectLyricsToTracks(d.Items));
|
.then((d) => d.Items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function retrieveTrackCodecMetadata(trackId: string): Promise<CodecMetadata> {
|
||||||
|
const url = generateTrackUrl(trackId);
|
||||||
|
const response = await fetch(url, { method: 'HEAD' });
|
||||||
|
|
||||||
|
return {
|
||||||
|
contentType: response.headers.get('Content-Type') || undefined,
|
||||||
|
isDirectPlay: response.headers.has('Content-Length'),
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user