Migrate aurora-sharp-desktop

This commit is contained in:
Brandon Watson
2021-03-05 23:10:12 -05:00
parent d6496355a9
commit b8c0dadf91
186 changed files with 8521 additions and 0 deletions

View File

@ -0,0 +1,46 @@
using Grpc.Core;
using Aurora.Proto.Party;
using Aurora.Services.Settings;
namespace Aurora.Services.Client
{
public class ClientService : IClientService
{
private RemotePartyService.RemotePartyServiceClient _remotePartyClient;
private Channel _channel;
private ISettingsService _settingsService;
public ClientService(ISettingsService settingsService)
{
this._settingsService = settingsService;
}
public bool IsStarted
{
get
{
return _remotePartyClient != null;
}
}
public RemotePartyService.RemotePartyServiceClient RemotePartyServiceClient
{
get { return this._remotePartyClient; }
}
public void Start(string hostname, string port)
{
_channel = new Channel(string.Format("{0}:{1}", hostname, port), ChannelCredentials.Insecure);
_remotePartyClient = new RemotePartyService.RemotePartyServiceClient(_channel);
}
public async void Close()
{
await _channel.ShutdownAsync();
_remotePartyClient = null;
}
}
}

View File

@ -0,0 +1,15 @@
using Aurora.Proto.Party;
namespace Aurora.Services.Client
{
public interface IClientService
{
bool IsStarted { get; }
RemotePartyService.RemotePartyServiceClient RemotePartyServiceClient { get; }
void Start(string hostname, string port);
void Close();
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Aurora.Proto.Party;
namespace Aurora.Services.EventManager
{
public class EventAction
{
public EventAction(Action<BaseEvent> callback, Action cancel)
{
Callback = callback;
Cancel = cancel;
}
public Action<BaseEvent> Callback { get; set; }
public Action Cancel { get; set; }
}
}

View File

@ -0,0 +1,216 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Aurora.Proto.Party;
namespace Aurora.Services.EventManager
{
public class EventManager : IEventManager
{
#region Fields
private Dictionary<string, List<EventType>> _subscriptionList;
private Dictionary<string, EventAction> _actionList;
#endregion Fields
public EventManager()
{
_subscriptionList = new Dictionary<string, List<EventType>>();
_actionList = new Dictionary<string, EventAction>();
}
#region Private Methods
#endregion Private Methods
#region Public Methods
/// <summary>
/// Get the list of event type subscriptions for a given sessionIdentifier id.
/// </summary>
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
/// <returns></returns>
public List<EventType> GetSubscriptionList(string sessionIdentifier)
{
List<EventType> eventList = new List<EventType>();
if (_subscriptionList.ContainsKey(sessionIdentifier))
{
_subscriptionList.TryGetValue(sessionIdentifier, out eventList);
}
return eventList;
}
/// <summary>
/// Get the number of event subscriptions for a given sessionIdentifier
/// </summary>
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
/// <returns></returns>
public int GetSubscriptionCount(string sessionIdentifier)
{
List<EventType> eventList = new List<EventType>();
if (_subscriptionList.ContainsKey(sessionIdentifier))
{
_subscriptionList.TryGetValue(sessionIdentifier, out eventList);
}
return eventList.Count();
}
/// <summary>
/// Add a new subscription
/// </summary>
/// <param name="sessionIdentifier"></param>
/// <param name="type"></param>
public bool AddSubscription(string sessionIdentifier, EventType type)
{
bool success = false;
lock (_subscriptionList)
{
if (!_subscriptionList.ContainsKey(sessionIdentifier))
{
//Add sessionIdentifier to subscription list
List<EventType> eventList = new List<EventType>();
eventList.Add(type);
_subscriptionList.Add(sessionIdentifier, eventList);
success = true;
}
else
{
_subscriptionList.TryGetValue(sessionIdentifier, out List<EventType> eventList);
if (eventList != null)
{
eventList.Add(type);
success = true;
}
}
}
return success;
}
/// <summary>
/// Add a list of subscriptions. This unsubscribes from unused events.
/// </summary>
/// <param name="sessionIdentifier">The browser sessionIdentifier id.</param>
/// <param name="types">The list of event types to subscribe to.</param>
public void AddSubscriptionList(string sessionIdentifier, List<EventType> types)
{
RemoveAllSubscriptions(sessionIdentifier);
foreach (EventType e in types)
{
AddSubscription(sessionIdentifier, e);
}
}
/// <summary>
/// Unsubscribe from a given event type.
/// </summary>
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
/// <param name="type">Event Type to be removed</param>
public void RemoveSubscription(string sessionIdentifier, EventType type)
{
lock (_subscriptionList)
{
if (_subscriptionList.ContainsKey(sessionIdentifier))
{
List<EventType> eventTypeList;
_subscriptionList.TryGetValue(sessionIdentifier, out eventTypeList);
if (eventTypeList != null && eventTypeList.Contains(type))
{
eventTypeList.Remove(type);
//base.LogInformation(string.Format("Subscription removed for event type {0} subscription on sessionIdentifier {1}", type.ToString(), sessionIdentifier));
}
}
}
}
public void RemoveSubscriptionList(string sessionIdentifier, List<EventType> types)
{
foreach (EventType e in types)
{
RemoveSubscription(sessionIdentifier, e);
}
}
/// <summary>
/// Remove all subscriptons for a given sessionIdentifier.
/// </summary>
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
public void RemoveAllSubscriptions(string sessionIdentifier)
{
if (_subscriptionList.ContainsKey(sessionIdentifier))
{
_subscriptionList.Remove(sessionIdentifier);
}
}
public void AddEventHandler(Action<BaseEvent> action, Action cancel, string sessionIdentifierId)
{
lock (_actionList)
{
_actionList.Add(sessionIdentifierId, new EventAction(action, cancel));
}
}
public void RemoveEventHandler(string sessionIdentifierId)
{
_actionList.Remove(sessionIdentifierId);
}
public void CancelEventStream(string sessionIdentifierId)
{
_actionList.TryGetValue(sessionIdentifierId, out EventAction value);
if (value != null)
{
value.Cancel();
}
RemoveEventHandler(sessionIdentifierId);
}
public void FireEvent(BaseEvent bEvent)
{
Dictionary<string, EventAction> actionsCopy = new Dictionary<string, EventAction>();
//Copy actions list
lock (_actionList)
{
foreach (KeyValuePair<string, EventAction> pair in _actionList)
{
actionsCopy.Add(pair.Key, pair.Value);
}
}
lock (_subscriptionList)
{
foreach (KeyValuePair<string, List<EventType>> pair in _subscriptionList)
{
Task.Delay(1000);
//If action list contains an action for id, invoke
if (actionsCopy.ContainsKey(pair.Key))
{
actionsCopy.TryGetValue(pair.Key, out EventAction action);
Task executionTask = new Task(() => action.Callback(bEvent));
//Execute task with exception handler
executionTask.ContinueWith((Task task) =>
{
var exception = executionTask.Exception;
Console.WriteLine(string.Format("SERVER --- Exception occurred firing event"));
this._actionList.Remove(pair.Key);
},
TaskContinuationOptions.OnlyOnFaulted);
executionTask.Start();
}
}
}
}
#endregion Public Methods
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using Aurora.Proto.Party;
namespace Aurora.Services.EventManager
{
public interface IEventManager
{
/// <summary>
/// Get the list of event type subscriptions for a given sessionIdentifier id.
/// </summary>
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
/// <returns></returns>
List<EventType> GetSubscriptionList(string sessionIdentifier);
/// <summary>
/// Get the number of event subscriptions for a given sessionIdentifier
/// </summary>
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
/// <returns></returns>
int GetSubscriptionCount(string sessionIdentifier);
/// <summary>
/// Add a new subscription
/// </summary>
/// <param name="sessionIdentifier"></param>
/// <param name="type"></param>
bool AddSubscription(string sessionIdentifier, EventType type);
/// <summary>
/// Add a list of subscriptions. This unsubscribes from unused events.
/// </summary>
/// <param name="sessionIdentifier">The browser sessionIdentifier id.</param>
/// <param name="types">The list of event types to subscribe to.</param>
void AddSubscriptionList(string sessionIdentifier, List<EventType> types);
/// <summary>
/// Unsubscribe from a given event type.
/// </summary>
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
/// <param name="type">Event Type to be removed</param>
void RemoveSubscription(string sessionIdentifier, EventType type);
void RemoveSubscriptionList(string sessionIdentifier, List<EventType> types);
/// <summary>
/// Remove all subscriptons for a given sessionIdentifier.
/// </summary>
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
void RemoveAllSubscriptions(string sessionIdentifier);
void AddEventHandler(Action<BaseEvent> action, Action cancel, string sessionIdentifierId);
void RemoveEventHandler(string sessionIdentifierId);
void CancelEventStream(string sessionIdentifierId);
void FireEvent(BaseEvent bEvent);
}
}

View File

@ -0,0 +1,12 @@
using System.Collections.ObjectModel;
using Aurora.Models.Media;
namespace Aurora.Services.Library
{
public interface ILibraryService
{
ObservableCollection<BaseMedia> GetLibrary();
BaseMedia GetSong(string Id);
}
}

View File

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using Aurora.Models.Media;
using Aurora.Services.Settings;
using Aurora.Utils;
namespace Aurora.Services.Library
{
public class LibraryService : ILibraryService
{
#region Fields
private string _pathName;
private string _extensions = ".wav,.mp3,.aiff,.flac,.m4a,.m4b,.wma";
private Dictionary<string, BaseMedia> _library;
#endregion Fields
public LibraryService(ISettingsService settingsService)
{
_library = new Dictionary<string, BaseMedia>();
this._pathName = settingsService.LibraryLocation;
LoadLibrary();
}
/// <summary>
/// Gets the songs.
/// </summary>
/// <returns>The songs.</returns>
public ObservableCollection<BaseMedia> GetLibrary()
{
ObservableCollection<BaseMedia> collection = new ObservableCollection<BaseMedia>();
foreach (KeyValuePair<string, BaseMedia> pair in _library)
{
collection.Add(pair.Value);
}
return collection;
}
public BaseMedia GetSong(string Id)
{
_library.TryGetValue(Id, out BaseMedia song);
return song;
}
/// <summary>
/// Loads library from files.
/// </summary>
private void LoadLibrary()
{
//Get songs
List<FileInfo> musicFiles = FileSystemUtils.TraverseFoldersAsync(_pathName, _extensions);
foreach (FileInfo file in musicFiles)
{
TagLib.File tagFile = TagLib.File.Create(file.FullName);
BaseMedia song = new LocalAudio(file);
_library.Add(song.Id, song);
}
}
}
}

View File

@ -0,0 +1,62 @@
using System;
using System.Threading.Tasks;
using System.Threading;
using Grpc.Core;
using Aurora.Models.Media;
namespace Aurora.Services.Player
{
public interface IPlayer
{
/// <summary>
/// Event handler for changing playback states.
/// </summary>
event PlaybackStateChangedEventHandler PlaybackStateChanged;
event MediaChangedEventHandler MediaChanged;
/// <summary>
/// The state of playback
/// </summary>
/// <value></value>
PlaybackState PlaybackState { get; }
bool IsLoaded { get; }
bool IsMediaLoaded(BaseMedia media);
BaseMedia CurrentMedia { get; }
float CurrentMediaPosition { get; }
long CurrentMediaLength { get; }
/// <summary>
/// Load media into the media player.
/// </summary>
/// <param name="media">Media to load</param>
Task LoadMedia(BaseMedia media);
/// <summary>
/// Play currently loaded media.
/// </summary>
void Play();
/// <summary>
/// Pause currently loaded media.
/// </summary>
void Pause();
/// <summary>
/// Stop currently loaded media.
/// </summary>
void Stop();
void Enqueue(BaseMedia song);
void Dequeue(BaseMedia song);
}
}

View File

@ -0,0 +1,19 @@
using System;
using Aurora.Models.Media;
namespace Aurora.Services.Player
{
public delegate void MediaChangedEventHandler(object source, MediaChangedEventArgs e);
public class MediaChangedEventArgs : EventArgs
{
public BaseMetadata NewMetadata { get; private set; }
public string NewId { get; private set; }
public MediaChangedEventArgs(string id, BaseMetadata metadata)
{
NewMetadata = metadata;
NewId = id;
}
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace Aurora.Services.Player
{
public enum PlaybackState
{
Playing,
Stopped,
Buffering,
}
}

View File

@ -0,0 +1,19 @@
using System;
using Aurora.Models.Media;
namespace Aurora.Services.Player
{
public delegate void PlaybackStateChangedEventHandler(object source, PlaybackStateChangedEventArgs e);
public class PlaybackStateChangedEventArgs : EventArgs
{
public PlaybackState OldState { get; }
public PlaybackState NewState { get; }
public PlaybackStateChangedEventArgs(PlaybackState oldState, PlaybackState newState)
{
OldState = oldState;
NewState = newState;
}
}
}

View File

@ -0,0 +1,263 @@
using System;
using System.Threading.Tasks;
using System.Threading;
using Grpc.Core;
using Aurora.Models.Media;
using Aurora.Proto.Party;
using LibVLCSharp.Shared;
namespace Aurora.Services.Player
{
public class PlayerService : IPlayer
{
private const long _ticksPerMillisecond = 10000;
private BaseMedia _currentMedia;
private MediaPlayer _mediaPlayer;
private LibVLC _libvlc;
private PlaybackState _state;
private CancellationTokenSource _remoteSyncCancellationTokenSource;
public PlayerService()
{
_libvlc = new LibVLC();
_state = PlaybackState.Stopped;
}
/// <summary>
/// Event handler for changing playback states.
/// </summary>
public event PlaybackStateChangedEventHandler PlaybackStateChanged;
public event MediaChangedEventHandler MediaChanged;
/// <summary>
/// The state of playback
/// </summary>
/// <value></value>
public PlaybackState PlaybackState
{
get { return _state; }
}
public bool IsLoaded
{
get
{
return this._currentMedia != null;
}
}
public bool IsMediaLoaded(BaseMedia media)
{
return _currentMedia == media;
}
public BaseMedia CurrentMedia
{
get { return _currentMedia; }
}
public float CurrentMediaPosition
{
get
{
return _mediaPlayer != null ? _mediaPlayer.Position : -1;
}
}
public long CurrentMediaLength
{
get
{
return _mediaPlayer != null ? _mediaPlayer.Length : -1;
}
}
/// <summary>
/// Load media into the media player.
/// </summary>
/// <param name="media">Media to load</param>
public async Task LoadMedia(BaseMedia media)
{
if (_state == PlaybackState.Playing || _state == PlaybackState.Buffering)
{
Unload();
}
_currentMedia = media;
await _currentMedia.Load();
var md = new LibVLCSharp.Shared.Media(_libvlc, _currentMedia.DataStream);
_mediaPlayer = new MediaPlayer(md);
_mediaPlayer.Stopped += OnStopped;
md.Dispose();
if (MediaChanged != null)
{
MediaChanged.Invoke(this, new MediaChangedEventArgs(_currentMedia.Id, _currentMedia.Metadata));
}
}
/// <summary>
/// Play currently loaded media.
/// </summary>
public void Play()
{
PlaybackState oldState = _state;
_state = PlaybackState.Playing;
//Cancel sync if not cancelled
if (_remoteSyncCancellationTokenSource != null && !_remoteSyncCancellationTokenSource.IsCancellationRequested)
{
_remoteSyncCancellationTokenSource.Cancel();
_remoteSyncCancellationTokenSource = null;
}
_mediaPlayer.Play();
//Use sync RPC for remote audio
if (_currentMedia is RemoteAudio)
{
RemoteAudio media = _currentMedia as RemoteAudio;
if (!media.FromHost)
{
RemotePartyService.RemotePartyServiceClient remotePartyServiceClient = media.RemotePartyServiceClient;
//Sync playback in a separate task
//Task completes when host stops syncing (when a song is complete)
Task syncTask = new Task(async () =>
{
_remoteSyncCancellationTokenSource = new CancellationTokenSource();
using (AsyncServerStreamingCall<Sync> syncStream = remotePartyServiceClient
.SyncMedia(new SyncMediaRequest() { }))
{
try
{
while (await syncStream.ResponseStream.MoveNext(_remoteSyncCancellationTokenSource.Token))
{
Sync sync = new Sync(syncStream.ResponseStream.Current);
if (sync != null)
{
Utils.Time time = Utils.TimeUtils.GetNetworkTime();
//Adjust position based on sync
DateTime localTime = time.DateTime;
//Get offset - elapsed time converted to milliseconds
float length = CurrentMediaLength;
float offset = (localTime.Ticks - sync.ServerTimeTicks) * _ticksPerMillisecond;
float newPosition = (sync.TrackPosition + offset);
//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(string.Format("**Audio synced**"));
// Console.WriteLine(string.Format("Remote Server Time {0}", new DateTime(sync.ServerTimeTicks).ToLongTimeString()));
// Console.WriteLine(string.Format("Remote Track Time: {0}", sync.TrackPosition));
// Console.WriteLine(string.Format("Local Server Time: {0}", time.DateTime.ToLongTimeString()));
// Console.WriteLine(string.Format("Local Track Time: {0}", _mediaPlayer.Position));
// Console.WriteLine(string.Format("Offset: {0}", offset));
// Console.WriteLine(string.Format("Old Position: {0}", oldPosition));
// Console.WriteLine(string.Format("New Position: {0}", newPosition));
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Exception caught while attempting to sync: " + ex.Message + ": " + ex.StackTrace);
}
}
});
syncTask.Start();
}
}
if (PlaybackStateChanged != null)
{
PlaybackStateChanged.Invoke(this, new PlaybackStateChangedEventArgs(oldState, _state));
}
}
/// <summary>
/// Pause currently loaded media.
/// </summary>
public void Pause()
{
PlaybackState oldState = _state;
_state = PlaybackState.Buffering;
_mediaPlayer.Pause();
if (PlaybackStateChanged != null)
{
PlaybackStateChanged.Invoke(this, new PlaybackStateChangedEventArgs(oldState, _state));
}
}
/// <summary>
/// Stop currently loaded media.
/// </summary>
public void Stop()
{
PlaybackState oldState = _state;
_state = PlaybackState.Stopped;
_mediaPlayer.Stop();
//Cancel sync if not cancelled
if (_remoteSyncCancellationTokenSource != null && !_remoteSyncCancellationTokenSource.IsCancellationRequested)
{
_remoteSyncCancellationTokenSource.Cancel();
_remoteSyncCancellationTokenSource = null;
}
if (PlaybackStateChanged != null)
{
PlaybackStateChanged.Invoke(this, new PlaybackStateChangedEventArgs(oldState, _state));
}
}
public void Enqueue(BaseMedia song)
{
throw new NotImplementedException();
}
public void Dequeue(BaseMedia song)
{
throw new NotImplementedException();
}
/// <summary>
/// Unload currently loaded media.
/// </summary>
private void Unload()
{
if (_currentMedia == null)
{
return;
}
_currentMedia.Unload();
_currentMedia = null;
_mediaPlayer.Media = null;
_mediaPlayer = null;
}
/// <summary>
/// Event fired when currently loaded media player stops.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void OnStopped(object sender, EventArgs args)
{
if (PlaybackStateChanged != null)
{
PlaybackStateChanged.Invoke(this, new PlaybackStateChangedEventArgs(_state, PlaybackState.Stopped));
}
_state = PlaybackState.Stopped;
this.Unload();
}
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using Aurora.Proto.Party;
using Aurora.Services.Library;
using Aurora.Services.Settings;
using Aurora.Models.Media;
using Aurora.Services.EventManager;
using Autofac;
namespace Aurora.Services.Server.Controllers
{
public partial class RemotePartyController : RemotePartyService.RemotePartyServiceBase
{
private ILibraryService _libraryService;
private ISettingsService _settingsService;
private IEventManager _eventManager;
/// <summary>
/// Constructor for partial class
/// </summary>
public RemotePartyController(string partyName, string description, ILibraryService libraryService, ISettingsService settingsService, IEventManager eventManager)
{
this._startDateTime = DateTime.UtcNow;
this._displayName = partyName;
this._description = description;
this._memberList = new SortedList<string, Member>();
this._mediaList = new SortedList<string, Media>();
_libraryService = libraryService;
this._settingsService = settingsService;
string userName = _settingsService.Username;
this._eventManager = eventManager;
this._hostMember = new Member()
{
Name = GetNewMemberResourceName(_partyResourceName, ServerService.GetLocalIPAddress(), userName),
UserName = userName,
IpAddress = ServerService.GetLocalIPAddress(),
};
//Add media from library
//This will change as queuing operation gets solidified
//Simply return the hosts library
ObservableCollection<BaseMedia> queue = _libraryService.GetLibrary();
foreach (BaseMedia media in queue)
{
AudioMetadata metadata = new AudioMetadata();
try
{
if (media.Metadata is AudioMetadata)
{
metadata = media.Metadata as AudioMetadata;
Media data = new Media();
data.Name = string.Format("{0}/{1}", partyName, media.Id);
if (metadata.Title != null)
{
data.Title = metadata.Title;
}
if (metadata.Artist != null)
{
data.Artist = metadata.Artist;
}
if (metadata.Album != null)
{
data.Album = metadata.Album;
}
if (metadata.Duration != null)
{
data.Duration = metadata.Duration;
}
_mediaList.Add(data.Name, data);
}
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Error preparing queue: {0}", ex.Message));
}
}
}
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Threading.Tasks;
using System.Threading;
using Aurora.Proto.Party;
using Aurora.Utils;
namespace Aurora.Services.Server.Controllers
{
public partial class RemotePartyController : RemotePartyService.RemotePartyServiceBase
{
public override Task GetEvents(GetEventsRequest request, Grpc.Core.IServerStreamWriter<BaseEvent> responseStream, Grpc.Core.ServerCallContext context)
{
string peerId = Misc.Combine(new string[] { context.Peer, request.Parent });
Console.WriteLine(string.Format("SERVER - Events request received from peer: {0}", peerId));
AutoResetEvent are = new AutoResetEvent(false);
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);
};
Action cancelled = () =>
{
are.Set();
};
this._eventManager.AddEventHandler(callback, cancelled, Misc.Combine(new string[] { context.Peer, request.Parent }));
are.WaitOne();
return Task.FromResult<object>(null);
}
}
}

View File

@ -0,0 +1,66 @@
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Aurora.Proto.Party;
using Aurora.Proto.General;
using Aurora.Utils;
namespace Aurora.Services.Server.Controllers
{
public partial class RemotePartyController : RemotePartyService.RemotePartyServiceBase
{
public override Task<ListEventSubscriptionsResponse> ListEventSubscriptions(
ListEventSubscriptionsRequest request,
Grpc.Core.ServerCallContext context)
{
throw new NotImplementedException();
}
public override Task<EventSubscription> CreateEventSubscription(
CreateEventSubscriptionRequest request,
Grpc.Core.ServerCallContext context)
{
Console.WriteLine(string.Format("SERVER - Subscription from client with id: {0}", request.Parent));
this._eventManager.AddSubscription(Misc.Combine(new string[] { context.Peer, request.Parent }), request.EventSubscription.Type);
return Task.FromResult(request.EventSubscription);
}
public override Task<CreateEventSubscriptionListResponse> CreateEventSubscriptionList(
CreateEventSubscriptionListRequest request,
Grpc.Core.ServerCallContext context)
{
Console.WriteLine(string.Format("SERVER - Subscription from client with id: {0}", request.Parent));
List<EventType> eventTypes = new List<EventType>();
foreach (EventSubscription subscription in request.EventSubscriptions)
{
eventTypes.Add(subscription.Type);
}
this._eventManager.AddSubscriptionList(Misc.Combine(new string[] { context.Peer, request.Parent }), eventTypes);
CreateEventSubscriptionListResponse resp = new CreateEventSubscriptionListResponse();
resp.EventSubscriptions.AddRange(request.EventSubscriptions);
return Task.FromResult(resp);
}
public override Task<Empty> DeleteEventSubscription(
DeleteEventSubscriptionRequest request,
Grpc.Core.ServerCallContext context)
{
this._eventManager.RemoveSubscription(Misc.Combine(new string[] { context.Peer, request.Parent }), request.Type);
return Task.FromResult(new Empty());
}
public override Task<Empty> DeleteAllEventSubscriptions(
DeleteAllEventSubscriptionsRequest request,
Grpc.Core.ServerCallContext context)
{
this._eventManager.RemoveAllSubscriptions(Misc.Combine(new string[] { context.Peer, request.Parent }));
return Task.FromResult(new Empty());
}
}
}

View File

@ -0,0 +1,142 @@
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Aurora.Proto.Party;
using Aurora.Models.Media;
using Aurora.Proto.General;
using Aurora.Services.Library;
using Aurora.Services.Player;
using Autofac;
namespace Aurora.Services.Server.Controllers
{
public partial class RemotePartyController : RemotePartyService.RemotePartyServiceBase
{
private SortedList<string, Media> _mediaList;
public override Task<ListMediaResponse> ListMedia(ListMediaRequest request, Grpc.Core.ServerCallContext context)
{
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<Media> mediaList = new List<Media>(_mediaList.Values);
resp.Media.AddRange(mediaList.GetRange(startIdx, pageSize));
resp.NextPageToken = resp.Media[resp.Media.Count - 1].Name;
return Task.FromResult(resp);
}
public override Task<Media> GetMedia(GetMediaRequest request, Grpc.Core.ServerCallContext context)
{
_mediaList.TryGetValue(request.Name, out Media baseMedia);
if (baseMedia == null)
{
throw new KeyNotFoundException();
}
return Task.FromResult(baseMedia);
}
public override Task<Media> CreateMedia(CreateMediaRequest request, Grpc.Core.ServerCallContext context)
{
throw new NotImplementedException();
}
public override Task<Empty> DeleteMedia(DeleteMediaRequest request, Grpc.Core.ServerCallContext context)
{
throw new NotImplementedException();
}
public override async Task StreamMedia(StreamMediaRequest request, Grpc.Core.IServerStreamWriter<Proto.General.Chunk> responseStream, Grpc.Core.ServerCallContext context)
{
using (var scope = App.Container.BeginLifetimeScope())
{
ILibraryService library = scope.Resolve<ILibraryService>();
string mediaName = request.Name.Split('/')[1];
BaseMedia originalSong = library.GetSong(mediaName);
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<Sync> responseStream, Grpc.Core.ServerCallContext context)
{
bool continueSync = true;
using (var scope = App.Container.BeginLifetimeScope())
{
try
{
IPlayer player = scope.Resolve<IPlayer>();
string currentId = player.CurrentMedia.Id;
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);
}
}
catch (Exception ex)
{
Console.WriteLine("Error sending sync: " + ex.Message);
}
}
}
}
}

View File

@ -0,0 +1,129 @@
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Aurora.Proto.Party;
using Aurora.Proto.General;
using Aurora.Utils;
using Grpc.Core;
using Google.Protobuf.WellKnownTypes;
namespace Aurora.Services.Server.Controllers
{
public partial class RemotePartyController : RemotePartyService.RemotePartyServiceBase
{
private SortedList<string, Member> _memberList;
public override Task<ListMembersResponse> ListMembers(ListMembersRequest request, Grpc.Core.ServerCallContext context)
{
//Ignoring parent field because there is only one instance of the party
ListMembersResponse resp = new ListMembersResponse();
//Determine start idx
int startIdx = 0;
if (!string.IsNullOrEmpty(request.PageToken))
{
startIdx = _memberList.IndexOfKey(request.PageToken) + 1;
}
int pageSize = request.PageSize;
//Assign pageSize
if (pageSize > _memberList.Count)
{
pageSize = _memberList.Count;
}
//Gather page
List<Member> members = new List<Member>(_memberList.Values);
resp.Members.AddRange(members.GetRange(startIdx, pageSize));
//Set next page token
resp.NextPageToken = resp.Members[resp.Members.Count - 1].Name;
return Task.FromResult(resp);
}
public override Task<Member> GetMember(GetMemberRequest request, Grpc.Core.ServerCallContext context)
{
_memberList.TryGetValue(request.Name, out Member member);
if (member == null)
{
throw new KeyNotFoundException();
}
return Task.FromResult(member);
}
public override Task<Member> UpdateMember(UpdateMemberRequest request, Grpc.Core.ServerCallContext context)
{
throw new NotImplementedException();
}
public override Task<Member> CreateMember(CreateMemberRequest request, Grpc.Core.ServerCallContext context)
{
//Generate Guid
string resourceName = GetNewMemberResourceName(request.Parent, context.Peer, request.Member.UserName);
//Check if already added
if (_memberList.ContainsKey(resourceName))
{
throw new RpcException(new Status(StatusCode.AlreadyExists, "Member already exists"));
}
request.Member.Name = resourceName;
request.Member.AddedOn = Timestamp.FromDateTime(DateTime.UtcNow);
request.Member.IpAddress = context.Host;
_memberList.Add(resourceName, request.Member);
BaseEvent @event = new BaseEvent
{
EventType = EventType.MemberCreated,
MemberCreatedEvent = new MemberCreatedEvent
{
Member = request.Member,
}
};
//Fire event manager event
this._eventManager.FireEvent(@event);
return Task.FromResult(request.Member);
}
public override Task<Aurora.Proto.General.Empty> DeleteMember(DeleteMemberRequest request, Grpc.Core.ServerCallContext context)
{
string memberResourceName = request.Name;
//Check if member exists
if (!_memberList.ContainsKey(request.Name))
{
throw new RpcException(new Status(StatusCode.NotFound, "Member not found"));
}
_memberList.Remove(memberResourceName);
BaseEvent @event = new BaseEvent
{
EventType = EventType.MemberDeleted,
MemberDeletedEvent = new MemberDeletedEvent
{
MemberName = memberResourceName,
}
};
_eventManager.FireEvent(@event);
_eventManager.RemoveAllSubscriptions(memberResourceName);
_eventManager.CancelEventStream(memberResourceName);
return Task.FromResult(new Aurora.Proto.General.Empty());
}
private string GetNewMemberResourceName(string parent, string contextPeer, string userName)
{
string memberNameGuid = HashUtil.GetHash(new string[] { contextPeer, userName }).ToString();
return string.Format("{0}/members/{1}", parent, memberNameGuid);
}
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Aurora.Proto.Party;
using Google.Protobuf.WellKnownTypes;
namespace Aurora.Services.Server.Controllers
{
public partial class RemotePartyController : RemotePartyService.RemotePartyServiceBase
{
private string _partyResourceName = "party/party1";
private string _displayName;
private string _description;
private Member _hostMember;
private DateTime _startDateTime;
public override Task<Party> GetParty(Proto.General.Empty request, Grpc.Core.ServerCallContext context)
{
Party party = new Party()
{
Name = _partyResourceName,
DisplayName = this._displayName,
Description = this._description,
HostIp = ServerService.GetLocalIPAddress(),
HostMember = this._hostMember,
CreatedOn = Timestamp.FromDateTime(_startDateTime)
};
return Task.FromResult(party);
}
}
}

View File

@ -0,0 +1,27 @@
using System.Threading.Tasks;
namespace Aurora.Services.Server
{
public interface IServerService
{
int Port { get; }
string Hostname { get; }
bool Initialized { get; }
/// <summary>
/// Start Server
/// </summary>
void Start(string partyName, string description);
/// <summary>
/// Shutdown server async.
/// </summary>
/// <returns>Task</returns>
Task Stop();
Task Reset();
}
}

View File

@ -0,0 +1,144 @@
using System;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using Grpc.Core;
using Aurora.Services.Server.Controllers;
using Aurora.Services.Settings;
using Aurora.Services.Library;
using Aurora.Services.EventManager;
using Aurora.Proto.Party;
namespace Aurora.Services.Server
{
public class ServerService : IServerService
{
private int _port = 8080;
private string _hostname;
private Grpc.Core.Server _server;
private ILibraryService _libraryService;
private ISettingsService _settingsService;
private IEventManager _eventManager;
//Implementation class declarations
private RemotePartyController _remotePartyController;
/// <summary>
/// Constructor. Registers GRPC service implementations.
/// </summary>
public ServerService(ILibraryService libraryService, ISettingsService settingsService, IEventManager eventManager)
{
string host = GetLocalIPAddress();
this._libraryService = libraryService;
this._settingsService = settingsService;
this._eventManager = eventManager;
if (string.IsNullOrWhiteSpace(host))
{
throw new Exception("This device must have a valid IP address");
}
_hostname = host;
}
public int Port
{
get { return _port; }
}
public string Hostname
{
get { return _hostname; }
}
public bool Initialized
{
get
{
return (_remotePartyController != null &&
_server != null);
}
}
/// <summary>
/// Start Server
/// </summary>
public void Start(string partyName, string description)
{
try
{
Console.WriteLine(string.Format("Starting gRPC server at hostname: {0}, port: {1}", _hostname, _port));
_server = new Grpc.Core.Server
{
Ports = { new ServerPort(_hostname, _port, ServerCredentials.Insecure) }
};
//Construct implementations
_remotePartyController = new RemotePartyController(
partyName,
description,
_libraryService,
_settingsService,
_eventManager);
// Register grpc RemoteService with singleton server service
RegisterService(RemotePartyService.BindService(_remotePartyController));
_server.Start();
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Error starting gRPC server: {0}", ex.Message));
}
}
/// <summary>
/// Shutdown server async.
/// </summary>
/// <returns>Task</returns>
public async Task Stop()
{
try
{
await _server.ShutdownAsync();
await _server.ShutdownTask;
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Error stopping gRPC server: {0}", ex.Message));
}
}
public async Task Reset()
{
await Stop();
_server = new Grpc.Core.Server
{
Ports = { new ServerPort("localhost", _port, ServerCredentials.Insecure) }
};
}
private void RegisterService(ServerServiceDefinition definition)
{
_server.Services.Add(definition);
}
public static string GetLocalIPAddress()
{
string returnIp = "";
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
returnIp = ip.ToString();
}
}
return returnIp;
}
}
}

View File

@ -0,0 +1,29 @@
using Plugin.Settings.Abstractions;
namespace Aurora.Services.Settings
{
public interface ISettingsService
{
ISettings AppSettings { get; set; }
/// <summary>
/// The user's username. This is persisted.
/// </summary>
/// <value></value>
string Username { get; set; }
/// <summary>
/// The default port to use. This is persisted.
/// </summary>
/// <value></value>
int DefaultPort { get; set; }
/// <summary>
/// The current sessions clientId. This is assigned by the server. This is not persisted.
/// </summary>
/// <value></value>
string ClientName { get; set; }
string LibraryLocation { get; set; }
}
}

View File

@ -0,0 +1,76 @@
using System;
using System.Threading;
using Plugin.Settings;
using Plugin.Settings.Abstractions;
namespace Aurora.Services.Settings
{
public class SettingsService : ISettingsService
{
private Lazy<ISettings> _appSettings;
private string _usernameKey = "username";
private string _defaultPortKey = "port";
private string _libraryLocationKey = "libraryLocation";
public SettingsService()
{
}
public ISettings AppSettings
{
get
{
if (_appSettings == null)
{
_appSettings = new Lazy<ISettings>(() => CrossSettings.Current, LazyThreadSafetyMode.PublicationOnly);
}
return _appSettings.Value;
}
set
{
_appSettings = new Lazy<ISettings>(() => value, LazyThreadSafetyMode.PublicationOnly);
}
}
/// <summary>
/// The user's username. This is persisted.
/// </summary>
/// <value></value>
public string Username
{
get { return AppSettings.GetValueOrDefault(_usernameKey, ""); }
set
{
AppSettings.AddOrUpdateValue(_usernameKey, value);
}
}
/// <summary>
/// The default port to use. This is persisted.
/// </summary>
/// <value></value>
public int DefaultPort
{
get { return AppSettings.GetValueOrDefault(_defaultPortKey, 4005); }
set { AppSettings.AddOrUpdateValue(_defaultPortKey, value); }
}
public string LibraryLocation
{
get { return AppSettings.GetValueOrDefault(_libraryLocationKey, "~/Music"); }
set { AppSettings.AddOrUpdateValue(_libraryLocationKey, value); }
}
/// <summary>
/// The current sessions clientId. This is assigned by the server. This is not persisted.
/// </summary>
/// <value></value>
public string ClientName
{
get; set;
}
}
}