Snapcast is a Snapcast control client and player for Android. It uses the Snapcast JSON-RPC API to control your synchronous multi-room audio player.
2 |
This App is not meant for productive use and will only run when you have a Snapserver installed in your local network.
3 |
Snapcast is a multi-room client-server audio player, where all clients are time synchronized with the server to play perfectly synced audio. It’s not a standalone player, but an extension that turns your existing audio player into a Sonos-like multi-room solution.
4 |
The server’s audio input is a named pipe /tmp/snapfifo. All data that is fed into this file will be send to the connected clients. One of the most generic ways to use Snapcast is in conjunction with the music player daemon (MPD) or Mopidy, which can be configured to use a named pipe as audio output.
5 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 | android.enableJetifier=true
20 | android.useAndroidX=true
21 | android.prefabVersion=1.1.2
22 | android.defaults.buildfeatures.buildconfig=true
23 | android.nonTransitiveRClass=false
24 | android.nonFinalResIds=false
25 |
--------------------------------------------------------------------------------
/Snapcast/src/main/java/de/badaix/snapcast/control/json/JsonSerialisable.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of snapcast
3 | * Copyright (C) 2014-2018 Johannes Pohl
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package de.badaix.snapcast.control.json;
20 |
21 | import org.json.JSONObject;
22 |
23 | /**
24 | * Created by johannes on 08.01.16.
25 | */
26 | public interface JsonSerialisable {
27 | void fromJson(JSONObject json);
28 |
29 | JSONObject toJson();
30 | }
31 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de/full_description.txt:
--------------------------------------------------------------------------------
1 |
Snapcast ist ein Snapcast Control-Client und Player für Android. Es verwendet die Snapcast JSON-RPC API zur Steuerung der synchronen Multi-Room Audio-Wiedergabe.
2 |
Diese App ist nicht für den produktiven Einsatz gedacht und wird nur funktionieren, wenn ein Snapserver im lokalen Netzwerk verfügbar ist.
3 |
Snapcast ist ein Multi-Room Client-Server Audio Player, der alle Clients zeitlich mit dem Server synchronisiert – um perfekt synchronisierte Audio-Wiedergabe zu ermöglichen. Es ist kein stand-alone Player – sondern eine Erweiterung, die bereits existierende Audio-Player zu einer Sonos-ähnlichen Multi-Room Lösung verbindet.
4 |
Die Audio-Eingabe des Servers ist eine so genannte „named pipe“, die unter /tmp/snapfifo zu finden ist. Alle dort eingespeisten Daten werden an alle verbundenen Clients gesendet. Ein weit verbreiteter Einsatz ist die Verwendung von Snapcast zusammen mit dem Music Player Daemon (MPD) oder Mopidy, die sich für die Verwendung einer „named pipe“ als Audio-Ausgabe konfigurieren lassen.
5 |
--------------------------------------------------------------------------------
/Snapcast/src/main/java/de/badaix/snapcast/control/json/Snapclient.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of snapcast
3 | * Copyright (C) 2014-2018 Johannes Pohl
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package de.badaix.snapcast.control.json;
20 |
21 | import org.json.JSONObject;
22 |
23 | /**
24 | * Created by johannes on 06.01.16.
25 | */
26 | public class Snapclient extends Snapcast {
27 |
28 | public Snapclient() {
29 | super();
30 | }
31 |
32 | public Snapclient(JSONObject json) {
33 | super(json);
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/Snapcast/src/main/res/layout/activity_about.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
24 |
25 |
29 |
30 |
--------------------------------------------------------------------------------
/Snapcast/src/main/res/drawable/volume_up_24px.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
25 |
28 |
29 |
--------------------------------------------------------------------------------
/Snapcast/Ressources/Speaker_Icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Snapcast/src/main/res/drawable/ic_menu_overflow_material.xml:
--------------------------------------------------------------------------------
1 |
18 |
24 |
27 |
28 |
--------------------------------------------------------------------------------
/Snapcast/src/main/res/layout/fragment_group_list.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
24 |
25 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Snapcast/src/main/res/drawable/volume_off_24px.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
25 |
28 |
29 |
--------------------------------------------------------------------------------
/Snapcast/src/main/res/menu/menu_snapcast.xml:
--------------------------------------------------------------------------------
1 |
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 | *.aab
5 |
6 | # Files for the ART/Dalvik VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # Generated files
13 | bin/
14 | gen/
15 | out/
16 |
17 | # Gradle files
18 | .gradle/
19 | build/
20 |
21 | # Local configuration file (sdk path, etc)
22 | local.properties
23 |
24 | # Proguard folder generated by Eclipse
25 | proguard/
26 |
27 | # Log Files
28 | *.log
29 |
30 | # Android Studio Navigation editor temp files
31 | .navigation/
32 |
33 | # Android Studio captures folder
34 | captures/
35 |
36 | # IntelliJ
37 | *.iml
38 | .idea/workspace.xml
39 | .idea/tasks.xml
40 | .idea/gradle.xml
41 | .idea/assetWizardSettings.xml
42 | .idea/dictionaries
43 | .idea/libraries
44 | .idea/caches
45 | # Android Studio 3 in .gitignore file.
46 | .idea/caches/build_file_checksums.ser
47 | .idea/modules.xml
48 |
49 | # Keystore files
50 | # Uncomment the following lines if you do not want to check your keystore files in.
51 | #*.jks
52 | #*.keystore
53 |
54 | # External native build folder generated in Android Studio 2.2 and later
55 | .externalNativeBuild
56 |
57 | # Google Services (e.g. APIs or Firebase)
58 | # google-services.json
59 |
60 | # Freeline
61 | freeline.py
62 | freeline/
63 | freeline_project_description.json
64 |
65 | # fastlane
66 | fastlane/report.xml
67 | fastlane/Preview.html
68 | fastlane/screenshots
69 | fastlane/test_output
70 | fastlane/readme.md
71 |
72 | # Version control
73 | vcs.xml
74 |
75 | # lint
76 | lint/intermediates/
77 | lint/generated/
78 | lint/outputs/
79 | lint/tmp/
80 | # lint/reports/
81 |
82 | Snapcast/release/output.json
83 |
84 | Snapcast/.cxx
85 | Snapcast/libs/*.aar
86 |
--------------------------------------------------------------------------------
/Snapcast/src/main/res/xml/client_preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
12 |
17 |
22 |
27 |
32 |
37 |
42 |
47 |
48 |
--------------------------------------------------------------------------------
/Snapcast/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 | #EEEEEE
3 | #37474F
4 |
5 |
9 |
10 |
11 |
12 |
13 |
14 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Snapcast/src/main/res/drawable/settings_24px.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
25 |
28 |
29 |
--------------------------------------------------------------------------------
/Snapcast/src/main/java/de/badaix/snapcast/AboutActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of snapcast
3 | * Copyright (C) 2014-2018 Johannes Pohl
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package de.badaix.snapcast;
20 |
21 | import android.content.pm.PackageInfo;
22 | import android.content.pm.PackageManager;
23 | import android.os.Bundle;
24 | import android.webkit.WebView;
25 |
26 | import androidx.appcompat.app.AppCompatActivity;
27 |
28 | import com.google.android.material.snackbar.Snackbar;
29 |
30 | public class AboutActivity extends AppCompatActivity {
31 |
32 | @Override
33 | protected void onCreate(Bundle savedInstanceState) {
34 | super.onCreate(savedInstanceState);
35 | setContentView(R.layout.activity_about);
36 | try {
37 | getSupportActionBar().setTitle(getString(R.string.about) + " Snapcast");
38 | } catch (Exception e) {
39 | Snackbar.make(findViewById(R.id.webView), getText(R.string.action_bar_failed), Snackbar.LENGTH_LONG);
40 | }
41 | PackageInfo pInfo;
42 | try {
43 | pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
44 | getSupportActionBar().setSubtitle("v" + pInfo.versionName);
45 | } catch (PackageManager.NameNotFoundException e) {
46 | e.printStackTrace();
47 | }
48 | WebView wv = findViewById(R.id.webView);
49 | wv.loadUrl("file:///android_asset/" + this.getText(R.string.about_file));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Snapcast/src/main/res/values-ja/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Snapcast
3 | Snapclient 開始しました
4 | Snapclient
5 | Snapclient は実行中です…
6 | 情報…
7 | 停止
8 | 詳細
9 | 削除
10 |
11 | Snapcast
12 |
13 | アプリについて…
14 | 設定…
15 | サーバーのスキャン
16 | クライアント一覧の更新
17 | オフラインのクライアントを非表示
18 |
19 | クライアント設定
20 | 名前
21 | MAC
22 | IP
23 | ホスト
24 | OS
25 | バージョン
26 | 最後の視聴
27 | レイテンシー
28 | オンライン
29 | 再生/停止
30 | クライアント %1$s を削除しました
31 | ストリームはネイティブのサンプリングレートではありません: %1$d\nネイティブのサンプリングレート: %2$d
32 | ネイティブのサンプリングレートではないストリームは同期して再生されないことがあります
33 | 元に戻す
34 | アプリについて
35 | files/about.html
36 | アプリについて
37 |
38 | ストリーム
39 | ホスト
40 | ストリーム ポート
41 | コントロール ポート
42 | Snapserver ホスト
43 | Snapclient を自動開始
44 |
45 |
46 |
--------------------------------------------------------------------------------
/PRIVACY_POLICY.md:
--------------------------------------------------------------------------------
1 | # Snapdroid: Privacy policy
2 |
3 | This is an open source Android app developed by Johannes Pohl and [contributors](https://github.com/badaix/snapdroid/graphs/contributors). The source code is available on GitHub under the GPL-3.0; the app is also available on Google Play.
4 |
5 | As an avid Android user myself, I take privacy very seriously.
6 | I know how irritating it is when apps collect your data without your knowledge.
7 |
8 | I hereby state, to the best of my knowledge and belief, that I have not programmed this app to collect any personally identifiable information.
9 |
10 | ## Explanation of permissions requested in the app
11 |
12 | The list of permissions required by the app can be found in the `AndroidManifest.xml` file:
13 |
14 | https://github.com/badaix/snapdroid/blob/master/Snapcast/src/main/AndroidManifest.xml
15 |
16 |
17 |
18 | | Permission | Why it is required |
19 | | :---: | --- |
20 | | `android.permission.WAKE_LOCK` | Required to play audio in the background and while the screen is locked. Permission automatically granted by the system; can't be revoked by user. |
21 | | `android.permission.INTERNET` | Required to open a TCP connection to the Snapcast server. Permission automatically granted by the system; can't be revoked by user. |
22 | | `android.permission.FOREGROUND_SERVICE` | Enables the app to create spawn the native Snapcast client as a server, running independly from the control app. Permission automatically granted by the system; can't be revoked by user. |
23 | | `android.permission.RECEIVE_BOOT_COMPLETED` | This permission enables the app to receive a message from the system once the system has rebooted and to start the Snapcast client automatically, if "auto start" is enabled. Permission automatically granted by the system; can't be revoked by user. |
24 | | `android.permission.CHANGE_WIFI_MULTICAST_STATE` | Enables automatic discovery of the Snapcast server in the LAN via mDNS. Permission automatically granted by the system; can't be revoked by user. |
25 | | `android.permission.POST_NOTIFICATIONS` | Required to show a notification while audio is being played. |
26 |
27 |
28 |
29 | If you find any security vulnerability that has been inadvertently caused by me, or have any question regarding how the app protectes your privacy, please send me an email or post a discussion on GitHub, and I will surely try to fix it/help you.
30 |
31 | Yours sincerely,
32 | Johannes Pohl.
33 | Aachen, Germany.
34 | snapcast@badaix.de
35 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request, workflow_dispatch]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-24.04
9 |
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v4
13 | - name: Checkout submodules
14 | run: git submodule update --init --recursive
15 | - name: Download dependencies
16 | run: |
17 | LIBS_DIR="Snapcast/libs/"
18 | wget https://github.com/badaix/snapcast-deps/releases/download/v0.29.0/boost-1.85.0.aar -P $LIBS_DIR
19 | wget https://github.com/badaix/snapcast-deps/releases/download/v0.29.0/flac-1.4.2.aar -P $LIBS_DIR
20 | wget https://github.com/badaix/snapcast-deps/releases/download/v0.29.0/oboe-1.9.0.aar -P $LIBS_DIR
21 | wget https://github.com/badaix/snapcast-deps/releases/download/v0.29.0/ogg-1.3.5.aar -P $LIBS_DIR
22 | wget https://github.com/badaix/snapcast-deps/releases/download/v0.29.0/opus-1.1.2.aar -P $LIBS_DIR
23 | wget https://github.com/badaix/snapcast-deps/releases/download/v0.29.0/soxr-0.1.3.aar -P $LIBS_DIR
24 | wget https://github.com/badaix/snapcast-deps/releases/download/v0.29.0/tremor-1.0.1.aar -P $LIBS_DIR
25 | wget https://github.com/badaix/snapcast-deps/releases/download/v0.29.0/vorbis-1.3.7.aar -P $LIBS_DIR
26 |
27 | - name: Build the app
28 | run: JAVA_HOME=$JAVA_HOME_17_X64 ./gradlew build
29 |
30 | - name: rename apk
31 | run: mv /home/runner/work/snapdroid/snapdroid/Snapcast/build/outputs/apk/release/Snapcast-release-unsigned.apk /home/runner/work/snapdroid/snapdroid/Snapcast/build/outputs/apk/release/Snapcast.apk
32 |
33 | - uses: filippoLeporati93/android-release-signer@v1
34 | if: github.event_name == 'push'
35 | name: Sign app APK
36 | # ID used to access action output
37 | id: sign_app
38 | with:
39 | releaseDirectory: /home/runner/work/snapdroid/snapdroid/Snapcast/build/outputs/apk/release
40 | signingKeyBase64: ${{ secrets.SIGNING_KEY }}
41 | alias: ${{ secrets.ALIAS }}
42 | keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
43 | keyPassword: ${{ secrets.KEY_PASSWORD }}
44 | env:
45 | # override default build-tools version (29.0.3) -- optional
46 | BUILD_TOOLS_VERSION: "35.0.0"
47 |
48 | - name: Archive artifacts
49 | if: github.event_name == 'push'
50 | uses: actions/upload-artifact@v4
51 | with:
52 | name: develop_snapshot-${{github.sha}}
53 | path: ${{steps.sign_app.outputs.signedReleaseFile}}
54 |
--------------------------------------------------------------------------------
/Snapcast/src/main/java/de/badaix/snapcast/control/json/Snapserver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of snapcast
3 | * Copyright (C) 2014-2018 Johannes Pohl
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package de.badaix.snapcast.control.json;
20 |
21 | import org.json.JSONException;
22 | import org.json.JSONObject;
23 |
24 | /**
25 | * Created by johannes on 06.01.16.
26 | */
27 | public class Snapserver extends Snapcast {
28 | int controlProtocolVersion = 1;
29 |
30 | public Snapserver() {
31 | super();
32 | }
33 |
34 | public Snapserver(JSONObject json) {
35 | super(json);
36 | }
37 |
38 | @Override
39 | public void fromJson(JSONObject json) {
40 | try {
41 | super.fromJson(json);
42 | controlProtocolVersion = json.getInt("controlProtocolVersion");
43 | } catch (JSONException e) {
44 | e.printStackTrace();
45 | }
46 | }
47 |
48 | @Override
49 | public JSONObject toJson() {
50 | JSONObject json = super.toJson();
51 | try {
52 | json.put("controlProtocolVersion", controlProtocolVersion);
53 | } catch (JSONException e) {
54 | e.printStackTrace();
55 | }
56 | return json;
57 | }
58 |
59 | public int getControlProtocolVersion() {
60 | return controlProtocolVersion;
61 | }
62 |
63 | @Override
64 | public boolean equals(Object o) {
65 | if (this == o) return true;
66 | if (o == null || getClass() != o.getClass()) return false;
67 |
68 | Snapserver that = (Snapserver) o;
69 |
70 | if (controlProtocolVersion != that.controlProtocolVersion) return false;
71 | return super.equals(o);
72 | }
73 |
74 | @Override
75 | public int hashCode() {
76 | int result = super.hashCode();
77 | result = 31 * result + controlProtocolVersion;
78 | return result;
79 | }
80 | }
81 |
82 |
--------------------------------------------------------------------------------
/Snapcast/src/main/java/de/badaix/snapcast/ClientSettingsActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of snapcast
3 | * Copyright (C) 2014-2018 Johannes Pohl
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package de.badaix.snapcast;
20 |
21 | import android.app.Activity;
22 | import android.content.Intent;
23 | import android.os.Bundle;
24 | import android.view.MenuItem;
25 |
26 | import androidx.activity.OnBackPressedCallback;
27 | import androidx.appcompat.app.AppCompatActivity;
28 |
29 | /**
30 | * Created by johannes on 11.01.16.
31 | */
32 | public class ClientSettingsActivity extends AppCompatActivity {
33 | private ClientSettingsFragment clientSettingsFragment = null;
34 |
35 | @Override
36 | protected void onCreate(Bundle savedInstanceState) {
37 | super.onCreate(savedInstanceState);
38 | clientSettingsFragment = new ClientSettingsFragment();
39 | clientSettingsFragment.setArguments(getIntent().getExtras());
40 |
41 | // Display the fragment as the main content.
42 | getFragmentManager().beginTransaction()
43 | .replace(android.R.id.content, clientSettingsFragment)
44 | .commit();
45 |
46 | getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
47 | @Override
48 | public void handleOnBackPressed() {
49 | Intent intent = new Intent();
50 | intent.putExtra("client", clientSettingsFragment.getClient().toJson().toString());
51 | intent.putExtra("clientOriginal", clientSettingsFragment.getOriginalClientInfo().toJson().toString());
52 | setResult(Activity.RESULT_OK, intent);
53 | finish();
54 | }
55 | });
56 | }
57 |
58 | @Override
59 | public boolean onOptionsItemSelected(MenuItem item) {
60 | if (item.getItemId() == android.R.id.home) {
61 | onBackPressed();
62 | return true;
63 | }
64 | return false;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/Snapcast/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Snapcast
3 | Snapclient started
4 | Snapclient
5 | Snapclient is running…
6 | info…
7 | stop
8 | Details
9 | Delete
10 | Group
11 |
12 | Snapcast
13 |
14 | About…
15 | Settings…
16 | Scan for server
17 | Refresh client list
18 | Hide offline clients
19 | Cannot set ActionBar!
20 |
21 | Client settings
22 | Name
23 | MAC
24 | ID
25 | IP
26 | Host
27 | Operating system
28 | Version
29 | Last seen
30 | Latency
31 | online
32 |
33 | Group settings
34 |
35 | Cannot connect: host is not configured
36 | Play/Stop
37 | Client %1$s deleted
38 | Stream not in native sample rate: %1$d\nNative sample rate: %2$d
39 | Streams in non-native sample rate might not play in sync
40 | undo
41 | About
42 | files/about.html
43 | About
44 |
45 | Stream
46 | Host
47 | stream port
48 | control port
49 | Settings
50 | audio engine
51 | resample audio stream
52 | Auto start Snapclient
53 | Connect
54 |
55 |
56 |
--------------------------------------------------------------------------------
/Snapcast/Ressources/Mute_Icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Snapcast/src/main/java/de/badaix/snapcast/GroupSettingsActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of snapcast
3 | * Copyright (C) 2014-2018 Johannes Pohl
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package de.badaix.snapcast;
20 |
21 | import android.app.Activity;
22 | import android.content.Intent;
23 | import android.os.Bundle;
24 | import android.view.MenuItem;
25 |
26 | import androidx.activity.OnBackPressedCallback;
27 | import androidx.appcompat.app.AppCompatActivity;
28 |
29 | /**
30 | * Created by johannes on 06.12.16.
31 | */
32 |
33 | public class GroupSettingsActivity extends AppCompatActivity {
34 |
35 | private GroupSettingsFragment groupSettingsFragment;
36 |
37 | @Override
38 | protected void onCreate(Bundle savedInstanceState) {
39 | super.onCreate(savedInstanceState);
40 |
41 | groupSettingsFragment = new GroupSettingsFragment();
42 | groupSettingsFragment.setArguments(getIntent().getExtras());
43 | // Display the fragment as the main content.
44 | getFragmentManager().beginTransaction()
45 | .replace(android.R.id.content, groupSettingsFragment).commit();
46 |
47 | getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
48 | @Override
49 | public void handleOnBackPressed() {
50 | Intent intent = new Intent();
51 | intent.putExtra("group", groupSettingsFragment.getGroup().getId());
52 | if (groupSettingsFragment.didStreamChange())
53 | intent.putExtra("stream", groupSettingsFragment.getStream());
54 | if (groupSettingsFragment.didClientsChange())
55 | intent.putStringArrayListExtra("clients", groupSettingsFragment.getClients());
56 | setResult(Activity.RESULT_OK, intent);
57 | finish();
58 | }
59 | });
60 | }
61 |
62 | @Override
63 | public boolean onOptionsItemSelected(MenuItem item) {
64 | if (item.getItemId() == android.R.id.home) {
65 | onBackPressed();
66 | return true;
67 | }
68 | return false;
69 | }
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/Snapcast/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Snapcast
3 | Snapclient gestartet
4 | Snapclient
5 | Snapclient läuft…
6 | Info…
7 | Stop
8 | Details
9 | Löschen
10 | Gruppe
11 |
12 | Snapcast
13 |
14 | Über…
15 | Einstellungen…
16 | Suche nach Server
17 | Aktualisiere Client-Liste
18 | Verstecke offline Clients
19 |
20 | Client Einstellungen
21 | Name
22 | MAC
23 | ID
24 | IP
25 | Host
26 | Betriebssystem
27 | Version
28 | Zuletzt gesehen
29 | Latenz
30 | Online
31 |
32 | Gruppe
33 |
34 | Verbindung fehlgeschlagen: Host ist nicht konfiguriert
35 | Play/Stop
36 | Client %1$s gelöscht
37 | Stream ist nicht in nativer sample rate: %1$d\nNative sample rate: %2$d
38 | Streams in nicht-nativer sample rate sind evtl. nicht synchron
39 | Rückgängig
40 | Über
41 | files/about.html
42 | Über
43 | ActionBar kann nicht erstellt werden
44 |
45 | Stream
46 | Host
47 | Stream-Port
48 | Control-Port
49 | Audio engine
50 | Resample audio stream
51 | Einstellungen
52 | Auto-start Snapclient
53 | Verbinden
54 |
55 |
56 |
--------------------------------------------------------------------------------
/Snapcast/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdk 35
5 |
6 | defaultConfig {
7 | applicationId "de.badaix.snapcast"
8 | minSdkVersion 21
9 | targetSdkVersion 35
10 | versionCode 2900
11 | versionName '0.29.0.0'
12 | multiDexEnabled true
13 | vectorDrawables.useSupportLibrary = true
14 | externalNativeBuild {
15 | cmake {
16 | version "3.22.1"
17 | cppFlags "-std=c++14"
18 | arguments '-DANDROID_STL=c++_static', '-DBUILD_SERVER=OFF', '-DBUILD_TESTS=OFF'
19 | abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
20 | }
21 | }
22 | }
23 | buildFeatures {
24 | prefab true
25 | }
26 | buildTypes {
27 | release {
28 | minifyEnabled false
29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
30 | }
31 | }
32 | externalNativeBuild {
33 | cmake {
34 | path 'src/main/cpp/snapcast/CMakeLists.txt'
35 | }
36 | }
37 |
38 | buildToolsVersion = '35.0.0'
39 | ndkVersion '27.0.12077973'
40 | lint {
41 | disable 'MissingTranslation'
42 | }
43 | namespace 'de.badaix.snapcast'
44 | compileOptions {
45 | sourceCompatibility JavaVersion.VERSION_17
46 | targetCompatibility JavaVersion.VERSION_17
47 | }
48 | }
49 |
50 | dependencies {
51 | implementation fileTree(include: ['*.jar'], dir: 'libs')
52 | testImplementation 'junit:junit:4.13.2'
53 | // https://dl.google.com/dl/android/maven2/index.html
54 | implementation 'androidx.appcompat:appcompat:1.7.0'
55 | implementation 'androidx.cardview:cardview:1.0.0'
56 | implementation 'androidx.legacy:legacy-support-v4:1.0.0'
57 | // compile 'com.android.support:recyclerview-v7:23.1.1'
58 | implementation 'com.google.android.material:material:1.12.0'
59 | implementation 'com.github.badaix:oboe:1.9.0@aar'
60 | implementation 'com.github.badaix:boost:1.85.0@aar'
61 | implementation 'com.github.badaix:flac:1.4.2@aar'
62 | implementation 'com.github.badaix:ogg:1.3.5@aar'
63 | implementation 'com.github.badaix:opus:1.1.2@aar'
64 | implementation 'com.github.badaix:soxr:0.1.3@aar'
65 | implementation 'com.github.badaix:tremor:1.0.1@aar'
66 | implementation 'com.github.badaix:vorbis:1.3.7@aar'
67 | }
68 |
69 |
70 | repositories{
71 | flatDir{
72 | dirs 'libs'
73 | }
74 |
75 | maven {
76 | name = "GithubPackages"
77 | url = uri("https://maven.pkg.github.com/badaix/snapcast-deps")
78 | credentials {
79 | username = project.findProperty("GITHUB_USER") ?: System.getenv("GITHUB_USER")
80 | password = project.findProperty("GITHUB_TOKEN") ?: System.getenv("GITHUB_TOKEN")
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Snapcast/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
27 |
28 |
33 |
34 |
35 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
55 |
56 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Snapcast/src/main/java/de/badaix/snapcast/BroadcastReceiver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of snapcast
3 | * Copyright (C) 2014-2018 Johannes Pohl
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package de.badaix.snapcast;
20 |
21 | import android.content.Context;
22 | import android.content.Intent;
23 | import android.os.Build;
24 | import android.text.TextUtils;
25 |
26 | import de.badaix.snapcast.utils.Settings;
27 |
28 | /**
29 | * Created by johannes on 05.05.16.
30 | */
31 | public class BroadcastReceiver extends android.content.BroadcastReceiver {
32 | @Override
33 | public void onReceive(Context context, Intent intent) {
34 | switch (intent.getAction()) {
35 | // Auto start snapclient on boot
36 | case "android.intent.action.BOOT_COMPLETED":
37 | if (Settings.getInstance(context).isAutostart()) {
38 | startService(context, SnapclientService.ACTION_START);
39 | }
40 | break;
41 |
42 | // Control snapclient service via broadcast intents
43 | case "de.badaix.snapcast.START_SERVICE":
44 | startService(context, SnapclientService.ACTION_START);
45 | break;
46 | case "de.badaix.snapcast.STOP_SERVICE":
47 | startService(context, SnapclientService.ACTION_STOP);
48 | break;
49 | }
50 | }
51 |
52 | private void startService(Context context, String action) {
53 | Intent i = new Intent(context, SnapclientService.class);
54 | i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
55 | i.setAction(action);
56 |
57 | if (action == SnapclientService.ACTION_START) {
58 | String host = Settings.getInstance(context).getHost();
59 | int port = Settings.getInstance(context).getStreamPort();
60 | if (TextUtils.isEmpty(host))
61 | return;
62 |
63 | i.putExtra(SnapclientService.EXTRA_HOST, host);
64 | i.putExtra(SnapclientService.EXTRA_PORT, port);
65 | }
66 |
67 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
68 | context.startForegroundService(i);
69 | } else {
70 | context.startService(i);
71 | }
72 | }
73 | }
74 |
75 |
--------------------------------------------------------------------------------
/Snapcast/src/main/java/de/badaix/snapcast/control/json/Time_t.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of snapcast
3 | * Copyright (C) 2014-2018 Johannes Pohl
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package de.badaix.snapcast.control.json;
20 |
21 | import org.json.JSONException;
22 | import org.json.JSONObject;
23 |
24 | /**
25 | * Created by johannes on 06.01.16.
26 | */
27 | public class Time_t implements JsonSerialisable {
28 | private long sec = 0;
29 | private long usec = 0;
30 |
31 | public Time_t() {
32 |
33 | }
34 |
35 | public Time_t(JSONObject json) {
36 | fromJson(json);
37 | }
38 |
39 | public Time_t(long sec, long usec) {
40 | this.sec = sec;
41 | this.usec = usec;
42 | }
43 |
44 | @Override
45 | public void fromJson(JSONObject json) {
46 | try {
47 | sec = json.getLong("sec");
48 | usec = json.getLong("usec");
49 | } catch (JSONException e) {
50 | e.printStackTrace();
51 | }
52 | }
53 |
54 | @Override
55 | public JSONObject toJson() {
56 | JSONObject json = new JSONObject();
57 | try {
58 | json.put("sec", sec);
59 | json.put("usec", usec);
60 | } catch (JSONException e) {
61 | e.printStackTrace();
62 | }
63 | return json;
64 | }
65 |
66 | public long getSec() {
67 | return sec;
68 | }
69 |
70 | public void setSec(long sec) {
71 | this.sec = sec;
72 | }
73 |
74 | public long getUsec() {
75 | return usec;
76 | }
77 |
78 | public void setUsec(long usec) {
79 | this.usec = usec;
80 | }
81 |
82 | @Override
83 | public String toString() {
84 | return toJson().toString();
85 | }
86 |
87 | @Override
88 | public boolean equals(Object o) {
89 | if (this == o) return true;
90 | if (o == null || getClass() != o.getClass()) return false;
91 |
92 | Time_t time_t = (Time_t) o;
93 |
94 | if (sec != time_t.sec) return false;
95 | return usec == time_t.usec;
96 |
97 | }
98 |
99 | @Override
100 | public int hashCode() {
101 | int result = (int) sec;
102 | result = (int) (31 * result + usec);
103 | return result;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Snapcast/src/main/java/de/badaix/snapcast/control/json/Volume.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of snapcast
3 | * Copyright (C) 2014-2018 Johannes Pohl
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package de.badaix.snapcast.control.json;
20 |
21 | import org.json.JSONException;
22 | import org.json.JSONObject;
23 |
24 | /**
25 | * Created by johannes on 06.01.16.
26 | */
27 | public class Volume implements JsonSerialisable {
28 | private boolean muted = false;
29 | private int percent = 100;
30 |
31 | public Volume(JSONObject json) {
32 | fromJson(json);
33 | }
34 |
35 | public Volume() {
36 |
37 | }
38 |
39 | public Volume(int percent, boolean muted) {
40 | this.percent = percent;
41 | this.muted = muted;
42 | }
43 |
44 | @Override
45 | public void fromJson(JSONObject json) {
46 | try {
47 | percent = json.getInt("percent");
48 | muted = json.getBoolean("muted");
49 | } catch (JSONException e) {
50 | e.printStackTrace();
51 | }
52 | }
53 |
54 | @Override
55 | public JSONObject toJson() {
56 | JSONObject json = new JSONObject();
57 | try {
58 | json.put("percent", percent);
59 | json.put("muted", muted);
60 | } catch (JSONException e) {
61 | e.printStackTrace();
62 | }
63 | return json;
64 | }
65 |
66 | public int getPercent() {
67 | return percent;
68 | }
69 |
70 | public void setPercent(int percent) {
71 | this.percent = percent;
72 | }
73 |
74 | public boolean isMuted() {
75 | return muted;
76 | }
77 |
78 | public void setMuted(boolean muted) {
79 | this.muted = muted;
80 | }
81 |
82 | @Override
83 | public String toString() {
84 | return toJson().toString();
85 | }
86 |
87 | @Override
88 | public boolean equals(Object o) {
89 | if (this == o) return true;
90 | if (o == null || getClass() != o.getClass()) return false;
91 |
92 | Volume volume = (Volume) o;
93 |
94 | if (muted != volume.muted) return false;
95 | return percent == volume.percent;
96 |
97 | }
98 |
99 | @Override
100 | public int hashCode() {
101 | int result = (muted ? 1 : 0);
102 | result = 31 * result + percent;
103 | return result;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Snapcast/src/main/res/layout/client_item.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
24 |
25 |
33 |
34 |
40 |
41 |
42 |
53 |
54 |
55 |
61 |
62 |
68 |
69 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/Snapcast/src/main/java/de/badaix/snapcast/control/json/Snapcast.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of snapcast
3 | * Copyright (C) 2014-2018 Johannes Pohl
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package de.badaix.snapcast.control.json;
20 |
21 | import org.json.JSONException;
22 | import org.json.JSONObject;
23 |
24 | import java.util.Objects;
25 |
26 | /**
27 | * Created by johannes on 06.01.16.
28 | */
29 | public class Snapcast implements JsonSerialisable {
30 | String name = "";
31 | String version = "";
32 | int protocolVersion = 1;
33 |
34 | public Snapcast() {
35 | }
36 |
37 | public Snapcast(JSONObject json) {
38 | fromJson(json);
39 | }
40 |
41 | @Override
42 | public void fromJson(JSONObject json) {
43 | try {
44 | name = json.getString("name");
45 | version = json.getString("version");
46 | protocolVersion = json.getInt("protocolVersion");
47 | } catch (JSONException e) {
48 | e.printStackTrace();
49 | }
50 | }
51 |
52 | @Override
53 | public JSONObject toJson() {
54 | JSONObject json = new JSONObject();
55 | try {
56 | json.put("name", name);
57 | json.put("version", version);
58 | json.put("protocolVersion", protocolVersion);
59 | } catch (JSONException e) {
60 | e.printStackTrace();
61 | }
62 | return json;
63 | }
64 |
65 | public String getName() {
66 | return name;
67 | }
68 |
69 | public String getVersion() {
70 | return version;
71 | }
72 |
73 | public int getProtocolVersion() {
74 | return protocolVersion;
75 | }
76 |
77 | @Override
78 | public String toString() {
79 | return toJson().toString();
80 | }
81 |
82 | @Override
83 | public boolean equals(Object o) {
84 | if (this == o) return true;
85 | if (o == null || getClass() != o.getClass()) return false;
86 |
87 | Snapcast that = (Snapcast) o;
88 |
89 | if (!Objects.equals(name, that.name)) return false;
90 | if (!Objects.equals(version, that.version)) return false;
91 | return (protocolVersion == that.protocolVersion);
92 | }
93 |
94 | @Override
95 | public int hashCode() {
96 | int result = name != null ? name.hashCode() : 0;
97 | result = 31 * result + (version != null ? version.hashCode() : 0);
98 | result = 31 * result + protocolVersion;
99 | return result;
100 | }
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/Snapcast/src/main/java/de/badaix/snapcast/control/json/Server.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of snapcast
3 | * Copyright (C) 2014-2018 Johannes Pohl
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package de.badaix.snapcast.control.json;
20 |
21 | import org.json.JSONException;
22 | import org.json.JSONObject;
23 |
24 | import java.util.Objects;
25 |
26 | /**
27 | * Created by johannes on 02.03.16.
28 | */
29 | public class Server implements JsonSerialisable {
30 | private Host host;
31 | private Snapserver snapserver;
32 |
33 | public Server(JSONObject json) {
34 | fromJson(json);
35 | }
36 |
37 | @Override
38 | public void fromJson(JSONObject json) {
39 | try {
40 | if (json.has("host") && !(json.get("host") instanceof String))
41 | host = new Host(json.getJSONObject("host"));
42 | else {
43 | host = new Host();
44 | host.name = json.getString("host");
45 | }
46 |
47 | if (json.has("snapserver"))
48 | snapserver = new Snapserver(json.getJSONObject("snapserver"));
49 | else {
50 | snapserver = new Snapserver();
51 | snapserver.version = json.getString("version");
52 | }
53 | } catch (JSONException e) {
54 | e.printStackTrace();
55 | }
56 | }
57 |
58 | @Override
59 | public JSONObject toJson() {
60 | JSONObject json = new JSONObject();
61 | try {
62 | json.put("host", host.toJson());
63 | json.put("snapserver", snapserver.toJson());
64 | } catch (JSONException e) {
65 | e.printStackTrace();
66 | }
67 | return json;
68 | }
69 |
70 | public Host getHost() {
71 | return host;
72 | }
73 |
74 | public Snapcast getSnapserver() {
75 | return snapserver;
76 | }
77 |
78 | @Override
79 | public String toString() {
80 | return toJson().toString();
81 | }
82 |
83 | @Override
84 | public boolean equals(Object o) {
85 | if (this == o) return true;
86 | if (o == null || getClass() != o.getClass()) return false;
87 |
88 | Server server = (Server) o;
89 |
90 | if (!Objects.equals(host, server.host)) return false;
91 | return Objects.equals(snapserver, server.snapserver);
92 |
93 | }
94 |
95 | @Override
96 | public int hashCode() {
97 | int result = host != null ? host.hashCode() : 0;
98 | result = 31 * result + (snapserver != null ? snapserver.hashCode() : 0);
99 | return result;
100 | }
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | xmlns:android
11 |
12 | ^$
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | xmlns:.*
22 |
23 | ^$
24 |
25 |
26 | BY_NAME
27 |
28 |
29 |
30 |
31 |
32 |
33 | .*:id
34 |
35 | http://schemas.android.com/apk/res/android
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | .*:name
45 |
46 | http://schemas.android.com/apk/res/android
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | name
56 |
57 | ^$
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | style
67 |
68 | ^$
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | .*
78 |
79 | ^$
80 |
81 |
82 | BY_NAME
83 |
84 |
85 |
86 |
87 |
88 |
89 | .*
90 |
91 | http://schemas.android.com/apk/res/android
92 |
93 |
94 | ANDROID_ATTRIBUTE_ORDER
95 |
96 |
97 |
98 |
99 |
100 |
101 | .*
102 |
103 | .*
104 |
105 |
106 | BY_NAME
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/Snapcast/src/main/java/de/badaix/snapcast/control/json/Host.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of snapcast
3 | * Copyright (C) 2014-2018 Johannes Pohl
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package de.badaix.snapcast.control.json;
20 |
21 | import org.json.JSONException;
22 | import org.json.JSONObject;
23 |
24 | import java.util.Objects;
25 |
26 | /**
27 | * Created by johannes on 06.01.16.
28 | */
29 | public class Host implements JsonSerialisable {
30 | String name = "";
31 | String mac = "";
32 | String os = "";
33 | String arch = "";
34 | String ip = "";
35 |
36 | public Host(JSONObject json) {
37 | fromJson(json);
38 | }
39 |
40 | public Host() {
41 |
42 | }
43 |
44 | @Override
45 | public void fromJson(JSONObject json) {
46 | try {
47 | name = json.getString("name");
48 | mac = json.getString("mac");
49 | os = json.getString("os");
50 | arch = json.getString("arch");
51 | ip = json.getString("ip");
52 | } catch (JSONException e) {
53 | e.printStackTrace();
54 | }
55 | }
56 |
57 | @Override
58 | public JSONObject toJson() {
59 | JSONObject json = new JSONObject();
60 | try {
61 | json.put("name", name);
62 | json.put("mac", mac);
63 | json.put("os", os);
64 | json.put("arch", arch);
65 | json.put("ip", ip);
66 | } catch (JSONException e) {
67 | e.printStackTrace();
68 | }
69 | return json;
70 | }
71 |
72 | public String getName() {
73 | return name;
74 | }
75 |
76 | public String getMac() {
77 | return mac;
78 | }
79 |
80 | public String getOs() {
81 | return os;
82 | }
83 |
84 | public String getArch() {
85 | return arch;
86 | }
87 |
88 | public String getIp() {
89 | return ip;
90 | }
91 |
92 | @Override
93 | public String toString() {
94 | return toJson().toString();
95 | }
96 |
97 | @Override
98 | public boolean equals(Object o) {
99 | if (this == o) return true;
100 | if (o == null || getClass() != o.getClass()) return false;
101 |
102 | Host that = (Host) o;
103 |
104 | if (!Objects.equals(name, that.name)) return false;
105 | if (!Objects.equals(mac, that.mac)) return false;
106 | if (!Objects.equals(os, that.os)) return false;
107 | if (!Objects.equals(arch, that.arch)) return false;
108 | return Objects.equals(ip, that.ip);
109 | }
110 |
111 | @Override
112 | public int hashCode() {
113 | int result = name != null ? name.hashCode() : 0;
114 | result = 31 * result + (mac != null ? mac.hashCode() : 0);
115 | result = 31 * result + (os != null ? os.hashCode() : 0);
116 | result = 31 * result + (arch != null ? arch.hashCode() : 0);
117 | result = 31 * result + (ip != null ? ip.hashCode() : 0);
118 | return result;
119 | }
120 | }
121 |
122 |
--------------------------------------------------------------------------------
/Snapcast/src/main/java/de/badaix/snapcast/control/json/ClientConfig.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of snapcast
3 | * Copyright (C) 2014-2018 Johannes Pohl
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package de.badaix.snapcast.control.json;
20 |
21 | import org.json.JSONException;
22 | import org.json.JSONObject;
23 |
24 | import java.util.Objects;
25 |
26 | /**
27 | * Created by johannes on 06.01.16.
28 | */
29 | public class ClientConfig implements JsonSerialisable {
30 | String name = "";
31 | Volume volume;
32 | int latency = 0;
33 | int instance = 1;
34 |
35 | public ClientConfig() {
36 | volume = new Volume();
37 | }
38 |
39 | public ClientConfig(JSONObject json) {
40 | fromJson(json);
41 | }
42 |
43 | @Override
44 | public void fromJson(JSONObject json) {
45 | try {
46 | name = json.getString("name");
47 | volume = new Volume(json.getJSONObject("volume"));
48 | latency = json.getInt("latency");
49 | instance = json.getInt("instance");
50 | } catch (JSONException e) {
51 | e.printStackTrace();
52 | }
53 | }
54 |
55 | @Override
56 | public JSONObject toJson() {
57 | JSONObject json = new JSONObject();
58 | try {
59 | json.put("name", name);
60 | json.put("volume", volume.toJson());
61 | json.put("latency", latency);
62 | json.put("instance", instance);
63 | } catch (JSONException e) {
64 | e.printStackTrace();
65 | }
66 | return json;
67 | }
68 |
69 | public Volume getVolume() {
70 | return volume;
71 | }
72 |
73 | public void setVolume(Volume volume) {
74 | this.volume = volume;
75 | }
76 |
77 | public String getName() {
78 | return name;
79 | }
80 |
81 | public void setName(String name) {
82 | this.name = name;
83 | }
84 |
85 | public int getLatency() {
86 | return latency;
87 | }
88 |
89 | public void setLatency(int latency) {
90 | this.latency = latency;
91 | }
92 |
93 | public int getInstance() {
94 | return instance;
95 | }
96 |
97 | @Override
98 | public String toString() {
99 | return toJson().toString();
100 | }
101 |
102 | @Override
103 | public boolean equals(Object o) {
104 | if (this == o) return true;
105 | if (o == null || getClass() != o.getClass()) return false;
106 |
107 | ClientConfig that = (ClientConfig) o;
108 |
109 | if (latency != that.latency) return false;
110 | if (!Objects.equals(name, that.name)) return false;
111 | if (instance != that.instance) return false;
112 | return Objects.equals(volume, that.volume);
113 |
114 | }
115 |
116 | @Override
117 | public int hashCode() {
118 | int result = name != null ? name.hashCode() : 0;
119 | result = 31 * result + (volume != null ? volume.hashCode() : 0);
120 | result = 31 * result + latency;
121 | result = 31 * result + instance;
122 | return result;
123 | }
124 | }
125 |
126 |
--------------------------------------------------------------------------------
/Snapcast/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
27 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
55 |
56 |
57 |
61 |
64 |
65 |
69 |
72 |
73 |
78 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/Snapcast/src/main/java/de/badaix/snapcast/utils/MD5.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of snapcast
3 | * Copyright (C) 2014-2018 Johannes Pohl
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package de.badaix.snapcast.utils;
20 |
21 | /*
22 | * Copyright (C) 2012 The CyanogenMod Project
23 | *
24 | * * Licensed under the GNU GPLv2 license
25 | *
26 | * The text of the license can be found in the LICENSE file
27 | * or at https://www.gnu.org/licenses/gpl-2.0.txt
28 | */
29 |
30 | import android.text.TextUtils;
31 | import android.util.Log;
32 |
33 | import java.io.File;
34 | import java.io.FileInputStream;
35 | import java.io.FileNotFoundException;
36 | import java.io.IOException;
37 | import java.io.InputStream;
38 | import java.math.BigInteger;
39 | import java.security.MessageDigest;
40 | import java.security.NoSuchAlgorithmException;
41 |
42 | public class MD5 {
43 | private static final String TAG = "MD5";
44 |
45 | public static boolean checkMD5(String md5, File updateFile) {
46 | if (TextUtils.isEmpty(md5) || updateFile == null) {
47 | Log.e(TAG, "MD5 string empty or updateFile null");
48 | return false;
49 | }
50 |
51 | String calculatedDigest = calculateMD5(updateFile);
52 | if (calculatedDigest == null) {
53 | Log.e(TAG, "calculatedDigest null");
54 | return false;
55 | }
56 |
57 | Log.v(TAG, "Calculated digest: " + calculatedDigest);
58 | Log.v(TAG, "Provided digest: " + md5);
59 |
60 | return calculatedDigest.equalsIgnoreCase(md5);
61 | }
62 |
63 | public static String calculateMD5(File updateFile) {
64 | InputStream is;
65 | try {
66 | is = new FileInputStream(updateFile);
67 | } catch (FileNotFoundException e) {
68 | Log.e(TAG, "Exception while getting FileInputStream", e);
69 | return null;
70 | }
71 | try {
72 | return calculateMD5(is);
73 | } finally {
74 | try {
75 | is.close();
76 | } catch (IOException e) {
77 | Log.e(TAG, "Exception on closing MD5 input stream", e);
78 | }
79 | }
80 | }
81 |
82 | public static String calculateMD5(InputStream is) {
83 | MessageDigest digest;
84 | try {
85 | digest = MessageDigest.getInstance("MD5");
86 | } catch (NoSuchAlgorithmException e) {
87 | Log.e(TAG, "Exception while getting digest", e);
88 | return null;
89 | }
90 |
91 | byte[] buffer = new byte[8192];
92 | int read;
93 | try {
94 | while ((read = is.read(buffer)) > 0) {
95 | digest.update(buffer, 0, read);
96 | }
97 | byte[] md5sum = digest.digest();
98 | BigInteger bigInt = new BigInteger(1, md5sum);
99 | String output = bigInt.toString(16);
100 | // Fill to 32 chars
101 | output = String.format("%32s", output).replace(' ', '0');
102 | return output;
103 | } catch (IOException e) {
104 | throw new RuntimeException("Unable to process file for MD5", e);
105 | } finally {
106 | try {
107 | is.reset();
108 | } catch (IOException e) {
109 | Log.e(TAG, "Exception on closing MD5 input stream", e);
110 | }
111 | }
112 | }
113 | }
114 |
115 |
--------------------------------------------------------------------------------
/Snapcast/src/main/java/de/badaix/snapcast/control/json/Stream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of snapcast
3 | * Copyright (C) 2014-2018 Johannes Pohl
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package de.badaix.snapcast.control.json;
20 |
21 | import org.json.JSONException;
22 | import org.json.JSONObject;
23 |
24 | import java.util.Objects;
25 |
26 | /**
27 | * Created by johannes on 06.01.16.
28 | */
29 | public class Stream implements JsonSerialisable {
30 | private StreamUri uri;
31 | private String id;
32 | private Status status;
33 |
34 | public Stream(JSONObject json) {
35 | fromJson(json);
36 | }
37 |
38 | @Override
39 | public void fromJson(JSONObject json) {
40 | try {
41 | if (json.has("uri") && (json.get("uri") instanceof JSONObject)) {
42 | uri = new StreamUri(json.getJSONObject("uri"));
43 | id = json.getString("id");
44 | status = Status.fromString(json.getString("status"));
45 | } else {
46 | uri = new StreamUri(json);
47 | id = json.getString("id");
48 | status = Status.unknown;
49 | }
50 | } catch (JSONException e) {
51 | e.printStackTrace();
52 | }
53 | }
54 |
55 | @Override
56 | public JSONObject toJson() {
57 | JSONObject json = new JSONObject();
58 | try {
59 | json.put("uri", uri.toJson());
60 | json.put("id", id);
61 | json.put("status", status);
62 | } catch (JSONException e) {
63 | e.printStackTrace();
64 | }
65 | return json;
66 | }
67 |
68 | @Override
69 | public boolean equals(Object o) {
70 | if (this == o) return true;
71 | if (o == null || getClass() != o.getClass()) return false;
72 |
73 | Stream stream = (Stream) o;
74 |
75 | if (!Objects.equals(uri, stream.uri)) return false;
76 | if (!Objects.equals(id, stream.id)) return false;
77 | return Objects.equals(status, stream.status);
78 | }
79 |
80 | @Override
81 | public int hashCode() {
82 | int result = uri != null ? uri.hashCode() : 0;
83 | result = 31 * result + (id != null ? id.hashCode() : 0);
84 | result = 31 * result + (status != null ? status.hashCode() : 0);
85 | return result;
86 | }
87 |
88 | public StreamUri getUri() {
89 | return uri;
90 | }
91 |
92 | public void setUri(StreamUri uri) {
93 | this.uri = uri;
94 | }
95 |
96 | public String getId() {
97 | return id;
98 | }
99 |
100 | public void setId(String id) {
101 | this.id = id;
102 | }
103 |
104 | public Status getStatus() {
105 | return status;
106 | }
107 |
108 | public void setStatus(Status status) {
109 | this.status = status;
110 | }
111 |
112 | public String getName() {
113 | return uri.getName();
114 | }
115 |
116 | @Override
117 | public String toString() {
118 | return toJson().toString();
119 | }
120 |
121 | public enum Status {
122 | unknown("unknown"),
123 | idle("idle"),
124 | playing("playing"),
125 | disabled("disabled");
126 |
127 | private final String status;
128 |
129 | Status(String status) {
130 | this.status = status;
131 | }
132 |
133 | public static Status fromString(String status) {
134 | if (status != null) {
135 | for (Status s : Status.values()) {
136 | if (status.equalsIgnoreCase(s.status)) {
137 | return s;
138 | }
139 | }
140 | }
141 | return null;
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |