From bbf8f3ae576ad460d85615dee38ba70668536814 Mon Sep 17 00:00:00 2001 From: watsonb8 Date: Sat, 9 Nov 2019 14:55:09 -0500 Subject: [PATCH] I think i've got simultaneous playback working but it is hard to test --- Aurora/Design/Views/BaseViewModel.cs | 14 +- Aurora/Design/Views/MainView/MainView.xaml.cs | 46 ++- Aurora/Design/Views/Party/PartyViewModel.cs | 280 +++++++++++------- Aurora/Design/Views/Songs/SongsViewModel.cs | 14 +- Aurora/Proto/events.proto | 16 +- .../Services/ClientService/ClientService.cs | 3 +- 6 files changed, 234 insertions(+), 139 deletions(-) diff --git a/Aurora/Design/Views/BaseViewModel.cs b/Aurora/Design/Views/BaseViewModel.cs index 48edd6e..b8de4f6 100644 --- a/Aurora/Design/Views/BaseViewModel.cs +++ b/Aurora/Design/Views/BaseViewModel.cs @@ -21,8 +21,8 @@ namespace Aurora.Design.Views /// /// Command event handler for player play button /// - public virtual void OnPlayExecute() { } - public virtual bool CanPlayExecute() + public virtual void OnPlayButtonExecute() { } + public virtual bool CanPlayButtonExecute() { return true; } @@ -30,8 +30,8 @@ namespace Aurora.Design.Views /// /// Command event handler for player next button /// - public virtual void OnNextExecute() { } - public virtual bool CanNextExecute() + public virtual void OnNextButtonExecute() { } + public virtual bool CanNextButtonExecute() { return true; } @@ -39,8 +39,8 @@ namespace Aurora.Design.Views /// /// Command event handler for player previous button /// - public virtual void OnPreviousExecute() { } - public virtual bool CanPreviousExecute() + public virtual void OnPreviousButtonExecute() { } + public virtual bool CanPreviousButtonExecute() { return true; } @@ -70,6 +70,8 @@ namespace Aurora.Design.Views public SetPlayerMetadataDelegate SetPlayerMetadata { get; set; } + public SetPlayerVisibleDelegate SetPlayerVisible { get; set; } + #endregion Player #region Lifecycle diff --git a/Aurora/Design/Views/MainView/MainView.xaml.cs b/Aurora/Design/Views/MainView/MainView.xaml.cs index d1fae37..9db6227 100644 --- a/Aurora/Design/Views/MainView/MainView.xaml.cs +++ b/Aurora/Design/Views/MainView/MainView.xaml.cs @@ -17,17 +17,25 @@ namespace Aurora.Design.Views.Main /// public delegate void SetPlayerMetadataDelegate(BaseMedia media); + public delegate void SetPlayerVisibleDelegate(Boolean visible); + [XamlCompilation(XamlCompilationOptions.Compile)] public partial class MainView : MasterDetailPage, IDisposable { private Dictionary _viewModels; private BaseViewModel _lastViewModel; + private Player _player; + private ContentPresenter _viewContent; public MainView() { InitializeComponent(); BindingContext = new MainViewModel(); _viewModels = new Dictionary(); + + _player = (Player)ContentPage.FindByName("Player"); + _viewContent = (ContentPresenter)ContentPage.Content.FindByName("ViewContent"); + MasterPage.ListView.ItemSelected += OnNavItemSelected; Appearing += OnAppearing; @@ -84,8 +92,7 @@ namespace Aurora.Design.Views.Main //Assign player controls to viewmodel AssignPlayerControls(vm); - ContentPresenter viewContent = (ContentPresenter)ContentPage.Content.FindByName("ViewContent"); - viewContent.Content = view; + _viewContent.Content = view; MasterPage.ListView.SelectedItem = null; } @@ -121,9 +128,7 @@ namespace Aurora.Design.Views.Main AssignPlayerControls(vm); vm.OnActive(); - - ContentPresenter viewContent = (ContentPresenter)ContentPage.Content.FindByName("ViewContent"); - viewContent.Content = view; + _viewContent.Content = view; MasterPage.ListView.SelectedItem = screenList.FirstOrDefault(); } @@ -135,6 +140,7 @@ namespace Aurora.Design.Views.Main private void UnassignPlayerControls(BaseViewModel vm) { vm.SetPlayerMetadata = null; + vm.SetPlayerVisible = null; } /// @@ -143,25 +149,37 @@ namespace Aurora.Design.Views.Main /// BaseViewModel to assign controls to private void AssignPlayerControls(BaseViewModel vm) { - Player player = (Player)ContentPage.FindByName("Player"); - player.PlayButtonCommand = new Command(vm.OnPlayExecute, vm.CanPlayExecute); - player.NextButtonCommand = new Command(vm.OnNextExecute, vm.CanNextExecute); - player.PreviousButtonCommand = new Command(vm.OnPreviousExecute, vm.CanPreviousExecute); + _player.PlayButtonCommand = new Command(vm.OnPlayButtonExecute, vm.CanPlayButtonExecute); + _player.NextButtonCommand = new Command(vm.OnNextButtonExecute, vm.CanNextButtonExecute); + _player.PreviousButtonCommand = new Command(vm.OnPreviousButtonExecute, vm.CanPreviousButtonExecute); //Assign SetPlayer delegate - vm.SetPlayerMetadata = SetPlayerDelegate; + vm.SetPlayerMetadata = SetPlayer; + vm.SetPlayerVisible = SetPlayerVisible; } - private void SetPlayerDelegate(BaseMedia media) + /// + /// SetPlayerDelegate implementation + /// + /// + private void SetPlayer(BaseMedia media) { - Player player = (Player)ContentPage.FindByName("Player"); if (media.Metadata is AudioMetadata) { AudioMetadata meta = (AudioMetadata)media.Metadata; - player.ArtistName = meta.Artist; - player.SongTitle = meta.Title; + _player.ArtistName = meta.Artist; + _player.SongTitle = meta.Title; } } + + /// + /// SetPlayerVisibleDelegate implementation + /// + /// + private void SetPlayerVisible(Boolean visible) + { + _player.IsVisible = visible; + } } } diff --git a/Aurora/Design/Views/Party/PartyViewModel.cs b/Aurora/Design/Views/Party/PartyViewModel.cs index e03e642..1f9384a 100644 --- a/Aurora/Design/Views/Party/PartyViewModel.cs +++ b/Aurora/Design/Views/Party/PartyViewModel.cs @@ -9,10 +9,12 @@ using Aurora.Proto.Party; using Aurora.Proto.Events; using Aurora.Services.ClientService; using Aurora.Services.PlayerService; +using Aurora.Services.EventManager; using Aurora.Models.Media; namespace Aurora.Design.Views.Party { + //TODO refactor enum PartyState { SelectingHost, @@ -29,6 +31,7 @@ namespace Aurora.Design.Views.Party private ObservableCollection _queue; private BaseMedia _selectedMedia; private PlayerService _player; + private ClientService _client; public PartyViewModel() { @@ -41,10 +44,12 @@ namespace Aurora.Design.Views.Party SetState(PartyState.SelectingHost); this._player = PlayerService.Instance; - PlayCommand = new Command(OnPlayExecute); + PlayCommand = new Command(OnDoubleClickExecute, CanDoubleClickExecute); + + _client = ClientService.Instance; //Hook up event handler - ClientService.Instance.EventReceived += this.OnEventReceived; + _client.EventReceived += this.OnEventReceived; } ~PartyViewModel() @@ -198,6 +203,18 @@ namespace Aurora.Design.Views.Party } break; } + case BaseEvent.DerivedEventOneofCase.MediaPlayingEvent: + { + MediaPlayingEvent derivedEvent = eventArgs.BaseEvent.MediaPlayingEvent; + Play(derivedEvent); + break; + } + case BaseEvent.DerivedEventOneofCase.MediaPausedEvent: + { + MediaPausedEvent derivedEvent = eventArgs.BaseEvent.MediaPausedEvent; + StopPlaying(); + break; + } } } @@ -207,7 +224,7 @@ namespace Aurora.Design.Views.Party private async void OnJoinExecute() { SetState(PartyState.Connecting); - ClientService.Instance.Start(Hostname, SettingsService.Instance.DefaultPort.ToString()); + _client.Start(Hostname, SettingsService.Instance.DefaultPort.ToString()); await JoinParty(); SetState(PartyState.InParty); @@ -224,13 +241,16 @@ namespace Aurora.Design.Views.Party SetState(PartyState.Connecting); ServerService.Instance.Start(); string localHost = ServerService.GetLocalIPAddress(); - ClientService.Instance.Start(localHost, SettingsService.Instance.DefaultPort.ToString()); + _client.IsHost = true; + _client.Start(localHost, SettingsService.Instance.DefaultPort.ToString()); await JoinParty(); + + //TODO add cancellation token try { SetState(PartyState.Hosting); - await ClientService.Instance.GetEvents().ConfigureAwait(true); + await _client.GetEvents().ConfigureAwait(true); } catch (Exception ex) { @@ -243,108 +263,8 @@ namespace Aurora.Design.Views.Party return true; } - - #endregion Commands - - #region Private Methods - /// - /// Join the remote party. - /// - /// - private async Task JoinParty() + public override void OnPlayButtonExecute() { - try - { - JoinPartyResponse resp = await ClientService.Instance.RemotePartyClient.JoinPartyAsync(new JoinPartyRequest - { - UserName = SettingsService.Instance.Username, - }); - - SettingsService.Instance.ClientId = resp.ClientId; - - RefreshMembers(); - - //Subscribe to events - SubscribeRequest req = new SubscribeRequest(); - req.EventTypes.Add(EventType.PartyMemberJoined); - req.EventTypes.Add(EventType.PartyMemberLeft); - if (!string.IsNullOrWhiteSpace(SettingsService.Instance.ClientId)) - { - req.ClientId = SettingsService.Instance.ClientId; - } - - - Console.WriteLine(string.Format("CLIENT {0} - SubscribeToEvents called from client with id", SettingsService.Instance.ClientId)); - ClientService.Instance.RemoteEventClient.SubscribeToEvents(req); - - QueueResponse queueResponse = ClientService.Instance.RemotePartyClient.GetQueue(new Empty()); - - Queue.Clear(); - //Convert received data to remote audio models - foreach (RemoteMediaData data in queueResponse.MediaList) - { - //Assign received metadata (since this can't be aquired from a file) - AudioMetadata meta = new AudioMetadata(); - meta.Title = data.Title; - meta.Album = data.Album; - meta.Artist = data.Artist; - meta.Duration = data.Duration; - - RemoteAudio remote = new RemoteAudio(data.Id, - meta, - ClientService.Instance.RemotePartyClient); - - Queue.Add(remote); - } - } - catch (Exception ex) - { - Console.WriteLine("Error subscribing to events: " + ex.Message); - } - } - - private async Task LeaveParty() - { - //Stop receiving events - ClientService.Instance.StopEvents(); - - //Unsubscribe - UnsubscribeAllRequest unsubscribeReq = new UnsubscribeAllRequest(); - await ClientService.Instance.RemoteEventClient.UnsubscribeFromAllAsync(unsubscribeReq); - - //Leave party - LeavePartyRequest leaveReq = new LeavePartyRequest(); - await ClientService.Instance.RemotePartyClient.LeavePartyAsync(leaveReq); - } - - /// - /// Refresh members list. - /// - private void RefreshMembers() - { - MembersResponse response = ClientService.Instance.RemotePartyClient.GetPartyMembers(new Empty()); - //Add members - foreach (PartyMember member in response.Members) - { - Members.Add(member); - } - } - - private void SetState(PartyState state) - { - _state = state; - OnPropertyChanged("IsSelectingHost"); - OnPropertyChanged("IsNotSelectingHost"); - } - - public override async void OnPlayExecute() - { - base.Media = this._selectedMedia; - if (!_player.IsMediaLoaded(base.Media)) - { - await _player.LoadMedia(base.Media).ConfigureAwait(true); - } - _player.Play(); switch (_player.PlaybackState) { @@ -361,21 +281,163 @@ namespace Aurora.Design.Views.Party } } - public override bool CanPlayExecute() + public override bool CanPlayButtonExecute() + { + return this._state == PartyState.Hosting && this._player.IsLoaded; + } + + public override bool CanNextButtonExecute() { return this._state == PartyState.Hosting; } - public override bool CanNextExecute() + public override bool CanPreviousButtonExecute() { return this._state == PartyState.Hosting; } - public override bool CanPreviousExecute() + /// + /// On double click execute, fire media playing event + /// + public void OnDoubleClickExecute() + { + AudioMetadata meta = _selectedMedia.Metadata as AudioMetadata; + MediaPlayingEvent mediaPlaying = new MediaPlayingEvent() + { + Media = new RemoteMediaData() + { + Id = _selectedMedia.Id, + Title = meta.Title, + Artist = meta.Artist, + Album = meta.Album, + } + }; + + EventManager.Instance.FireEvent(new BaseEvent() + { + MediaPlayingEvent = mediaPlaying + }); + } + + public bool CanDoubleClickExecute() { return this._state == PartyState.Hosting; } + + #endregion Commands + + #region Private Methods + /// + /// Join the remote party. + /// + /// + private async Task JoinParty() + { + try + { + JoinPartyResponse resp = await _client.RemotePartyClient.JoinPartyAsync(new JoinPartyRequest + { + UserName = SettingsService.Instance.Username, + }); + + SettingsService.Instance.ClientId = resp.ClientId; + + RefreshMembers(); + + //Subscribe to events + SubscribeRequest req = new SubscribeRequest(); + req.EventTypes.Add(EventType.PartyMemberJoined); + req.EventTypes.Add(EventType.PartyMemberLeft); + req.EventTypes.Add(EventType.MediaPlaying); + req.EventTypes.Add(EventType.MediaStopped); + if (!string.IsNullOrWhiteSpace(SettingsService.Instance.ClientId)) + { + req.ClientId = SettingsService.Instance.ClientId; + } + + + Console.WriteLine(string.Format("CLIENT {0} - SubscribeToEvents called from client with id", SettingsService.Instance.ClientId)); + _client.RemoteEventClient.SubscribeToEvents(req); + + QueueResponse queueResponse = _client.RemotePartyClient.GetQueue(new Empty()); + + Queue.Clear(); + //Convert received data to remote audio models + foreach (RemoteMediaData data in queueResponse.MediaList) + { + //Assign received metadata (since this can't be aquired from a file) + AudioMetadata meta = new AudioMetadata(); + meta.Title = data.Title; + meta.Album = data.Album; + meta.Artist = data.Artist; + meta.Duration = data.Duration; + + RemoteAudio remote = new RemoteAudio(data.Id, + meta, + _client.RemotePartyClient); + + Queue.Add(remote); + } + } + catch (Exception ex) + { + Console.WriteLine("Error subscribing to events: " + ex.Message); + } + } + + private async Task LeaveParty() + { + //Stop receiving events + _client.StopEvents(); + + //Unsubscribe + UnsubscribeAllRequest unsubscribeReq = new UnsubscribeAllRequest(); + await _client.RemoteEventClient.UnsubscribeFromAllAsync(unsubscribeReq); + + //Leave party + LeavePartyRequest leaveReq = new LeavePartyRequest(); + await _client.RemotePartyClient.LeavePartyAsync(leaveReq); + } + + /// + /// Refresh members list. + /// + private void RefreshMembers() + { + MembersResponse response = _client.RemotePartyClient.GetPartyMembers(new Empty()); + //Add members + foreach (PartyMember member in response.Members) + { + Members.Add(member); + } + } + + private void SetState(PartyState state) + { + _state = state; + OnPropertyChanged("IsSelectingHost"); + OnPropertyChanged("IsNotSelectingHost"); + } + + private async void Play(MediaPlayingEvent args) + { + //TODO this design assumes all played music is in a queue + //TODO this is a slow design depending on size of queue + if (_queue.Any((BaseMedia media) => media.Id == args.Media.Id)) + { + BaseMedia media = _queue.First((BaseMedia med) => med.Id == args.Media.Id); + base.Media = media; + await _player.LoadMedia(base.Media).ConfigureAwait(true); + _player.Play(); + } + } + + private void StopPlaying() + { + _player.Pause(); + } + #endregion Private Methods } } \ No newline at end of file diff --git a/Aurora/Design/Views/Songs/SongsViewModel.cs b/Aurora/Design/Views/Songs/SongsViewModel.cs index c699361..3798aab 100644 --- a/Aurora/Design/Views/Songs/SongsViewModel.cs +++ b/Aurora/Design/Views/Songs/SongsViewModel.cs @@ -20,7 +20,7 @@ namespace Aurora.Design.Views.Songs { _player = PlayerService.Instance; _songsList = new ObservableCollection(); - PlayCommand = new Command(OnPlayExecute, CanPlayExecute); + PlayCommand = new Command(OnPlayButtonExecute, CanPlayButtonExecute); _player.PlaybackStateChanged += OnPlaybackStateChanged; @@ -56,16 +56,16 @@ namespace Aurora.Design.Views.Songs #endregion Methods #region Commmands - public override bool CanPreviousExecute() + public override bool CanPreviousButtonExecute() { return true; } - public override void OnPreviousExecute() + public override void OnPreviousButtonExecute() { } - public override bool CanPlayExecute() + public override bool CanPlayButtonExecute() { switch (_player.PlaybackState) { @@ -85,7 +85,7 @@ namespace Aurora.Design.Views.Songs return false; } - public async override void OnPlayExecute() + public async override void OnPlayButtonExecute() { base.Media = _selectedSong; if (!_player.IsMediaLoaded(base.Media)) @@ -109,12 +109,12 @@ namespace Aurora.Design.Views.Songs } } - public override bool CanNextExecute() + public override bool CanNextButtonExecute() { return true; } - public override void OnNextExecute() + public override void OnNextButtonExecute() { } diff --git a/Aurora/Proto/events.proto b/Aurora/Proto/events.proto index f8acbdd..29c6fa7 100644 --- a/Aurora/Proto/events.proto +++ b/Aurora/Proto/events.proto @@ -40,6 +40,8 @@ message SubscriptionResponse { enum EventType { PartyMemberJoined = 0; PartyMemberLeft = 1; + MediaPlaying = 2; + MediaStopped = 3; } message BaseEvent { EventType eventType = 1; @@ -48,13 +50,23 @@ message BaseEvent { oneof derivedEvent { PartyMemberJoinedEvent partyMemberJoinedEvent = 3; PartyMemberLeftEvent partyMemberLeftEvent = 4; + MediaPlayingEvent mediaPlayingEvent = 5; + MediaPausedEvent mediaPausedEvent = 6; } } +message MediaPlayingEvent { + Aurora.Proto.Party.RemoteMediaData media = 1; +} + +message MediaPausedEvent { + Aurora.Proto.General.Empty empty = 1; +} + message PartyMemberJoinedEvent { - Aurora.Proto.Party.PartyMember member = 3; + Aurora.Proto.Party.PartyMember member = 1; } message PartyMemberLeftEvent { - Aurora.Proto.Party.PartyMember member = 3; + Aurora.Proto.Party.PartyMember member = 1; } \ No newline at end of file diff --git a/Aurora/Services/ClientService/ClientService.cs b/Aurora/Services/ClientService/ClientService.cs index 2f72b35..1dd04b4 100644 --- a/Aurora/Services/ClientService/ClientService.cs +++ b/Aurora/Services/ClientService/ClientService.cs @@ -17,7 +17,6 @@ namespace Aurora.Services.ClientService public ClientService() { - } public EventReceivedEventHandler EventReceived; @@ -44,6 +43,8 @@ namespace Aurora.Services.ClientService } } + public bool IsHost { get; set; } + public void Start(string hostname, string port) { _channel = new Channel(string.Format("{0}:{1}", hostname, port), ChannelCredentials.Insecure);