Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e0809bcdc | ||
|
|
5b54760e4e |
@@ -138,8 +138,8 @@ android {
|
|||||||
applicationId "com.jellyfinaudioplayer"
|
applicationId "com.jellyfinaudioplayer"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 10
|
versionCode 11
|
||||||
versionName "1.2.6"
|
versionName "1.2.7"
|
||||||
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
||||||
|
|
||||||
if (isNewArchitectureEnabled()) {
|
if (isNewArchitectureEnabled()) {
|
||||||
|
|||||||
@@ -555,7 +555,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 38;
|
CURRENT_PROJECT_VERSION = 39;
|
||||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
DEVELOPMENT_TEAM = 238P3C58WC;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
@@ -591,7 +591,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 38;
|
CURRENT_PROJECT_VERSION = 39;
|
||||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
DEVELOPMENT_TEAM = 238P3C58WC;
|
||||||
INFOPLIST_FILE = JellyfinAudioPlayer/Info.plist;
|
INFOPLIST_FILE = JellyfinAudioPlayer/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.2.6</string>
|
<string>1.2.7</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>38</string>
|
<string>39</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>BNDL</string>
|
<string>BNDL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.2.6</string>
|
<string>1.2.7</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>38</string>
|
<string>39</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "JellyfinAudioPlayer",
|
"name": "JellyfinAudioPlayer",
|
||||||
"version": "1.2.6",
|
"version": "1.2.7",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "JellyfinAudioPlayer",
|
"name": "JellyfinAudioPlayer",
|
||||||
"version": "1.2.6",
|
"version": "1.2.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-native-async-storage/async-storage": "^1.17.6",
|
"@react-native-async-storage/async-storage": "^1.17.6",
|
||||||
"@react-native-community/blur": "^3.6.0",
|
"@react-native-community/blur": "^3.6.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "JellyfinAudioPlayer",
|
"name": "JellyfinAudioPlayer",
|
||||||
"version": "1.2.6",
|
"version": "1.2.7",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { createAction, createAsyncThunk, createEntityAdapter, EntityId } from '@reduxjs/toolkit';
|
import { createAction, createAsyncThunk, createEntityAdapter, EntityId } from '@reduxjs/toolkit';
|
||||||
import { AppState } from 'store';
|
import { AppState } from 'store';
|
||||||
import { generateTrackUrl } from 'utility/JellyfinApi';
|
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 { DownloadEntity } from './types';
|
||||||
|
import MimeTypes from 'utility/MimeTypes';
|
||||||
|
|
||||||
export const downloadAdapter = createEntityAdapter<DownloadEntity>({
|
export const downloadAdapter = createEntityAdapter<DownloadEntity>({
|
||||||
selectId: (entity) => entity.id,
|
selectId: (entity) => entity.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const queueTrackForDownload = createAction<EntityId>('download/queue');
|
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 progressDownload = createAction<{ id: EntityId, progress: number, jobId?: number }>('download/progress');
|
||||||
export const completeDownload = createAction<{ id: EntityId, location: string, size?: number }>('download/complete');
|
export const completeDownload = createAction<{ id: EntityId, location: string, size?: number }>('download/complete');
|
||||||
export const failDownload = createAction<{ id: EntityId }>('download/fail');
|
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
|
// Generate the URL we can use to download the file
|
||||||
const url = generateTrackUrl(id as string, credentials);
|
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
|
// Actually kick off the download
|
||||||
const { promise } = await downloadFile({
|
const { promise } = await downloadFile({
|
||||||
@@ -31,7 +46,7 @@ export const downloadTrack = createAsyncThunk(
|
|||||||
background: true,
|
background: true,
|
||||||
begin: ({ jobId, contentLength }) => {
|
begin: ({ jobId, contentLength }) => {
|
||||||
// Dispatch the initialization
|
// Dispatch the initialization
|
||||||
dispatch(initializeDownload({ id, jobId, size: contentLength }));
|
dispatch(initializeDownload({ id, jobId, size: contentLength, location }));
|
||||||
},
|
},
|
||||||
progress: (result) => {
|
progress: (result) => {
|
||||||
// Dispatch a progress update
|
// Dispatch a progress update
|
||||||
@@ -48,8 +63,20 @@ export const downloadTrack = createAsyncThunk(
|
|||||||
|
|
||||||
export const removeDownloadedTrack = createAsyncThunk(
|
export const removeDownloadedTrack = createAsyncThunk(
|
||||||
'/downloads/remove/track',
|
'/downloads/remove/track',
|
||||||
async(id: EntityId) => {
|
async(id: EntityId, { getState }) => {
|
||||||
return unlink(`${DocumentDirectoryPath}/${id}.mp3`);
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,6 @@ export interface DownloadEntity {
|
|||||||
isFailed: boolean;
|
isFailed: boolean;
|
||||||
isComplete: boolean;
|
isComplete: boolean;
|
||||||
size?: number;
|
size?: number;
|
||||||
location?: string;
|
location: string;
|
||||||
jobId?: number;
|
jobId?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ const baseTrackOptions: Record<string, string> = {
|
|||||||
// This must be set to support client seeking
|
// This must be set to support client seeking
|
||||||
TranscodingProtocol: 'http',
|
TranscodingProtocol: 'http',
|
||||||
TranscodingContainer: 'aac',
|
TranscodingContainer: 'aac',
|
||||||
Container: 'mp3,aac,m4a,m4b|aac,alac,m4a,m4b|alac,flac|ogg',
|
Container: 'mp3,aac,m4a,m4b|aac,flac,alac,m4a,m4b|alac,flac|ogg',
|
||||||
AudioCodec: 'aac',
|
|
||||||
static: 'true',
|
static: 'true',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
18
src/utility/MimeTypes.ts
Normal file
18
src/utility/MimeTypes.ts
Normal 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;
|
||||||
Reference in New Issue
Block a user