I think i've got simultaneous playback working but it is hard to test

This commit is contained in:
watsonb8 2019-11-09 14:55:09 -05:00
parent 426a368385
commit bbf8f3ae57
6 changed files with 234 additions and 139 deletions

View File

@ -21,8 +21,8 @@ namespace Aurora.Design.Views
/// <summary> /// <summary>
/// Command event handler for player play button /// Command event handler for player play button
/// </summary> /// </summary>
public virtual void OnPlayExecute() { } public virtual void OnPlayButtonExecute() { }
public virtual bool CanPlayExecute() public virtual bool CanPlayButtonExecute()
{ {
return true; return true;
} }
@ -30,8 +30,8 @@ namespace Aurora.Design.Views
/// <summary> /// <summary>
/// Command event handler for player next button /// Command event handler for player next button
/// </summary> /// </summary>
public virtual void OnNextExecute() { } public virtual void OnNextButtonExecute() { }
public virtual bool CanNextExecute() public virtual bool CanNextButtonExecute()
{ {
return true; return true;
} }
@ -39,8 +39,8 @@ namespace Aurora.Design.Views
/// <summary> /// <summary>
/// Command event handler for player previous button /// Command event handler for player previous button
/// </summary> /// </summary>
public virtual void OnPreviousExecute() { } public virtual void OnPreviousButtonExecute() { }
public virtual bool CanPreviousExecute() public virtual bool CanPreviousButtonExecute()
{ {
return true; return true;
} }
@ -70,6 +70,8 @@ namespace Aurora.Design.Views
public SetPlayerMetadataDelegate SetPlayerMetadata { get; set; } public SetPlayerMetadataDelegate SetPlayerMetadata { get; set; }
public SetPlayerVisibleDelegate SetPlayerVisible { get; set; }
#endregion Player #endregion Player
#region Lifecycle #region Lifecycle

View File

@ -17,17 +17,25 @@ namespace Aurora.Design.Views.Main
/// <param name="media"></param> /// <param name="media"></param>
public delegate void SetPlayerMetadataDelegate(BaseMedia media); public delegate void SetPlayerMetadataDelegate(BaseMedia media);
public delegate void SetPlayerVisibleDelegate(Boolean visible);
[XamlCompilation(XamlCompilationOptions.Compile)] [XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainView : MasterDetailPage, IDisposable public partial class MainView : MasterDetailPage, IDisposable
{ {
private Dictionary<int, BaseViewModel> _viewModels; private Dictionary<int, BaseViewModel> _viewModels;
private BaseViewModel _lastViewModel; private BaseViewModel _lastViewModel;
private Player _player;
private ContentPresenter _viewContent;
public MainView() public MainView()
{ {
InitializeComponent(); InitializeComponent();
BindingContext = new MainViewModel(); BindingContext = new MainViewModel();
_viewModels = new Dictionary<int, BaseViewModel>(); _viewModels = new Dictionary<int, BaseViewModel>();
_player = (Player)ContentPage.FindByName("Player");
_viewContent = (ContentPresenter)ContentPage.Content.FindByName("ViewContent");
MasterPage.ListView.ItemSelected += OnNavItemSelected; MasterPage.ListView.ItemSelected += OnNavItemSelected;
Appearing += OnAppearing; Appearing += OnAppearing;
@ -84,8 +92,7 @@ namespace Aurora.Design.Views.Main
//Assign player controls to viewmodel //Assign player controls to viewmodel
AssignPlayerControls(vm); AssignPlayerControls(vm);
ContentPresenter viewContent = (ContentPresenter)ContentPage.Content.FindByName("ViewContent"); _viewContent.Content = view;
viewContent.Content = view;
MasterPage.ListView.SelectedItem = null; MasterPage.ListView.SelectedItem = null;
} }
@ -121,9 +128,7 @@ namespace Aurora.Design.Views.Main
AssignPlayerControls(vm); AssignPlayerControls(vm);
vm.OnActive(); vm.OnActive();
_viewContent.Content = view;
ContentPresenter viewContent = (ContentPresenter)ContentPage.Content.FindByName("ViewContent");
viewContent.Content = view;
MasterPage.ListView.SelectedItem = screenList.FirstOrDefault(); MasterPage.ListView.SelectedItem = screenList.FirstOrDefault();
} }
@ -135,6 +140,7 @@ namespace Aurora.Design.Views.Main
private void UnassignPlayerControls(BaseViewModel vm) private void UnassignPlayerControls(BaseViewModel vm)
{ {
vm.SetPlayerMetadata = null; vm.SetPlayerMetadata = null;
vm.SetPlayerVisible = null;
} }
/// <summary> /// <summary>
@ -143,25 +149,37 @@ namespace Aurora.Design.Views.Main
/// <param name="vm">BaseViewModel to assign controls to</param> /// <param name="vm">BaseViewModel to assign controls to</param>
private void AssignPlayerControls(BaseViewModel vm) private void AssignPlayerControls(BaseViewModel vm)
{ {
Player player = (Player)ContentPage.FindByName("Player"); _player.PlayButtonCommand = new Command(vm.OnPlayButtonExecute, vm.CanPlayButtonExecute);
player.PlayButtonCommand = new Command(vm.OnPlayExecute, vm.CanPlayExecute); _player.NextButtonCommand = new Command(vm.OnNextButtonExecute, vm.CanNextButtonExecute);
player.NextButtonCommand = new Command(vm.OnNextExecute, vm.CanNextExecute); _player.PreviousButtonCommand = new Command(vm.OnPreviousButtonExecute, vm.CanPreviousButtonExecute);
player.PreviousButtonCommand = new Command(vm.OnPreviousExecute, vm.CanPreviousExecute);
//Assign SetPlayer delegate //Assign SetPlayer delegate
vm.SetPlayerMetadata = SetPlayerDelegate; vm.SetPlayerMetadata = SetPlayer;
vm.SetPlayerVisible = SetPlayerVisible;
} }
private void SetPlayerDelegate(BaseMedia media) /// <summary>
/// SetPlayerDelegate implementation
/// </summary>
/// <param name="media"></param>
private void SetPlayer(BaseMedia media)
{ {
Player player = (Player)ContentPage.FindByName("Player");
if (media.Metadata is AudioMetadata) if (media.Metadata is AudioMetadata)
{ {
AudioMetadata meta = (AudioMetadata)media.Metadata; AudioMetadata meta = (AudioMetadata)media.Metadata;
player.ArtistName = meta.Artist; _player.ArtistName = meta.Artist;
player.SongTitle = meta.Title; _player.SongTitle = meta.Title;
} }
} }
/// <summary>
/// SetPlayerVisibleDelegate implementation
/// </summary>
/// <param name="visible"></param>
private void SetPlayerVisible(Boolean visible)
{
_player.IsVisible = visible;
}
} }
} }

