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

@ -65,8 +65,7 @@ namespace QuickPlay
// Make sure that the configuration is same
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]
@ -85,12 +84,11 @@ namespace QuickPlay
string filename = playerConfigUrl.Substring("file://".Length);
var reader = new StreamReader(filename);
cfgProvider = new FileConfigurationProvider(reader);
} else if (playerConfigUrl == "dummy") {
} else if (playerConfigUrl == "default") {
cfgProvider = new DummyConfigurationProvider();
} else
{
Android.Widget.Toast.MakeText(Android.App.Application.Context, "Schema of player config URL not supported, using dummy", Android.Widget.ToastLength.Short).Show();
cfgProvider = new DummyConfigurationProvider();
throw new InvalidOperationException("Schema of player config URL not supported");
}
PlayerConfiguration playercfg;
try

@ -3,11 +3,9 @@ using Android.App;
using Android.OS;
using Android.Views;
using Android.Widget;
using Android.Content;
using Android.Support.V7.App;
using Toolbar = Android.Support.V7.Widget.Toolbar;
using GridLayoutManager = Android.Support.V7.Widget.GridLayoutManager;
using Android.Runtime;
namespace QuickPlay
{
@ -27,6 +25,15 @@ namespace QuickPlay
appConfig = AppConfiguration.loadConfiguration();
var playerConfig = await appConfig.GetPlayerConfig();
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
SetContentView(Resource.Layout.activity_main);
@ -44,7 +51,8 @@ namespace QuickPlay
// Since this is rather complicated, it is in a separate method
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();
}
@ -69,33 +77,9 @@ namespace QuickPlay
{
if (item.ItemId == Resource.Id.action_settings)
{
// Set player config URL
var b = new Android.Support.V7.App.AlertDialog.Builder(this);
b.SetTitle("Player config URL");
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();
// Show the play bar
var bar = FindViewById(Resource.Id.currentSongBar);
bar.Visibility = ViewStates.Visible;
}
if (item.ItemId == Resource.Id.action_edit)
@ -151,51 +135,5 @@ namespace QuickPlay
{
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)
{
// Make sure we are connected
await this.ConnectAsync();
await mpd.SendAsync(new MpcCore.Commands.Player.Stop());
await SetReasonableOptions();
await mpd.SendAsync(new MpcCore.Commands.Queue.ClearQueue());
@ -31,8 +29,6 @@ namespace QuickPlay
}
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.SetRandom(false));
await mpd.SendAsync(new MpcCore.Commands.Options.SetRepeat(false));

@ -1,5 +1,5 @@
<?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-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>

@ -42,7 +42,7 @@
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
<BundleAssemblies>false</BundleAssemblies>
<AndroidKeyStore>false</AndroidKeyStore>
<AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidUseAapt2>false</AndroidUseAapt2>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
@ -134,6 +134,12 @@
<Version>28.0.0.3</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Third-party\mpcCore\src\MpcCore\MpcCore.csproj">
<Project>{d0a5ad05-b98c-45e6-b61d-4700f7aa72cf}</Project>
<Name>MpcCore</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-mdpi\material_settings.png" />
</ItemGroup>
@ -229,9 +235,6 @@
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MpcCore\src\MpcCore\MpcCore.csproj" />
</ItemGroup>
<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.
Other similar extension points exist, see Microsoft.Common.targets.
@ -240,4 +243,4 @@
<Target Name="AfterBuild">
</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
{

@ -99,8 +99,8 @@ the shown name.
Planned Features
====
- [x] Working application configuration
- [x] Player configuration sharing via QR codes
- [ ] Working application configuration
- [ ] Player configuration sharing via QR codes
- [ ] Showing current song progress
- [ ] Possibility to stop current song
- [ ] MPD player monitoring service
@ -109,20 +109,9 @@ Planned Features
Bugs
---
None currently known :-)
- [ ] The MpcCore client seems to disconnect after some time for no apparent reason.
The far future
---
- [ ] 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.)
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