From 48d0ffa77d4dcb15988f9a96294f0ee936bacf24 Mon Sep 17 00:00:00 2001 From: watsonb8 Date: Fri, 31 Jan 2020 20:41:45 -0500 Subject: [PATCH] Adding dependency injection --- Aurora.gtk/Aurora.gtk.csproj | 6 +- Aurora.gtk/packages.config | 3 +- Aurora.test/Aurora.test.csproj | 1 + .../ControllerTests/MediaControllerTest.cs | 29 ++++ .../ControllerTests/MembersControllerTest.cs | 45 ++++++ Aurora/App.xaml.cs | 36 ++++- Aurora/Aurora.csproj | 1 + Aurora/Design/Views/MainView/MainView.xaml.cs | 14 +- Aurora/Design/Views/Party/PartyViewModel.cs | 26 ++-- .../Design/Views/Profile/ProfileViewModel.cs | 14 +- Aurora/Proto/party.v2.proto | 28 +++- Aurora/RemoteImpl/RemoteSyncImpl.cs | 51 ++++--- .../Services/ClientService/ClientService.cs | 23 +-- .../Services/ClientService/IClientService.cs | 42 ++++++ Aurora/Services/Player/IPlayer.cs | 64 ++++++++ .../MediaChangedEvent.cs | 2 +- Aurora/Services/Player/PlaybackState.cs | 12 ++ .../PlaybackStateChangedEvent.cs | 2 +- .../PlayerService.cs | 4 +- .../Services/PlayerService/PlaybackState.cs | 9 -- .../Server/Controllers/Constructor.cs | 38 +++++ .../Server/Controllers/MediaController.cs | 139 +++++++++++++++++- .../Server/Controllers/MemberController.cs | 10 +- .../Server/Controllers/PartyController.cs | 24 --- Aurora/Services/ServerService.cs | 10 +- Aurora/Services/Settings/ISettingsService.cs | 27 ++++ .../{ => Settings}/SettingsService.cs | 4 +- 27 files changed, 550 insertions(+), 114 deletions(-) create mode 100644 Aurora.test/ControllerTests/MediaControllerTest.cs create mode 100644 Aurora/Services/ClientService/IClientService.cs create mode 100644 Aurora/Services/Player/IPlayer.cs rename Aurora/Services/{PlayerService => Player}/MediaChangedEvent.cs (92%) create mode 100644 Aurora/Services/Player/PlaybackState.cs rename Aurora/Services/{PlayerService => Player}/PlaybackStateChangedEvent.cs (92%) rename Aurora/Services/{PlayerService => Player}/PlayerService.cs (98%) delete mode 100644 Aurora/Services/PlayerService/PlaybackState.cs create mode 100644 Aurora/Services/Server/Controllers/Constructor.cs create mode 100644 Aurora/Services/Settings/ISettingsService.cs rename Aurora/Services/{ => Settings}/SettingsService.cs (95%) diff --git a/Aurora.gtk/Aurora.gtk.csproj b/Aurora.gtk/Aurora.gtk.csproj index 34acb22..d80583a 100644 --- a/Aurora.gtk/Aurora.gtk.csproj +++ b/Aurora.gtk/Aurora.gtk.csproj @@ -150,7 +150,7 @@ ..\packages\System.Threading.Tasks.Extensions.4.5.2\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll - ..\packages\Microsoft.Bcl.AsyncInterfaces.1.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll ..\packages\System.Linq.Async.4.0.0\lib\net461\System.Linq.Async.dll @@ -164,6 +164,10 @@ ..\packages\CarouselView.FormsPlugin.5.2.0\lib\netstandard2.0\CarouselView.FormsPlugin.Abstractions.dll + + ..\packages\Autofac.5.0.0\lib\net461\Autofac.dll + + diff --git a/Aurora.gtk/packages.config b/Aurora.gtk/packages.config index 652707f..45a8c07 100644 --- a/Aurora.gtk/packages.config +++ b/Aurora.gtk/packages.config @@ -1,5 +1,6 @@  + @@ -13,7 +14,7 @@ - + diff --git a/Aurora.test/Aurora.test.csproj b/Aurora.test/Aurora.test.csproj index 9b7378f..8aafe1a 100644 --- a/Aurora.test/Aurora.test.csproj +++ b/Aurora.test/Aurora.test.csproj @@ -13,6 +13,7 @@ all + diff --git a/Aurora.test/ControllerTests/MediaControllerTest.cs b/Aurora.test/ControllerTests/MediaControllerTest.cs new file mode 100644 index 0000000..902b672 --- /dev/null +++ b/Aurora.test/ControllerTests/MediaControllerTest.cs @@ -0,0 +1,29 @@ +using NUnit.Framework; +using Aurora.Proto.PartyV2; +using Aurora.Services.Server; +using Grpc.Core; +using System.Threading.Tasks; +using System.Linq; + +namespace Aurora.test.ControllerTests +{ + public class MediaControllerTests + { + private RemotePartyService.RemotePartyServiceClient _remotePartyService; + private Channel _channel; + [SetUp] + public void Setup() + { + ServerService.Instance.Start("testParty", "asdf"); + _channel = new Channel(string.Format("{0}:{1}", ServerService.GetLocalIPAddress(), 8080), ChannelCredentials.Insecure); + _remotePartyService = new RemotePartyService.RemotePartyServiceClient(_channel); + } + + [TearDown] + public async Task TearDown() + { + await ServerService.Instance.Stop(); + await _channel.ShutdownAsync(); + } + } +} \ No newline at end of file diff --git a/Aurora.test/ControllerTests/MembersControllerTest.cs b/Aurora.test/ControllerTests/MembersControllerTest.cs index d9122d5..0671801 100644 --- a/Aurora.test/ControllerTests/MembersControllerTest.cs +++ b/Aurora.test/ControllerTests/MembersControllerTest.cs @@ -139,5 +139,50 @@ namespace Aurora.test.ControllerTests Assert.AreEqual(resp.Members.Count, 5); Assert.False(resp.Members.Any(member => member.UserName == "Ke$sha")); } + + static object[] PagingCases = + { + new object[] {"Tupac", "Aubrey Grahm", "Beyonce Knowls", "Ke$ha", "A$ap Ferg", "asdf", "sdfa", "dfas", "fasd"}, + }; + + [Test] + [TestCaseSource("PagingCases")] + public void MemberPagingTest(object[] members) + { + foreach (string name in members) + { + Member member = _remotePartyService.CreateMember(new CreateMemberRequest() + { + Parent = "party1", + Member = new Member() + { + UserName = name + } + }); + + Assert.NotNull(member); + } + + //List members + ListMembersResponse resp = _remotePartyService.ListMembers(new ListMembersRequest() + { + Parent = "party1", + PageSize = 2, + }); + + string nextPageToken = resp.NextPageToken; + + Assert.AreEqual(resp.Members.Count, 2); + + //List members + resp = _remotePartyService.ListMembers(new ListMembersRequest() + { + Parent = "party1", + PageSize = 2, + PageToken = nextPageToken, + }); + + Assert.AreEqual(resp.Members.Count, 2); + } } } \ No newline at end of file diff --git a/Aurora/App.xaml.cs b/Aurora/App.xaml.cs index cc5f24c..ca655b9 100644 --- a/Aurora/App.xaml.cs +++ b/Aurora/App.xaml.cs @@ -1,19 +1,51 @@ using System; using Aurora.Design.Views.Main; +using Aurora.Design.Views.Albums; +using Aurora.Design.Views.Artists; +using Aurora.Design.Views.Party; +using Aurora.Design.Views.Profile; +using Aurora.Design.Views.Songs; +using Aurora.Design.Views.Stations; +using Aurora.Services.ClientService; +using Autofac; using LibVLCSharp.Shared; using Xamarin.Forms; -using Xamarin.Forms.Xaml; +using Aurora.Services.Player; +using Aurora.Services.Settings; namespace Aurora { public partial class App : Application { + private static IContainer _container; public App() { InitializeComponent(); Core.Initialize(); - MainPage = new MainView(); + //Register DI + ContainerBuilder _builder = new ContainerBuilder(); + // _builder.RegisterInstance(new PlayerService()).SingleInstance(); + _builder.RegisterType().As().SingleInstance(); + _builder.RegisterType().As().SingleInstance(); + _builder.RegisterType().As().SingleInstance(); + _builder.RegisterType().SingleInstance(); + _builder.RegisterType(); + _builder.RegisterType(); + _builder.RegisterType(); + _builder.RegisterType(); + _builder.RegisterType(); + _builder.RegisterType(); + + // _builder.RegisterInstance(new SettingsService()).SingleInstance(); + _container = _builder.Build(); + + MainPage = _container.Resolve(); + } + + public static IContainer Container + { + get { return _container; } } protected override void OnStart() diff --git a/Aurora/Aurora.csproj b/Aurora/Aurora.csproj index 2301ec8..85cc85a 100644 --- a/Aurora/Aurora.csproj +++ b/Aurora/Aurora.csproj @@ -23,6 +23,7 @@ + diff --git a/Aurora/Design/Views/MainView/MainView.xaml.cs b/Aurora/Design/Views/MainView/MainView.xaml.cs index a008571..67d7f16 100644 --- a/Aurora/Design/Views/MainView/MainView.xaml.cs +++ b/Aurora/Design/Views/MainView/MainView.xaml.cs @@ -9,8 +9,8 @@ using Xamarin.Forms; using Xamarin.Forms.Xaml; using Aurora.Models.Media; using Aurora.Design.Components.MediaPlayer; -using Aurora.Services.PlayerService; -using System.Threading; +using Aurora.Services.Player; +using Autofac; namespace Aurora.Design.Views.Main { @@ -38,10 +38,10 @@ namespace Aurora.Design.Views.Main private Dictionary _viewModels; private BaseViewModel _lastViewModel; private Player _playerComponent; - private PlayerService _playerService; + private IPlayer _playerService; private ContentPresenter _viewContent; - public MainView() + public MainView(IPlayer player) { InitializeComponent(); BindingContext = new MainViewModel(); @@ -50,7 +50,7 @@ namespace Aurora.Design.Views.Main _playerComponent = Player; _viewContent = (ContentPresenter)Content.FindByName("ViewContent"); - _playerService = PlayerService.Instance; + _playerService = player; MasterPage.ListView.ItemSelected += OnNavItemSelected; @@ -89,7 +89,7 @@ namespace Aurora.Design.Views.Main } //Instantiate new view model - vm = (BaseViewModel)Activator.CreateInstance(item.TargetViewModelType); + vm = (BaseViewModel)App.Container.Resolve(item.TargetViewModelType); //Activator.CreateInstance(item.TargetViewModelType); _viewModels.Add(item.Id, vm); } @@ -135,7 +135,7 @@ namespace Aurora.Design.Views.Main else { //Instantiate new view model - vm = (BaseViewModel)Activator.CreateInstance(firstNavItem.TargetViewModelType); + vm = (BaseViewModel)App.Container.Resolve(firstNavItem.TargetViewModelType); //(BaseViewModel)Activator.CreateInstance(firstNavItem.TargetViewModelType); _viewModels.Add(firstNavItem.Id, vm); } diff --git a/Aurora/Design/Views/Party/PartyViewModel.cs b/Aurora/Design/Views/Party/PartyViewModel.cs index b9daa6d..be1e970 100644 --- a/Aurora/Design/Views/Party/PartyViewModel.cs +++ b/Aurora/Design/Views/Party/PartyViewModel.cs @@ -9,10 +9,11 @@ using Aurora.Proto.Party; using Aurora.Proto.Events; using Aurora.Services.ClientService; using Aurora.Services.ClientService.Events; -using Aurora.Services.PlayerService; +using Aurora.Services.Player; using Aurora.Services.EventManager; using Aurora.Models.Media; using Aurora.Design.Views.Party.NewPartyDialog; +using Aurora.Services.Settings; namespace Aurora.Design.Views.Party { @@ -32,22 +33,25 @@ namespace Aurora.Design.Views.Party private ObservableCollection _members; private ObservableCollection _queue; private BaseMedia _selectedMedia; - private ClientService _client; + private IClientService _client; + private ISettingsService _settingsService; private int _selectedTabIndex; - public PartyViewModel() + public PartyViewModel(ISettingsService settingsService, IClientService clientService) { _members = new ObservableCollection(); _queue = new ObservableCollection(); + this._settingsService = settingsService; + SetState(PartyState.SelectingHost); PlayCommand = new Command(OnDoubleClickCommandExecute, CanDoubleClickCommandExecute); LeavePartyCommand = new Command(OnLeavePartyCommandExecute, CanLeavePartyCommandExecute); - _client = ClientService.Instance; + _client = clientService; _client.OnMediaPaused += this.OnRemoteMediaPaused; _client.OnMediaResumed += this.OnRemoteMediaResumed; @@ -245,7 +249,7 @@ namespace Aurora.Design.Views.Party private async void OnJoinCommandExecute() { SetState(PartyState.Connecting); - _client.Start(_hostname, SettingsService.Instance.DefaultPort.ToString()); + _client.Start(_hostname, this._settingsService.DefaultPort.ToString()); await JoinParty(false); //TODO add cancellation token @@ -272,7 +276,7 @@ namespace Aurora.Design.Views.Party ServerService.Instance.Start(); string localHost = ServerService.GetLocalIPAddress(); _client.IsHost = true; - _client.Start(localHost, SettingsService.Instance.DefaultPort.ToString()); + _client.Start(localHost, this._settingsService.DefaultPort.ToString()); await JoinParty(true); @@ -387,10 +391,10 @@ namespace Aurora.Design.Views.Party { JoinPartyResponse resp = await _client.RemotePartyClient.JoinPartyAsync(new JoinPartyRequest { - UserName = SettingsService.Instance.Username, + UserName = this._settingsService.Username, }); - SettingsService.Instance.ClientId = resp.ClientId; + this._settingsService.ClientId = resp.ClientId; RefreshMembers(); @@ -447,13 +451,13 @@ namespace Aurora.Design.Views.Party req.EventTypes.Add(EventType.PartyMemberLeft); req.EventTypes.Add(EventType.MediaPlaying); req.EventTypes.Add(EventType.MediaStopped); - if (!string.IsNullOrWhiteSpace(SettingsService.Instance.ClientId)) + if (!string.IsNullOrWhiteSpace(this._settingsService.ClientId)) { - req.ClientId = SettingsService.Instance.ClientId; + req.ClientId = this._settingsService.ClientId; } - Console.WriteLine(string.Format("CLIENT {0} - SubscribeToEvents called from client with id", SettingsService.Instance.ClientId)); + Console.WriteLine(string.Format("CLIENT {0} - SubscribeToEvents called from client with id", this._settingsService.ClientId)); await _client.RemoteEventClient.SubscribeToEventsAsync(req); } private async Task UnsubscribeFromEvents() diff --git a/Aurora/Design/Views/Profile/ProfileViewModel.cs b/Aurora/Design/Views/Profile/ProfileViewModel.cs index b71e876..ac86be5 100644 --- a/Aurora/Design/Views/Profile/ProfileViewModel.cs +++ b/Aurora/Design/Views/Profile/ProfileViewModel.cs @@ -1,32 +1,34 @@ using System; -using Aurora.Services; +using Aurora.Services.Settings; namespace Aurora.Design.Views.Profile { public class ProfileViewModel : BaseViewModel { + private ISettingsService _settingsService; - public ProfileViewModel() + public ProfileViewModel(ISettingsService settingsService) { + this._settingsService = settingsService; } public string Username { - get { return SettingsService.Instance.Username; } + get { return this._settingsService.Username; } set { - SettingsService.Instance.Username = value; + this._settingsService.Username = value; OnPropertyChanged("Username"); } } public string Port { - get { return SettingsService.Instance.DefaultPort.ToString(); } + get { return this._settingsService.DefaultPort.ToString(); } set { Int32.TryParse(value, out int portNum); - SettingsService.Instance.DefaultPort = portNum; + this._settingsService.DefaultPort = portNum; OnPropertyChanged("Port"); } } diff --git a/Aurora/Proto/party.v2.proto b/Aurora/Proto/party.v2.proto index 52efeee..3b7532d 100644 --- a/Aurora/Proto/party.v2.proto +++ b/Aurora/Proto/party.v2.proto @@ -44,7 +44,13 @@ service RemotePartyService { rpc ListMedia(ListMediaRequest) returns (ListMediaResponse); //Get - rpc GetMedia(GetMediaRequest) returns (RemoteMedia); + rpc GetMedia(GetMediaRequest) returns (Media); + + //Create + rpc CreateMedia(CreateMediaRequest) returns (Media); + + //Delete + rpc DeleteMedia(DeleteMediaRequest) returns (Aurora.Proto.General.Empty) {}; //CUSTOM: Stream rpc StreamMedia(StreamMediaRequest) returns (stream Aurora.Proto.General.Chunk) {}; @@ -97,7 +103,10 @@ message Member { //Resource name of the party member to be returned (Added by server) string name = 1; string userName = 2; + + //Added by server string ipAddress = 3; + //Added by server google.protobuf.Timestamp addedOn = 4; } @@ -134,7 +143,7 @@ message DeleteMemberRequest { string name = 1; } -message RemoteMedia { +message Media { //Resource name of the remote media object string name = 1; string title = 2; @@ -151,7 +160,7 @@ message ListMediaRequest { } message ListMediaResponse { - repeated RemoteMedia media = 1; + repeated Media media = 1; string nextPageToken = 3; } @@ -160,6 +169,17 @@ message GetMediaRequest { string name = 1; } +message CreateMediaRequest { + //Resource name of the parent collection of the member to be created (The party) + string parent = 1; + Media media = 2; +} + +message DeleteMediaRequest { + //Resource name of the member to be deleted + string name = 1; +} + message StreamMediaRequest { //Resource name of the media requested string name = 1; @@ -199,7 +219,7 @@ message BaseEvent { } message NewMediaPlayingEvent { - RemoteMedia media = 1; + Media media = 1; } message MediaResumedEvent { diff --git a/Aurora/RemoteImpl/RemoteSyncImpl.cs b/Aurora/RemoteImpl/RemoteSyncImpl.cs index eddaac0..1b32902 100644 --- a/Aurora/RemoteImpl/RemoteSyncImpl.cs +++ b/Aurora/RemoteImpl/RemoteSyncImpl.cs @@ -2,7 +2,9 @@ using System; using System.Threading.Tasks; using Aurora.Proto.Sync; using Aurora.Proto.General; -using Aurora.Services.PlayerService; +using Aurora.Services.Player; +using Aurora; +using Autofac; namespace Aurora.RemoteImpl { @@ -19,31 +21,36 @@ namespace Aurora.RemoteImpl Grpc.Core.IServerStreamWriter responseStream, Grpc.Core.ServerCallContext context) { - bool continueSync = true; - string currentId = PlayerService.Instance.CurrentMedia.Id; - MediaChangedEventHandler mediaChanged = (sender, e) => + using (var scope = App.Container.BeginLifetimeScope()) { - if (e.NewId != currentId) + IPlayer player = scope.Resolve(); + bool continueSync = true; + string currentId = player.CurrentMedia.Id; + MediaChangedEventHandler mediaChanged = (sender, e) => { - continueSync = false; - } - }; - - PlayerService.Instance.MediaChanged += mediaChanged; - - while (continueSync) - { - float length = PlayerService.Instance.CurrentMediaLength; - - Sync sync = new Sync() - { - TrackPosition = PlayerService.Instance.CurrentMediaPosition, - ServerTimeTicks = Utils.TimeUtils.GetNetworkTime().DateTime.Ticks + if (e.NewId != currentId) + { + continueSync = false; + } }; - await responseStream.WriteAsync(sync); - Console.WriteLine("Sent Sync"); - await Task.Delay(5000); + + player.MediaChanged += mediaChanged; + + while (continueSync) + { + float length = player.CurrentMediaLength; + + Sync sync = new Sync() + { + TrackPosition = player.CurrentMediaPosition, + ServerTimeTicks = Utils.TimeUtils.GetNetworkTime().DateTime.Ticks + }; + await responseStream.WriteAsync(sync); + Console.WriteLine("Sent Sync"); + await Task.Delay(5000); + } } + } } } \ No newline at end of file diff --git a/Aurora/Services/ClientService/ClientService.cs b/Aurora/Services/ClientService/ClientService.cs index a3c0c58..3b69d25 100644 --- a/Aurora/Services/ClientService/ClientService.cs +++ b/Aurora/Services/ClientService/ClientService.cs @@ -8,11 +8,12 @@ using Aurora.Proto.Playback; using Aurora.Proto.Sync; using Aurora.Services.ClientService.Events; using System.Collections.Generic; +using Aurora.Services.Settings; namespace Aurora.Services.ClientService { - public class ClientService : BaseService + public class ClientService : IClientService { private RemotePartyService.RemotePartyServiceClient _remotePartyClient; private RemoteEventService.RemoteEventServiceClient _remoteEventsClient; @@ -20,17 +21,19 @@ namespace Aurora.Services.ClientService private RemoteSyncService.RemoteSyncServiceClient _remoteSyncClient; private Channel _channel; - CancellationTokenSource _eventCancellationTokenSource; + private CancellationTokenSource _eventCancellationTokenSource; + private ISettingsService _settingsService; - public ClientService() + public ClientService(ISettingsService settingsService) { + this._settingsService = settingsService; } - public MediaPausedEventHandler OnMediaPaused; - public NewMediaPlayingEventHandler OnNewMediaPlaying; - public PartyMemberJoinedEventHandler OnPartyMemberJoined; - public PartyMemberLeftEventHandler OnPartyMemberLeft; - public MediaResumedEventHandler OnMediaResumed; + public MediaPausedEventHandler OnMediaPaused { get; set; } + public NewMediaPlayingEventHandler OnNewMediaPlaying { get; set; } + public PartyMemberJoinedEventHandler OnPartyMemberJoined { get; set; } + public PartyMemberLeftEventHandler OnPartyMemberLeft { get; set; } + public MediaResumedEventHandler OnMediaResumed { get; set; } public RemotePartyService.RemotePartyServiceClient RemotePartyClient { @@ -94,10 +97,10 @@ namespace Aurora.Services.ClientService public async Task GetEvents() { _eventCancellationTokenSource = new CancellationTokenSource(); - string clientId = SettingsService.Instance.ClientId; + string clientId = this._settingsService.ClientId; Console.WriteLine(string.Format("CLIENT {0} - GetEvents called from client with id", clientId)); using (AsyncServerStreamingCall eventStream = _remoteEventsClient - .GetEvents(new EventsRequest { ClientId = SettingsService.Instance.ClientId })) + .GetEvents(new EventsRequest { ClientId = this._settingsService.ClientId })) { try { diff --git a/Aurora/Services/ClientService/IClientService.cs b/Aurora/Services/ClientService/IClientService.cs new file mode 100644 index 0000000..ec116c1 --- /dev/null +++ b/Aurora/Services/ClientService/IClientService.cs @@ -0,0 +1,42 @@ +using Aurora.Services.ClientService.Events; +using Aurora.Proto.Events; +using Aurora.Proto.Party; +using Aurora.Proto.Playback; +using Aurora.Proto.Sync; +using System.Threading.Tasks; + +namespace Aurora.Services.ClientService +{ + public interface IClientService + { + MediaPausedEventHandler OnMediaPaused { get; set; } + NewMediaPlayingEventHandler OnNewMediaPlaying { get; set; } + PartyMemberJoinedEventHandler OnPartyMemberJoined { get; set; } + PartyMemberLeftEventHandler OnPartyMemberLeft { get; set; } + MediaResumedEventHandler OnMediaResumed { get; set; } + + RemotePartyService.RemotePartyServiceClient RemotePartyClient { get; } + + RemoteEventService.RemoteEventServiceClient RemoteEventClient { get; } + + RemotePlaybackService.RemotePlaybackServiceClient RemotePlaybackClient { get; } + + RemoteSyncService.RemoteSyncServiceClient RemoteSyncClient { get; } + + bool IsStarted { get; } + + bool IsHost { get; set; } + + void Start(string hostname, string port); + + void Close(); + + /// + /// Asynchronous function for processing events off of the event stream. + /// + /// + Task GetEvents(); + + void StopEvents(); + } +} \ No newline at end of file diff --git a/Aurora/Services/Player/IPlayer.cs b/Aurora/Services/Player/IPlayer.cs new file mode 100644 index 0000000..8fde035 --- /dev/null +++ b/Aurora/Services/Player/IPlayer.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading.Tasks; +using System.Threading; +using Grpc.Core; +using Aurora.Models.Media; +using Aurora.Proto.Sync; +using LibVLCSharp.Shared; + +namespace Aurora.Services.Player +{ + + public interface IPlayer + { + + /// + /// Event handler for changing playback states. + /// + event PlaybackStateChangedEventHandler PlaybackStateChanged; + + event MediaChangedEventHandler MediaChanged; + + /// + /// The state of playback + /// + /// + PlaybackState PlaybackState { get; } + + bool IsLoaded { get; } + + bool IsMediaLoaded(BaseMedia media); + + BaseMedia CurrentMedia { get; } + + float CurrentMediaPosition { get; } + + long CurrentMediaLength { get; } + + /// + /// Load media into the media player. + /// + /// Media to load + Task LoadMedia(BaseMedia media); + + /// + /// Play currently loaded media. + /// + void Play(); + + /// + /// Pause currently loaded media. + /// + void Pause(); + + /// + /// Stop currently loaded media. + /// + void Stop(); + + void Enqueue(BaseMedia song); + + void Dequeue(BaseMedia song); + + } +} diff --git a/Aurora/Services/PlayerService/MediaChangedEvent.cs b/Aurora/Services/Player/MediaChangedEvent.cs similarity index 92% rename from Aurora/Services/PlayerService/MediaChangedEvent.cs rename to Aurora/Services/Player/MediaChangedEvent.cs index e78ce14..a598c9a 100644 --- a/Aurora/Services/PlayerService/MediaChangedEvent.cs +++ b/Aurora/Services/Player/MediaChangedEvent.cs @@ -1,7 +1,7 @@ using System; using Aurora.Models.Media; -namespace Aurora.Services.PlayerService +namespace Aurora.Services.Player { public delegate void MediaChangedEventHandler(object source, MediaChangedEventArgs e); diff --git a/Aurora/Services/Player/PlaybackState.cs b/Aurora/Services/Player/PlaybackState.cs new file mode 100644 index 0000000..aed0ad7 --- /dev/null +++ b/Aurora/Services/Player/PlaybackState.cs @@ -0,0 +1,12 @@ +using System; + +namespace Aurora.Services.Player +{ + public enum PlaybackState + { + Playing, + Stopped, + Buffering, + + } +} diff --git a/Aurora/Services/PlayerService/PlaybackStateChangedEvent.cs b/Aurora/Services/Player/PlaybackStateChangedEvent.cs similarity index 92% rename from Aurora/Services/PlayerService/PlaybackStateChangedEvent.cs rename to Aurora/Services/Player/PlaybackStateChangedEvent.cs index 2decee4..a07cf7a 100644 --- a/Aurora/Services/PlayerService/PlaybackStateChangedEvent.cs +++ b/Aurora/Services/Player/PlaybackStateChangedEvent.cs @@ -1,7 +1,7 @@ using System; using Aurora.Models.Media; -namespace Aurora.Services.PlayerService +namespace Aurora.Services.Player { public delegate void PlaybackStateChangedEventHandler(object source, PlaybackStateChangedEventArgs e); diff --git a/Aurora/Services/PlayerService/PlayerService.cs b/Aurora/Services/Player/PlayerService.cs similarity index 98% rename from Aurora/Services/PlayerService/PlayerService.cs rename to Aurora/Services/Player/PlayerService.cs index 9c18a46..bf85e4e 100644 --- a/Aurora/Services/PlayerService/PlayerService.cs +++ b/Aurora/Services/Player/PlayerService.cs @@ -6,10 +6,10 @@ using Aurora.Models.Media; using Aurora.Proto.Sync; using LibVLCSharp.Shared; -namespace Aurora.Services.PlayerService +namespace Aurora.Services.Player { - public class PlayerService : BaseService + public class PlayerService : BaseService, IPlayer { private const long _ticksPerMillisecond = 10000; private BaseMedia _currentMedia; diff --git a/Aurora/Services/PlayerService/PlaybackState.cs b/Aurora/Services/PlayerService/PlaybackState.cs deleted file mode 100644 index 37c6773..0000000 --- a/Aurora/Services/PlayerService/PlaybackState.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -public enum PlaybackState -{ - Playing, - Stopped, - Buffering, - -} \ No newline at end of file diff --git a/Aurora/Services/Server/Controllers/Constructor.cs b/Aurora/Services/Server/Controllers/Constructor.cs new file mode 100644 index 0000000..5bdb63c --- /dev/null +++ b/Aurora/Services/Server/Controllers/Constructor.cs @@ -0,0 +1,38 @@ +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using Aurora.Proto.PartyV2; + +namespace Aurora.Services.Server.Controllers +{ + public partial class RemotePartyController : RemotePartyService.RemotePartyServiceBase + { + /// + /// Constructor for partial class + /// + public RemotePartyController(string partyName, string description) + { + this._startDateTime = DateTime.UtcNow; + this._displayName = partyName; + this._description = description; + this._memberList = new SortedList(); + this._mediaList = new SortedList(); + + string userName = "testUser"; + + this._eventManager = new EventManager.EventManager(); + + this._hostMember = new Member() + { + Name = GetNewMemberResourceName(_partyResourceName, ServerService.GetLocalIPAddress(), userName), + UserName = userName, + IpAddress = ServerService.GetLocalIPAddress(), + }; + + this._memberList.Add(_hostMember.Name, _hostMember); + + //Add media from library + + } + } +} \ No newline at end of file diff --git a/Aurora/Services/Server/Controllers/MediaController.cs b/Aurora/Services/Server/Controllers/MediaController.cs index 7af204e..cb35f5b 100644 --- a/Aurora/Services/Server/Controllers/MediaController.cs +++ b/Aurora/Services/Server/Controllers/MediaController.cs @@ -1,29 +1,158 @@ using System; using System.Threading.Tasks; +using System.Collections.Generic; using Aurora.Proto.PartyV2; +using Aurora.Models.Media; +using Aurora.Proto.General; +using Aurora.Services.Player; +using Autofac; namespace Aurora.Services.Server.Controllers { public partial class RemotePartyController : RemotePartyService.RemotePartyServiceBase { + private SortedList _mediaList; + public override Task ListMedia(ListMediaRequest request, Grpc.Core.ServerCallContext context) { - throw new NotImplementedException(); + ListMediaResponse resp = new ListMediaResponse(); + + int startIdx = 0; + if (!string.IsNullOrEmpty(request.PageToken)) + { + startIdx = _memberList.IndexOfKey(request.PageToken) + 1; + } + + int pageSize = request.PageSize; + + if (pageSize > _mediaList.Count) + { + pageSize = _mediaList.Count; + } + + //Gather page + List baseMedia = new List(_mediaList.Values); + foreach (BaseMedia media in baseMedia) + { + if (media.Metadata is AudioMetadata) + { + AudioMetadata meta = media.Metadata as AudioMetadata; + resp.Media.Add(new Media() + { + Name = media.Id, + Title = meta.Title, + Album = meta.Album, + Artist = meta.Artist, + Duration = meta.Duration + }); + } + } + + resp.NextPageToken = resp.Media[resp.Media.Count - 1].Name; + return Task.FromResult(resp); } - public override Task GetMedia(GetMediaRequest request, Grpc.Core.ServerCallContext context) + public override Task GetMedia(GetMediaRequest request, Grpc.Core.ServerCallContext context) + { + _mediaList.TryGetValue(request.Name, out BaseMedia baseMedia); + + if (baseMedia == null) + { + throw new KeyNotFoundException(); + } + + Media media = new Media(); + if (baseMedia.Metadata != null && baseMedia.Metadata is AudioMetadata) + { + AudioMetadata metadata = baseMedia.Metadata as AudioMetadata; + media.Name = baseMedia.Id; + media.Title = metadata.Title; + media.Artist = metadata.Artist; + media.Album = metadata.Album; + } + + return Task.FromResult(media); + } + + public override Task CreateMedia(CreateMediaRequest request, Grpc.Core.ServerCallContext context) { throw new NotImplementedException(); } - public override Task StreamMedia(StreamMediaRequest request, Grpc.Core.IServerStreamWriter responseStream, Grpc.Core.ServerCallContext context) + public override Task DeleteMedia(DeleteMediaRequest request, Grpc.Core.ServerCallContext context) { throw new NotImplementedException(); } - public override Task SyncMedia(SyncMediaRequest request, Grpc.Core.IServerStreamWriter responseStream, Grpc.Core.ServerCallContext context) + public override async Task StreamMedia(StreamMediaRequest request, Grpc.Core.IServerStreamWriter responseStream, Grpc.Core.ServerCallContext context) { - throw new NotImplementedException(); + BaseMedia originalSong = LibraryService.Instance.GetSong(request.Name); + if (!(originalSong is LocalAudio)) + { + return; + } + + //Copy media object to not interfere with other threads + LocalAudio songCopy = new LocalAudio((LocalAudio)originalSong); + + try + { + //Load only if not already loaded. (Multiple clients may be requesting media) + if (!songCopy.IsLoaded) + { + await songCopy.Load(); + } + + //Send stream + Console.WriteLine("Begin sending file"); + byte[] buffer = new byte[2048]; // read in chunks of 2KB + int bytesRead; + while ((bytesRead = songCopy.DataStream.Read(buffer, 0, buffer.Length)) > 0) + { + Google.Protobuf.ByteString bufferByteString = Google.Protobuf.ByteString.CopyFrom(buffer); + await responseStream.WriteAsync(new Chunk { Content = bufferByteString }); + } + Console.WriteLine("Done sending file"); + } + catch (Exception ex) + { + Console.WriteLine("Exception caught while sending audio file: " + ex.Message); + } + } + + public override async Task SyncMedia(SyncMediaRequest request, Grpc.Core.IServerStreamWriter responseStream, Grpc.Core.ServerCallContext context) + { + bool continueSync = true; + using (var scope = App.Container.BeginLifetimeScope()) + { + IPlayer player = scope.Resolve(); + + string currentId = player.CurrentMedia.Id; + MediaChangedEventHandler mediaChanged = (sender, e) => + { + if (e.NewId != currentId) + { + continueSync = false; + } + }; + + player.MediaChanged += mediaChanged; + + while (continueSync) + { + float length = player.CurrentMediaLength; + + Sync sync = new Sync() + { + TrackPosition = player.CurrentMediaPosition, + ServerTimeTicks = Utils.TimeUtils.GetNetworkTime().DateTime.Ticks + }; + await responseStream.WriteAsync(sync); + Console.WriteLine("Sent Sync"); + await Task.Delay(5000); + } + } + } } } \ No newline at end of file diff --git a/Aurora/Services/Server/Controllers/MemberController.cs b/Aurora/Services/Server/Controllers/MemberController.cs index 2dbc70c..02b4a65 100644 --- a/Aurora/Services/Server/Controllers/MemberController.cs +++ b/Aurora/Services/Server/Controllers/MemberController.cs @@ -25,19 +25,21 @@ namespace Aurora.Services.Server.Controllers startIdx = _memberList.IndexOfKey(request.PageToken) + 1; } + int pageSize = request.PageSize; + //Assign pageSize - if (request.PageSize > _memberList.Count) + if (pageSize > _memberList.Count) { - request.PageSize = _memberList.Count; + pageSize = _memberList.Count; } //Gather page List members = new List(_memberList.Values); - resp.Members.AddRange(members.GetRange(startIdx, request.PageSize)); + resp.Members.AddRange(members.GetRange(startIdx, pageSize)); //Set next page token - resp.NextPageToken = resp.Members[(startIdx + request.PageSize) - 1].Name; + resp.NextPageToken = resp.Members[resp.Members.Count - 1].Name; return Task.FromResult(resp); } diff --git a/Aurora/Services/Server/Controllers/PartyController.cs b/Aurora/Services/Server/Controllers/PartyController.cs index 1ee14ea..d8ad019 100644 --- a/Aurora/Services/Server/Controllers/PartyController.cs +++ b/Aurora/Services/Server/Controllers/PartyController.cs @@ -17,30 +17,6 @@ namespace Aurora.Services.Server.Controllers private EventManager.EventManager _eventManager; - /// - /// Constructor for partial class - /// - public RemotePartyController(string partyName, string description) - { - this._startDateTime = DateTime.UtcNow; - this._displayName = partyName; - this._description = description; - this._memberList = new SortedList(); - - string userName = "testUser"; - - this._eventManager = new EventManager.EventManager(); - - this._hostMember = new Member() - { - Name = GetNewMemberResourceName(_partyResourceName, ServerService.GetLocalIPAddress(), userName), - UserName = userName, - IpAddress = ServerService.GetLocalIPAddress(), - }; - - this._memberList.Add(_hostMember.Name, _hostMember); - } - public override Task GetParty(Proto.General.Empty request, Grpc.Core.ServerCallContext context) { Party party = new Party() diff --git a/Aurora/Services/ServerService.cs b/Aurora/Services/ServerService.cs index 3205ea1..8d2c0ac 100644 --- a/Aurora/Services/ServerService.cs +++ b/Aurora/Services/ServerService.cs @@ -8,13 +8,14 @@ using Aurora.Proto.Events; using Aurora.Proto.Party; using Aurora.Proto.Playback; using Aurora.Proto.Sync; - +using Aurora.Services.Settings; +using Autofac; namespace Aurora.Services { public class ServerService : BaseService { - private int _port = SettingsService.Instance.DefaultPort; + private int _port; private string _hostname; private Grpc.Core.Server _server; @@ -30,6 +31,11 @@ namespace Aurora.Services public ServerService() { string host = GetLocalIPAddress(); + using (var scope = App.Container.BeginLifetimeScope()) + { + var service = scope.Resolve(); + this._port = service.DefaultPort; + } if (string.IsNullOrWhiteSpace(host)) { diff --git a/Aurora/Services/Settings/ISettingsService.cs b/Aurora/Services/Settings/ISettingsService.cs new file mode 100644 index 0000000..ddb0816 --- /dev/null +++ b/Aurora/Services/Settings/ISettingsService.cs @@ -0,0 +1,27 @@ +using Plugin.Settings.Abstractions; + +namespace Aurora.Services.Settings +{ + public interface ISettingsService + { + ISettings AppSettings { get; set; } + + /// + /// The user's username. This is persisted. + /// + /// + string Username { get; set; } + + /// + /// The default port to use. This is persisted. + /// + /// + int DefaultPort { get; set; } + + /// + /// The current sessions clientId. This is assigned by the server. This is not persisted. + /// + /// + string ClientId { get; set; } + } +} \ No newline at end of file diff --git a/Aurora/Services/SettingsService.cs b/Aurora/Services/Settings/SettingsService.cs similarity index 95% rename from Aurora/Services/SettingsService.cs rename to Aurora/Services/Settings/SettingsService.cs index ae8c768..485f63c 100644 --- a/Aurora/Services/SettingsService.cs +++ b/Aurora/Services/Settings/SettingsService.cs @@ -3,9 +3,9 @@ using System.Threading; using Plugin.Settings; using Plugin.Settings.Abstractions; -namespace Aurora.Services +namespace Aurora.Services.Settings { - public class SettingsService : BaseService + public class SettingsService : ISettingsService { private Lazy _appSettings; private string _usernameKey = "username";