View File

@ -9,10 +9,12 @@ using Aurora.Proto.Party;
using Aurora.Proto.Events; using Aurora.Proto.Events;
using Aurora.Services.ClientService; using Aurora.Services.ClientService;
using Aurora.Services.PlayerService; using Aurora.Services.PlayerService;
using Aurora.Services.EventManager;
using Aurora.Models.Media; using Aurora.Models.Media;
namespace Aurora.Design.Views.Party namespace Aurora.Design.Views.Party
{ {
//TODO refactor
enum PartyState enum PartyState
{ {
SelectingHost, SelectingHost,
@ -29,6 +31,7 @@ namespace Aurora.Design.Views.Party
private ObservableCollection<BaseMedia> _queue; private ObservableCollection<BaseMedia> _queue;
private BaseMedia _selectedMedia; private BaseMedia _selectedMedia;
private PlayerService _player; private PlayerService _player;
private ClientService _client;
public PartyViewModel() public PartyViewModel()
{ {
@ -41,10 +44,12 @@ namespace Aurora.Design.Views.Party
SetState(PartyState.SelectingHost); SetState(PartyState.SelectingHost);
this._player = PlayerService.Instance; this._player = PlayerService.Instance;
PlayCommand = new Command(OnPlayExecute); PlayCommand = new Command(OnDoubleClickExecute, CanDoubleClickExecute);
_client = ClientService.Instance;
//Hook up event handler //Hook up event handler
ClientService.Instance.EventReceived += this.OnEventReceived; _client.EventReceived += this.OnEventReceived;
} }
~PartyViewModel() ~PartyViewModel()
@ -198,6 +203,18 @@ namespace Aurora.Design.Views.Party
} }
break; 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() private async void OnJoinExecute()
{ {
SetState(PartyState.Connecting); SetState(PartyState.Connecting);
ClientService.Instance.Start(Hostname, SettingsService.Instance.DefaultPort.ToString()); _client.Start(Hostname, SettingsService.Instance.DefaultPort.ToString());
await JoinParty(); await JoinParty();
SetState(PartyState.InParty); SetState(PartyState.InParty);
@ -224,13 +241,16 @@ namespace Aurora.Design.Views.Party
SetState(PartyState.Connecting); SetState(PartyState.Connecting);
ServerService.Instance.Start(); ServerService.Instance.Start();
string localHost = ServerService.GetLocalIPAddress(); string localHost = ServerService.GetLocalIPAddress();
ClientService.Instance.Start(localHost, SettingsService.Instance.DefaultPort.ToString()); _client.IsHost = true;
_client.Start(localHost, SettingsService.Instance.DefaultPort.ToString());
await JoinParty(); await JoinParty();
//TODO add cancellation token
try try
{ {
SetState(PartyState.Hosting); SetState(PartyState.Hosting);
await ClientService.Instance.GetEvents().ConfigureAwait(true); await _client.GetEvents().ConfigureAwait(true);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -243,108 +263,8 @@ namespace Aurora.Design.Views.Party
return true; return true;
} }
public override void OnPlayButtonExecute()
#endregion Commands
#region Private Methods
/// <summary>
/// Join the remote party.
/// </summary>
/// <returns></returns>
private async Task JoinParty()
{ {
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);
}
/// <summary>
/// Refresh members list.
/// </summary>
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(); _player.Play();
switch (_player.PlaybackState) 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; return this._state == PartyState.Hosting;
} }
public override bool CanNextExecute() public override bool CanPreviousButtonExecute()
{ {
return this._state == PartyState.Hosting; return this._state == PartyState.Hosting;
} }
public override bool CanPreviousExecute() /// <summary>
/// On double click execute, fire media playing event
/// </summary>
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; return this._state == PartyState.Hosting;
} }
#endregion Commands
#region Private Methods
/// <summary>
/// Join the remote party.
/// </summary>
/// <returns></returns>
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);
}
/// <summary>
/// Refresh members list.
/// </summary>
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 #endregion Private Methods
} }
} }

