Code refactoring for cleaner view model design

This commit is contained in:
watsonb8 2019-11-29 12:37:57 -05:00
parent 3398d145ac
commit 85ab39defd
15 changed files with 351 additions and 123 deletions

View File

@ -75,9 +75,9 @@ namespace Aurora.Design.Views
#endregion Player
#region Lifecycle
public virtual void OnActive() { }
public virtual Task OnActive() { return Task.FromResult<object>(null); }
public virtual void OnInactive() { }
public virtual Task OnInactive() { return Task.FromResult<object>(null); }
#endregion

View File

@ -8,6 +8,7 @@ using Aurora.Proto.General;
using Aurora.Proto.Party;
using Aurora.Proto.Events;
using Aurora.Services.ClientService;
using Aurora.Services.ClientService.Events;
using Aurora.Services.PlayerService;
using Aurora.Services.EventManager;
using Aurora.Models.Media;
@ -47,9 +48,6 @@ namespace Aurora.Design.Views.Party
PlayCommand = new Command(OnDoubleClickExecute, CanDoubleClickExecute);
_client = ClientService.Instance;
//Hook up event handler
_client.EventReceived += this.OnEventReceived;
}
~PartyViewModel()
@ -151,71 +149,101 @@ namespace Aurora.Design.Views.Party
#endregion Properties
#region Events
public override void OnActive()
/// <summary>
/// Called by framework when view becomes active
/// </summary>
/// <returns></returns>
public override async Task OnActive()
{
//TODO
//If in party subscribe to events
//If in party get events
if (this._state == PartyState.Hosting)
{
await SubscribeToEvents();
}
public override void OnInactive()
{
//TODO
//unsubscribe
//stop getting events
_client.OnMediaPaused += this.OnMediaPaused;
_client.OnMediaResumed += this.OnMediaResumed;
_client.OnNewMediaPlaying += this.OnNewMediaPlaying;
_client.OnPartyMemberJoined += this.OnPartyMemberJoined;
_client.OnPartyMemberLeft += this.OnPartyMemberLeft;
}
/// <summary>
/// An event handler for the client receiving update events
/// Called by framework when view becomes inactive
/// </summary>
/// <param name="sender">The object that sent the event</param>
/// <param name="eventArgs">The event arguments</param>
public void OnEventReceived(object sender, EventReceivedEventArgs eventArgs)
/// <returns></returns>
public override async Task OnInactive()
{
switch (eventArgs.BaseEvent.DerivedEventCase)
{
case BaseEvent.DerivedEventOneofCase.None:
{
throw new InvalidOperationException();
_client.StopEvents();
await UnsubscribeFromEvents();
//Stop event stream and un hook events
_client.OnMediaPaused -= this.OnMediaPaused;
_client.OnMediaResumed -= this.OnMediaResumed;
_client.OnNewMediaPlaying -= this.OnNewMediaPlaying;
_client.OnPartyMemberJoined -= this.OnPartyMemberJoined;
_client.OnPartyMemberLeft -= this.OnPartyMemberLeft;
}
case BaseEvent.DerivedEventOneofCase.PartyMemberJoinedEvent:
/// <summary>
/// Remote media paused event
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public void OnMediaPaused(object sender, MediaPausedEventArgs args)
{
StopPlaying();
}
/// <summary>
/// Remote playing new media event
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public void OnNewMediaPlaying(object sender, NewMediaPlayingEventArgs args)
{
PlayFromBeginning(GetMediaFromQueue(args.Event.Media.Id));
}
/// <summary>
/// Remote resumed playing event
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public void OnMediaResumed(object sender, MediaResumedEventArgs args)
{
PlayResume();
}
/// <summary>
/// Member joined party event
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public void OnPartyMemberJoined(object sender, PartyMemberJoinedEventArgs args)
{
PartyMemberJoinedEvent derivedEvent = eventArgs.BaseEvent.PartyMemberJoinedEvent;
PartyMember member = new PartyMember
{
UserName = derivedEvent.Member.UserName,
Id = derivedEvent.Member.Id,
IpAddress = derivedEvent.Member.IpAddress,
Port = derivedEvent.Member.Port
UserName = args.Event.Member.UserName,
Id = args.Event.Member.Id,
IpAddress = args.Event.Member.IpAddress,
Port = args.Event.Member.Port
};
Members.Add(member);
break;
}
case BaseEvent.DerivedEventOneofCase.PartyMemberLeftEvent:
/// <summary>
/// Member left party event
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public void OnPartyMemberLeft(object sender, PartyMemberLeftEventArgs args)
{
PartyMemberJoinedEvent derivedEvent = eventArgs.BaseEvent.PartyMemberJoinedEvent;
var found = Members.Where(x => x.Id == derivedEvent.Member.Id);
var found = Members.Where(x => x.Id == args.Event.Member.Id);
foreach (PartyMember member in found)
{
_members.Remove(member);
}
break;
}
case BaseEvent.DerivedEventOneofCase.MediaPlayingEvent:
{
MediaPlayingEvent derivedEvent = eventArgs.BaseEvent.MediaPlayingEvent;
Play(derivedEvent);
break;
}
case BaseEvent.DerivedEventOneofCase.MediaPausedEvent:
{
MediaPausedEvent derivedEvent = eventArgs.BaseEvent.MediaPausedEvent;
StopPlaying();
break;
}
}
}
#endregion Events
@ -274,17 +302,32 @@ namespace Aurora.Design.Views.Party
public override void OnPlayButtonExecute()
{
_player.Play();
switch (_player.PlaybackState)
{
case PlaybackState.Buffering:
{
_player.Play();
//Fire play resume event
AudioMetadata meta = _selectedMedia.Metadata as AudioMetadata;
MediaResumedEvent mediaResumed = new MediaResumedEvent();
EventManager.Instance.FireEvent(new BaseEvent()
{
MediaResumedEvent = mediaResumed
});
break;
}
case PlaybackState.Playing:
{
_player.Pause();
//Fire play stopped event
AudioMetadata meta = _selectedMedia.Metadata as AudioMetadata;
MediaPausedEvent mediaPaused = new MediaPausedEvent();
EventManager.Instance.FireEvent(new BaseEvent()
{
MediaPausedEvent = mediaPaused
});
break;
}
}
@ -310,8 +353,9 @@ namespace Aurora.Design.Views.Party
/// </summary>
public void OnDoubleClickExecute()
{
//Fire Playing event
AudioMetadata meta = _selectedMedia.Metadata as AudioMetadata;
MediaPlayingEvent mediaPlaying = new MediaPlayingEvent()
NewMediaPlayingEvent mediaPlaying = new NewMediaPlayingEvent()
{
Media = new RemoteMediaData()
{
@ -324,7 +368,7 @@ namespace Aurora.Design.Views.Party
EventManager.Instance.FireEvent(new BaseEvent()
{
MediaPlayingEvent = mediaPlaying
NewMediaPlayingEvent = mediaPlaying
});
}
@ -355,19 +399,7 @@ namespace Aurora.Design.Views.Party
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);
await SubscribeToEvents();
QueueResponse queueResponse = _client.RemotePartyClient.GetQueue(new Empty());
@ -403,19 +435,41 @@ namespace Aurora.Design.Views.Party
_client.StopEvents();
//Unsubscribe
UnsubscribeAllRequest unsubscribeReq = new UnsubscribeAllRequest();
await _client.RemoteEventClient.UnsubscribeFromAllAsync(unsubscribeReq);
await UnsubscribeFromEvents();
//Leave party
LeavePartyRequest leaveReq = new LeavePartyRequest();
await _client.RemotePartyClient.LeavePartyAsync(leaveReq);
}
private async Task SubscribeToEvents()
{
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));
await _client.RemoteEventClient.SubscribeToEventsAsync(req);
}
private async Task UnsubscribeFromEvents()
{
UnsubscribeAllRequest unsubscribeReq = new UnsubscribeAllRequest();
await _client.RemoteEventClient.UnsubscribeFromAllAsync(unsubscribeReq);
}
/// <summary>
/// Refresh members list.
/// </summary>
private void RefreshMembers()
{
Members.Clear();
MembersResponse response = _client.RemotePartyClient.GetPartyMembers(new Empty());
//Add members
foreach (PartyMember member in response.Members)
@ -431,17 +485,28 @@ namespace Aurora.Design.Views.Party
OnPropertyChanged("IsNotSelectingHost");
}
private async void Play(MediaPlayingEvent args)
private BaseMedia GetMediaFromQueue(string Id)
{
//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))
if (_queue.Any((BaseMedia media) => media.Id == Id))
{
BaseMedia media = _queue.First((BaseMedia med) => med.Id == args.Media.Id);
base.Media = media;
BaseMedia media = _queue.First((BaseMedia med) => med.Id == Id);
return media;
}
else
{
return null;
}
}
private async void PlayFromBeginning(BaseMedia args)
{
base.Media = args;
await _player.LoadMedia(base.Media).ConfigureAwait(true);
_player.Play();
}
private void PlayResume()
{
_player.Play();
}
private void StopPlaying()

View File

@ -93,7 +93,6 @@ namespace Aurora.Design.Views.Songs
await _player.LoadMedia(base.Media).ConfigureAwait(true);
}
_player.Play();
switch (_player.PlaybackState)
{
case PlaybackState.Buffering:
@ -106,6 +105,11 @@ namespace Aurora.Design.Views.Songs
_player.Pause();
break;
}
case PlaybackState.Stopped:
{
_player.Play();
break;
}
}
}

View File

@ -50,15 +50,20 @@ message BaseEvent {
oneof derivedEvent {
PartyMemberJoinedEvent partyMemberJoinedEvent = 3;
PartyMemberLeftEvent partyMemberLeftEvent = 4;
MediaPlayingEvent mediaPlayingEvent = 5;
NewMediaPlayingEvent newMediaPlayingEvent = 5;
MediaPausedEvent mediaPausedEvent = 6;
MediaResumedEvent mediaResumedEvent = 7;
}
}
message MediaPlayingEvent {
message NewMediaPlayingEvent {
Aurora.Proto.Party.RemoteMediaData media = 1;
}
message MediaResumedEvent {
Aurora.Proto.General.Empty empty = 1;
}
message MediaPausedEvent {
Aurora.Proto.General.Empty empty = 1;
}

View File

@ -34,6 +34,7 @@ namespace Aurora.RemoteImpl
Action<BaseEvent> callback = (BaseEvent bEvent) =>
{
Console.WriteLine(string.Format("SERVER - Event fired for peer: {0}", peerId));
//TODO need to remove callback if stream no longer exists IE. Client crashed or stopped
responseStream.WriteAsync(bEvent);
};

View File

@ -6,9 +6,12 @@ using Aurora.Proto.Events;
using Aurora.Proto.Party;
using Aurora.Proto.Playback;
using Aurora.Proto.Sync;
using Aurora.Services.ClientService.Events;
using System.Collections.Generic;
namespace Aurora.Services.ClientService
{
public class ClientService : BaseService<ClientService>
{
private RemotePartyService.RemotePartyServiceClient _remotePartyClient;
@ -23,7 +26,11 @@ namespace Aurora.Services.ClientService
{
}
public EventReceivedEventHandler EventReceived;
public MediaPausedEventHandler OnMediaPaused;
public NewMediaPlayingEventHandler OnNewMediaPlaying;
public PartyMemberJoinedEventHandler OnPartyMemberJoined;
public PartyMemberLeftEventHandler OnPartyMemberLeft;
public MediaResumedEventHandler OnMediaResumed;
public RemotePartyService.RemotePartyServiceClient RemotePartyClient
{
@ -99,16 +106,36 @@ namespace Aurora.Services.ClientService
while (!_eventCancellationTokenSource.Token.IsCancellationRequested &&
await eventStream.ResponseStream.MoveNext(_eventCancellationTokenSource.Token))
{
BaseEvent e = new BaseEvent(eventStream.ResponseStream.Current);
if (this.EventReceived != null)
try
{
this.EventReceived.Invoke(this, new EventReceivedEventArgs(e));
}
BaseEvent e = new BaseEvent(eventStream.ResponseStream.Current);
Dictionary<BaseEvent.DerivedEventOneofCase, EventInfo> events = new Dictionary<BaseEvent.DerivedEventOneofCase, EventInfo>()
{
{BaseEvent.DerivedEventOneofCase.MediaPausedEvent, new EventInfo(this.OnMediaPaused, typeof(MediaPausedEventArgs))},
{BaseEvent.DerivedEventOneofCase.MediaResumedEvent, new EventInfo(this.OnMediaResumed, typeof(MediaResumedEventArgs))},
{BaseEvent.DerivedEventOneofCase.NewMediaPlayingEvent, new EventInfo(this.OnNewMediaPlaying, typeof(NewMediaPlayingEventArgs))},
{BaseEvent.DerivedEventOneofCase.PartyMemberJoinedEvent, new EventInfo(this.OnPartyMemberJoined, typeof(PartyMemberJoinedEventArgs))},
{BaseEvent.DerivedEventOneofCase.PartyMemberLeftEvent, new EventInfo(this.OnPartyMemberLeft, typeof(PartyMemberLeftEventArgs))}
};
events.TryGetValue(e.DerivedEventCase, out EventInfo eventInfo);
if (eventInfo != null && eventInfo.Handler != null)
{
eventInfo.Handler.DynamicInvoke(new object[] { this, Activator.CreateInstance(eventInfo.ArgsType, new object[] { e }) });
}
}
catch (Exception ex)
{
Console.WriteLine(string.Format("EXCEPTION --- " + ex.Message));
Console.WriteLine("Exception while parsing event ---" + ex.Message);
}
}
}
catch (Exception ex)
{
Console.WriteLine(string.Format("EXCEPTION while parsing events --- " + ex.Message));
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Threading.Tasks;
using System.Threading;
using Grpc.Core;
using Aurora.Proto.Events;
using Aurora.Proto.Party;
using Aurora.Proto.Playback;
using Aurora.Proto.Sync;
using Aurora.Services.ClientService.Events;
using System.Collections.Generic;
namespace Aurora.Services.ClientService
{
public class EventInfo
{
public EventInfo(Delegate handler, Type argsType)
{
this.Handler = handler;
ArgsType = argsType;
}
public Delegate Handler { get; private set; }
public Type ArgsType { get; private set; }
}
}

View File

@ -1,16 +0,0 @@
using System;
using Aurora.Proto.Events;
namespace Aurora.Services.ClientService
{
public delegate void EventReceivedEventHandler(object sender, EventReceivedEventArgs e);
public class EventReceivedEventArgs
{
public BaseEvent BaseEvent { get; private set; }
public EventReceivedEventArgs(BaseEvent e)
{
BaseEvent = e;
}
}
}

View File

@ -0,0 +1,16 @@
using System;
using Aurora.Proto.Events;
namespace Aurora.Services.ClientService.Events
{
public delegate void MediaPausedEventHandler(object sender, MediaPausedEventArgs e);
public class MediaPausedEventArgs
{
public MediaPausedEvent Event { get; private set; }
public MediaPausedEventArgs(BaseEvent e)
{
Event = e.MediaPausedEvent;
}
}
}

View File

@ -0,0 +1,16 @@
using System;
using Aurora.Proto.Events;
namespace Aurora.Services.ClientService
{
public delegate void MediaResumedEventHandler(object sender, MediaResumedEventArgs e);
public class MediaResumedEventArgs
{
public MediaResumedEvent Event { get; private set; }
public MediaResumedEventArgs(BaseEvent e)
{
Event = e.MediaResumedEvent;
}
}
}

View File

@ -0,0 +1,16 @@
using System;
using Aurora.Proto.Events;
namespace Aurora.Services.ClientService.Events
{
public delegate void NewMediaPlayingEventHandler(object sender, NewMediaPlayingEventArgs e);
public class NewMediaPlayingEventArgs
{
public NewMediaPlayingEvent Event { get; private set; }
public NewMediaPlayingEventArgs(BaseEvent e)
{
Event = e.NewMediaPlayingEvent;
}
}
}

View File

@ -0,0 +1,16 @@
using System;
using Aurora.Proto.Events;
namespace Aurora.Services.ClientService.Events
{
public delegate void PartyMemberJoinedEventHandler(object sender, PartyMemberJoinedEventArgs e);
public class PartyMemberJoinedEventArgs
{
public PartyMemberJoinedEvent Event { get; private set; }
public PartyMemberJoinedEventArgs(BaseEvent e)
{
Event = e.PartyMemberJoinedEvent;
}
}
}

View File

@ -0,0 +1,16 @@
using System;
using Aurora.Proto.Events;
namespace Aurora.Services.ClientService.Events
{
public delegate void PartyMemberLeftEventHandler(object sender, PartyMemberLeftEventArgs e);
public class PartyMemberLeftEventArgs
{
public PartyMemberLeftEvent Event { get; private set; }
public PartyMemberLeftEventArgs(BaseEvent e)
{
Event = e.PartyMemberLeftEvent;
}
}
}

View File

@ -1,16 +1,9 @@
using System;
using System.IO;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Google.Protobuf.Reflection;
using Aurora.Proto.Events;
using Aurora.Models;
namespace Aurora.Services.EventManager
{
@ -183,8 +176,6 @@ namespace Aurora.Services.EventManager
{
foreach (KeyValuePair<string, List<EventType>> pair in _subscriptionList)
{
Console.WriteLine("SERVER - Invoking action for client: " + pair.Key);
Task.Delay(1000);
//If action list contains an action for id, invoke
if (actionsCopy.ContainsKey(pair.Key))

View File

@ -4,6 +4,8 @@ using System.Threading;
using Grpc.Core;
using Aurora.Models.Media;
using Aurora.Proto.Sync;
using Aurora.Proto.Events;
using Aurora.Proto.Party;
using LibVLCSharp.Shared;
namespace Aurora.Services.PlayerService
@ -110,14 +112,14 @@ namespace Aurora.Services.PlayerService
RemoteAudio media = _currentMedia as RemoteAudio;
if (!media.FromHost)
{
RemoteSyncService.RemoteSyncServiceClient _remoteSyncClient = media.RemoteSyncClient;
RemoteSyncService.RemoteSyncServiceClient remoteSyncClient = media.RemoteSyncClient;
//Sync playback in a separate task
//Task completes when host stops syncing (when a song is complete)
Task syncTask = new Task(async () =>
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
using (AsyncServerStreamingCall<Sync> syncStream = _remoteSyncClient
using (AsyncServerStreamingCall<Sync> syncStream = remoteSyncClient
.GetMediaSync(new Proto.General.Empty()))
{
try
@ -154,6 +156,7 @@ namespace Aurora.Services.PlayerService
});
syncTask.Start();
// Task syncTask = Task.Run(() => Sync(remoteSyncClient));
}
}
@ -163,6 +166,50 @@ namespace Aurora.Services.PlayerService
}
}
/// <summary>
/// Async method to synchronize music playback with host
/// </summary>
/// <param name="remoteSyncClient"></param>
/// <returns></returns>
private async Task Sync(RemoteSyncService.RemoteSyncServiceClient remoteSyncClient)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
using (AsyncServerStreamingCall<Sync> syncStream = remoteSyncClient
.GetMediaSync(new Proto.General.Empty()))
{
try
{
while (await syncStream.ResponseStream.MoveNext(cancellationTokenSource.Token))
{
Sync sync = new Sync(syncStream.ResponseStream.Current);
if (sync != null)
{
//Adjust position based on sync
DateTime localTime = Utils.TimeUtils.GetNetworkTime();
//Get offset converted to milliseconds
float offset = ((localTime.Ticks - sync.ServerTime) * 100) / (1000 * 1000);
float length = CurrentMediaLength;
float newPosition = (sync.TrackTime + offset) / length;
//Adjust position if greater than 10 percent difference
float oldPosition = _mediaPlayer.Position;
if (newPosition - oldPosition > 0.001 ||
newPosition - oldPosition < -0.001)
{
_mediaPlayer.Position = newPosition;
Console.WriteLine("Audio synced");
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Exception caught while attempting to sync: " + ex.Message);
}
}
}
/// <summary>
/// Pause currently loaded media.
/// </summary>
@ -191,7 +238,6 @@ namespace Aurora.Services.PlayerService
{
PlaybackStateChanged.Invoke(this, new PlaybackStateChangedEventArgs(oldState, _state));
}
}
public void Enqueue(BaseMedia song)