Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a97611c0ad | ||
|
|
e511f744ad | ||
|
|
a6a306b5be | ||
|
|
881ab95029 | ||
|
|
968e98d8df | ||
|
|
b01470bde8 | ||
|
|
823f7b59e8 | ||
|
|
16162d8e35 | ||
|
|
ea817025e1 | ||
|
|
00675bbbd3 | ||
|
|
24b5a47a7c | ||
|
|
bb655cb719 | ||
|
|
be0c7002ff | ||
|
|
e472d043cf | ||
|
|
366d16c485 | ||
|
|
845eac70a0 | ||
|
|
c9662769fa |
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,3 +1,37 @@
|
|||||||
|
## [2.3.3](https://github.com/leinelissen/jellyfin-audio-player/compare/v2.3.2...v2.3.3) (2024-06-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* actually send out /Playing events as session updates. ([b01470b](https://github.com/leinelissen/jellyfin-audio-player/commit/b01470bde8ea353ea7139c0708ec9cfdaf600fe4)), closes [#218](https://github.com/leinelissen/jellyfin-audio-player/issues/218)
|
||||||
|
* do extra checks for album ids in ([00675bb](https://github.com/leinelissen/jellyfin-audio-player/commit/00675bbbd3e72e8e710d8aa9b73b491e65153d40))
|
||||||
|
* double-check albums have dates ([881ab95](https://github.com/leinelissen/jellyfin-audio-player/commit/881ab9502960786dc9685cf3612793fea3c1be4c))
|
||||||
|
* hermes version in cocoapods ([ea81702](https://github.com/leinelissen/jellyfin-audio-player/commit/ea817025e1bf67fcd3c183c12f4f1f93c3218785))
|
||||||
|
* react-native-screens android setup ([968e98d](https://github.com/leinelissen/jellyfin-audio-player/commit/968e98d8dffa79ea3165d1209542bd91dd914ef5))
|
||||||
|
* refactor JellyfinApi to be less burdensome to implement ([a6a306b](https://github.com/leinelissen/jellyfin-audio-player/commit/a6a306b5be6988469449b17ed527f1d365901e6d))
|
||||||
|
* throw errors when requests do not yield 200 OKs ([16162d8](https://github.com/leinelissen/jellyfin-audio-player/commit/16162d8e3505ea195c8aaf03b82df88405196025))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.3.2](https://github.com/leinelissen/jellyfin-audio-player/compare/v2.3.1...v2.3.2) (2024-03-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* build with xcode 15.3 ([845eac7](https://github.com/leinelissen/jellyfin-audio-player/commit/845eac70a0afa189cd76e97f739ad627f648566a))
|
||||||
|
* remove conflicting app transport properties ([c966276](https://github.com/leinelissen/jellyfin-audio-player/commit/c9662769faec8771b6a70da815ec36e62c8c43a2))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.3.1](https://github.com/leinelissen/jellyfin-audio-player/compare/v2.3.0...v2.3.1) (2024-03-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* revert to supporting HTTP-based backends ([f310bb8](https://github.com/leinelissen/jellyfin-audio-player/commit/f310bb82f61f532f9557787d364e9f342166806d)), closes [#205](https://github.com/leinelissen/jellyfin-audio-player/issues/205)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [2.3.0](https://github.com/leinelissen/jellyfin-audio-player/compare/v2.2.0...v2.3.0) (2024-02-11)
|
# [2.3.0](https://github.com/leinelissen/jellyfin-audio-player/compare/v2.2.0...v2.3.0) (2024-02-11)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -85,8 +85,8 @@ android {
|
|||||||
applicationId "nl.moeilijkedingen.jellyfinaudioplayer"
|
applicationId "nl.moeilijkedingen.jellyfinaudioplayer"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 26
|
versionCode 29
|
||||||
versionName "2.3.1"
|
versionName "2.3.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import com.facebook.react.ReactActivityDelegate
|
|||||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
|
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
|
||||||
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
class MainActivity : ReactActivity() {
|
class MainActivity : ReactActivity() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,4 +21,8 @@ class MainActivity : ReactActivity() {
|
|||||||
*/
|
*/
|
||||||
override fun createReactActivityDelegate(): ReactActivityDelegate =
|
override fun createReactActivityDelegate(): ReactActivityDelegate =
|
||||||
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
|
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
Privacy policy for Fintunes
|
Privacy policy for Fintunes
|
||||||
|
|
||||||
Fintunes does not collect any personal data. Period. We respect your right to
|
Fintunes does not collect any personal data. Period. We respect your right to
|
||||||
autonomy and vow to not collect any information without user consent at all.
|
autonomy and vow to not collect any information without user consent at all.
|
||||||
|
|
||||||
If you opt-in to crash logging, we will collect analytics data from your device,
|
If you opt-in to crash logging, we will collect analytics data from your device,
|
||||||
every time a crash occurs. This data includes debugging information such as
|
every time a crash occurs. This data includes debugging information such as
|
||||||
devices, versions and the specific error. All data is sent to a server
|
devices, versions and the specific error. All data is sent to a server
|
||||||
controlled by the first party. No third parties can access this data in any
|
controlled by the first party. No third parties can access this data in any
|
||||||
form. No personal data is included in the analytics data.
|
form. No personal data is included in the analytics data.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package_name("nl.moeilijkedingen.jellyfinaudioplayer")
|
package_name("nl.moeilijkedingen.jellyfinaudioplayer")
|
||||||
app_identifier("nl.moeilijkedingen.jellyfinaudioplayer")
|
app_identifier("nl.moeilijkedingen.jellyfinaudioplayer")
|
||||||
apple_id("lei@moeilijkedingen.nl")
|
apple_id("lei@codified.nl")
|
||||||
team_id("238P3C58WC")
|
team_id("HD2D35G9Y4")
|
||||||
json_key_file("./fastlane/play-store-credentials.json")
|
json_key_file("./fastlane/play-store-credentials.json")
|
||||||
|
itc_team_id("127114471")
|
||||||
@@ -253,19 +253,19 @@
|
|||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
00E356ED1AD99517003FC87E = {
|
00E356ED1AD99517003FC87E = {
|
||||||
CreatedOnToolsVersion = 6.2;
|
CreatedOnToolsVersion = 6.2;
|
||||||
DevelopmentTeam = 238P3C58WC;
|
DevelopmentTeam = HD2D35G9Y4;
|
||||||
ProvisioningStyle = Manual;
|
ProvisioningStyle = Manual;
|
||||||
TestTargetID = 13B07F861A680F5B00A75B9A;
|
TestTargetID = 13B07F861A680F5B00A75B9A;
|
||||||
};
|
};
|
||||||
13B07F861A680F5B00A75B9A = {
|
13B07F861A680F5B00A75B9A = {
|
||||||
DevelopmentTeam = 238P3C58WC;
|
DevelopmentTeam = HD2D35G9Y4;
|
||||||
LastSwiftMigration = 1210;
|
LastSwiftMigration = 1210;
|
||||||
ProvisioningStyle = Manual;
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
AB4A8DFA2857C8DA005A1ED0 = {
|
AB4A8DFA2857C8DA005A1ED0 = {
|
||||||
CreatedOnToolsVersion = 13.4.1;
|
CreatedOnToolsVersion = 13.4.1;
|
||||||
DevelopmentTeam = 238P3C58WC;
|
DevelopmentTeam = HD2D35G9Y4;
|
||||||
ProvisioningStyle = Manual;
|
ProvisioningStyle = Automatic;
|
||||||
TestTargetID = 13B07F861A680F5B00A75B9A;
|
TestTargetID = 13B07F861A680F5B00A75B9A;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -341,16 +341,10 @@
|
|||||||
);
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Fintunes/Pods-Fintunes-frameworks.sh",
|
"${PODS_ROOT}/Target Support Files/Pods-Fintunes/Pods-Fintunes-frameworks.sh",
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-DoubleConversion/double-conversion.framework/double-conversion",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-Glog/glog.framework/glog",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
|
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
|
||||||
);
|
);
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/double-conversion.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/glog.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -365,16 +359,10 @@
|
|||||||
);
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Fintunes-FintunesTests/Pods-Fintunes-FintunesTests-frameworks.sh",
|
"${PODS_ROOT}/Target Support Files/Pods-Fintunes-FintunesTests/Pods-Fintunes-FintunesTests-frameworks.sh",
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-DoubleConversion/double-conversion.framework/double-conversion",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-Glog/glog.framework/glog",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
|
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
|
||||||
);
|
);
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/double-conversion.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/glog.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -530,7 +518,7 @@
|
|||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
DEVELOPMENT_TEAM = HD2D35G9Y4;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
"DEBUG=1",
|
"DEBUG=1",
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -561,7 +549,7 @@
|
|||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
DEVELOPMENT_TEAM = HD2D35G9Y4;
|
||||||
INFOPLIST_FILE = FintunesTests/Info.plist;
|
INFOPLIST_FILE = FintunesTests/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||||
@@ -588,11 +576,10 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 83;
|
CURRENT_PROJECT_VERSION = 91;
|
||||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
DEVELOPMENT_TEAM = HD2D35G9Y4;
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 238P3C58WC;
|
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -613,7 +600,6 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = nl.moeilijkedingen.jellyfinaudioplayer;
|
PRODUCT_BUNDLE_IDENTIFIER = nl.moeilijkedingen.jellyfinaudioplayer;
|
||||||
PRODUCT_NAME = Fintunes;
|
PRODUCT_NAME = Fintunes;
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "nl.moeilijkedingen.jellyfinaudioplayer AppStore 1707846041";
|
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Fintunes-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Fintunes-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -630,9 +616,9 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 83;
|
CURRENT_PROJECT_VERSION = 91;
|
||||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
DEVELOPMENT_TEAM = HD2D35G9Y4;
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 238P3C58WC;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = HD2D35G9Y4;
|
||||||
INFOPLIST_FILE = Fintunes/Info.plist;
|
INFOPLIST_FILE = Fintunes/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
@@ -649,7 +635,7 @@
|
|||||||
PRODUCT_NAME = Fintunes;
|
PRODUCT_NAME = Fintunes;
|
||||||
PROVISIONING_PROFILE = "915c5213-22f6-4f9d-8065-2a06300f9bfb";
|
PROVISIONING_PROFILE = "915c5213-22f6-4f9d-8065-2a06300f9bfb";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "nl.moeilijkedingen.jellyfinaudioplayer AppStore 1707846041";
|
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "nl.moeilijkedingen.jellyfinaudioplayer AppStore";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Fintunes-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Fintunes-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
@@ -800,10 +786,11 @@
|
|||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 83;
|
CURRENT_PROJECT_VERSION = 91;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
DEVELOPMENT_TEAM = HD2D35G9Y4;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
||||||
@@ -813,6 +800,7 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = nl.moeilijkedingen.FintunesUITests;
|
PRODUCT_BUNDLE_IDENTIFIER = nl.moeilijkedingen.FintunesUITests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE = "915c5213-22f6-4f9d-8065-2a06300f9bfb";
|
PROVISIONING_PROFILE = "915c5213-22f6-4f9d-8065-2a06300f9bfb";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
@@ -832,11 +820,12 @@
|
|||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 83;
|
CURRENT_PROJECT_VERSION = 91;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
DEVELOPMENT_TEAM = HD2D35G9Y4;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
||||||
@@ -845,6 +834,7 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = nl.moeilijkedingen.FintunesUITests;
|
PRODUCT_BUNDLE_IDENTIFIER = nl.moeilijkedingen.FintunesUITests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE = "915c5213-22f6-4f9d-8065-2a06300f9bfb";
|
PROVISIONING_PROFILE = "915c5213-22f6-4f9d-8065-2a06300f9bfb";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|||||||
@@ -17,19 +17,19 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.3.1</string>
|
<string>2.3.3</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>83</string>
|
<string>91</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
|
<true/>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSAllowsLocalNetworking</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
@@ -42,6 +42,8 @@
|
|||||||
<string>audio</string>
|
<string>audio</string>
|
||||||
<string>fetch</string>
|
<string>fetch</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>UIFileSharingEnabled</key>
|
||||||
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
@@ -54,13 +56,9 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>UISupportsDocumentBrowser</key>
|
||||||
|
<true/>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>UIFileSharingEnabled</key>
|
|
||||||
<true/>
|
|
||||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
|
||||||
<true/>
|
|
||||||
<key>UISupportsDocumentBrowser</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ target 'Fintunes' do
|
|||||||
#
|
#
|
||||||
# Note that if you have use_frameworks! enabled, Flipper will not work and
|
# Note that if you have use_frameworks! enabled, Flipper will not work and
|
||||||
# you should disable the next line.
|
# you should disable the next line.
|
||||||
:flipper_configuration => flipper_config,
|
# :flipper_configuration => flipper_config,
|
||||||
# An absolute path to your application root.
|
# An absolute path to your application root.
|
||||||
:app_path => "#{Pod::Config.instance.installation_root}/.."
|
:app_path => "#{Pod::Config.instance.installation_root}/.."
|
||||||
)
|
)
|
||||||
|
|||||||
112
ios/Podfile.lock
112
ios/Podfile.lock
@@ -1,6 +1,5 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- boost (1.83.0)
|
- boost (1.83.0)
|
||||||
- CocoaAsyncSocket (7.6.5)
|
|
||||||
- DoubleConversion (1.1.6)
|
- DoubleConversion (1.1.6)
|
||||||
- FBLazyVector (0.73.4)
|
- FBLazyVector (0.73.4)
|
||||||
- FBReactNativeSpec (0.73.4):
|
- FBReactNativeSpec (0.73.4):
|
||||||
@@ -10,67 +9,11 @@ PODS:
|
|||||||
- React-Core (= 0.73.4)
|
- React-Core (= 0.73.4)
|
||||||
- React-jsi (= 0.73.4)
|
- React-jsi (= 0.73.4)
|
||||||
- ReactCommon/turbomodule/core (= 0.73.4)
|
- ReactCommon/turbomodule/core (= 0.73.4)
|
||||||
- Flipper (0.201.0):
|
|
||||||
- Flipper-Folly (~> 2.6)
|
|
||||||
- Flipper-Boost-iOSX (1.76.0.1.11)
|
|
||||||
- Flipper-DoubleConversion (3.2.0.1)
|
|
||||||
- Flipper-Fmt (7.1.7)
|
|
||||||
- Flipper-Folly (2.6.10):
|
|
||||||
- Flipper-Boost-iOSX
|
|
||||||
- Flipper-DoubleConversion
|
|
||||||
- Flipper-Fmt (= 7.1.7)
|
|
||||||
- Flipper-Glog
|
|
||||||
- libevent (~> 2.1.12)
|
|
||||||
- OpenSSL-Universal (= 1.1.1100)
|
|
||||||
- Flipper-Glog (0.5.0.5)
|
|
||||||
- Flipper-PeerTalk (0.0.4)
|
|
||||||
- FlipperKit (0.201.0):
|
|
||||||
- FlipperKit/Core (= 0.201.0)
|
|
||||||
- FlipperKit/Core (0.201.0):
|
|
||||||
- Flipper (~> 0.201.0)
|
|
||||||
- FlipperKit/CppBridge
|
|
||||||
- FlipperKit/FBCxxFollyDynamicConvert
|
|
||||||
- FlipperKit/FBDefines
|
|
||||||
- FlipperKit/FKPortForwarding
|
|
||||||
- SocketRocket (~> 0.6.0)
|
|
||||||
- FlipperKit/CppBridge (0.201.0):
|
|
||||||
- Flipper (~> 0.201.0)
|
|
||||||
- FlipperKit/FBCxxFollyDynamicConvert (0.201.0):
|
|
||||||
- Flipper-Folly (~> 2.6)
|
|
||||||
- FlipperKit/FBDefines (0.201.0)
|
|
||||||
- FlipperKit/FKPortForwarding (0.201.0):
|
|
||||||
- CocoaAsyncSocket (~> 7.6)
|
|
||||||
- Flipper-PeerTalk (~> 0.0.4)
|
|
||||||
- FlipperKit/FlipperKitHighlightOverlay (0.201.0)
|
|
||||||
- FlipperKit/FlipperKitLayoutHelpers (0.201.0):
|
|
||||||
- FlipperKit/Core
|
|
||||||
- FlipperKit/FlipperKitHighlightOverlay
|
|
||||||
- FlipperKit/FlipperKitLayoutTextSearchable
|
|
||||||
- FlipperKit/FlipperKitLayoutIOSDescriptors (0.201.0):
|
|
||||||
- FlipperKit/Core
|
|
||||||
- FlipperKit/FlipperKitHighlightOverlay
|
|
||||||
- FlipperKit/FlipperKitLayoutHelpers
|
|
||||||
- FlipperKit/FlipperKitLayoutPlugin (0.201.0):
|
|
||||||
- FlipperKit/Core
|
|
||||||
- FlipperKit/FlipperKitHighlightOverlay
|
|
||||||
- FlipperKit/FlipperKitLayoutHelpers
|
|
||||||
- FlipperKit/FlipperKitLayoutIOSDescriptors
|
|
||||||
- FlipperKit/FlipperKitLayoutTextSearchable
|
|
||||||
- FlipperKit/FlipperKitLayoutTextSearchable (0.201.0)
|
|
||||||
- FlipperKit/FlipperKitNetworkPlugin (0.201.0):
|
|
||||||
- FlipperKit/Core
|
|
||||||
- FlipperKit/FlipperKitReactPlugin (0.201.0):
|
|
||||||
- FlipperKit/Core
|
|
||||||
- FlipperKit/FlipperKitUserDefaultsPlugin (0.201.0):
|
|
||||||
- FlipperKit/Core
|
|
||||||
- FlipperKit/SKIOSNetworkPlugin (0.201.0):
|
|
||||||
- FlipperKit/Core
|
|
||||||
- FlipperKit/FlipperKitNetworkPlugin
|
|
||||||
- fmt (6.2.1)
|
- fmt (6.2.1)
|
||||||
- glog (0.3.5)
|
- glog (0.3.5)
|
||||||
- hermes-engine (0.73.3):
|
- hermes-engine (0.73.4):
|
||||||
- hermes-engine/Pre-built (= 0.73.3)
|
- hermes-engine/Pre-built (= 0.73.4)
|
||||||
- hermes-engine/Pre-built (0.73.3)
|
- hermes-engine/Pre-built (0.73.4)
|
||||||
- libevent (2.1.12)
|
- libevent (2.1.12)
|
||||||
- libwebp (1.3.2):
|
- libwebp (1.3.2):
|
||||||
- libwebp/demux (= 1.3.2)
|
- libwebp/demux (= 1.3.2)
|
||||||
@@ -84,7 +27,6 @@ PODS:
|
|||||||
- libwebp/sharpyuv (1.3.2)
|
- libwebp/sharpyuv (1.3.2)
|
||||||
- libwebp/webp (1.3.2):
|
- libwebp/webp (1.3.2):
|
||||||
- libwebp/sharpyuv
|
- libwebp/sharpyuv
|
||||||
- OpenSSL-Universal (1.1.1100)
|
|
||||||
- RCT-Folly (2022.05.16.00):
|
- RCT-Folly (2022.05.16.00):
|
||||||
- boost
|
- boost
|
||||||
- DoubleConversion
|
- DoubleConversion
|
||||||
@@ -1200,31 +1142,10 @@ DEPENDENCIES:
|
|||||||
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||||
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
|
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
|
||||||
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
|
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
|
||||||
- Flipper (= 0.201.0)
|
|
||||||
- Flipper-Boost-iOSX (= 1.76.0.1.11)
|
|
||||||
- Flipper-DoubleConversion (= 3.2.0.1)
|
|
||||||
- Flipper-Fmt (= 7.1.7)
|
|
||||||
- Flipper-Folly (= 2.6.10)
|
|
||||||
- Flipper-Glog (= 0.5.0.5)
|
|
||||||
- Flipper-PeerTalk (= 0.0.4)
|
|
||||||
- FlipperKit (= 0.201.0)
|
|
||||||
- FlipperKit/Core (= 0.201.0)
|
|
||||||
- FlipperKit/CppBridge (= 0.201.0)
|
|
||||||
- FlipperKit/FBCxxFollyDynamicConvert (= 0.201.0)
|
|
||||||
- FlipperKit/FBDefines (= 0.201.0)
|
|
||||||
- FlipperKit/FKPortForwarding (= 0.201.0)
|
|
||||||
- FlipperKit/FlipperKitHighlightOverlay (= 0.201.0)
|
|
||||||
- FlipperKit/FlipperKitLayoutPlugin (= 0.201.0)
|
|
||||||
- FlipperKit/FlipperKitLayoutTextSearchable (= 0.201.0)
|
|
||||||
- FlipperKit/FlipperKitNetworkPlugin (= 0.201.0)
|
|
||||||
- FlipperKit/FlipperKitReactPlugin (= 0.201.0)
|
|
||||||
- FlipperKit/FlipperKitUserDefaultsPlugin (= 0.201.0)
|
|
||||||
- FlipperKit/SKIOSNetworkPlugin (= 0.201.0)
|
|
||||||
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||||
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
|
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
|
||||||
- libevent (~> 2.1.12)
|
- libevent (~> 2.1.12)
|
||||||
- libwebp (= 1.3.2)
|
- libwebp (= 1.3.2)
|
||||||
- OpenSSL-Universal (= 1.1.1100)
|
|
||||||
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
|
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
|
||||||
- RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
|
- RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
|
||||||
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
|
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
|
||||||
@@ -1234,7 +1155,6 @@ DEPENDENCIES:
|
|||||||
- React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`)
|
- React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`)
|
||||||
- React-Codegen (from `build/generated/ios`)
|
- React-Codegen (from `build/generated/ios`)
|
||||||
- React-Core (from `../node_modules/react-native/`)
|
- React-Core (from `../node_modules/react-native/`)
|
||||||
- React-Core/DevSupport (from `../node_modules/react-native/`)
|
|
||||||
- React-Core/RCTWebSocket (from `../node_modules/react-native/`)
|
- React-Core/RCTWebSocket (from `../node_modules/react-native/`)
|
||||||
- React-CoreModules (from `../node_modules/react-native/React/CoreModules`)
|
- React-CoreModules (from `../node_modules/react-native/React/CoreModules`)
|
||||||
- React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`)
|
- React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`)
|
||||||
@@ -1291,19 +1211,9 @@ DEPENDENCIES:
|
|||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
- CocoaAsyncSocket
|
|
||||||
- Flipper
|
|
||||||
- Flipper-Boost-iOSX
|
|
||||||
- Flipper-DoubleConversion
|
|
||||||
- Flipper-Fmt
|
|
||||||
- Flipper-Folly
|
|
||||||
- Flipper-Glog
|
|
||||||
- Flipper-PeerTalk
|
|
||||||
- FlipperKit
|
|
||||||
- fmt
|
- fmt
|
||||||
- libevent
|
- libevent
|
||||||
- libwebp
|
- libwebp
|
||||||
- OpenSSL-Universal
|
|
||||||
- SDWebImage
|
- SDWebImage
|
||||||
- SDWebImageWebPCoder
|
- SDWebImageWebPCoder
|
||||||
- Sentry
|
- Sentry
|
||||||
@@ -1448,24 +1358,14 @@ EXTERNAL SOURCES:
|
|||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
boost: d3f49c53809116a5d38da093a8aa78bf551aed09
|
boost: d3f49c53809116a5d38da093a8aa78bf551aed09
|
||||||
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
|
|
||||||
DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953
|
DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953
|
||||||
FBLazyVector: 84f6edbe225f38aebd9deaf1540a4160b1f087d7
|
FBLazyVector: 84f6edbe225f38aebd9deaf1540a4160b1f087d7
|
||||||
FBReactNativeSpec: d0086a479be91c44ce4687a962956a352d2dc697
|
FBReactNativeSpec: d0086a479be91c44ce4687a962956a352d2dc697
|
||||||
Flipper: c7a0093234c4bdd456e363f2f19b2e4b27652d44
|
|
||||||
Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c
|
|
||||||
Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30
|
|
||||||
Flipper-Fmt: 60cbdd92fc254826e61d669a5d87ef7015396a9b
|
|
||||||
Flipper-Folly: 584845625005ff068a6ebf41f857f468decd26b3
|
|
||||||
Flipper-Glog: 70c50ce58ddaf67dc35180db05f191692570f446
|
|
||||||
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
|
|
||||||
FlipperKit: 37525a5d056ef9b93d1578e04bc3ea1de940094f
|
|
||||||
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
|
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
|
||||||
glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2
|
glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2
|
||||||
hermes-engine: 5420539d016f368cd27e008f65f777abd6098c56
|
hermes-engine: b2669ce35fc4ac14f523b307aff8896799829fe2
|
||||||
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
||||||
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
|
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
|
||||||
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
|
|
||||||
RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0
|
RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0
|
||||||
RCTRequired: ab7f915c15569f04a49669e573e6e319a53f9faa
|
RCTRequired: ab7f915c15569f04a49669e573e6e319a53f9faa
|
||||||
RCTTypeSafety: 63b97ced7b766865057e7154db0e81ce4ee6cf1e
|
RCTTypeSafety: 63b97ced7b766865057e7154db0e81ce4ee6cf1e
|
||||||
@@ -1533,6 +1433,6 @@ SPEC CHECKSUMS:
|
|||||||
SwiftAudioEx: 6f511018b7a0fdfd14ed1bb4081f953588245cc0
|
SwiftAudioEx: 6f511018b7a0fdfd14ed1bb4081f953588245cc0
|
||||||
Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312
|
Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312
|
||||||
|
|
||||||
PODFILE CHECKSUM: 0061941757351ef20fb9ad4bc98e51b1212f87d3
|
PODFILE CHECKSUM: 292b785d92bbff1138baa390bf579dd7147228be
|
||||||
|
|
||||||
COCOAPODS: 1.14.3
|
COCOAPODS: 1.15.2
|
||||||
|
|||||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "fintunes",
|
"name": "fintunes",
|
||||||
"version": "2.3.1",
|
"version": "2.3.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "fintunes",
|
"name": "fintunes",
|
||||||
"version": "2.3.1",
|
"version": "2.3.3",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-native-async-storage/async-storage": "^1.21.0",
|
"@react-native-async-storage/async-storage": "^1.21.0",
|
||||||
@@ -6876,9 +6876,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ip": {
|
"node_modules/ip": {
|
||||||
"version": "1.1.8",
|
"version": "1.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||||
"integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
|
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ=="
|
||||||
},
|
},
|
||||||
"node_modules/is-array-buffer": {
|
"node_modules/is-array-buffer": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fintunes",
|
"name": "fintunes",
|
||||||
"version": "2.3.1",
|
"version": "2.3.3",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
13
patches/@react-native-community+datetimepicker+7.6.2.patch
Normal file
13
patches/@react-native-community+datetimepicker+7.6.2.patch
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
diff --git a/node_modules/@react-native-community/datetimepicker/ios/RNDateTimePickerShadowView.m b/node_modules/@react-native-community/datetimepicker/ios/RNDateTimePickerShadowView.m
|
||||||
|
index c139440..4ff3362 100644
|
||||||
|
--- a/node_modules/@react-native-community/datetimepicker/ios/RNDateTimePickerShadowView.m
|
||||||
|
+++ b/node_modules/@react-native-community/datetimepicker/ios/RNDateTimePickerShadowView.m
|
||||||
|
@@ -41,7 +41,7 @@
|
||||||
|
YGNodeMarkDirty(self.yogaNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
-static YGSize RNDateTimePickerShadowViewMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
|
||||||
|
+static YGSize RNDateTimePickerShadowViewMeasure(YGNodeConstRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
|
||||||
|
{
|
||||||
|
RNDateTimePickerShadowView *shadowPickerView = (__bridge RNDateTimePickerShadowView *)YGNodeGetContext(node);
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ import DownloadIcon from '@/components/DownloadIcon';
|
|||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import { Text } from '@/components/Typography';
|
import { Text } from '@/components/Typography';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import { useGetImage } from '@/utility/JellyfinApi';
|
import { useGetImage } from '@/utility/JellyfinApi/lib';
|
||||||
import { ShadowWrapper } from '@/components/Shadow';
|
import { ShadowWrapper } from '@/components/Shadow';
|
||||||
import { SafeFlatList } from '@/components/SafeNavigatorView';
|
import { SafeFlatList } from '@/components/SafeNavigatorView';
|
||||||
import { t } from '@/localisation';
|
import { t } from '@/localisation';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { t } from '@/localisation';
|
|||||||
import { NavigationProp, StackParams } from '@/screens/types';
|
import { NavigationProp, StackParams } from '@/screens/types';
|
||||||
import { SubHeader, Text } from '@/components/Typography';
|
import { SubHeader, Text } from '@/components/Typography';
|
||||||
import { ScrollView } from 'react-native-gesture-handler';
|
import { ScrollView } from 'react-native-gesture-handler';
|
||||||
import { useGetImage } from '@/utility/JellyfinApi';
|
import { useGetImage } from '@/utility/JellyfinApi/lib';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Dimensions, Pressable } from 'react-native';
|
import { Dimensions, Pressable } from 'react-native';
|
||||||
import AlbumImage from './components/AlbumImage';
|
import AlbumImage from './components/AlbumImage';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, useRef, ReactText, useMemo } from 'react';
|
import React, { useCallback, useEffect, useRef, ReactText, useMemo } from 'react';
|
||||||
import { useGetImage } from '@/utility/JellyfinApi';
|
import { useGetImage } from '@/utility/JellyfinApi/lib';
|
||||||
import { SectionList, View } from 'react-native';
|
import { SectionList, View } from 'react-native';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, ReactText } from 'react';
|
import React, { useCallback, useEffect, ReactText } from 'react';
|
||||||
import { useGetImage } from '@/utility/JellyfinApi';
|
import { useGetImage } from '@/utility/JellyfinApi/lib';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
|
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useMemo } from 'react';
|
import React, { useCallback, useEffect, useRef, useMemo } from 'react';
|
||||||
import { useGetImage } from '@/utility/JellyfinApi';
|
import { useGetImage } from '@/utility/JellyfinApi/lib';
|
||||||
import { SectionList, View } from 'react-native';
|
import { SectionList, View } from 'react-native';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, useRef, ReactText } from 'react';
|
import React, { useCallback, useEffect, useRef, ReactText } from 'react';
|
||||||
import { useGetImage } from '@/utility/JellyfinApi';
|
import { useGetImage } from '@/utility/JellyfinApi/lib';
|
||||||
import { Text, View, FlatList, ListRenderItem, RefreshControl } from 'react-native';
|
import { Text, View, FlatList, ListRenderItem, RefreshControl } from 'react-native';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { useGetImage } from '@/utility/JellyfinApi';
|
import { useGetImage } from '@/utility/JellyfinApi/lib';
|
||||||
import { Text, SafeAreaView, StyleSheet } from 'react-native';
|
import { Text, SafeAreaView, StyleSheet } from 'react-native';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { useAppDispatch, useTypedSelector } from '@/store';
|
import { useAppDispatch, useTypedSelector } from '@/store';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { PropsWithChildren, useCallback, useMemo } from 'react';
|
import React, { PropsWithChildren, useCallback, useMemo } from 'react';
|
||||||
import { Platform, RefreshControl, StyleSheet, View } from 'react-native';
|
import { Platform, RefreshControl, StyleSheet, View } from 'react-native';
|
||||||
import { useGetImage } from '@/utility/JellyfinApi';
|
import { useGetImage } from '@/utility/JellyfinApi/lib';
|
||||||
import styled, { css } from 'styled-components/native';
|
import styled, { css } from 'styled-components/native';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { useAppDispatch, useTypedSelector } from '@/store';
|
import { useAppDispatch, useTypedSelector } from '@/store';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Album, AlbumTrack } from '@/store/music/types';
|
|||||||
import { FlatList } from 'react-native-gesture-handler';
|
import { FlatList } from 'react-native-gesture-handler';
|
||||||
import TouchableHandler from '@/components/TouchableHandler';
|
import TouchableHandler from '@/components/TouchableHandler';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { useGetImage } from '@/utility/JellyfinApi';
|
import { useGetImage } from '@/utility/JellyfinApi/lib';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import { t } from '@/localisation';
|
import { t } from '@/localisation';
|
||||||
import useDefaultStyles, { ColoredBlurView } from '@/components/Colors';
|
import useDefaultStyles, { ColoredBlurView } from '@/components/Colors';
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import CoverImage from '@/components/CoverImage';
|
|||||||
import { queueTrackForDownload, removeDownloadedTrack } from '@/store/downloads/actions';
|
import { queueTrackForDownload, removeDownloadedTrack } from '@/store/downloads/actions';
|
||||||
import usePlayTracks from '@/utility/usePlayTracks';
|
import usePlayTracks from '@/utility/usePlayTracks';
|
||||||
import { selectIsDownloaded } from '@/store/downloads/selectors';
|
import { selectIsDownloaded } from '@/store/downloads/selectors';
|
||||||
import { useGetImage } from '@/utility/JellyfinApi';
|
import { useGetImage } from '@/utility/JellyfinApi/lib';
|
||||||
|
|
||||||
type Route = RouteProp<StackParams, 'TrackPopupMenu'>;
|
type Route = RouteProp<StackParams, 'TrackPopupMenu'>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createAction, createAsyncThunk, createEntityAdapter } from '@reduxjs/toolkit';
|
import { createAction, createAsyncThunk, createEntityAdapter } from '@reduxjs/toolkit';
|
||||||
import { AppState } from '@/store';
|
import { AppState } from '@/store';
|
||||||
import { generateTrackUrl } from '@/utility/JellyfinApi';
|
|
||||||
import { downloadFile, unlink, DocumentDirectoryPath, exists } 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';
|
import MimeTypes from '@/utility/MimeTypes';
|
||||||
|
import { generateTrackUrl } from '@/utility/JellyfinApi/track';
|
||||||
|
|
||||||
export const downloadAdapter = createEntityAdapter<DownloadEntity>();
|
export const downloadAdapter = createEntityAdapter<DownloadEntity>();
|
||||||
|
|
||||||
@@ -15,12 +15,9 @@ export const failDownload = createAction<{ id: string }>('download/fail');
|
|||||||
|
|
||||||
export const downloadTrack = createAsyncThunk(
|
export const downloadTrack = createAsyncThunk(
|
||||||
'/downloads/track',
|
'/downloads/track',
|
||||||
async (id: string, { dispatch, getState }) => {
|
async (id: string, { dispatch }) => {
|
||||||
// Get the credentials from the store
|
|
||||||
const { settings: { jellyfin: credentials } } = (getState() as AppState);
|
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
// Get the content-type from the URL by doing a HEAD-only request
|
// Get the content-type from the URL by doing a HEAD-only request
|
||||||
const contentType = (await fetch(url, { method: 'HEAD' })).headers.get('Content-Type');
|
const contentType = (await fetch(url, { method: 'HEAD' })).headers.get('Content-Type');
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ const store = configureStore({
|
|||||||
export type AppState = ReturnType<typeof reducers> & { _persist: PersistState };
|
export type AppState = ReturnType<typeof reducers> & { _persist: PersistState };
|
||||||
export type AppDispatch = typeof store.dispatch;
|
export type AppDispatch = typeof store.dispatch;
|
||||||
export type AsyncThunkAPI = { state: AppState, dispatch: AppDispatch };
|
export type AsyncThunkAPI = { state: AppState, dispatch: AppDispatch };
|
||||||
|
export type Store = typeof store;
|
||||||
export const useTypedSelector: TypedUseSelectorHook<AppState> = useSelector;
|
export const useTypedSelector: TypedUseSelectorHook<AppState> = useSelector;
|
||||||
export const useAppDispatch: () => AppDispatch = useDispatch;
|
export const useAppDispatch: () => AppDispatch = useDispatch;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { createAsyncThunk, createEntityAdapter } from '@reduxjs/toolkit';
|
import { createAsyncThunk, createEntityAdapter } from '@reduxjs/toolkit';
|
||||||
import { Album, AlbumTrack, Playlist } from './types';
|
import { Album, AlbumTrack, Playlist } from './types';
|
||||||
import { AsyncThunkAPI } from '..';
|
import { AsyncThunkAPI } from '..';
|
||||||
import { retrieveAllAlbums, retrieveAlbumTracks, retrieveRecentAlbums, searchItem, retrieveAlbum, retrieveAllPlaylists, retrievePlaylistTracks } from '@/utility/JellyfinApi';
|
import { retrieveAllAlbums, retrieveRecentAlbums, retrieveAlbumTracks, retrieveAlbum } from '@/utility/JellyfinApi/album';
|
||||||
|
import { retrieveAllPlaylists, retrievePlaylistTracks } from '@/utility/JellyfinApi/playlist';
|
||||||
|
import { searchItem } from '@/utility/JellyfinApi/search';
|
||||||
|
|
||||||
export const albumAdapter = createEntityAdapter<Album, string>({
|
export const albumAdapter = createEntityAdapter<Album, string>({
|
||||||
selectId: album => album.Id,
|
selectId: album => album.Id,
|
||||||
@@ -13,10 +15,7 @@ export const albumAdapter = createEntityAdapter<Album, string>({
|
|||||||
*/
|
*/
|
||||||
export const fetchAllAlbums = createAsyncThunk<Album[], undefined, AsyncThunkAPI>(
|
export const fetchAllAlbums = createAsyncThunk<Album[], undefined, AsyncThunkAPI>(
|
||||||
'/albums/all',
|
'/albums/all',
|
||||||
async (empty, thunkAPI) => {
|
retrieveAllAlbums,
|
||||||
const credentials = thunkAPI.getState().settings.jellyfin;
|
|
||||||
return retrieveAllAlbums(credentials) as Promise<Album[]>;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,10 +23,7 @@ export const fetchAllAlbums = createAsyncThunk<Album[], undefined, AsyncThunkAPI
|
|||||||
*/
|
*/
|
||||||
export const fetchRecentAlbums = createAsyncThunk<Album[], number | undefined, AsyncThunkAPI>(
|
export const fetchRecentAlbums = createAsyncThunk<Album[], number | undefined, AsyncThunkAPI>(
|
||||||
'/albums/recent',
|
'/albums/recent',
|
||||||
async (numberOfAlbums, thunkAPI) => {
|
retrieveRecentAlbums,
|
||||||
const credentials = thunkAPI.getState().settings.jellyfin;
|
|
||||||
return retrieveRecentAlbums(credentials, numberOfAlbums) as Promise<Album[]>;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const trackAdapter = createEntityAdapter<AlbumTrack, string>({
|
export const trackAdapter = createEntityAdapter<AlbumTrack, string>({
|
||||||
@@ -40,18 +36,12 @@ export const trackAdapter = createEntityAdapter<AlbumTrack, string>({
|
|||||||
*/
|
*/
|
||||||
export const fetchTracksByAlbum = createAsyncThunk<AlbumTrack[], string, AsyncThunkAPI>(
|
export const fetchTracksByAlbum = createAsyncThunk<AlbumTrack[], string, AsyncThunkAPI>(
|
||||||
'/tracks/byAlbum',
|
'/tracks/byAlbum',
|
||||||
async (ItemId, thunkAPI) => {
|
retrieveAlbumTracks,
|
||||||
const credentials = thunkAPI.getState().settings.jellyfin;
|
|
||||||
return retrieveAlbumTracks(ItemId, credentials) as Promise<AlbumTrack[]>;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const fetchAlbum = createAsyncThunk<Album, string, AsyncThunkAPI>(
|
export const fetchAlbum = createAsyncThunk<Album, string, AsyncThunkAPI>(
|
||||||
'/albums/single',
|
'/albums/single',
|
||||||
async (ItemId, thunkAPI) => {
|
retrieveAlbum,
|
||||||
const credentials = thunkAPI.getState().settings.jellyfin;
|
|
||||||
return retrieveAlbum(credentials, ItemId) as Promise<Album>;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
type SearchAndFetchResults = {
|
type SearchAndFetchResults = {
|
||||||
@@ -67,16 +57,17 @@ AsyncThunkAPI
|
|||||||
'/search',
|
'/search',
|
||||||
async ({ term, limit = 24 }, thunkAPI) => {
|
async ({ term, limit = 24 }, thunkAPI) => {
|
||||||
const state = thunkAPI.getState();
|
const state = thunkAPI.getState();
|
||||||
const results = await searchItem(state.settings.jellyfin, term, limit);
|
const results = await searchItem(term, limit);
|
||||||
|
|
||||||
const albums = await Promise.all(results.filter((item) => (
|
const albums = await Promise.all(results.filter((item) => (
|
||||||
!state.music.albums.ids.includes(item.Type === 'MusicAlbum' ? item.Id : item.AlbumId)
|
!state.music.albums.ids.includes(item.Type === 'MusicAlbum' ? item.Id : item.AlbumId)
|
||||||
|
&& (item.Type === 'Audio' ? item.AlbumId : true)
|
||||||
)).map(async (item) => {
|
)).map(async (item) => {
|
||||||
if (item.Type === 'MusicAlbum') {
|
if (item.Type === 'MusicAlbum') {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
return retrieveAlbum(state.settings.jellyfin, item.AlbumId);
|
return retrieveAlbum(item.AlbumId);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -96,10 +87,7 @@ export const playlistAdapter = createEntityAdapter<Playlist, string>({
|
|||||||
*/
|
*/
|
||||||
export const fetchAllPlaylists = createAsyncThunk<Playlist[], undefined, AsyncThunkAPI>(
|
export const fetchAllPlaylists = createAsyncThunk<Playlist[], undefined, AsyncThunkAPI>(
|
||||||
'/playlists/all',
|
'/playlists/all',
|
||||||
async (empty, thunkAPI) => {
|
retrieveAllPlaylists,
|
||||||
const credentials = thunkAPI.getState().settings.jellyfin;
|
|
||||||
return retrieveAllPlaylists(credentials) as Promise<Playlist[]>;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -107,8 +95,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',
|
||||||
async (ItemId, thunkAPI) => {
|
retrievePlaylistTracks,
|
||||||
const credentials = thunkAPI.getState().settings.jellyfin;
|
|
||||||
return retrievePlaylistTracks(ItemId, credentials) as Promise<AlbumTrack[]>;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
@@ -15,8 +15,8 @@ export function useRecentAlbums(amount: number) {
|
|||||||
const sorted = [...albumIds].sort((a, b) => {
|
const sorted = [...albumIds].sort((a, b) => {
|
||||||
const albumA = albums[a];
|
const albumA = albums[a];
|
||||||
const albumB = albums[b];
|
const albumB = albums[b];
|
||||||
const dateA = albumA ? parseISO(albumA.DateCreated).getTime() : 0;
|
const dateA = albumA && albumA.DateCreated ? parseISO(albumA.DateCreated).getTime() : 0;
|
||||||
const dateB = albumB ? parseISO(albumB.DateCreated).getTime() : 0;
|
const dateB = albumB && albumB.DateCreated ? parseISO(albumB.DateCreated).getTime() : 0;
|
||||||
return dateB - dateA;
|
return dateB - dateA;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,322 +0,0 @@
|
|||||||
import TrackPlayer, { RepeatMode, State, Track } from 'react-native-track-player';
|
|
||||||
import { AppState, useTypedSelector } from '@/store';
|
|
||||||
import { Album, AlbumTrack, SimilarAlbum } from '@/store/music/types';
|
|
||||||
import { Platform } from 'react-native';
|
|
||||||
|
|
||||||
type Credentials = AppState['settings']['jellyfin'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a convenience function that converts a set of Jellyfin credentials
|
|
||||||
* from the Redux store to a HTTP Header that authenticates the user against the
|
|
||||||
* Jellyfin server.
|
|
||||||
*/
|
|
||||||
function generateConfig(credentials: Credentials): RequestInit {
|
|
||||||
return {
|
|
||||||
headers: {
|
|
||||||
'X-Emby-Authorization': `MediaBrowser Client="", Device="", DeviceId="", Version="", Token="${credentials?.access_token}"`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const trackOptionsOsOverrides: Record<typeof Platform.OS, Record<string, string>> = {
|
|
||||||
ios: {
|
|
||||||
Container: 'mp3,aac,m4a|aac,m4b|aac,flac,alac,m4a|alac,m4b|alac,wav,m4a,aiff,aif',
|
|
||||||
},
|
|
||||||
android: {
|
|
||||||
Container: 'mp3,aac,flac,wav,ogg,ogg|vorbis,ogg|opus,mka|mp3,mka|opus,mka|mp3',
|
|
||||||
},
|
|
||||||
macos: {},
|
|
||||||
web: {},
|
|
||||||
windows: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const baseTrackOptions: Record<string, string> = {
|
|
||||||
TranscodingProtocol: 'http',
|
|
||||||
TranscodingContainer: 'aac',
|
|
||||||
AudioCodec: 'aac',
|
|
||||||
Container: 'mp3,aac',
|
|
||||||
...trackOptionsOsOverrides[Platform.OS],
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a track object from a Jellyfin ItemId so that
|
|
||||||
* react-native-track-player can easily consume it.
|
|
||||||
*/
|
|
||||||
export function generateTrack(track: AlbumTrack, credentials: Credentials): Track {
|
|
||||||
// Also construct the URL for the stream
|
|
||||||
const url = generateTrackUrl(track.Id, credentials);
|
|
||||||
|
|
||||||
return {
|
|
||||||
url,
|
|
||||||
backendId: track.Id,
|
|
||||||
title: track.Name,
|
|
||||||
artist: track.Artists.join(', '),
|
|
||||||
album: track.Album,
|
|
||||||
duration: track.RunTimeTicks,
|
|
||||||
artwork: track.AlbumId
|
|
||||||
? getImage(track.AlbumId, credentials)
|
|
||||||
: getImage(track.Id, credentials),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the track streaming url from the trackId
|
|
||||||
*/
|
|
||||||
export function generateTrackUrl(trackId: string, credentials: Credentials) {
|
|
||||||
const trackOptions = {
|
|
||||||
...baseTrackOptions,
|
|
||||||
UserId: credentials?.user_id || '',
|
|
||||||
api_key: credentials?.access_token || '',
|
|
||||||
DeviceId: credentials?.device_id || '',
|
|
||||||
};
|
|
||||||
|
|
||||||
const trackParams = new URLSearchParams(trackOptions).toString();
|
|
||||||
const url = encodeURI(`${credentials?.uri}/Audio/${trackId}/universal?`) + trackParams;
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
const albumOptions = {
|
|
||||||
SortBy: 'AlbumArtist,SortName',
|
|
||||||
SortOrder: 'Ascending',
|
|
||||||
IncludeItemTypes: 'MusicAlbum',
|
|
||||||
Recursive: 'true',
|
|
||||||
Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo,DateCreated',
|
|
||||||
ImageTypeLimit: '1',
|
|
||||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
|
||||||
};
|
|
||||||
|
|
||||||
const albumParams = new URLSearchParams(albumOptions).toString();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve all albums that are available on the Jellyfin server
|
|
||||||
*/
|
|
||||||
export async function retrieveAllAlbums(credentials: Credentials) {
|
|
||||||
const config = generateConfig(credentials);
|
|
||||||
const albums = await fetch(`${credentials?.uri}/Users/${credentials?.user_id}/Items?${albumParams}`, config)
|
|
||||||
.then(response => response.json());
|
|
||||||
|
|
||||||
return albums.Items;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a single album
|
|
||||||
*/
|
|
||||||
export async function retrieveAlbum(credentials: Credentials, id: string): Promise<Album> {
|
|
||||||
const config = generateConfig(credentials);
|
|
||||||
|
|
||||||
const Similar = await fetch(`${credentials?.uri}/Items/${id}/Similar?userId=${credentials?.user_id}&limit=12`, config)
|
|
||||||
.then(response => response.json() as Promise<{ Items: SimilarAlbum[] }>)
|
|
||||||
.then((albums) => albums.Items.map((a) => a.Id));
|
|
||||||
|
|
||||||
return fetch(`${credentials?.uri}/Users/${credentials?.user_id}/Items/${id}`, config)
|
|
||||||
.then(response => response.json() as Promise<Album>)
|
|
||||||
.then(album => ({ ...album, Similar }));
|
|
||||||
}
|
|
||||||
|
|
||||||
const latestAlbumsOptions = {
|
|
||||||
IncludeItemTypes: 'MusicAlbum',
|
|
||||||
Fields: 'DateCreated',
|
|
||||||
SortOrder: 'Ascending',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the most recently added albums on the Jellyfin server
|
|
||||||
*/
|
|
||||||
export async function retrieveRecentAlbums(credentials: Credentials, numberOfAlbums = 24) {
|
|
||||||
const config = generateConfig(credentials);
|
|
||||||
|
|
||||||
// Generate custom config based on function input
|
|
||||||
const options = {
|
|
||||||
...latestAlbumsOptions,
|
|
||||||
Limit: numberOfAlbums.toString(),
|
|
||||||
};
|
|
||||||
const params = new URLSearchParams(options).toString();
|
|
||||||
|
|
||||||
// Retrieve albums
|
|
||||||
const albums = await fetch(`${credentials?.uri}/Users/${credentials?.user_id}/Items/Latest?${params}`, config)
|
|
||||||
.then(response => response.json());
|
|
||||||
|
|
||||||
return albums;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a single album from the Emby server
|
|
||||||
*/
|
|
||||||
export async function retrieveAlbumTracks(ItemId: string, credentials: Credentials) {
|
|
||||||
const singleAlbumOptions = {
|
|
||||||
ParentId: ItemId,
|
|
||||||
SortBy: 'SortName',
|
|
||||||
};
|
|
||||||
const singleAlbumParams = new URLSearchParams(singleAlbumOptions).toString();
|
|
||||||
|
|
||||||
const config = generateConfig(credentials);
|
|
||||||
const album = await fetch(`${credentials?.uri}/Users/${credentials?.user_id}/Items?${singleAlbumParams}`, config)
|
|
||||||
.then(response => response.json());
|
|
||||||
|
|
||||||
return album.Items;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve an image URL for a given ItemId
|
|
||||||
*/
|
|
||||||
export function getImage(ItemId: string, credentials: Credentials): string {
|
|
||||||
return encodeURI(`${credentials?.uri}/Items/${ItemId}/Images/Primary?format=jpeg`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a hook that can convert ItemIds to image URLs
|
|
||||||
*/
|
|
||||||
export function useGetImage() {
|
|
||||||
const credentials = useTypedSelector((state) => state.settings.jellyfin);
|
|
||||||
return (ItemId: string) => getImage(ItemId, credentials);
|
|
||||||
}
|
|
||||||
|
|
||||||
const trackParams = {
|
|
||||||
SortBy: 'AlbumArtist,SortName',
|
|
||||||
SortOrder: 'Ascending',
|
|
||||||
IncludeItemTypes: 'Audio',
|
|
||||||
Recursive: 'true',
|
|
||||||
Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo,DateCreated',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve all possible tracks that can be found in Jellyfin
|
|
||||||
*/
|
|
||||||
export async function retrieveAllTracks(credentials: Credentials) {
|
|
||||||
const config = generateConfig(credentials);
|
|
||||||
const tracks = await fetch(`${credentials?.uri}/Users/${credentials?.user_id}/Items?${trackParams}`, config)
|
|
||||||
.then(response => response.json());
|
|
||||||
|
|
||||||
return tracks.Items;
|
|
||||||
}
|
|
||||||
|
|
||||||
const searchParams = {
|
|
||||||
IncludeItemTypes: 'Audio,MusicAlbum',
|
|
||||||
SortBy: 'Album,SortName',
|
|
||||||
SortOrder: 'Ascending',
|
|
||||||
Recursive: 'true',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remotely search the Jellyfin library for a particular search term
|
|
||||||
*/
|
|
||||||
export async function searchItem(
|
|
||||||
credentials: Credentials,
|
|
||||||
term: string, limit = 24
|
|
||||||
): Promise<(Album | AlbumTrack)[]> {
|
|
||||||
const config = generateConfig(credentials);
|
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
...searchParams,
|
|
||||||
SearchTerm: term,
|
|
||||||
Limit: limit.toString(),
|
|
||||||
}).toString();
|
|
||||||
|
|
||||||
const results = await fetch(`${credentials?.uri}/Users/${credentials?.user_id}/Items?${params}`, config)
|
|
||||||
.then(response => response.json());
|
|
||||||
|
|
||||||
return results.Items;
|
|
||||||
}
|
|
||||||
|
|
||||||
const playlistOptions = {
|
|
||||||
SortBy: 'SortName',
|
|
||||||
SortOrder: 'Ascending',
|
|
||||||
IncludeItemTypes: 'Playlist',
|
|
||||||
Recursive: 'true',
|
|
||||||
Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo,DateCreated',
|
|
||||||
ImageTypeLimit: '1',
|
|
||||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
|
||||||
MediaTypes: 'Audio',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve all albums that are available on the Jellyfin server
|
|
||||||
*/
|
|
||||||
export async function retrieveAllPlaylists(credentials: Credentials) {
|
|
||||||
const config = generateConfig(credentials);
|
|
||||||
const playlistParams = new URLSearchParams(playlistOptions).toString();
|
|
||||||
|
|
||||||
const albums = await fetch(`${credentials?.uri}/Users/${credentials?.user_id}/Items?${playlistParams}`, config)
|
|
||||||
.then(response => response.json());
|
|
||||||
|
|
||||||
return albums.Items;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve all albums that are available on the Jellyfin server
|
|
||||||
*/
|
|
||||||
export async function retrievePlaylistTracks(ItemId: string, credentials: Credentials) {
|
|
||||||
const singlePlaylistOptions = {
|
|
||||||
SortBy: 'SortName',
|
|
||||||
UserId: credentials?.user_id || '',
|
|
||||||
};
|
|
||||||
const singlePlaylistParams = new URLSearchParams(singlePlaylistOptions).toString();
|
|
||||||
|
|
||||||
const config = generateConfig(credentials);
|
|
||||||
const playlists = await fetch(`${credentials?.uri}/Playlists/${ItemId}/Items?${singlePlaylistParams}`, config)
|
|
||||||
.then(response => response.json());
|
|
||||||
|
|
||||||
return playlists.Items;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This maps the react-native-track-player RepeatMode to a RepeatMode that is
|
|
||||||
* expected by Jellyfin when reporting playback events.
|
|
||||||
*/
|
|
||||||
const RepeatModeMap: Record<RepeatMode, string> = {
|
|
||||||
[RepeatMode.Off]: 'RepeatNone',
|
|
||||||
[RepeatMode.Track]: 'RepeatOne',
|
|
||||||
[RepeatMode.Queue]: 'RepeatAll',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This will generate the payload that is required for playback events and send
|
|
||||||
* it to the supplied path.
|
|
||||||
*/
|
|
||||||
export async function sendPlaybackEvent(path: string, credentials: Credentials, trackIndex?: number) {
|
|
||||||
// Extract all data from react-native-track-player
|
|
||||||
const [
|
|
||||||
currentTrack, position, repeatMode, volume, queue, state,
|
|
||||||
] = await Promise.all([
|
|
||||||
TrackPlayer.getCurrentTrack(),
|
|
||||||
TrackPlayer.getPosition(),
|
|
||||||
TrackPlayer.getRepeatMode(),
|
|
||||||
TrackPlayer.getVolume(),
|
|
||||||
TrackPlayer.getQueue(),
|
|
||||||
TrackPlayer.getState(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Switch between overriden track index and current track
|
|
||||||
const track = trackIndex !== undefined ? trackIndex : currentTrack;
|
|
||||||
|
|
||||||
// Generate a payload from the gathered data
|
|
||||||
const payload = {
|
|
||||||
VolumeLevel: volume * 100,
|
|
||||||
IsMuted: false,
|
|
||||||
IsPaused: state === State.Paused,
|
|
||||||
RepeatMode: RepeatModeMap[repeatMode],
|
|
||||||
ShuffleMode: 'Sorted',
|
|
||||||
PositionTicks: Math.round(position * 10_000_000),
|
|
||||||
PlaybackRate: 1,
|
|
||||||
PlayMethod: 'transcode',
|
|
||||||
MediaSourceId: track !== null ? queue[track].backendId : null,
|
|
||||||
ItemId: track !== null ? queue[track].backendId : null,
|
|
||||||
CanSeek: true,
|
|
||||||
PlaybackStartTimeTicks: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate a config from the credentials and dispatch the request
|
|
||||||
const config = generateConfig(credentials);
|
|
||||||
await fetch(`${credentials?.uri}${path}`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
...config.headers,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
// Swallow and errors from the request
|
|
||||||
}).catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
68
src/utility/JellyfinApi/album.ts
Normal file
68
src/utility/JellyfinApi/album.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { Album, AlbumTrack, SimilarAlbum } from '@/store/music/types';
|
||||||
|
import { fetchApi } from './lib';
|
||||||
|
|
||||||
|
const albumOptions = {
|
||||||
|
SortBy: 'AlbumArtist,SortName',
|
||||||
|
SortOrder: 'Ascending',
|
||||||
|
IncludeItemTypes: 'MusicAlbum',
|
||||||
|
Recursive: 'true',
|
||||||
|
Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo,DateCreated',
|
||||||
|
ImageTypeLimit: '1',
|
||||||
|
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
||||||
|
};
|
||||||
|
|
||||||
|
const albumParams = new URLSearchParams(albumOptions).toString();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all albums that are available on the Jellyfin server
|
||||||
|
*/
|
||||||
|
export async function retrieveAllAlbums() {
|
||||||
|
return fetchApi<{ Items: Album[] }>(({ user_id }) => `/Users/${user_id}/Items?${albumParams}`)
|
||||||
|
.then((data) => data.Items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a single album
|
||||||
|
*/
|
||||||
|
export async function retrieveAlbum(id: string): Promise<Album> {
|
||||||
|
const Similar = await fetchApi<{ Items: SimilarAlbum[] }>(({ user_id }) => `/Items/${id}/Similar?userId=${user_id}&limit=12`)
|
||||||
|
.then((albums) => albums.Items.map((a) => a.Id));
|
||||||
|
|
||||||
|
return fetchApi<Album>(({ user_id }) => `/Users/${user_id}/Items/${id}`)
|
||||||
|
.then(album => ({ ...album, Similar }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestAlbumsOptions = {
|
||||||
|
IncludeItemTypes: 'MusicAlbum',
|
||||||
|
Fields: 'DateCreated',
|
||||||
|
SortOrder: 'Ascending',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the most recently added albums on the Jellyfin server
|
||||||
|
*/
|
||||||
|
export async function retrieveRecentAlbums(numberOfAlbums = 24) {
|
||||||
|
// Generate custom config based on function input
|
||||||
|
const options = {
|
||||||
|
...latestAlbumsOptions,
|
||||||
|
Limit: numberOfAlbums.toString(),
|
||||||
|
};
|
||||||
|
const params = new URLSearchParams(options).toString();
|
||||||
|
|
||||||
|
// Retrieve albums
|
||||||
|
return fetchApi<Album[]>(({ user_id }) => `/Users/${user_id}/Items/Latest?${params}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a single album from the Emby server
|
||||||
|
*/
|
||||||
|
export async function retrieveAlbumTracks(ItemId: string) {
|
||||||
|
const singleAlbumOptions = {
|
||||||
|
ParentId: ItemId,
|
||||||
|
SortBy: 'SortName',
|
||||||
|
};
|
||||||
|
const singleAlbumParams = new URLSearchParams(singleAlbumOptions).toString();
|
||||||
|
|
||||||
|
return fetchApi<{ Items: AlbumTrack[] }>(({ user_id }) => `/Users/${user_id}/Items?${singleAlbumParams}`)
|
||||||
|
.then((data) => data.Items);
|
||||||
|
}
|
||||||
87
src/utility/JellyfinApi/lib.ts
Normal file
87
src/utility/JellyfinApi/lib.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import type { AppState, Store } from '@/store';
|
||||||
|
|
||||||
|
type Credentials = AppState['settings']['jellyfin'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a convenience function that converts a set of Jellyfin credentials
|
||||||
|
* from the Redux store to a HTTP Header that authenticates the user against the
|
||||||
|
* Jellyfin server.
|
||||||
|
*/
|
||||||
|
function generateConfig(credentials: Credentials): RequestInit {
|
||||||
|
return {
|
||||||
|
headers: {
|
||||||
|
'X-Emby-Authorization': `MediaBrowser Client="", Device="", DeviceId="", Version="", Token="${credentials?.access_token}"`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function asyncFetchStore() {
|
||||||
|
return require('@/store').default as Store;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience function that accepts a request for fetch, injects it with the
|
||||||
|
* proper Jellyfin credentials and attempts to catch any errors along the way.
|
||||||
|
*/
|
||||||
|
export async function fetchApi<T>(
|
||||||
|
path: string | ((credentials: NonNullable<Credentials>) => string),
|
||||||
|
config?: RequestInit
|
||||||
|
) {
|
||||||
|
// Retrieve the latest credentials from the Redux store
|
||||||
|
const credentials = asyncFetchStore().getState().settings.jellyfin;
|
||||||
|
|
||||||
|
// GUARD: Check that the credentials are present
|
||||||
|
if (!credentials) {
|
||||||
|
throw new Error('Missing Jellyfin credentials when attempting API request');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the URL from the path and the credentials
|
||||||
|
const resolvedPath = typeof path === 'function' ? path(credentials) : path;
|
||||||
|
const url = `${credentials.uri}${resolvedPath.startsWith('/') ? '' : '/'}${resolvedPath}`;
|
||||||
|
|
||||||
|
// Actually perform the request
|
||||||
|
const response = await fetch(url, {
|
||||||
|
...config,
|
||||||
|
headers: {
|
||||||
|
...config?.headers,
|
||||||
|
...generateConfig(credentials).headers,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GUARD: Check if the response is as expected
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 403 || response.status === 401) {
|
||||||
|
throw new Error('AuthenticationFailed');
|
||||||
|
} else if (response.status === 404) {
|
||||||
|
throw new Error('ResourceNotFound');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to parse the error message
|
||||||
|
try {
|
||||||
|
const data = await response.json();
|
||||||
|
throw data;
|
||||||
|
} catch {
|
||||||
|
throw response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse body as JSON
|
||||||
|
const data = await response.json() as Promise<T>;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an image URL for a given ItemId
|
||||||
|
*/
|
||||||
|
export function getImage(ItemId: string): string {
|
||||||
|
const credentials = asyncFetchStore().getState().settings.jellyfin;
|
||||||
|
return encodeURI(`${credentials?.uri}/Items/${ItemId}/Images/Primary?format=jpeg`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a hook that can convert ItemIds to image URLs
|
||||||
|
*/
|
||||||
|
export function useGetImage() {
|
||||||
|
return (ItemId: string) => getImage(ItemId);
|
||||||
|
}
|
||||||
60
src/utility/JellyfinApi/playback.ts
Normal file
60
src/utility/JellyfinApi/playback.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import TrackPlayer, { RepeatMode, State, Track } from 'react-native-track-player';
|
||||||
|
import { fetchApi } from './lib';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This maps the react-native-track-player RepeatMode to a RepeatMode that is
|
||||||
|
* expected by Jellyfin when reporting playback events.
|
||||||
|
*/
|
||||||
|
const RepeatModeMap: Record<RepeatMode, string> = {
|
||||||
|
[RepeatMode.Off]: 'RepeatNone',
|
||||||
|
[RepeatMode.Track]: 'RepeatOne',
|
||||||
|
[RepeatMode.Queue]: 'RepeatAll',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will generate the payload that is required for playback events and send
|
||||||
|
* it to the supplied path.
|
||||||
|
*/
|
||||||
|
export async function sendPlaybackEvent(
|
||||||
|
path: string,
|
||||||
|
track?: Track
|
||||||
|
) {
|
||||||
|
// Extract all data from react-native-track-player
|
||||||
|
const [
|
||||||
|
activeTrack, { position }, repeatMode, volume, { state },
|
||||||
|
] = await Promise.all([
|
||||||
|
track || TrackPlayer.getActiveTrack(),
|
||||||
|
TrackPlayer.getProgress(),
|
||||||
|
TrackPlayer.getRepeatMode(),
|
||||||
|
TrackPlayer.getVolume(),
|
||||||
|
TrackPlayer.getPlaybackState(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Generate a payload from the gathered data
|
||||||
|
const payload = {
|
||||||
|
VolumeLevel: volume * 100,
|
||||||
|
IsMuted: false,
|
||||||
|
IsPaused: state === State.Paused,
|
||||||
|
RepeatMode: RepeatModeMap[repeatMode],
|
||||||
|
ShuffleMode: 'Sorted',
|
||||||
|
PositionTicks: Math.round(position * 10_000_000),
|
||||||
|
PlaybackRate: 1,
|
||||||
|
PlayMethod: 'transcode',
|
||||||
|
MediaSourceId: activeTrack?.backendId || null,
|
||||||
|
ItemId: activeTrack?.backendId || null,
|
||||||
|
CanSeek: true,
|
||||||
|
PlaybackStartTimeTicks: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate a config from the credentials and dispatch the request
|
||||||
|
await fetchApi(path, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
// Swallow and errors from the request
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
38
src/utility/JellyfinApi/playlist.ts
Normal file
38
src/utility/JellyfinApi/playlist.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { AlbumTrack, Playlist } from '@/store/music/types';
|
||||||
|
import { asyncFetchStore, fetchApi } from './lib';
|
||||||
|
|
||||||
|
const playlistOptions = {
|
||||||
|
SortBy: 'SortName',
|
||||||
|
SortOrder: 'Ascending',
|
||||||
|
IncludeItemTypes: 'Playlist',
|
||||||
|
Recursive: 'true',
|
||||||
|
Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo,DateCreated',
|
||||||
|
ImageTypeLimit: '1',
|
||||||
|
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
||||||
|
MediaTypes: 'Audio',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all albums that are available on the Jellyfin server
|
||||||
|
*/
|
||||||
|
export async function retrieveAllPlaylists() {
|
||||||
|
const playlistParams = new URLSearchParams(playlistOptions).toString();
|
||||||
|
|
||||||
|
return fetchApi<{ Items: Playlist[] }>(({ user_id }) => `/Users/${user_id}/Items?${playlistParams}`)
|
||||||
|
.then((d) => d.Items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all albums that are available on the Jellyfin server
|
||||||
|
*/
|
||||||
|
export async function retrievePlaylistTracks(ItemId: string) {
|
||||||
|
const credentials = asyncFetchStore().getState().settings.jellyfin;
|
||||||
|
const singlePlaylistOptions = {
|
||||||
|
SortBy: 'SortName',
|
||||||
|
UserId: credentials?.user_id || '',
|
||||||
|
};
|
||||||
|
const singlePlaylistParams = new URLSearchParams(singlePlaylistOptions).toString();
|
||||||
|
|
||||||
|
return fetchApi<{ Items: AlbumTrack[] }>(`/Playlists/${ItemId}/Items?${singlePlaylistParams}`)
|
||||||
|
.then((d) => d.Items);
|
||||||
|
}
|
||||||
30
src/utility/JellyfinApi/search.ts
Normal file
30
src/utility/JellyfinApi/search.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { Album, AlbumTrack } from '@/store/music/types';
|
||||||
|
import { fetchApi } from './lib';
|
||||||
|
|
||||||
|
const searchParams = {
|
||||||
|
IncludeItemTypes: 'Audio,MusicAlbum',
|
||||||
|
SortBy: 'Album,SortName',
|
||||||
|
SortOrder: 'Ascending',
|
||||||
|
Recursive: 'true',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remotely search the Jellyfin library for a particular search term
|
||||||
|
*/
|
||||||
|
export async function searchItem(
|
||||||
|
term: string, limit = 24
|
||||||
|
) {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
...searchParams,
|
||||||
|
SearchTerm: term,
|
||||||
|
Limit: limit.toString(),
|
||||||
|
}).toString();
|
||||||
|
|
||||||
|
const results = await fetchApi<{ Items: (Album | AlbumTrack)[]}>(({ user_id }) => `/Users/${user_id}/Items?${params}`);
|
||||||
|
|
||||||
|
return results.Items
|
||||||
|
.filter((item) => (
|
||||||
|
// GUARD: Ensure that we're either dealing with an album or a track from an album.
|
||||||
|
item.Type === 'MusicAlbum' || (item.Type === 'Audio' && item.AlbumId)
|
||||||
|
));
|
||||||
|
}
|
||||||
81
src/utility/JellyfinApi/track.ts
Normal file
81
src/utility/JellyfinApi/track.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { AlbumTrack } from '@/store/music/types';
|
||||||
|
import { Platform } from 'react-native';
|
||||||
|
import { Track } from 'react-native-track-player';
|
||||||
|
import { fetchApi, getImage } from './lib';
|
||||||
|
import store from '@/store';
|
||||||
|
|
||||||
|
const trackOptionsOsOverrides: Record<typeof Platform.OS, Record<string, string>> = {
|
||||||
|
ios: {
|
||||||
|
Container: 'mp3,aac,m4a|aac,m4b|aac,flac,alac,m4a|alac,m4b|alac,wav,m4a,aiff,aif',
|
||||||
|
},
|
||||||
|
android: {
|
||||||
|
Container: 'mp3,aac,flac,wav,ogg,ogg|vorbis,ogg|opus,mka|mp3,mka|opus,mka|mp3',
|
||||||
|
},
|
||||||
|
macos: {},
|
||||||
|
web: {},
|
||||||
|
windows: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseTrackOptions: Record<string, string> = {
|
||||||
|
TranscodingProtocol: 'http',
|
||||||
|
TranscodingContainer: 'aac',
|
||||||
|
AudioCodec: 'aac',
|
||||||
|
Container: 'mp3,aac',
|
||||||
|
...trackOptionsOsOverrides[Platform.OS],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the track streaming url from the trackId
|
||||||
|
*/
|
||||||
|
export function generateTrackUrl(trackId: string) {
|
||||||
|
const credentials = store.getState().settings.jellyfin;
|
||||||
|
const trackOptions = {
|
||||||
|
...baseTrackOptions,
|
||||||
|
UserId: credentials?.user_id || '',
|
||||||
|
api_key: credentials?.access_token || '',
|
||||||
|
DeviceId: credentials?.device_id || '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const trackParams = new URLSearchParams(trackOptions).toString();
|
||||||
|
const url = encodeURI(`${credentials?.uri}/Audio/${trackId}/universal?`) + trackParams;
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a track object from a Jellyfin ItemId so that
|
||||||
|
* react-native-track-player can easily consume it.
|
||||||
|
*/
|
||||||
|
export function generateTrack(track: AlbumTrack): Track {
|
||||||
|
// Also construct the URL for the stream
|
||||||
|
const url = generateTrackUrl(track.Id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
backendId: track.Id,
|
||||||
|
title: track.Name,
|
||||||
|
artist: track.Artists.join(', '),
|
||||||
|
album: track.Album,
|
||||||
|
duration: track.RunTimeTicks,
|
||||||
|
artwork: track.AlbumId
|
||||||
|
? getImage(track.AlbumId)
|
||||||
|
: getImage(track.Id),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const trackParams = {
|
||||||
|
SortBy: 'AlbumArtist,SortName',
|
||||||
|
SortOrder: 'Ascending',
|
||||||
|
IncludeItemTypes: 'Audio',
|
||||||
|
Recursive: 'true',
|
||||||
|
Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo,DateCreated',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all possible tracks that can be found in Jellyfin
|
||||||
|
*/
|
||||||
|
export async function retrieveAllTracks() {
|
||||||
|
return fetchApi<{ Items: AlbumTrack[] }>(({ user_id }) => `/Users/${user_id}/Items?${trackParams}`)
|
||||||
|
.then((d) => d.Items);
|
||||||
|
}
|
||||||
@@ -9,8 +9,8 @@
|
|||||||
|
|
||||||
import TrackPlayer, { Event, State } from 'react-native-track-player';
|
import TrackPlayer, { Event, State } from 'react-native-track-player';
|
||||||
import store from '@/store';
|
import store from '@/store';
|
||||||
import { sendPlaybackEvent } from './JellyfinApi';
|
|
||||||
import { setTimerDate } from '@/store/sleep-timer';
|
import { setTimerDate } from '@/store/sleep-timer';
|
||||||
|
import { sendPlaybackEvent } from './JellyfinApi/playback';
|
||||||
|
|
||||||
export default async function() {
|
export default async function() {
|
||||||
TrackPlayer.addEventListener(Event.RemotePlay, () => {
|
TrackPlayer.addEventListener(Event.RemotePlay, () => {
|
||||||
@@ -37,18 +37,18 @@ export default async function() {
|
|||||||
TrackPlayer.seekTo(event.position);
|
TrackPlayer.seekTo(event.position);
|
||||||
});
|
});
|
||||||
|
|
||||||
TrackPlayer.addEventListener(Event.PlaybackTrackChanged, async (e) => {
|
TrackPlayer.addEventListener(Event.PlaybackActiveTrackChanged, async (e) => {
|
||||||
// Retrieve the current settings from the Redux store
|
// Retrieve the current settings from the Redux store
|
||||||
const settings = store.getState().settings;
|
const settings = store.getState().settings;
|
||||||
|
|
||||||
// GUARD: Only report playback when the settings is enabled
|
// GUARD: Only report playback when the settings is enabled
|
||||||
if (settings.enablePlaybackReporting && 'track' in e) {
|
if (settings.enablePlaybackReporting && 'track' in e) {
|
||||||
// GUARD: End the previous track if it's about to end
|
// GUARD: End the previous track if it's about to end
|
||||||
if ('nextTrack' in e && typeof e.track === 'number') {
|
if (e.lastTrack) {
|
||||||
sendPlaybackEvent('/Sessions/Playing/Stopped', settings.jellyfin, e.track);
|
await sendPlaybackEvent('/Sessions/Playing/Stopped', e.lastTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendPlaybackEvent('/Sessions/Playing', settings.jellyfin);
|
await sendPlaybackEvent('/Sessions/Playing', e.track);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ export default async function() {
|
|||||||
|
|
||||||
// GUARD: Only report playback when the settings is enabled
|
// GUARD: Only report playback when the settings is enabled
|
||||||
if (settings.enablePlaybackReporting) {
|
if (settings.enablePlaybackReporting) {
|
||||||
sendPlaybackEvent('/Sessions/Playing/Progress', settings.jellyfin);
|
sendPlaybackEvent('/Sessions/Playing/Progress');
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if timerDate is undefined, otherwise start timer
|
// check if timerDate is undefined, otherwise start timer
|
||||||
@@ -69,14 +69,16 @@ export default async function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
TrackPlayer.addEventListener(Event.PlaybackState, (event) => {
|
TrackPlayer.addEventListener(Event.PlaybackState, (event) => {
|
||||||
// GUARD: Only respond to stopped events
|
// Retrieve the current settings from the Redux store
|
||||||
if (event.state === State.Stopped) {
|
const settings = store.getState().settings;
|
||||||
// Retrieve the current settings from the Redux store
|
|
||||||
const settings = store.getState().settings;
|
|
||||||
|
|
||||||
// GUARD: Only report playback when the settings is enabled
|
// GUARD: Only report playback when the settings is enabled
|
||||||
if (settings.enablePlaybackReporting) {
|
if (settings.enablePlaybackReporting) {
|
||||||
sendPlaybackEvent('/Sessions/Playing/Stopped', settings.jellyfin);
|
// GUARD: Only respond to stopped events
|
||||||
|
if (event.state === State.Stopped) {
|
||||||
|
sendPlaybackEvent('/Sessions/Playing/Stopped');
|
||||||
|
} else if (event.state === State.Paused) {
|
||||||
|
sendPlaybackEvent('/Sessions/Playing/Progress');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useTypedSelector } from '@/store';
|
import { useTypedSelector } from '@/store';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import TrackPlayer, { Track } from 'react-native-track-player';
|
import TrackPlayer, { Track } from 'react-native-track-player';
|
||||||
import { generateTrack } from './JellyfinApi';
|
|
||||||
import { shuffle as shuffleArray } from 'lodash';
|
import { shuffle as shuffleArray } from 'lodash';
|
||||||
|
import { generateTrack } from './JellyfinApi/track';
|
||||||
|
|
||||||
interface PlayOptions {
|
interface PlayOptions {
|
||||||
play: boolean;
|
play: boolean;
|
||||||
@@ -21,7 +21,6 @@ const defaults: PlayOptions = {
|
|||||||
* supplied id.
|
* supplied id.
|
||||||
*/
|
*/
|
||||||
export default function usePlayTracks() {
|
export default function usePlayTracks() {
|
||||||
const credentials = useTypedSelector(state => state.settings.jellyfin);
|
|
||||||
const tracks = useTypedSelector(state => state.music.tracks.entities);
|
const tracks = useTypedSelector(state => state.music.tracks.entities);
|
||||||
const downloads = useTypedSelector(state => state.downloads.entities);
|
const downloads = useTypedSelector(state => state.downloads.entities);
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@ export default function usePlayTracks() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the generated track from Jellyfin
|
// Retrieve the generated track from Jellyfin
|
||||||
const generatedTrack = generateTrack(track, credentials);
|
const generatedTrack = generateTrack(track);
|
||||||
|
|
||||||
// Check if a downloaded version exists, and if so rewrite the URL
|
// Check if a downloaded version exists, and if so rewrite the URL
|
||||||
const download = downloads[trackId];
|
const download = downloads[trackId];
|
||||||
@@ -114,5 +113,5 @@ export default function usePlayTracks() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [credentials, downloads, tracks]);
|
}, [downloads, tracks]);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user