feat: Allow FLAC playback

This commit is contained in:
Lei Nelissen
2022-08-14 00:30:20 +02:00
parent 8a9c14e940
commit 5b54760e4e
4 changed files with 53 additions and 9 deletions

View File

@@ -1,15 +1,16 @@
import { createAction, createAsyncThunk, createEntityAdapter, EntityId } from '@reduxjs/toolkit';
import { AppState } from 'store';
import { generateTrackUrl } from 'utility/JellyfinApi';
import { downloadFile, unlink, DocumentDirectoryPath } from 'react-native-fs';
import { downloadFile, unlink, DocumentDirectoryPath, exists } from 'react-native-fs';
import { DownloadEntity } from './types';
import MimeTypes from 'utility/MimeTypes';
export const downloadAdapter = createEntityAdapter<DownloadEntity>({
selectId: (entity) => entity.id,
});
export const queueTrackForDownload = createAction<EntityId>('download/queue');
export const initializeDownload = createAction<{ id: EntityId, size?: number, jobId?: number }>('download/initialize');
export const initializeDownload = createAction<{ id: EntityId, size?: number, jobId?: number, location: string }>('download/initialize');
export const progressDownload = createAction<{ id: EntityId, progress: number, jobId?: number }>('download/progress');
export const completeDownload = createAction<{ id: EntityId, location: string, size?: number }>('download/complete');
export const failDownload = createAction<{ id: EntityId }>('download/fail');
@@ -22,7 +23,21 @@ export const downloadTrack = createAsyncThunk(
// Generate the URL we can use to download the file
const url = generateTrackUrl(id as string, credentials);
const location = `${DocumentDirectoryPath}/${id}.mp3`;
// Get the content-type from the URL by doing a HEAD-only request
const contentType = (await fetch(url, { method: 'HEAD' })).headers.get('Content-Type');
if (!contentType) {
throw new Error('Jellyfin did not return a Content-Type for a streaming URL.');
}
// Then convert the MIME-type to an extension
const extension = MimeTypes[contentType as keyof typeof MimeTypes];
if (!extension) {
throw new Error('Jellyfin returned an unrecognized MIME-type');
}
// Then generate the proper location
const location = `${DocumentDirectoryPath}/${id}${extension}`;
// Actually kick off the download
const { promise } = await downloadFile({
@@ -31,7 +46,7 @@ export const downloadTrack = createAsyncThunk(
background: true,
begin: ({ jobId, contentLength }) => {
// Dispatch the initialization
dispatch(initializeDownload({ id, jobId, size: contentLength }));
dispatch(initializeDownload({ id, jobId, size: contentLength, location }));
},
progress: (result) => {
// Dispatch a progress update
@@ -48,8 +63,20 @@ export const downloadTrack = createAsyncThunk(
export const removeDownloadedTrack = createAsyncThunk(
'/downloads/remove/track',
async(id: EntityId) => {
return unlink(`${DocumentDirectoryPath}/${id}.mp3`);
async(id: EntityId, { getState }) => {
// Retrieve the state
const { downloads: { entities }} = getState() as AppState;
// Attempt to retrieve the entity from the state
const download = entities[id];
if (!download) {
throw new Error('Attempted to remove unknown downloaded track.');
}
// Then unlink the file, if it exists
if (await exists(download.location)) {
return unlink(download.location);
}
}
);

View File

@@ -6,6 +6,6 @@ export interface DownloadEntity {
isFailed: boolean;
isComplete: boolean;
size?: number;
location?: string;
location: string;
jobId?: number;
}

View File

@@ -22,8 +22,7 @@ const baseTrackOptions: Record<string, string> = {
// This must be set to support client seeking
TranscodingProtocol: 'http',
TranscodingContainer: 'aac',
Container: 'mp3,aac,m4a,m4b|aac,alac,m4a,m4b|alac,flac|ogg',
AudioCodec: 'aac',
Container: 'mp3,aac,m4a,m4b|aac,flac,alac,m4a,m4b|alac,flac|ogg',
static: 'true',
};

18
src/utility/MimeTypes.ts Normal file
View File

@@ -0,0 +1,18 @@
/**
* The filetypes this application will most probably receive.
* Adapted from: https://github.com/jellyfin/jellyfin/blob/63d943aab92a4b5f69e625a269eb830bcbfb4d22/MediaBrowser.Model/Net/MimeTypes.cs#L107
*/
const MimeTypes = {
'audio/aac': '.aac',
'audio/ac3': '.ac3',
'audio/dsf': '.dsf',
'audio/dsp': '.dsp',
'audio/flac': '.flac',
'audio/m4b': '.m4b',
'audio/vorbis': '.vorbis',
'audio/x-ape': '.ape',
'audio/xsp': '.xsp',
'audio/x-wavpack': '.wv',
};
export default MimeTypes;