diff --git a/QuickPlay/Configuration.cs b/QuickPlay/Configuration.cs index 4f06c9f..d681e31 100644 --- a/QuickPlay/Configuration.cs +++ b/QuickPlay/Configuration.cs @@ -71,9 +71,9 @@ namespace QuickPlay [NonSerialized] public static readonly AppConfiguration defaultConfiguration = new AppConfiguration { - playerConfigUrl = "file:///dev/null", + playerConfigUrl = "http://www.ms.mff.cuni.cz/~turinskp/znelky.ini", }; - public PlayerConfiguration GetPlayerConfig() + public async Task GetPlayerConfig() { IPlayerConfigurationProvider cfgProvider; if (playerConfigUrl.StartsWith("http:") || playerConfigUrl.StartsWith("https:")) @@ -84,11 +84,13 @@ namespace QuickPlay string filename = playerConfigUrl.Substring("file://".Length); var reader = new StreamReader(filename); cfgProvider = new FileConfigurationProvider(reader); + } else if (playerConfigUrl == "default") { + cfgProvider = new DummyConfigurationProvider(); } else { throw new InvalidOperationException("Schema of player config URL not supported"); } - return cfgProvider.GetConfigurationAsync().Result; // Bad code, but hopefully we dont hang. + return await cfgProvider.GetConfigurationAsync(); } // We want to compare by values. @@ -116,10 +118,10 @@ namespace QuickPlay /// public class PlayerConfiguration { - public Dictionary songs; - public string playerName; + public Dictionary songs = new Dictionary(); + public string playerName = ""; public PlayerType playerType; - public string playerConnectionDetails; // This is opaque to this class, the player will make sense of it. + public string playerConnectionDetails = ""; // This is opaque to this class, the player will make sense of it. public static PlayerConfiguration FromFile(StreamReader reader) { @@ -199,7 +201,15 @@ namespace QuickPlay sealed class DummyConfigurationProvider : IPlayerConfigurationProvider { - public Task GetConfigurationAsync() => null; + public Task GetConfigurationAsync() + { + PlayerConfiguration cfg = new PlayerConfiguration(); + cfg.playerType = PlayerType.MPD; + cfg.playerName = "Dummy player"; + cfg.playerConnectionDetails = "127.0.0.1"; + + return Task.FromResult(cfg); + } } class Song: IPlayable diff --git a/QuickPlay/Interfaces.cs b/QuickPlay/Interfaces.cs index 8f0b6f5..9b35594 100644 --- a/QuickPlay/Interfaces.cs +++ b/QuickPlay/Interfaces.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; +using Android.Content; // MPD client abstractions and simplifications namespace QuickPlay @@ -16,6 +18,17 @@ namespace QuickPlay float CurrentProgress { get; } bool IsReady { get; } void SetReasonableOptions(); //TODO + /// + /// Attach to the real player. + /// + /// Since this operation can be asynchronous, we cannot put it in th + /// constructor. And this allows for some tweaks before connecting. + /// + /// + /// Context to run possible services from. + /// + /// + Task ConnectAsync(Context context); } /// /// A simple dataclass to hold auxiliary data of the Playable objects. @@ -45,4 +58,11 @@ namespace QuickPlay ILayout LayOut(ICollection playables); } + public class CannotConnectException: Exception + { + public CannotConnectException(string msg, Exception e) : base(msg, e) { } + public CannotConnectException(string msg) : base(msg) { } + public CannotConnectException() : base() { } + } + } \ No newline at end of file diff --git a/QuickPlay/MainActivity.cs b/QuickPlay/MainActivity.cs index 9e2fd28..0d1bc87 100644 --- a/QuickPlay/MainActivity.cs +++ b/QuickPlay/MainActivity.cs @@ -29,14 +29,25 @@ namespace QuickPlay private Android.Support.V7.Widget.RecyclerView recyclerView; private IPlayer currentPlayer; - protected override void OnCreate(Bundle savedInstanceState) + protected override async void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); // App initialization appConfig = AppConfiguration.loadConfiguration(); - currentPlayer = appConfig.GetPlayerConfig().GetPlayer(); + var playerConfig = await appConfig.GetPlayerConfig(); + currentPlayer = playerConfig.GetPlayer(); + try + { + await currentPlayer.ConnectAsync(this); + } catch (CannotConnectException e) + { + //TODO: View a toast with details and change some colors? + Console.WriteLine("This is bad."); + var t = Toast.MakeText(this, e.Message + ": " + e.InnerException.Message ?? "", ToastLength.Long); + t.Show(); + } // UI initialization SetContentView(Resource.Layout.activity_main); @@ -87,5 +98,17 @@ namespace QuickPlay } return base.OnOptionsItemSelected(item); } + + /// + /// A callback for all player updates. + /// + /// The update should be easy on resources, so no need to have separate + /// partial updates. This simply performs full update of the activity + /// state and views. + /// + public void OnPlayerUpdate() + { + throw new NotImplementedException("Activity should update."); + } } } diff --git a/QuickPlay/MpdMonitorService.cs b/QuickPlay/MpdMonitorService.cs index bef31d7..6de4936 100644 --- a/QuickPlay/MpdMonitorService.cs +++ b/QuickPlay/MpdMonitorService.cs @@ -11,7 +11,7 @@ using System.Text; namespace QuickPlay { - [Service(Exported = true, Name = "cz.ledoian.quickplay.mpdmonior")] + [Service] class MpdMonitorService : Service { public override void OnCreate() diff --git a/QuickPlay/MpdPlayer.cs b/QuickPlay/MpdPlayer.cs index 6cba223..c91d894 100644 --- a/QuickPlay/MpdPlayer.cs +++ b/QuickPlay/MpdPlayer.cs @@ -6,11 +6,20 @@ using Android.Views; using Android.Widget; using System; using System.Collections.Generic; +using System.Threading.Tasks; +using System.Threading; +using MpcCore; +using System.Net.Sockets; namespace QuickPlay { class MpdPlayer: IPlayer { + MpcCoreClient mpd; + Thread serviceThread; + // MpcCore uses strings, so be it + string mpdIP, mpdPort; + public Dictionary Songs { get; private set; } public float CurrentProgress { get { throw new NotImplementedException(); @@ -29,8 +38,34 @@ namespace QuickPlay } public MpdPlayer(PlayerConfiguration cfg) { - //TODO + // Populate known fields/properties Songs = cfg.songs; + // NOTE: We separate the port by '@', since ':' could be part of IPv6 and we do not want to complicate parsing. + var connDetails = cfg.playerConnectionDetails.Split('@'); + if (connDetails.Length > 2) throw new InvalidOperationException("Bad connection details"); + mpdIP = connDetails[0]; + mpdPort = connDetails.Length >=2 ? connDetails[1] : "6600"; // XXX: Unneccessary default here... + // Connecting and monitoring remote player is done in ConnectAsync. + } + public async Task ConnectAsync(Context ctx) + { + // Create a persistent connection + var conn = new MpcCoreConnection(mpdIP, mpdPort); + mpd = new MpcCoreClient(conn); + try + { + await mpd.ConnectAsync(); + } catch (SocketException e) + { + throw new CannotConnectException("MPD connect failed", e); + } + // Start the monitoring service + Console.WriteLine("Hello! Will run thr."); + serviceThread = new Thread(() => + { + var intent = new Intent(ctx, typeof(MpdMonitorService)); + }); + Console.WriteLine("Thread possibly started"); } } } \ No newline at end of file