Compare commits
12 Commits
v0.0.1-alp
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9138946ba9 | ||
|
|
ea11a7d317 | ||
|
|
91344300c8 | ||
|
|
52146a6e12 | ||
|
|
0712e6b8ca | ||
|
|
2155320f6b | ||
|
|
4ff1e173ba | ||
|
|
c1f6a2984a | ||
|
|
8ba3080734 | ||
|
|
fe22661036 | ||
|
|
3762d9166b | ||
|
|
48dd8c23aa |
5
.github/workflows/fastlane.yml
vendored
5
.github/workflows/fastlane.yml
vendored
@@ -27,6 +27,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set outputs
|
||||
id: vars
|
||||
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
||||
- name: Set up Ruby 2.6
|
||||
uses: actions/setup-ruby@v1
|
||||
with:
|
||||
@@ -42,5 +45,5 @@ jobs:
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: jellyfin-audio-player-android.apk
|
||||
name: jellyfin-audio-player-android-${{ steps.vars.outputs.sha_short }}.apk
|
||||
path: android/app/build/outputs/**/*.apk
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -63,4 +63,6 @@ buck-out/
|
||||
/ios/Pods/
|
||||
|
||||
build/
|
||||
fastlane/report.xml
|
||||
fastlane/report.xml
|
||||
fastlane/Appfile
|
||||
certificates/
|
||||
20
Gemfile.lock
20
Gemfile.lock
@@ -6,7 +6,7 @@ GEM
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.1.0)
|
||||
aws-partitions (1.345.0)
|
||||
aws-partitions (1.355.0)
|
||||
aws-sdk-core (3.104.3)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
@@ -15,11 +15,11 @@ GEM
|
||||
aws-sdk-kms (1.36.0)
|
||||
aws-sdk-core (~> 3, >= 3.99.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.75.0)
|
||||
aws-sdk-core (~> 3, >= 3.104.1)
|
||||
aws-sdk-s3 (1.78.0)
|
||||
aws-sdk-core (~> 3, >= 3.104.3)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.2.1)
|
||||
aws-sigv4 (1.2.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.3)
|
||||
claide (1.0.3)
|
||||
@@ -35,7 +35,7 @@ GEM
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
emoji_regex (3.0.0)
|
||||
excon (0.75.0)
|
||||
excon (0.76.0)
|
||||
faraday (1.0.1)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-cookie_jar (0.0.6)
|
||||
@@ -44,7 +44,7 @@ GEM
|
||||
faraday_middleware (1.0.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.0)
|
||||
fastlane (2.153.1)
|
||||
fastlane (2.156.1)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.3, < 3.0.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
@@ -95,14 +95,14 @@ GEM
|
||||
google-cloud-env (1.3.3)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-errors (1.0.1)
|
||||
google-cloud-storage (1.26.2)
|
||||
google-cloud-storage (1.27.0)
|
||||
addressable (~> 2.5)
|
||||
digest-crc (~> 0.4)
|
||||
google-api-client (~> 0.33)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (~> 0.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (0.13.0)
|
||||
googleauth (0.13.1)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
@@ -123,7 +123,7 @@ GEM
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.3.0)
|
||||
naturally (2.2.0)
|
||||
os (1.1.0)
|
||||
os (1.1.1)
|
||||
plist (3.5.0)
|
||||
public_suffix (4.0.5)
|
||||
rake (13.0.1)
|
||||
@@ -157,7 +157,7 @@ GEM
|
||||
unf_ext (0.0.7.7)
|
||||
unicode-display_width (1.7.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.17.1)
|
||||
xcodeproj (1.18.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
|
||||
60
README.md
60
README.md
@@ -1,25 +1,63 @@
|
||||
# Jellyfin Audio Player
|
||||
This is a React Native-based audio streaming app for Jellyfin.
|
||||

|
||||

|
||||
|
||||
This is a [React Native](https://reactnative.dev/)-based audio streaming app for [Jellyfin](https://jellyfin.org/). Jellyfin is a community-based piece of software that allows you to stream your media library over the internet. By means of React Native, Jellyfin Audio Player allows you to stream your Jellyfin Music library, with full support for background audio and casting (ie. Airplay and Chromecast).
|
||||
|
||||
## ❗️Now open for beta testing on iOS
|
||||
Please follow this link to enroll for the TestFlight beta release of Jellyfin Audio Player: https://testflight.apple.com/join/cf2AMDpx.
|
||||
|
||||
|||||
|
||||
|-|-|-|-|
|
||||
|
||||
## Features
|
||||
* Sorting by recent albums
|
||||
* Browsing through all available albums
|
||||
* Searching based on album and artist names
|
||||
* Queuing tracks and albums
|
||||
* AirPlay and Chromecast support
|
||||
* Background audio
|
||||
* Native Dark Mode
|
||||
|
||||
### Features being considered
|
||||
* Downloading music for offline playback
|
||||
* Searching based on track names
|
||||
* Looping and shuffling queue
|
||||
|
||||
## Getting Started
|
||||
What you want get started on depends on your intentions. As there is no build for general availability yet, you will need to build this project yourself.
|
||||
This piece of software is in alpha. I am working on getting this app in ~~TestFlight and~~ Google Play Developer Console, but this is contingent on keys being available. In the meantime, IPAs and APK are intermittenly released on the [Releases page](https://github.com/leinelissen/jellyfin-audio-player/releases). Alternatively, you can build this app from source using the build instructions.
|
||||
|
||||
### Using the app
|
||||
You will need to setup your Jellyfin account for the application to be able to pull in all your audio. To do this, go over to the "Settings" tab and click the "Set Jellyfin server"-button. A modal will pop up in which you will enter your Jellyfin server url, after which you enter your credentials in the provided browser view. When the app detects your credentials, they will automatically be remembered by the app.
|
||||
You will need to setup your Jellyfin account for the application to be able to pull in all your audio. To do this, go over to the "Settings" tab and click the "Set Jellyfin server"-button. A modal will pop up in which you will enter your Jellyfin server url, after which you enter your credentials in the provided browser view. When the app detects your credentials, they will automatically be remembered for the future.
|
||||
|
||||
## Building from source
|
||||
### Prerequisites
|
||||
This project is built on React Native, and first of all requires [NodeJS](https://nodejs.org/en/) to be installed. After installing it and cloning this repository, don't forget ton run `npm install` on your command line, so that all Node dependencies are installed.
|
||||
|
||||
#### iOS Prerequisites
|
||||
[XCode](https://developer.apple.com/download/) is required to build the iOS application. It also comes bundles with iOS simulators which make development exceedingly easy. This does mean that iOS development is limited to macs.
|
||||
|
||||
#### Android prerequisites
|
||||
[Android Studio](https://developer.android.com/studio/install) is recommended for development as it includes the Android SDK as well as Android Simulators for devleopment. At the very least, installing the Android SDK is neccessary for building any version of the app.
|
||||
|
||||
### Development Build
|
||||
This app has been mainly developed for iOS, but should mostly function on Android as well. To get started, do the following:
|
||||
1. Clone this repository
|
||||
2. Install [NodeJS](https://nodejs.org/en/) and [XCode](https://developer.apple.com/download/)
|
||||
3. `npm install`
|
||||
4. `npm run ios`
|
||||
As soon as all prerequisites are covered, you can start development in either iOS or Android simulators by running the following
|
||||
```
|
||||
npm run ios
|
||||
npm run android
|
||||
```
|
||||
|
||||
### Production Build
|
||||
Follow step 1-3 from the development build, then do the following:
|
||||
This project is configured using [Fastlane](https://docs.fastlane.tools/), which allows for easy IPA and APK generation. To get started with this, make sure you install Fastlane first either using bundler (see below), or alternatively via e.g. Homebrew ([see supported methods](https://docs.fastlane.tools/getting-started/ios/setup/)).
|
||||
```
|
||||
npm run build:ios
|
||||
gem install bundler
|
||||
bundle install -j 6
|
||||
```
|
||||
Then open `ios/JellyfinAudioPlayer.xcworkspace` and build the project in XCode.
|
||||
When fastlane is setup, you can run either commands for generating IPA (iOS) or APK (Android) bundles.
|
||||
```
|
||||
fastlane ios beta
|
||||
fastlane android beta
|
||||
```
|
||||
|
||||
## Licensing and Credits
|
||||
This work is licensed under the MIT license and was built by Lei Nelissen.
|
||||
@@ -79,7 +79,7 @@ import com.android.build.OutputFile
|
||||
|
||||
project.ext.react = [
|
||||
enableHermes: false, // clean and rebuild if changing
|
||||
entryFile: 'index.ts'
|
||||
entryFile: 'index.js'
|
||||
]
|
||||
|
||||
apply from: "../../node_modules/react-native/react.gradle"
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
package_name("org.leinelissen.jellyfinaudioplayer")
|
||||
app_identifier("org.leinelissen.JellyfinAudioPlayer")
|
||||
package_name("nl.moeilijkedingen.jellyfinaudioplayer")
|
||||
app_identifier("nl.moeilijkedingen.jellyfinaudioplayer")
|
||||
apple_id("lei@moeilijkedingen.nl")
|
||||
team_id("238P3C58WC")
|
||||
@@ -1,8 +1,11 @@
|
||||
default_platform(:ios)
|
||||
|
||||
platform :ios do
|
||||
lane :beta do
|
||||
enable_automatic_code_signing
|
||||
lane :alpha do
|
||||
get_certificates(
|
||||
development: true,
|
||||
output_path: 'certificates/'
|
||||
)
|
||||
build_app(
|
||||
scheme: "JellyfinAudioPlayer",
|
||||
export_method: "development",
|
||||
@@ -10,14 +13,38 @@ platform :ios do
|
||||
workspace: "ios/JellyfinAudioPlayer.xcworkspace"
|
||||
)
|
||||
end
|
||||
lane :beta do
|
||||
get_certificates(
|
||||
output_path: 'certificates/'
|
||||
)
|
||||
get_provisioning_profile(
|
||||
output_path: 'certificates/',
|
||||
filename: "provisioning.mobileprovision",
|
||||
fail_on_name_taken: true,
|
||||
)
|
||||
update_code_signing_settings(
|
||||
use_automatic_signing: true,
|
||||
path: "ios/JellyfinAudioPlayer.xcodeproj"
|
||||
)
|
||||
increment_build_number(
|
||||
xcodeproj: "ios/JellyfinAudioPlayer.xcodeproj"
|
||||
)
|
||||
build_app(
|
||||
scheme: "JellyfinAudioPlayer",
|
||||
output_directory: "build",
|
||||
workspace: "ios/JellyfinAudioPlayer.xcworkspace",
|
||||
export_method: "app-store",
|
||||
)
|
||||
upload_to_testflight
|
||||
end
|
||||
end
|
||||
|
||||
platform :android do
|
||||
desc "Generate beta build"
|
||||
lane :beta do
|
||||
gradle(
|
||||
task: "clean assembleRelease",
|
||||
project_dir: "android"
|
||||
)
|
||||
end
|
||||
desc "Generate beta build"
|
||||
lane :beta do
|
||||
gradle(
|
||||
task: "clean assembleRelease",
|
||||
project_dir: "android"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
43
fastlane/README.md
Normal file
43
fastlane/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
fastlane documentation
|
||||
================
|
||||
# Installation
|
||||
|
||||
Make sure you have the latest version of the Xcode command line tools installed:
|
||||
|
||||
```
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
Install _fastlane_ using
|
||||
```
|
||||
[sudo] gem install fastlane -NV
|
||||
```
|
||||
or alternatively using `brew install fastlane`
|
||||
|
||||
# Available Actions
|
||||
## iOS
|
||||
### ios alpha
|
||||
```
|
||||
fastlane ios alpha
|
||||
```
|
||||
|
||||
### ios beta
|
||||
```
|
||||
fastlane ios beta
|
||||
```
|
||||
|
||||
|
||||
----
|
||||
|
||||
## Android
|
||||
### android beta
|
||||
```
|
||||
fastlane android beta
|
||||
```
|
||||
Generate beta build
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
|
||||
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
|
||||
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||
@@ -13,7 +13,6 @@
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
||||
463612208457EBB4B723000A /* libPods-JellyfinAudioPlayer-JellyfinAudioPlayerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 842AB597D56E84A4ACDC4735 /* libPods-JellyfinAudioPlayer-JellyfinAudioPlayerTests.a */; };
|
||||
4F46F441249A56FF00308470 /* main.jsbundle in Resources */ = {isa = PBXBuildFile; fileRef = 008F07F21AC5B25A0029DE68 /* main.jsbundle */; };
|
||||
A807E2BB233D6F9347D8A95C /* libPods-JellyfinAudioPlayer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 71370E61E2CC6BD9372ADCF3 /* libPods-JellyfinAudioPlayer.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -28,7 +27,6 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = "<group>"; };
|
||||
00E356EE1AD99517003FC87E /* JellyfinAudioPlayerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JellyfinAudioPlayerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
00E356F21AD99517003FC87E /* JellyfinAudioPlayerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JellyfinAudioPlayerTests.m; sourceTree = "<group>"; };
|
||||
@@ -95,7 +93,6 @@
|
||||
13B07FAE1A68108700A75B9A /* JellyfinAudioPlayer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
|
||||
13B07FB01A68108700A75B9A /* AppDelegate.m */,
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
||||
@@ -219,12 +216,12 @@
|
||||
TargetAttributes = {
|
||||
00E356ED1AD99517003FC87E = {
|
||||
CreatedOnToolsVersion = 6.2;
|
||||
DevelopmentTeam = HD2D35G9Y4;
|
||||
DevelopmentTeam = 238P3C58WC;
|
||||
ProvisioningStyle = Automatic;
|
||||
TestTargetID = 13B07F861A680F5B00A75B9A;
|
||||
};
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
DevelopmentTeam = HD2D35G9Y4;
|
||||
DevelopmentTeam = 238P3C58WC;
|
||||
LastSwiftMigration = 1120;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
@@ -261,7 +258,6 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4F46F441249A56FF00308470 /* main.jsbundle in Resources */,
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
|
||||
);
|
||||
@@ -275,14 +271,17 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Bundle React Native code and images";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/main.jsbundle",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n";
|
||||
shellScript = "export NODE_BINARY=$(which node)\n../node_modules/react-native/scripts/react-native-xcode.sh\n";
|
||||
};
|
||||
B9FB8FC65CEFF9AFAC71127E /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
@@ -432,7 +431,7 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = HD2D35G9Y4;
|
||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
@@ -458,7 +457,7 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEVELOPMENT_TEAM = HD2D35G9Y4;
|
||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
||||
INFOPLIST_FILE = JellyfinAudioPlayerTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
@@ -469,6 +468,8 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "915c5213-22f6-4f9d-8065-2a06300f9bfb";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/JellyfinAudioPlayer.app/JellyfinAudioPlayer";
|
||||
};
|
||||
name = Release;
|
||||
@@ -480,8 +481,8 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = HD2D35G9Y4;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
@@ -494,8 +495,9 @@
|
||||
"-ObjC",
|
||||
"-lc++",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.leinelissen.JellyfinAudioPlayer;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = nl.moeilijkedingen.jellyfinaudioplayer;
|
||||
PRODUCT_NAME = "Jellyfin Player";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
@@ -509,8 +511,8 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = HD2D35G9Y4;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
||||
INFOPLIST_FILE = JellyfinAudioPlayer/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
OTHER_LDFLAGS = (
|
||||
@@ -518,8 +520,10 @@
|
||||
"-ObjC",
|
||||
"-lc++",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.leinelissen.JellyfinAudioPlayer;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = nl.moeilijkedingen.jellyfinaudioplayer;
|
||||
PRODUCT_NAME = "Jellyfin Player";
|
||||
PROVISIONING_PROFILE = "915c5213-22f6-4f9d-8065-2a06300f9bfb";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
@@ -553,7 +557,8 @@
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@@ -613,7 +618,8 @@
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
COPY_PHASE_STRIP = YES;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<string>3</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
@@ -42,7 +42,7 @@
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
<string>processing</string>
|
||||
<string>fetch</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
|
||||
@@ -19,6 +19,6 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<string>3</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "JellyfinAudioPlayer",
|
||||
"version": "0.0.1",
|
||||
"main": "src/index.ts",
|
||||
"main": "src/index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
||||
BIN
src/assets/app-icon-white.png
Normal file
BIN
src/assets/app-icon-white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
@@ -22,5 +22,6 @@ export const colors = StyleSheet.create({
|
||||
},
|
||||
input: {
|
||||
backgroundColor: PlatformColor('systemGray5Color'),
|
||||
color: PlatformColor('label'),
|
||||
}
|
||||
});
|
||||
76
src/screens/Onboarding/index.tsx
Normal file
76
src/screens/Onboarding/index.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import styled from 'styled-components/native';
|
||||
import { THEME_COLOR } from 'CONSTANTS';
|
||||
import { Button } from 'react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NavigationProp } from 'screens';
|
||||
import { useTypedSelector } from 'store';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { setOnboardingStatus } from 'store/settings/actions';
|
||||
|
||||
const Container = styled.SafeAreaView`
|
||||
background-color: ${THEME_COLOR};
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const TextContainer = styled.ScrollView`
|
||||
padding: 25px;
|
||||
`;
|
||||
|
||||
const Text = styled.Text`
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const ButtonContainer = styled.View`
|
||||
margin-top: 50px;
|
||||
`;
|
||||
|
||||
const Logo = styled.Image`
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin: 0 auto 50px auto;
|
||||
`;
|
||||
|
||||
function Onboarding() {
|
||||
// Get account from Redux and dispatcher
|
||||
const account = useTypedSelector(state => state.settings.jellyfin);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Also retrieve the navigation handler so that we can open the modal in
|
||||
// which the Jellyfin server is set
|
||||
const navigation = useNavigation<NavigationProp>();
|
||||
const handleClick = useCallback(() => navigation.navigate('SetJellyfinServer'), [navigation]);
|
||||
|
||||
// We'll also respond to any change in the account, setting the onboarding
|
||||
// status to true, so that the app becomes available.
|
||||
useEffect(() => {
|
||||
if (account) {
|
||||
dispatch(setOnboardingStatus(true));
|
||||
}
|
||||
}, [account, dispatch]);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<TextContainer contentContainerStyle={{ flexGrow: 1, justifyContent: 'center' }}>
|
||||
<Logo source={require('../../assets/app-icon-white.png')} />
|
||||
<Text >
|
||||
Welcome!
|
||||
</Text>
|
||||
<Text>
|
||||
Jellyfin Audio Player will allow you to stream your music library from anywhere, with full support for background audio and casting.
|
||||
</Text>
|
||||
<Text>
|
||||
In order to get started, you need a Jellyfin server. Click the button below to enter your Jellyfin server address and login to it.
|
||||
</Text>
|
||||
<ButtonContainer>
|
||||
<Button title="Set Jellyfin Server" color="#ffffff" onPress={handleClick} />
|
||||
</ButtonContainer>
|
||||
</TextContainer>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default Onboarding;
|
||||
@@ -13,8 +13,6 @@ const styles = StyleSheet.create({
|
||||
}
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(styles));
|
||||
|
||||
export default function Player() {
|
||||
return (
|
||||
<ScrollView contentContainerStyle={styles.inner} style={styles.outer}>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { View, Text, SafeAreaView, Button } from 'react-native';
|
||||
import { Picker } from '@react-native-community/picker';
|
||||
import { View, Text, SafeAreaView, Button, StyleSheet } from 'react-native';
|
||||
import { ScrollView } from 'react-native-gesture-handler';
|
||||
import styled from 'styled-components/native';
|
||||
import { useSelector } from 'react-redux';
|
||||
@@ -16,7 +15,6 @@ const InputContainer = styled.View`
|
||||
`;
|
||||
|
||||
const Input = styled.TextInput`
|
||||
background-color: #fbfbfb;
|
||||
padding: 15px;
|
||||
margin-top: 5px;
|
||||
border-radius: 5px;
|
||||
@@ -34,23 +32,24 @@ export default function Settings() {
|
||||
<Header style={colors.text}>Settings</Header>
|
||||
<InputContainer>
|
||||
<Text style={colors.text}>Jellyfin Server URL</Text>
|
||||
<Input placeholder="https://jellyfin.yourserver.com/" value={jellyfin?.uri} editable={false} />
|
||||
<Input placeholder="https://jellyfin.yourserver.com/" value={jellyfin?.uri} editable={false} style={colors.input} />
|
||||
</InputContainer>
|
||||
<InputContainer>
|
||||
<Text style={colors.text}>Jellyfin Access Token</Text>
|
||||
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.access_token} editable={false} />
|
||||
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.access_token} editable={false} style={colors.input} />
|
||||
</InputContainer>
|
||||
<InputContainer>
|
||||
<Text style={colors.text}>Jellyfin User ID</Text>
|
||||
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.user_id} editable={false} />
|
||||
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.user_id} editable={false} style={colors.input} />
|
||||
</InputContainer>
|
||||
<Button title="Set Jellyfin server" onPress={handleClick} color={THEME_COLOR} />
|
||||
<InputContainer>
|
||||
{/* The bitrate setting is hidden for now, since Jellyfin does not appear to support custom bitrates */}
|
||||
{/* <InputContainer>
|
||||
<Text style={colors.text}>Bitrate</Text>
|
||||
<Picker selectedValue={bitrate}>
|
||||
<Picker.Item label="320kbps" value={140000000} />
|
||||
</Picker>
|
||||
</InputContainer>
|
||||
</InputContainer> */}
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
|
||||
@@ -10,6 +10,8 @@ import PlayPauseIcon from 'assets/play-pause-fill.svg';
|
||||
import NotesIcon from 'assets/notes.svg';
|
||||
import GearIcon from 'assets/gear.svg';
|
||||
import { THEME_COLOR } from 'CONSTANTS';
|
||||
import { useTypedSelector } from 'store';
|
||||
import Onboarding from './Onboarding';
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
const Tab = createBottomTabNavigator();
|
||||
@@ -34,6 +36,14 @@ function getIcon(route: string): React.FC<any> | null {
|
||||
}
|
||||
|
||||
function Screens() {
|
||||
const isOnboardingComplete = useTypedSelector(state => state.settings.isOnboardingComplete);
|
||||
|
||||
// GUARD: If onboarding has not been completed, we instead render the
|
||||
// onboarding component, so that the user can get setup in the app.
|
||||
if (!isOnboardingComplete) {
|
||||
return <Onboarding />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
screenOptions={({ route }) => ({
|
||||
|
||||
@@ -32,8 +32,10 @@ export default function SetJellyfinServer() {
|
||||
onCredentialsRetrieved={saveCredentials}
|
||||
/>
|
||||
) : (
|
||||
<View style={{ padding: 20 }}>
|
||||
<Text style={colors.text}>Please enter your Jellyfin server URL first. Make sure to include the protocol and port</Text>
|
||||
<View style={{ padding: 20, flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text style={colors.text}>
|
||||
Please enter your Jellyfin server URL. Make sure to include the protocol and port
|
||||
</Text>
|
||||
<Input
|
||||
placeholder="https://jellyfin.yourserver.io/"
|
||||
onChangeText={setServerUrl}
|
||||
@@ -41,7 +43,7 @@ export default function SetJellyfinServer() {
|
||||
keyboardType="url"
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
style={colors.input}
|
||||
style={{ ...colors.input, width: '100%' }}
|
||||
/>
|
||||
<Button
|
||||
title="Set server"
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { configureStore, getDefaultMiddleware, combineReducers } from '@reduxjs/toolkit';
|
||||
import { useSelector, TypedUseSelectorHook } from 'react-redux';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import { persistStore, persistReducer } from 'redux-persist';
|
||||
import { persistStore, persistReducer, PersistConfig } from 'redux-persist';
|
||||
import autoMergeLevel2 from 'redux-persist/es/stateReconciler/autoMergeLevel2';
|
||||
// import logger from 'redux-logger';
|
||||
|
||||
const persistConfig = {
|
||||
const persistConfig: PersistConfig<AppState> = {
|
||||
key: 'root',
|
||||
storage: AsyncStorage,
|
||||
stateReconciler: autoMergeLevel2
|
||||
};
|
||||
|
||||
import settings from './settings';
|
||||
@@ -26,7 +28,7 @@ const store = configureStore({
|
||||
),
|
||||
});
|
||||
|
||||
export type AppState = ReturnType<typeof store.getState>;
|
||||
export type AppState = ReturnType<typeof reducers>;
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
export type AsyncThunkAPI = { state: AppState, dispatch: AppDispatch };
|
||||
export const useTypedSelector: TypedUseSelectorHook<AppState> = useSelector;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
|
||||
export const setJellyfinCredentials = createAction<{ access_token: string, user_id: string, uri: string, deviced_id: string; }>('SET_JELLYFIN_CREDENTIALS');
|
||||
export const setBitrate = createAction<number>('SET_BITRATE');
|
||||
export const setBitrate = createAction<number>('SET_BITRATE');
|
||||
export const setOnboardingStatus = createAction<boolean>('SET_ONBOARDING_STATUS');
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createReducer } from '@reduxjs/toolkit';
|
||||
import { setBitrate, setJellyfinCredentials } from './actions';
|
||||
import { setBitrate, setJellyfinCredentials, setOnboardingStatus } from './actions';
|
||||
|
||||
interface State {
|
||||
jellyfin?: {
|
||||
@@ -9,10 +9,12 @@ interface State {
|
||||
device_id: string;
|
||||
}
|
||||
bitrate: number;
|
||||
isOnboardingComplete: boolean;
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
bitrate: 140000000
|
||||
bitrate: 140000000,
|
||||
isOnboardingComplete: false,
|
||||
};
|
||||
|
||||
const settings = createReducer(initialState, {
|
||||
@@ -24,6 +26,10 @@ const settings = createReducer(initialState, {
|
||||
...state,
|
||||
bitrate: action.payload,
|
||||
}),
|
||||
[setOnboardingStatus.type]: (state, action) => ({
|
||||
...state,
|
||||
isOnboardingComplete: action.payload,
|
||||
})
|
||||
});
|
||||
|
||||
export default settings;
|
||||
@@ -15,6 +15,8 @@ function generateConfig(credentials: Credentials): RequestInit {
|
||||
const baseTrackOptions: Record<string, string> = {
|
||||
// Not sure where this number refers to, but setting it to 140000000 appears
|
||||
// to do wonders for making stuff work
|
||||
// NOTE: Apparently setting a bitrate is as of yet unsupported in the
|
||||
// Jellyfin core, and hence this value is not used
|
||||
MaxStreamingBitrate: '140000000',
|
||||
MaxSampleRate: '48000',
|
||||
// This must be set to support client seeking
|
||||
|
||||
Reference in New Issue
Block a user