Compare commits

..

No commits in common. 'master' and 'v0.1' have entirely different histories.
master ... v0.1

3
.gitmodules vendored

@ -1,3 +0,0 @@
[submodule "MpcCore"]
path = MpcCore
url = https://github.com/LEdoian/MpcCore.git

@ -1,20 +0,0 @@
CONF?=/p:Configuration=Release
default: cleanbuild
build:
nuget restore
cd QuickPlay && msbuild /t:Build ${CONF}
deploy: build
cd QuickPlay && msbuild /t:Install ${CONF}
clean:
git clean -fxd
git submodule foreach --recursive git clean -fxd
cleanbuild: clean build
cleandeploy: clean deploy
launch:
adb shell monkey -p cz.ledoian.android.quickplay 1

@ -1 +0,0 @@
Subproject commit 50298b8ca8a2b2256e5af618b8d3ef878c636a7e

@ -5,11 +5,7 @@ VisualStudioVersion = 16.0.31112.23
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickPlay", "QuickPlay\QuickPlay.csproj", "{FDBCCBF8-7CA5-4719-8CBB-8E33C464B27C}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickPlay", "QuickPlay\QuickPlay.csproj", "{FDBCCBF8-7CA5-4719-8CBB-8E33C464B27C}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MpcCore", "MpcCore", "{43A22188-E0FB-4486-9623-10D8A1531AAF}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MpcCore", "..\..\Third-party\mpcCore\src\MpcCore\MpcCore.csproj", "{D0A5AD05-B98C-45E6-B61D-4700F7AA72CF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{242948C1-93ED-4D06-9238-B0634318B449}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MpcCore", "MpcCore\src\MpcCore\MpcCore.csproj", "{4E00B1E4-3141-4564-A333-2E8B5F3B0141}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -27,12 +23,12 @@ Global
{FDBCCBF8-7CA5-4719-8CBB-8E33C464B27C}.Release-Stable|Any CPU.ActiveCfg = Release|Any CPU {FDBCCBF8-7CA5-4719-8CBB-8E33C464B27C}.Release-Stable|Any CPU.ActiveCfg = Release|Any CPU
{FDBCCBF8-7CA5-4719-8CBB-8E33C464B27C}.Release-Stable|Any CPU.Build.0 = Release|Any CPU {FDBCCBF8-7CA5-4719-8CBB-8E33C464B27C}.Release-Stable|Any CPU.Build.0 = Release|Any CPU
{FDBCCBF8-7CA5-4719-8CBB-8E33C464B27C}.Release-Stable|Any CPU.Deploy.0 = Release|Any CPU {FDBCCBF8-7CA5-4719-8CBB-8E33C464B27C}.Release-Stable|Any CPU.Deploy.0 = Release|Any CPU
{4E00B1E4-3141-4564-A333-2E8B5F3B0141}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D0A5AD05-B98C-45E6-B61D-4700F7AA72CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E00B1E4-3141-4564-A333-2E8B5F3B0141}.Debug|Any CPU.Build.0 = Debug|Any CPU {D0A5AD05-B98C-45E6-B61D-4700F7AA72CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E00B1E4-3141-4564-A333-2E8B5F3B0141}.Release|Any CPU.ActiveCfg = Release|Any CPU {D0A5AD05-B98C-45E6-B61D-4700F7AA72CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E00B1E4-3141-4564-A333-2E8B5F3B0141}.Release|Any CPU.Build.0 = Release|Any CPU {D0A5AD05-B98C-45E6-B61D-4700F7AA72CF}.Release|Any CPU.Build.0 = Release|Any CPU
{4E00B1E4-3141-4564-A333-2E8B5F3B0141}.Release-Stable|Any CPU.ActiveCfg = Debug|Any CPU {D0A5AD05-B98C-45E6-B61D-4700F7AA72CF}.Release-Stable|Any CPU.ActiveCfg = Release|Any CPU
{4E00B1E4-3141-4564-A333-2E8B5F3B0141}.Release-Stable|Any CPU.Build.0 = Debug|Any CPU {D0A5AD05-B98C-45E6-B61D-4700F7AA72CF}.Release-Stable|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -40,8 +36,4 @@ Global
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {36EE9F92-9FC0-4EEE-A3AA-9EE43A4E2B86} SolutionGuid = {36EE9F92-9FC0-4EEE-A3AA-9EE43A4E2B86}
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{242948C1-93ED-4D06-9238-B0634318B449} = {43A22188-E0FB-4486-9623-10D8A1531AAF}
{4E00B1E4-3141-4564-A333-2E8B5F3B0141} = {242948C1-93ED-4D06-9238-B0634318B449}
EndGlobalSection
EndGlobal EndGlobal

@ -65,8 +65,7 @@ namespace QuickPlay
// Make sure that the configuration is same // Make sure that the configuration is same
var newConfig = AppConfiguration.loadSavedConfiguration(); var newConfig = AppConfiguration.loadSavedConfiguration();
// FIXME: Broken? Throws falsely. if (this != newConfig) throw new InvalidDataException("Saved configuration is different from the supplied one.");
//if (this != newConfig) throw new InvalidDataException("Saved configuration is different from the supplied one.");
} }
[NonSerialized] [NonSerialized]
@ -85,12 +84,11 @@ namespace QuickPlay
string filename = playerConfigUrl.Substring("file://".Length); string filename = playerConfigUrl.Substring("file://".Length);
var reader = new StreamReader(filename); var reader = new StreamReader(filename);
cfgProvider = new FileConfigurationProvider(reader); cfgProvider = new FileConfigurationProvider(reader);
} else if (playerConfigUrl == "dummy") { } else if (playerConfigUrl == "default") {
cfgProvider = new DummyConfigurationProvider(); cfgProvider = new DummyConfigurationProvider();
} else } else
{ {
Android.Widget.Toast.MakeText(Android.App.Application.Context, "Schema of player config URL not supported, using dummy", Android.Widget.ToastLength.Short).Show(); throw new InvalidOperationException("Schema of player config URL not supported");
cfgProvider = new DummyConfigurationProvider();
} }
PlayerConfiguration playercfg; PlayerConfiguration playercfg;
try try

@ -3,11 +3,9 @@ using Android.App;
using Android.OS; using Android.OS;
using Android.Views; using Android.Views;
using Android.Widget; using Android.Widget;
using Android.Content;
using Android.Support.V7.App; using Android.Support.V7.App;
using Toolbar = Android.Support.V7.Widget.Toolbar; using Toolbar = Android.Support.V7.Widget.Toolbar;
using GridLayoutManager = Android.Support.V7.Widget.GridLayoutManager; using GridLayoutManager = Android.Support.V7.Widget.GridLayoutManager;
using Android.Runtime;
namespace QuickPlay namespace QuickPlay
{ {
@ -27,6 +25,15 @@ namespace QuickPlay
appConfig = AppConfiguration.loadConfiguration(); appConfig = AppConfiguration.loadConfiguration();
var playerConfig = await appConfig.GetPlayerConfig(); var playerConfig = await appConfig.GetPlayerConfig();
currentPlayer = playerConfig.GetPlayer(); currentPlayer = playerConfig.GetPlayer();
try
{
await currentPlayer.ConnectAsync();
} catch (CannotConnectException e)
{
//TODO: View a toast with details and change some colors?
var t = Toast.MakeText(this, e.Message + ": " + e.InnerException.Message ?? "", ToastLength.Long);
t.Show();
}
// UI initialization // UI initialization
SetContentView(Resource.Layout.activity_main); SetContentView(Resource.Layout.activity_main);
@ -44,7 +51,8 @@ namespace QuickPlay
// Since this is rather complicated, it is in a separate method // Since this is rather complicated, it is in a separate method
InitializeRecyclerView(); InitializeRecyclerView();
// I don't know why, but this needs to be here in order to show correct colors and player name. // FIXME: This should be in OnResume...
// Refresh player info
OnPlayerUpdate(); OnPlayerUpdate();
} }
@ -69,33 +77,9 @@ namespace QuickPlay
{ {
if (item.ItemId == Resource.Id.action_settings) if (item.ItemId == Resource.Id.action_settings)
{ {
// Set player config URL // Show the play bar
var b = new Android.Support.V7.App.AlertDialog.Builder(this); var bar = FindViewById(Resource.Id.currentSongBar);
b.SetTitle("Player config URL"); bar.Visibility = ViewStates.Visible;
var input = new EditText(this);
input.Text = appConfig.playerConfigUrl;
b.SetView(input);
b.SetPositiveButton("Set", delegate
{
string text = input.Text;
appConfig.playerConfigUrl = text;
appConfig.saveConfiguration();
Toast.MakeText(this, "Configuration saved, reloading", ToastLength.Short).Show();
var i = new Intent(this, typeof(MainActivity));
StartActivity(i);
});
b.SetNegativeButton("Scan QR", delegate {
try
{
var i = new Intent("com.google.zxing.client.android.SCAN");
StartActivityForResult(i, 0);
} catch (ActivityNotFoundException) {
Toast.MakeText(this, "You need ZXing Barcode scanner for this", ToastLength.Long).Show();
}
});
b.SetCancelable(true);
b.Show();
} }
if (item.ItemId == Resource.Id.action_edit) if (item.ItemId == Resource.Id.action_edit)
@ -151,51 +135,5 @@ namespace QuickPlay
{ {
await currentPlayer.Play(song); await currentPlayer.Play(song);
} }
protected new async void OnResume()
{
base.OnResume();
// Reconnect the player
try
{
await currentPlayer.ConnectAsync();
} catch (CannotConnectException e)
{
//TODO: View a toast with details and change some colors?
var t = Toast.MakeText(this, e.Message + ": " + e.InnerException.Message ?? "", ToastLength.Long);
t.Show();
}
// Refresh player info
OnPlayerUpdate();
}
protected new void OnDestroy()
{
base.OnDestroy();
this.appConfig.saveConfiguration();
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == 0)
{
if (resultCode == Result.Ok)
{
string url = data.GetStringExtra("SCAN_RESULT");
var cfg = new AppConfiguration { playerConfigUrl = url };
cfg.saveConfiguration();
Toast.MakeText(this, "Configuration saved, reloading", ToastLength.Short).Show();
var i = new Intent(this, typeof(MainActivity));
StartActivity(i);
}
else if (resultCode == Result.Canceled)
{
Toast.MakeText(this, "Could not load QR code", ToastLength.Short).Show();
}
}
}
} }
} }

@ -21,8 +21,6 @@ namespace QuickPlay
} } } }
public async Task Play(IPlayable song) public async Task Play(IPlayable song)
{ {
// Make sure we are connected
await this.ConnectAsync();
await mpd.SendAsync(new MpcCore.Commands.Player.Stop()); await mpd.SendAsync(new MpcCore.Commands.Player.Stop());
await SetReasonableOptions(); await SetReasonableOptions();
await mpd.SendAsync(new MpcCore.Commands.Queue.ClearQueue()); await mpd.SendAsync(new MpcCore.Commands.Queue.ClearQueue());
@ -31,8 +29,6 @@ namespace QuickPlay
} }
public async Task SetReasonableOptions() public async Task SetReasonableOptions()
{ {
// Make sure we are connected
await this.ConnectAsync();
await mpd.SendAsync(new MpcCore.Commands.Options.SetConsume(true)); await mpd.SendAsync(new MpcCore.Commands.Options.SetConsume(true));
await mpd.SendAsync(new MpcCore.Commands.Options.SetRandom(false)); await mpd.SendAsync(new MpcCore.Commands.Options.SetRandom(false));
await mpd.SendAsync(new MpcCore.Commands.Options.SetRepeat(false)); await mpd.SendAsync(new MpcCore.Commands.Options.SetRepeat(false));

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="2" android:versionName="0.2" package="cz.ledoian.android.quickplay" android:installLocation="auto"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="0.1" package="cz.ledoian.android.quickplay" android:installLocation="auto">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30" /> <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"></application> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"></application>

@ -42,7 +42,7 @@
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot> <AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
<BundleAssemblies>false</BundleAssemblies> <BundleAssemblies>false</BundleAssemblies>
<AndroidKeyStore>false</AndroidKeyStore> <AndroidKeyStore>false</AndroidKeyStore>
<AndroidUseAapt2>true</AndroidUseAapt2> <AndroidUseAapt2>false</AndroidUseAapt2>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType> <AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
@ -134,6 +134,12 @@
<Version>28.0.0.3</Version> <Version>28.0.0.3</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Third-party\mpcCore\src\MpcCore\MpcCore.csproj">
<Project>{d0a5ad05-b98c-45e6-b61d-4700f7aa72cf}</Project>
<Name>MpcCore</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup> <ItemGroup>
<AndroidResource Include="Resources\drawable-mdpi\material_settings.png" /> <AndroidResource Include="Resources\drawable-mdpi\material_settings.png" />
</ItemGroup> </ItemGroup>
@ -229,9 +235,6 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
</AndroidResource> </AndroidResource>
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MpcCore\src\MpcCore\MpcCore.csproj" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
@ -240,4 +243,4 @@
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
</Project> </Project>

@ -14,7 +14,7 @@ namespace QuickPlay
{ {
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.2.99.85")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
public partial class Resource public partial class Resource
{ {

@ -99,8 +99,8 @@ the shown name.
Planned Features Planned Features
==== ====
- [x] Working application configuration - [ ] Working application configuration
- [x] Player configuration sharing via QR codes - [ ] Player configuration sharing via QR codes
- [ ] Showing current song progress - [ ] Showing current song progress
- [ ] Possibility to stop current song - [ ] Possibility to stop current song
- [ ] MPD player monitoring service - [ ] MPD player monitoring service
@ -109,20 +109,9 @@ Planned Features
Bugs Bugs
--- ---
None currently known :-) - [ ] The MpcCore client seems to disconnect after some time for no apparent reason.
The far future The far future
--- ---
- [ ] Editing the player config right on the device (with INI file export) - [ ] Editing the player config right on the device (with INI file export)
- [ ] Tests. (The code should be testable, but no tests has been written yet.) - [ ] Tests. (The code should be testable, but no tests has been written yet.)
Contributing
====
Please write me an [email](mailto:quickplaygt@pokemon.ledoian.cz), similar to how Linux kernel development works. In
the unlikely case you have an account on my Gitea you may submit issues and
pull requests directly.
(I would like to allow for some kind of federated Gitea accounts like
[ForgeFed](https://github.com/forgefed), but that is not possible at the
moment.)

@ -1,23 +0,0 @@
# Here are several notes on how I managed to get xamarin.android working.
# OS: Linux Mint 20.3 Mate x86_64
# Downloaded Android Studio, Added Mono, installed a lot of random packages, Android SDKs, etc.
# Xamarin.Android taken from the pipeline artifact
# Did some weird hacks with environment (TODO)
# Did awful things to the dotnet instalation:
# SO: https://askubuntu.com/questions/1177970/how-to-develop-for-android-with-xamarin
for x in /usr/lib/xamarin.android/xbuild/*; do ln -s $x /usr/share/dotnet/sdk/6.0.200/`basename $x`; done
for x in /usr/lib/xamarin.android/xbuild-frameworks/*; do ln -s $x /usr/share/dotnet/packs/`basename $x`; done
# Found some commands to be semi-working
dotnet build -p:TargetFrameworkRootPath=/usr/lib/xamarin.android/xbuild-frameworks/
nuget restore # In solution directory
msbuild /t:Build,Install # In project directory
# Launch yourself...
clean:
git clean -fxd `git rev-parse --show-toplevel`
git submodule foreach --recursive git clean -fxd

@ -1,59 +0,0 @@
---
type: slide
---
# QuickPlay
## A simple app to quickly use remote players
---
# Features
Easy to learn
Central management
(Theoretically) extensible / modular
Usable on Android 4.4+ (Tested on Marshmallow)
(Android only)
---
# Architecture
```graphviz
digraph "Arch" {
layout=fdp
RP -> Player [style="dotted", dir="both"];
RP[label="Remote player"]
XML -> AppConfig -> PlayerConfig -> {Player Playables HTTP};
UI -> LayoutStrategies;
Person -> UI -> Playables -> Metadata
Metadata -> LayoutStrategies
HTTP[label="HTTP + INI"]
Person, RP, UI, XML, HTTP [shape=box]
}
```
---
# Issues
Understanding Android ecosystem is hard
- unintuitive naming
- complex hierarchy of support libraries
- I didn't find comphrehensive overview
- many APIs are replaced
Quite a lot of boilerplate code (esp. UI)
Visual Studio not refreshing Resource files.
No sensible MPD client library.
---
# Lessons learned
- Basic Android architecture knowledge
- Network and asynchronous coding
- Library patching

@ -1,17 +0,0 @@
#!/bin/bash
set -euo pipefail
# This script takes current playlist as `mpc` sees it and converts it into
# QuickPlay INI sections.
# Use at own risk :-)
mpc -f '[[%artist% - ]%title%]#|%file%' playlist | while IFS='|' read title file; do
if test -z "$title"; then
title="$(basename "$file")"
fi
# Unfortunately, the "time" attribute is currently required
echo -e "[$title]\npath = $file\ntime = 2:00\n"
done
Loading…
Cancel
Save