Compare commits

...

12 Commits

Author SHA1 Message Date
Lei Nelissen
9138946ba9 Release TestFlight URL 2020-08-25 10:51:11 +02:00
Lei Nelissen
ea11a7d317 Resolve dark mode styling issues 2020-08-25 10:39:21 +02:00
Lei Nelissen
91344300c8 Update fastlane to support signing and TestFlight 2020-08-25 10:39:02 +02:00
Lei Nelissen
52146a6e12 Disable codec/bitrate settings
Appearently neither Jellyfin nor Emby support user-defined bitrates (see https://emby.media/community/index.php?/topic/65896-change-audio-bitrate-of-transcoder/ and https://github.com/jellyfin/jellyfin/issues/1124. These issues must be fixed first before any attempt at user control over bitrate and codecs can be considered.
2020-08-09 18:29:17 +02:00
Lei Nelissen
0712e6b8ca Add onboarding component 2020-08-09 17:49:36 +02:00
Lei Nelissen
2155320f6b Add fastlane to README and simplify structure 2020-08-09 13:32:20 +02:00
Lei Nelissen
4ff1e173ba Get short hash in separate step 2020-07-28 12:09:15 +02:00
Lei Nelissen
c1f6a2984a Fix variable name in artifact upload 2020-07-28 12:03:37 +02:00
Lei Nelissen
8ba3080734 Fix entry file for good
(Famous last words)
2020-07-28 11:52:09 +02:00
Lei Nelissen
fe22661036 Add commit hash to Android APK filename 2020-07-28 11:43:06 +02:00
Lei Nelissen
3762d9166b Update fastlane syntax 2020-07-28 11:42:43 +02:00
Lei Nelissen
48dd8c23aa Change entrypoint file so iOS bundle is automatically generated 2020-07-28 11:42:08 +02:00
24 changed files with 293 additions and 75 deletions

View File

@@ -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
View File

@@ -63,4 +63,6 @@ buck-out/
/ios/Pods/
build/
fastlane/report.xml
fastlane/report.xml
fastlane/Appfile
certificates/

View File

@@ -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)

View File

@@ -1,25 +1,63 @@
# Jellyfin Audio Player
This is a React Native-based audio streaming app for Jellyfin.
![Fastlane](https://github.com/leinelissen/jellyfin-audio-player/workflows/Fastlane/badge.svg)
![MIT License](https://img.shields.io/github/license/leinelissen/jellyfin-audio-player)
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.
|![](./docs/images/now-playing.png)|![](./docs/images/recent-albums.png)|![](./docs/images/album-list.png)|![](./docs/images/search.png)|
|-|-|-|-|
## 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.

View File

@@ -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"

View File

@@ -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")

View File

@@ -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
View 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).

View File

View File

@@ -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;

View File

@@ -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>

View File

@@ -19,6 +19,6 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>3</string>
</dict>
</plist>

View File

@@ -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",

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -22,5 +22,6 @@ export const colors = StyleSheet.create({
},
input: {
backgroundColor: PlatformColor('systemGray5Color'),
color: PlatformColor('label'),
}
});

View 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;

View File

@@ -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}>

View File

@@ -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>

View File

@@ -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 }) => ({

View File

@@ -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"

View File

@@ -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;

View File

@@ -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');

View File

@@ -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;

View File

@@ -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