View File

@ -20,7 +20,7 @@ namespace Aurora.Design.Views.Songs
{ {
_player = PlayerService.Instance; _player = PlayerService.Instance;
_songsList = new ObservableCollection<BaseMedia>(); _songsList = new ObservableCollection<BaseMedia>();
PlayCommand = new Command(OnPlayExecute, CanPlayExecute); PlayCommand = new Command(OnPlayButtonExecute, CanPlayButtonExecute);
_player.PlaybackStateChanged += OnPlaybackStateChanged; _player.PlaybackStateChanged += OnPlaybackStateChanged;
@ -56,16 +56,16 @@ namespace Aurora.Design.Views.Songs
#endregion Methods #endregion Methods
#region Commmands #region Commmands
public override bool CanPreviousExecute() public override bool CanPreviousButtonExecute()
{ {
return true; return true;
} }
public override void OnPreviousExecute() public override void OnPreviousButtonExecute()
{ {
} }
public override bool CanPlayExecute() public override bool CanPlayButtonExecute()
{ {
switch (_player.PlaybackState) switch (_player.PlaybackState)
{ {
@ -85,7 +85,7 @@ namespace Aurora.Design.Views.Songs
return false; return false;
} }
public async override void OnPlayExecute() public async override void OnPlayButtonExecute()
{ {
base.Media = _selectedSong; base.Media = _selectedSong;
if (!_player.IsMediaLoaded(base.Media)) if (!_player.IsMediaLoaded(base.Media))
@ -109,12 +109,12 @@ namespace Aurora.Design.Views.Songs
} }
} }
public override bool CanNextExecute() public override bool CanNextButtonExecute()
{ {
return true; return true;
} }
public override void OnNextExecute() public override void OnNextButtonExecute()
{ {
} }

View File

@ -40,6 +40,8 @@ message SubscriptionResponse {
enum EventType { enum EventType {
PartyMemberJoined = 0; PartyMemberJoined = 0;
PartyMemberLeft = 1; PartyMemberLeft = 1;
MediaPlaying = 2;
MediaStopped = 3;
} }
message BaseEvent { message BaseEvent {
EventType eventType = 1; EventType eventType = 1;
@ -48,13 +50,23 @@ message BaseEvent {
oneof derivedEvent { oneof derivedEvent {
PartyMemberJoinedEvent partyMemberJoinedEvent = 3; PartyMemberJoinedEvent partyMemberJoinedEvent = 3;
PartyMemberLeftEvent partyMemberLeftEvent = 4; 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 { message PartyMemberJoinedEvent {
Aurora.Proto.Party.PartyMember member = 3; Aurora.Proto.Party.PartyMember member = 1;
} }
message PartyMemberLeftEvent { message PartyMemberLeftEvent {
Aurora.Proto.Party.PartyMember member = 3; Aurora.Proto.Party.PartyMember member = 1;
} }

View File

@ -17,7 +17,6 @@ namespace Aurora.Services.ClientService
public ClientService() public ClientService()
{ {
} }
public EventReceivedEventHandler EventReceived; public EventReceivedEventHandler EventReceived;
@ -44,6 +43,8 @@ namespace Aurora.Services.ClientService
} }
} }
public bool IsHost { get; set; }
public void Start(string hostname, string port) public void Start(string hostname, string port)
{ {
_channel = new Channel(string.Format("{0}:{1}", hostname, port), ChannelCredentials.Insecure); _channel = new Channel(string.Format("{0}:{1}", hostname, port), ChannelCredentials.Insecure);