Merge branch 'feature/proto.v2'
This commit is contained in:
commit
aae221801a
@ -150,7 +150,7 @@
|
||||
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.2\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bcl.AsyncInterfaces">
|
||||
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.1.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
|
||||
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Linq.Async">
|
||||
<HintPath>..\packages\System.Linq.Async.4.0.0\lib\net461\System.Linq.Async.dll</HintPath>
|
||||
@ -164,6 +164,10 @@
|
||||
<Reference Include="CarouselView.FormsPlugin.Abstractions">
|
||||
<HintPath>..\packages\CarouselView.FormsPlugin.5.2.0\lib\netstandard2.0\CarouselView.FormsPlugin.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Autofac">
|
||||
<HintPath>..\packages\Autofac.5.0.0\lib\net461\Autofac.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ComponentModel.Composition" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="gtk-gui\gui.stetic">
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Autofac" version="5.0.0" targetFramework="net47" />
|
||||
<package id="CarouselView.FormsPlugin" version="5.2.0" targetFramework="net47" />
|
||||
<package id="DLToolkit.Forms.Controls.FlowListView" version="2.0.11" targetFramework="net47" />
|
||||
<package id="Google.Protobuf" version="3.10.1" targetFramework="net47" />
|
||||
@ -13,7 +14,7 @@
|
||||
<package id="LibVLCSharp.GTK" version="3.3.1" targetFramework="net47" />
|
||||
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="net47" />
|
||||
<package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net47" />
|
||||
<package id="Microsoft.Bcl.AsyncInterfaces" version="1.0.0" targetFramework="net47" />
|
||||
<package id="Microsoft.Bcl.AsyncInterfaces" version="1.1.0" targetFramework="net47" />
|
||||
<package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net47" />
|
||||
<package id="OpenTK" version="3.1.0" targetFramework="net47" />
|
||||
<package id="OpenTK.GLControl" version="3.0.1" targetFramework="net47" />
|
||||
|
14
Aurora.sln
14
Aurora.sln
@ -5,6 +5,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aurora", "Aurora\Aurora.csp
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aurora.gtk", "Aurora.gtk\Aurora.gtk.csproj", "{E8C8C24A-5C51-47CB-B241-F5A9F0E808B1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aurora.test", "Aurora.test\Aurora.test.csproj", "{45680D8A-1AF1-4D93-AAC0-59CDB01CED5D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|iPhoneSimulator = Debug|iPhoneSimulator
|
||||
@ -39,5 +41,17 @@ Global
|
||||
{E8C8C24A-5C51-47CB-B241-F5A9F0E808B1}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator
|
||||
{E8C8C24A-5C51-47CB-B241-F5A9F0E808B1}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator
|
||||
{E8C8C24A-5C51-47CB-B241-F5A9F0E808B1}.Release|Any CPU.Build.0 = Release|iPhoneSimulator
|
||||
{45680D8A-1AF1-4D93-AAC0-59CDB01CED5D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{45680D8A-1AF1-4D93-AAC0-59CDB01CED5D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{45680D8A-1AF1-4D93-AAC0-59CDB01CED5D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{45680D8A-1AF1-4D93-AAC0-59CDB01CED5D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{45680D8A-1AF1-4D93-AAC0-59CDB01CED5D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{45680D8A-1AF1-4D93-AAC0-59CDB01CED5D}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{45680D8A-1AF1-4D93-AAC0-59CDB01CED5D}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{45680D8A-1AF1-4D93-AAC0-59CDB01CED5D}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{45680D8A-1AF1-4D93-AAC0-59CDB01CED5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{45680D8A-1AF1-4D93-AAC0-59CDB01CED5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{45680D8A-1AF1-4D93-AAC0-59CDB01CED5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{45680D8A-1AF1-4D93-AAC0-59CDB01CED5D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
117
Aurora.test/Aurora.test.csproj
Normal file
117
Aurora.test/Aurora.test.csproj
Normal file
@ -0,0 +1,117 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="nunit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="Grpc.Core" Version="2.26.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.26.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Grpc" Version="2.26.0" />
|
||||
<PackageReference Include="Autofac" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Protobuf Remove="..\Aurora\Proto\general.proto" />
|
||||
<Protobuf Remove="..\Aurora\Proto\party.v2.proto" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Aurora\Aurora.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Resources\Jidenna\The Chief\01 A Bull%27s Tale.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Jidenna\The Chief\04 Bambi.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Jidenna\The Chief\14 Bully Of The Earth.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Jidenna\The Chief\12 Some Kind Of Way.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Jidenna\The Chief\03 Trampoline.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Jidenna\The Chief\09 Safari.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Jidenna\The Chief\05 Helicopters _ Beware.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Jidenna\The Chief\08 The Let Out.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Jidenna\The Chief\10 Adaora.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Jidenna\The Chief\07 2 Points.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Jidenna\The Chief\06 Long Live The Chief.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Jidenna\The Chief\02 Chief Don%27t Run.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Jidenna\The Chief\11 Little Bit More.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Jidenna\The Chief\13 White Niggas.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\16 BDE Bonus %28Prod. By_ ID Labs%29.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\11 Play Ya Cards %28Prod By_ Chuck Inglish%29.m4a">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\08 All Around The World %28Prod. By_ Just Blaze%29.m4a">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\10 In The Air %28Prod By_ Ritz Reynolds%29.m4a">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\05 I%27ll Be There %28feat. Phonte%29 %28Prod. By_ Beanz %27n%27 Kornbread%29.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\03 Donald Trump %28Prod. By_ Sap%29.m4a">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\13 Life Ain%27t Easy %28Prod. By_ ID Labs%29.m4a">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\09 Down The Rabbit Hole %28Prod. By_ Blue of The Sore Losers%29.m4a">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\07 Wake Up %28Prod By_ Sap & ID Labs%29.m4a">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\04 Oy Vey %28Prod By_ ID Labs%29.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\01 Best Day Ever %28Prod. By_ ID Labs%29.m4a">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\02 Get Up %28Prod. By_ Teddy Roxpin%29.m4a">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\14 Snooze %28Prod By_ ID Labs%29.m4a">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\12 She Said %28Prod By_ Khrysis%29.m4a">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\15 Keep Floatin%27 %28feat. Wiz Khalifa%29 %28Prod. By_ ID Labs%29.mp3">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Mac Miller\Best Day Ever\06 Wear My Hat %28Prod. By_ Chuck Inglish%29.m4a">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
100
Aurora.test/ControllerTests/EventTests.cs
Normal file
100
Aurora.test/ControllerTests/EventTests.cs
Normal file
@ -0,0 +1,100 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using Aurora.Proto.Party;
|
||||
using Aurora.Services.Server;
|
||||
using Aurora.Services.EventManager;
|
||||
using Grpc.Core;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Autofac;
|
||||
namespace Aurora.test.ControllerTests
|
||||
{
|
||||
public class EventTest
|
||||
{
|
||||
private RemotePartyService.RemotePartyServiceClient _remotePartyService;
|
||||
private Channel _channel;
|
||||
private IContainer _container;
|
||||
private IServerService _serverService;
|
||||
|
||||
#region Setup
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_container = SetupUtil.SetupOneTime();
|
||||
_remotePartyService = SetupUtil.Setup(ref _container, ref _serverService, ref _channel);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public async Task TearDown()
|
||||
{
|
||||
await _serverService.Stop();
|
||||
await _channel.ShutdownAsync();
|
||||
_container.Dispose();
|
||||
}
|
||||
#endregion Setup
|
||||
[Test]
|
||||
[TestCase(EventType.MediaPlaying)]
|
||||
public void Asdf(EventType value)
|
||||
{
|
||||
Assert.AreEqual(1, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(EventType.MediaPlaying)]
|
||||
[TestCase(EventType.MediaStopped)]
|
||||
[TestCase(EventType.MemberCreated)]
|
||||
[TestCase(EventType.MemberDeleted)]
|
||||
public async Task TestEventSubscriptions(EventType value)
|
||||
{
|
||||
using (var scope = _container.BeginLifetimeScope())
|
||||
{
|
||||
IEventManager eventManager = scope.Resolve<IEventManager>();
|
||||
|
||||
//Create new party member
|
||||
Member member = _remotePartyService.CreateMember(new CreateMemberRequest()
|
||||
{
|
||||
Parent = "party1",
|
||||
Member = new Member()
|
||||
{
|
||||
UserName = "newMember1",
|
||||
IpAddress = ServerService.GetLocalIPAddress(),
|
||||
}
|
||||
});
|
||||
|
||||
//Subscribe to event type
|
||||
_remotePartyService.CreateEventSubscription(new CreateEventSubscriptionRequest()
|
||||
{
|
||||
Parent = member.Name,
|
||||
EventSubscription = new EventSubscription()
|
||||
{
|
||||
Type = value
|
||||
}
|
||||
});
|
||||
|
||||
BaseEvent @event = new BaseEvent
|
||||
{
|
||||
EventType = value,
|
||||
};
|
||||
//Fire event
|
||||
|
||||
CancellationTokenSource eventCancellationTokenSource = new CancellationTokenSource();
|
||||
BaseEvent newEvent = null;
|
||||
|
||||
Task.Run(async () => { await Task.Delay(1000); eventManager.FireEvent(@event); });
|
||||
|
||||
using (AsyncServerStreamingCall<BaseEvent> eventStream = _remotePartyService
|
||||
.GetEvents(new GetEventsRequest() { Parent = member.Name }))
|
||||
{
|
||||
while ((!eventCancellationTokenSource.Token.IsCancellationRequested &&
|
||||
await eventStream.ResponseStream.MoveNext(eventCancellationTokenSource.Token)))
|
||||
{
|
||||
newEvent = new BaseEvent(eventStream.ResponseStream.Current);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Assert.AreEqual(newEvent.EventType, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
78
Aurora.test/ControllerTests/MediaControllerTest.cs
Normal file
78
Aurora.test/ControllerTests/MediaControllerTest.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using NUnit.Framework;
|
||||
using Aurora.Proto.Party;
|
||||
using Aurora.Services.Server;
|
||||
using Grpc.Core;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using Autofac;
|
||||
|
||||
namespace Aurora.test.ControllerTests
|
||||
{
|
||||
public class MediaControllerTests
|
||||
{
|
||||
private RemotePartyService.RemotePartyServiceClient _remotePartyService;
|
||||
private Channel _channel;
|
||||
private IContainer _container;
|
||||
private IServerService _serverService;
|
||||
|
||||
#region Setup
|
||||
[OneTimeSetUp]
|
||||
public void SetupOneTime()
|
||||
{
|
||||
_container = SetupUtil.SetupOneTime();
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void TearDownOneTime()
|
||||
{
|
||||
_container.Dispose();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_remotePartyService = SetupUtil.Setup(ref _container, ref _serverService, ref _channel);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public async Task TearDown()
|
||||
{
|
||||
await _serverService.Stop();
|
||||
await _channel.ShutdownAsync();
|
||||
}
|
||||
#endregion Setup
|
||||
|
||||
[Test]
|
||||
public void TestNotEmpty()
|
||||
{
|
||||
ListMediaResponse resp = _remotePartyService.ListMedia(new ListMediaRequest()
|
||||
{
|
||||
Parent = "testParty",
|
||||
PageSize = 5
|
||||
});
|
||||
|
||||
Assert.NotNull(resp.Media);
|
||||
Assert.NotZero(resp.Media.Count);
|
||||
Assert.AreEqual(resp.Media.Count, 5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetMediaTest()
|
||||
{
|
||||
ListMediaResponse resp = _remotePartyService.ListMedia(new ListMediaRequest()
|
||||
{
|
||||
Parent = "testParty",
|
||||
PageSize = 5
|
||||
});
|
||||
|
||||
Media media = _remotePartyService.GetMedia(new GetMediaRequest()
|
||||
{
|
||||
Name = resp.Media[0].Name
|
||||
});
|
||||
|
||||
Assert.NotNull(media);
|
||||
Assert.AreEqual(media.Name, resp.Media[0].Name);
|
||||
}
|
||||
}
|
||||
}
|
206
Aurora.test/ControllerTests/MembersControllerTest.cs
Normal file
206
Aurora.test/ControllerTests/MembersControllerTest.cs
Normal file
@ -0,0 +1,206 @@
|
||||
using NUnit.Framework;
|
||||
using Aurora.Proto.Party;
|
||||
using Aurora.Services.Server;
|
||||
using Grpc.Core;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using Autofac;
|
||||
|
||||
namespace Aurora.test.ControllerTests
|
||||
{
|
||||
public class MemberControllerTests
|
||||
{
|
||||
private RemotePartyService.RemotePartyServiceClient _remotePartyService;
|
||||
private Channel _channel;
|
||||
|
||||
private IContainer _container;
|
||||
private IServerService _serverService;
|
||||
|
||||
#region Setup
|
||||
[OneTimeSetUp]
|
||||
public void SetupOneTime()
|
||||
{
|
||||
_container = SetupUtil.SetupOneTime();
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void TearDownOneTime()
|
||||
{
|
||||
_container.Dispose();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_remotePartyService = SetupUtil.Setup(ref _container, ref _serverService, ref _channel);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public async Task TearDown()
|
||||
{
|
||||
await _serverService.Stop();
|
||||
await _channel.ShutdownAsync();
|
||||
}
|
||||
#endregion Setup
|
||||
|
||||
|
||||
[Test]
|
||||
public void DefaultTest()
|
||||
{
|
||||
ListMembersResponse resp = _remotePartyService.ListMembers(new ListMembersRequest()
|
||||
{
|
||||
Parent = "party1",
|
||||
PageSize = 10,
|
||||
});
|
||||
Assert.NotNull(resp);
|
||||
Assert.GreaterOrEqual(resp.Members.Count, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("Alex")]
|
||||
[TestCase("Alex Goldberg")]
|
||||
[TestCase("Alex/goldberg")]
|
||||
[TestCase("alex@welcome.com")]
|
||||
public void CreateMemberTest(string value)
|
||||
{
|
||||
Member member = _remotePartyService.CreateMember(new CreateMemberRequest()
|
||||
{
|
||||
Parent = "party1",
|
||||
Member = new Member()
|
||||
{
|
||||
UserName = value,
|
||||
IpAddress = ServerService.GetLocalIPAddress(),
|
||||
}
|
||||
});
|
||||
|
||||
Assert.NotNull(member);
|
||||
}
|
||||
|
||||
static object[] MultipleMembersCases =
|
||||
{
|
||||
new object[] {"Tupac", "Aubrey Grahm", "Beyonce Knowls", "Ke$ha", "A$ap Ferg"},
|
||||
};
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("MultipleMembersCases")]
|
||||
public void CreateMultiplMembersTest(object[] memberNames)
|
||||
{
|
||||
//Add members
|
||||
foreach (string name in memberNames)
|
||||
{
|
||||
Member member = _remotePartyService.CreateMember(new CreateMemberRequest()
|
||||
{
|
||||
Parent = "party1",
|
||||
Member = new Member()
|
||||
{
|
||||
UserName = name
|
||||
}
|
||||
});
|
||||
|
||||
Assert.NotNull(member);
|
||||
}
|
||||
|
||||
//List members
|
||||
ListMembersResponse resp = _remotePartyService.ListMembers(new ListMembersRequest()
|
||||
{
|
||||
Parent = "party1",
|
||||
PageSize = 10,
|
||||
});
|
||||
Assert.NotNull(resp);
|
||||
Assert.AreEqual(resp.Members.Count, 6);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("MultipleMembersCases")]
|
||||
public void DeleteMemberTest(object[] memberNames)
|
||||
{
|
||||
//Add members
|
||||
foreach (string name in memberNames)
|
||||
{
|
||||
Member member = _remotePartyService.CreateMember(new CreateMemberRequest()
|
||||
{
|
||||
Parent = "party1",
|
||||
Member = new Member()
|
||||
{
|
||||
UserName = name
|
||||
}
|
||||
});
|
||||
|
||||
Assert.NotNull(member);
|
||||
}
|
||||
|
||||
//List members
|
||||
ListMembersResponse resp = _remotePartyService.ListMembers(new ListMembersRequest()
|
||||
{
|
||||
Parent = "party1",
|
||||
PageSize = 10,
|
||||
});
|
||||
Assert.NotNull(resp);
|
||||
Assert.AreEqual(resp.Members.Count, 6);
|
||||
|
||||
string keshaResourceName = resp.Members.First(member => member.UserName == "Ke$ha").Name;
|
||||
|
||||
//Delete member
|
||||
|
||||
_remotePartyService.DeleteMember(new DeleteMemberRequest()
|
||||
{
|
||||
Name = keshaResourceName
|
||||
});
|
||||
|
||||
//List members
|
||||
resp = _remotePartyService.ListMembers(new ListMembersRequest()
|
||||
{
|
||||
Parent = "party1",
|
||||
PageSize = 10,
|
||||
});
|
||||
Assert.NotNull(resp);
|
||||
Assert.AreEqual(resp.Members.Count, 5);
|
||||
Assert.False(resp.Members.Any(member => member.UserName == "Ke$sha"));
|
||||
}
|
||||
|
||||
static object[] PagingCases =
|
||||
{
|
||||
new object[] {"Tupac", "Aubrey Grahm", "Beyonce Knowls", "Ke$ha", "A$ap Ferg", "asdf", "sdfa", "dfas", "fasd"},
|
||||
};
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("PagingCases")]
|
||||
public void MemberPagingTest(object[] members)
|
||||
{
|
||||
foreach (string name in members)
|
||||
{
|
||||
Member member = _remotePartyService.CreateMember(new CreateMemberRequest()
|
||||
{
|
||||
Parent = "party1",
|
||||
Member = new Member()
|
||||
{
|
||||
UserName = name
|
||||
}
|
||||
});
|
||||
|
||||
Assert.NotNull(member);
|
||||
}
|
||||
|
||||
//List members
|
||||
ListMembersResponse resp = _remotePartyService.ListMembers(new ListMembersRequest()
|
||||
{
|
||||
Parent = "party1",
|
||||
PageSize = 2,
|
||||
});
|
||||
|
||||
string nextPageToken = resp.NextPageToken;
|
||||
|
||||
Assert.AreEqual(resp.Members.Count, 2);
|
||||
|
||||
//List members
|
||||
resp = _remotePartyService.ListMembers(new ListMembersRequest()
|
||||
{
|
||||
Parent = "party1",
|
||||
PageSize = 2,
|
||||
PageToken = nextPageToken,
|
||||
});
|
||||
|
||||
Assert.AreEqual(resp.Members.Count, 2);
|
||||
}
|
||||
}
|
||||
}
|
53
Aurora.test/ControllerTests/PartyControllerTest.cs
Normal file
53
Aurora.test/ControllerTests/PartyControllerTest.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Aurora.Proto.Party;
|
||||
using Aurora.Services.Server;
|
||||
using Grpc.Core;
|
||||
using Autofac;
|
||||
|
||||
namespace Aurora.test.ControllerTests
|
||||
{
|
||||
public class PartyControllerTests
|
||||
{
|
||||
private RemotePartyService.RemotePartyServiceClient _remotePartyService;
|
||||
private Channel _channel;
|
||||
private IContainer _container;
|
||||
private IServerService _serverService;
|
||||
|
||||
#region Setup
|
||||
[OneTimeSetUp]
|
||||
public void SetupOneTime()
|
||||
{
|
||||
_container = SetupUtil.SetupOneTime();
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void TearDownOneTime()
|
||||
{
|
||||
_container.Dispose();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_remotePartyService = SetupUtil.Setup(ref _container, ref _serverService, ref _channel);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public async Task TearDown()
|
||||
{
|
||||
await _serverService.Stop();
|
||||
await _channel.ShutdownAsync();
|
||||
}
|
||||
#endregion Setup
|
||||
|
||||
[Test]
|
||||
public void DefaultTest()
|
||||
{
|
||||
Party party = _remotePartyService.GetParty(new Proto.General.Empty());
|
||||
|
||||
Assert.NotNull(party);
|
||||
Assert.AreEqual(party.Name, "party/party1");
|
||||
}
|
||||
}
|
||||
}
|
39
Aurora.test/ControllerTests/Setup.cs
Normal file
39
Aurora.test/ControllerTests/Setup.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using Autofac;
|
||||
using Aurora.Proto.Party;
|
||||
using Aurora.Services.Server;
|
||||
using Aurora.Services.Library;
|
||||
using Aurora.Services.Settings;
|
||||
using Aurora.Services.EventManager;
|
||||
using Aurora.test.Models.Mock;
|
||||
using System.IO;
|
||||
using Grpc.Core;
|
||||
|
||||
namespace Aurora.test.ControllerTests
|
||||
{
|
||||
public class SetupUtil
|
||||
{
|
||||
public static IContainer SetupOneTime()
|
||||
{
|
||||
ContainerBuilder builder = new ContainerBuilder();
|
||||
builder.RegisterType<ServerService>().As<IServerService>().SingleInstance();
|
||||
builder.RegisterInstance<ISettingsService>(new SettingsServiceMock()
|
||||
{
|
||||
Username = "Test User 1",
|
||||
DefaultPort = 4005,
|
||||
LibraryLocation = string.Format("{0}/Resources", Directory.GetCurrentDirectory())
|
||||
}).SingleInstance();
|
||||
builder.RegisterType<LibraryService>().As<ILibraryService>().SingleInstance();
|
||||
builder.RegisterType<EventManager>().As<IEventManager>().SingleInstance();
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
public static RemotePartyService.RemotePartyServiceClient Setup(ref IContainer container, ref IServerService serverService, ref Channel channel)
|
||||
{
|
||||
serverService = container.Resolve<IServerService>();
|
||||
serverService.Start("testParty", "asdf");
|
||||
channel = new Channel(string.Format("{0}:{1}", ServerService.GetLocalIPAddress(), 8080), ChannelCredentials.Insecure);
|
||||
return new RemotePartyService.RemotePartyServiceClient(channel);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
39
Aurora.test/Models/CallContext.cs
Normal file
39
Aurora.test/Models/CallContext.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Grpc.Core;
|
||||
|
||||
namespace Aurora.test.Models
|
||||
{
|
||||
public class CallContext : ServerCallContext
|
||||
{
|
||||
protected override string MethodCore => throw new NotImplementedException();
|
||||
|
||||
protected override string HostCore => throw new NotImplementedException();
|
||||
|
||||
protected override string PeerCore => throw new NotImplementedException();
|
||||
|
||||
protected override DateTime DeadlineCore => throw new NotImplementedException();
|
||||
|
||||
protected override Metadata RequestHeadersCore => throw new NotImplementedException();
|
||||
|
||||
protected override CancellationToken CancellationTokenCore => throw new NotImplementedException();
|
||||
|
||||
protected override Metadata ResponseTrailersCore => throw new NotImplementedException();
|
||||
|
||||
protected override Status StatusCore { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
protected override WriteOptions WriteOptionsCore { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
|
||||
protected override AuthContext AuthContextCore => throw new NotImplementedException();
|
||||
|
||||
protected override ContextPropagationToken CreatePropagationTokenCore(ContextPropagationOptions options)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
35
Aurora.test/Models/Mock/SettingsServiceMock.cs
Normal file
35
Aurora.test/Models/Mock/SettingsServiceMock.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using Aurora.Services.Settings;
|
||||
using Plugin.Settings.Abstractions;
|
||||
|
||||
namespace Aurora.test.Models.Mock
|
||||
{
|
||||
public class SettingsServiceMock : ISettingsService
|
||||
{
|
||||
public SettingsServiceMock()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ISettings AppSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user's username. This is persisted.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The default port to use. This is persisted.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public int DefaultPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current sessions clientId. This is assigned by the server. This is not persisted.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public string ClientName { get; set; }
|
||||
|
||||
public string LibraryLocation { get; set; }
|
||||
}
|
||||
}
|
BIN
Aurora.test/Resources/Jidenna/The Chief/01 A Bull's Tale.mp3
Executable file
BIN
Aurora.test/Resources/Jidenna/The Chief/01 A Bull's Tale.mp3
Executable file
Binary file not shown.
BIN
Aurora.test/Resources/Jidenna/The Chief/02 Chief Don't Run.mp3
Executable file
BIN
Aurora.test/Resources/Jidenna/The Chief/02 Chief Don't Run.mp3
Executable file
Binary file not shown.
BIN
Aurora.test/Resources/Jidenna/The Chief/03 Trampoline.mp3
Executable file
BIN
Aurora.test/Resources/Jidenna/The Chief/03 Trampoline.mp3
Executable file
Binary file not shown.
BIN
Aurora.test/Resources/Jidenna/The Chief/04 Bambi.mp3
Executable file
BIN
Aurora.test/Resources/Jidenna/The Chief/04 Bambi.mp3
Executable file
Binary file not shown.
BIN
Aurora.test/Resources/Jidenna/The Chief/05 Helicopters _ Beware.mp3
Executable file
BIN
Aurora.test/Resources/Jidenna/The Chief/05 Helicopters _ Beware.mp3
Executable file
Binary file not shown.
BIN
Aurora.test/Resources/Jidenna/The Chief/06 Long Live The Chief.mp3
Executable file
BIN
Aurora.test/Resources/Jidenna/The Chief/06 Long Live The Chief.mp3
Executable file
Binary file not shown.
BIN
Aurora.test/Resources/Jidenna/The Chief/07 2 Points.mp3
Executable file
BIN
Aurora.test/Resources/Jidenna/The Chief/07 2 Points.mp3
Executable file
Binary file not shown.
BIN
Aurora.test/Resources/Jidenna/The Chief/08 The Let Out.mp3
Executable file
BIN
Aurora.test/Resources/Jidenna/The Chief/08 The Let Out.mp3
Executable file
Binary file not shown.
BIN
Aurora.test/Resources/Jidenna/The Chief/09 Safari.mp3
Executable file
BIN
Aurora.test/Resources/Jidenna/The Chief/09 Safari.mp3
Executable file
Binary file not shown.
BIN
Aurora.test/Resources/Jidenna/The Chief/10 Adaora.mp3
Executable file
BIN
Aurora.test/Resources/Jidenna/The Chief/10 Adaora.mp3
Executable file
Binary file not shown.
BIN
Aurora.test/Resources/Jidenna/The Chief/11 Little Bit More.mp3
Executable file
BIN
Aurora.test/Resources/Jidenna/The Chief/11 Little Bit More.mp3
Executable file
Binary file not shown.
BIN
Aurora.test/Resources/Jidenna/The Chief/12 Some Kind Of Way.mp3
Executable file
BIN
Aurora.test/Resources/Jidenna/The Chief/12 Some Kind Of Way.mp3
Executable file
Binary file not shown.
BIN
Aurora.test/Resources/Jidenna/The Chief/13 White Niggas.mp3
Executable file
BIN
Aurora.test/Resources/Jidenna/The Chief/13 White Niggas.mp3
Executable file
Binary file not shown.
BIN
Aurora.test/Resources/Jidenna/The Chief/14 Bully Of The Earth.mp3
Executable file
BIN
Aurora.test/Resources/Jidenna/The Chief/14 Bully Of The Earth.mp3
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Aurora.test/Resources/Mac Miller/Best Day Ever/04 Oy Vey (Prod By_ ID Labs).mp3
Executable file
BIN
Aurora.test/Resources/Mac Miller/Best Day Ever/04 Oy Vey (Prod By_ ID Labs).mp3
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Aurora.test/Resources/Mac Miller/Best Day Ever/14 Snooze (Prod By_ ID Labs).m4a
Executable file
BIN
Aurora.test/Resources/Mac Miller/Best Day Ever/14 Snooze (Prod By_ ID Labs).m4a
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,19 +1,57 @@
|
||||
using System;
|
||||
using Aurora.Design.Views.Main;
|
||||
using Aurora.Design.Views.Albums;
|
||||
using Aurora.Design.Views.Artists;
|
||||
using Aurora.Design.Views.Party;
|
||||
using Aurora.Design.Views.Profile;
|
||||
using Aurora.Design.Views.Songs;
|
||||
using Aurora.Design.Views.Stations;
|
||||
using Aurora.Services.EventManager;
|
||||
using Aurora.Services.Server;
|
||||
using Aurora.Services.Client;
|
||||
using Autofac;
|
||||
using LibVLCSharp.Shared;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
using Aurora.Services.Player;
|
||||
using Aurora.Services.Settings;
|
||||
using Aurora.Services.Library;
|
||||
|
||||
namespace Aurora
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
private static IContainer _container;
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
Core.Initialize();
|
||||
|
||||
MainPage = new MainView();
|
||||
//Register DI
|
||||
ContainerBuilder _builder = new ContainerBuilder();
|
||||
// _builder.RegisterInstance<IPlayer>(new PlayerService()).SingleInstance();
|
||||
_builder.RegisterType<PlayerService>().As<IPlayer>().SingleInstance();
|
||||
_builder.RegisterType<SettingsService>().As<ISettingsService>().SingleInstance();
|
||||
_builder.RegisterType<LibraryService>().As<ILibraryService>().SingleInstance();
|
||||
_builder.RegisterType<EventManager>().As<IEventManager>().SingleInstance();
|
||||
_builder.RegisterType<ServerService>().As<IServerService>().SingleInstance();
|
||||
_builder.RegisterType<ClientService>().As<IClientService>().SingleInstance();
|
||||
_builder.RegisterType<MainView>().SingleInstance();
|
||||
_builder.RegisterType<AlbumsViewModel>();
|
||||
_builder.RegisterType<ArtistsViewModel>();
|
||||
_builder.RegisterType<PartyViewModel>();
|
||||
_builder.RegisterType<ProfileViewModel>();
|
||||
_builder.RegisterType<SongsViewModel>();
|
||||
_builder.RegisterType<StationsViewModel>();
|
||||
|
||||
// _builder.RegisterInstance<ISettingsService>(new SettingsService()).SingleInstance();
|
||||
_container = _builder.Build();
|
||||
|
||||
MainPage = _container.Resolve<MainView>();
|
||||
}
|
||||
|
||||
public static IContainer Container
|
||||
{
|
||||
get { return _container; }
|
||||
}
|
||||
|
||||
protected override void OnStart()
|
||||
|
@ -23,6 +23,7 @@
|
||||
<PackageReference Include="Sharpnado.Forms.HorizontalListView" Version="1.3.0" />
|
||||
<PackageReference Include="DLToolkit.Forms.Controls.FlowListView" Version="2.0.11" />
|
||||
<PackageReference Include="CarouselView.FormsPlugin" Version="5.2.0" />
|
||||
<PackageReference Include="Autofac" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Design\" />
|
||||
@ -51,7 +52,7 @@
|
||||
<Folder Include="Design\Components\TabView\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Design\Behaviors\DeselectItemBehaviorBase.cs" />
|
||||
<Compile Remove="Design\Behaviors\DeselectItemBehaviorBase.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Design\Components\MusicPlayer\Player.xaml.cs">
|
||||
@ -61,15 +62,12 @@
|
||||
<ItemGroup>
|
||||
<Protobuf Include="Proto\general.proto" />
|
||||
<Protobuf Include="Proto\party.proto" />
|
||||
<Protobuf Include="Proto\events.proto" />
|
||||
<Protobuf Include="Proto\playback.proto" />
|
||||
<Protobuf Include="Proto\sync.proto" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\backward.png" />
|
||||
<EmbeddedResource Include="Resources\forwards.png" />
|
||||
<EmbeddedResource Include="Resources\like.png" />
|
||||
<EmbeddedResource Include="Resources\play.png" />
|
||||
<EmbeddedResource Include="Resources\backward.png" />
|
||||
<EmbeddedResource Include="Resources\forwards.png" />
|
||||
<EmbeddedResource Include="Resources\like.png" />
|
||||
<EmbeddedResource Include="Resources\play.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Design\Components\NavigationMenu\NavigationMenu.css">
|
||||
@ -77,11 +75,11 @@
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Design\Resources\unselected.png" />
|
||||
<None Remove="Design\Components\MediaPlayer\play.png" />
|
||||
<None Remove="Resources\backward.png" />
|
||||
<None Remove="Resources\forwards.png" />
|
||||
<None Remove="Resources\like.png" />
|
||||
<None Remove="Resources\play.png" />
|
||||
<None Remove="Design\Resources\unselected.png" />
|
||||
<None Remove="Design\Components\MediaPlayer\play.png" />
|
||||
<None Remove="Resources\backward.png" />
|
||||
<None Remove="Resources\forwards.png" />
|
||||
<None Remove="Resources\like.png" />
|
||||
<None Remove="Resources\play.png" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -24,7 +24,7 @@ namespace Aurora.Design.Components.MemberList
|
||||
/// <returns></returns>
|
||||
public static readonly BindableProperty MembersProperty =
|
||||
BindableProperty.Create(propertyName: "Members",
|
||||
returnType: typeof(ObservableCollection<PartyMember>),
|
||||
returnType: typeof(ObservableCollection<Member>),
|
||||
declaringType: typeof(MemberList),
|
||||
defaultBindingMode: BindingMode.Default,
|
||||
propertyChanged: OnMembersChanged);
|
||||
@ -33,11 +33,11 @@ namespace Aurora.Design.Components.MemberList
|
||||
/// Backing property for MembersProperty
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public ObservableCollection<PartyMember> Members
|
||||
public ObservableCollection<Member> Members
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ObservableCollection<PartyMember>)GetValue(MembersProperty);
|
||||
return (ObservableCollection<Member>)GetValue(MembersProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
|
@ -9,8 +9,8 @@ using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
using Aurora.Models.Media;
|
||||
using Aurora.Design.Components.MediaPlayer;
|
||||
using Aurora.Services.PlayerService;
|
||||
using System.Threading;
|
||||
using Aurora.Services.Player;
|
||||
using Autofac;
|
||||
|
||||
namespace Aurora.Design.Views.Main
|
||||
{
|
||||
@ -38,10 +38,10 @@ namespace Aurora.Design.Views.Main
|
||||
private Dictionary<int, BaseViewModel> _viewModels;
|
||||
private BaseViewModel _lastViewModel;
|
||||
private Player _playerComponent;
|
||||
private PlayerService _playerService;
|
||||
private IPlayer _playerService;
|
||||
private ContentPresenter _viewContent;
|
||||
|
||||
public MainView()
|
||||
public MainView(IPlayer player)
|
||||
{
|
||||
InitializeComponent();
|
||||
BindingContext = new MainViewModel();
|
||||
@ -50,7 +50,7 @@ namespace Aurora.Design.Views.Main
|
||||
_playerComponent = Player;
|
||||
|
||||
_viewContent = (ContentPresenter)Content.FindByName("ViewContent");
|
||||
_playerService = PlayerService.Instance;
|
||||
_playerService = player;
|
||||
|
||||
MasterPage.ListView.ItemSelected += OnNavItemSelected;
|
||||
|
||||
@ -89,7 +89,7 @@ namespace Aurora.Design.Views.Main
|
||||
}
|
||||
|
||||
//Instantiate new view model
|
||||
vm = (BaseViewModel)Activator.CreateInstance(item.TargetViewModelType);
|
||||
vm = (BaseViewModel)App.Container.Resolve(item.TargetViewModelType); //Activator.CreateInstance(item.TargetViewModelType);
|
||||
_viewModels.Add(item.Id, vm);
|
||||
|
||||
}
|
||||
@ -135,7 +135,7 @@ namespace Aurora.Design.Views.Main
|
||||
else
|
||||
{
|
||||
//Instantiate new view model
|
||||
vm = (BaseViewModel)Activator.CreateInstance(firstNavItem.TargetViewModelType);
|
||||
vm = (BaseViewModel)App.Container.Resolve(firstNavItem.TargetViewModelType); //(BaseViewModel)Activator.CreateInstance(firstNavItem.TargetViewModelType);
|
||||
_viewModels.Add(firstNavItem.Id, vm);
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Linq;
|
||||
using Xamarin.Forms;
|
||||
using Aurora.Services;
|
||||
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;
|
||||
using Aurora.Services.Client;
|
||||
using Aurora.Design.Views.Party.NewPartyDialog;
|
||||
using Aurora.Services.Settings;
|
||||
using Aurora.Services.Server;
|
||||
using Aurora.Services.EventManager;
|
||||
using Grpc.Core;
|
||||
|
||||
namespace Aurora.Design.Views.Party
|
||||
{
|
||||
@ -24,42 +24,55 @@ namespace Aurora.Design.Views.Party
|
||||
Hosting,
|
||||
Connecting,
|
||||
}
|
||||
|
||||
delegate void EventHandler(BaseEvent e);
|
||||
public class PartyViewModel : BaseViewModel
|
||||
{
|
||||
private PartyState _state;
|
||||
private string _hostname = "";
|
||||
private ObservableCollection<PartyMember> _members;
|
||||
private ObservableCollection<Member> _members;
|
||||
private ObservableCollection<BaseMedia> _queue;
|
||||
private BaseMedia _selectedMedia;
|
||||
private ClientService _client;
|
||||
// private IClientService _client;
|
||||
private ISettingsService _settingsService;
|
||||
private IClientService _clientService;
|
||||
private IServerService _serverService;
|
||||
private IEventManager _eventManager;
|
||||
|
||||
private CancellationTokenSource _eventCancellationTokenSource;
|
||||
|
||||
private Dictionary<BaseEvent.DerivedEventOneofCase, EventHandler> _eventHandlers;
|
||||
|
||||
private int _selectedTabIndex;
|
||||
|
||||
public PartyViewModel()
|
||||
public PartyViewModel(
|
||||
ISettingsService settingsService,
|
||||
IServerService serverService,
|
||||
IEventManager eventManager,
|
||||
IClientService clientService)
|
||||
{
|
||||
_members = new ObservableCollection<PartyMember>();
|
||||
_members = new ObservableCollection<Member>();
|
||||
_queue = new ObservableCollection<BaseMedia>();
|
||||
|
||||
this._settingsService = settingsService;
|
||||
this._serverService = serverService;
|
||||
this._eventManager = eventManager;
|
||||
this._clientService = clientService;
|
||||
|
||||
SetState(PartyState.SelectingHost);
|
||||
|
||||
PlayCommand = new Command(OnDoubleClickCommandExecute, CanDoubleClickCommandExecute);
|
||||
|
||||
LeavePartyCommand = new Command(OnLeavePartyCommandExecute, CanLeavePartyCommandExecute);
|
||||
|
||||
_client = ClientService.Instance;
|
||||
|
||||
_client.OnMediaPaused += this.OnRemoteMediaPaused;
|
||||
_client.OnMediaResumed += this.OnRemoteMediaResumed;
|
||||
_client.OnNewMediaPlaying += this.OnNewRemoteMediaPlaying;
|
||||
_client.OnPartyMemberJoined += this.OnPartyMemberJoined;
|
||||
_client.OnPartyMemberLeft += this.OnPartyMemberLeft;
|
||||
|
||||
}
|
||||
|
||||
~PartyViewModel()
|
||||
{
|
||||
//Task.Run(ServerService.Instance.Stop);
|
||||
//Setup event handlers
|
||||
_eventHandlers = new Dictionary<BaseEvent.DerivedEventOneofCase, EventHandler>()
|
||||
{
|
||||
{BaseEvent.DerivedEventOneofCase.MediaPausedEvent, this.OnRemoteMediaPaused},
|
||||
{BaseEvent.DerivedEventOneofCase.MediaResumedEvent, this.OnRemoteMediaResumed},
|
||||
{BaseEvent.DerivedEventOneofCase.NewMediaPlayingEvent, this.OnNewRemoteMediaPlaying},
|
||||
{BaseEvent.DerivedEventOneofCase.MemberCreatedEvent, this.OnPartyMemberJoined},
|
||||
{BaseEvent.DerivedEventOneofCase.MemberDeletedEvent, this.OnPartyMemberLeft}
|
||||
};
|
||||
}
|
||||
|
||||
#region Properties
|
||||
@ -74,7 +87,7 @@ namespace Aurora.Design.Views.Party
|
||||
/// Publc property for the members list
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public ObservableCollection<PartyMember> Members
|
||||
public ObservableCollection<Member> Members
|
||||
{
|
||||
get
|
||||
{
|
||||
@ -136,7 +149,7 @@ namespace Aurora.Design.Views.Party
|
||||
if (this._state == PartyState.Hosting ||
|
||||
this._state == PartyState.InParty)
|
||||
{
|
||||
await _client.GetEvents().ConfigureAwait(false);
|
||||
await this.GetEvents().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -173,7 +186,7 @@ namespace Aurora.Design.Views.Party
|
||||
/// <returns></returns>
|
||||
public override Task OnInactive()
|
||||
{
|
||||
_client.StopEvents();
|
||||
this._eventCancellationTokenSource.Cancel();
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
@ -182,7 +195,7 @@ namespace Aurora.Design.Views.Party
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
public void OnRemoteMediaPaused(object sender, MediaPausedEventArgs args)
|
||||
public void OnRemoteMediaPaused(BaseEvent e)
|
||||
{
|
||||
StopPlaying();
|
||||
}
|
||||
@ -192,9 +205,9 @@ namespace Aurora.Design.Views.Party
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
public void OnNewRemoteMediaPlaying(object sender, NewMediaPlayingEventArgs args)
|
||||
public void OnNewRemoteMediaPlaying(BaseEvent e)
|
||||
{
|
||||
PlayFromBeginning(GetMediaFromQueue(args.Event.Media.Id));
|
||||
PlayFromBeginning(GetMediaFromQueue(e.NewMediaPlayingEvent.Media.Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -202,7 +215,7 @@ namespace Aurora.Design.Views.Party
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
public void OnRemoteMediaResumed(object sender, MediaResumedEventArgs args)
|
||||
public void OnRemoteMediaResumed(BaseEvent e)
|
||||
{
|
||||
PlayResume();
|
||||
}
|
||||
@ -212,17 +225,9 @@ namespace Aurora.Design.Views.Party
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
public void OnPartyMemberJoined(object sender, PartyMemberJoinedEventArgs args)
|
||||
public void OnPartyMemberJoined(BaseEvent e)
|
||||
{
|
||||
PartyMember member = new PartyMember
|
||||
{
|
||||
UserName = args.Event.Member.UserName,
|
||||
Id = args.Event.Member.Id,
|
||||
IpAddress = args.Event.Member.IpAddress,
|
||||
Port = args.Event.Member.Port
|
||||
};
|
||||
|
||||
Members.Add(member);
|
||||
Members.Add(e.MemberCreatedEvent.Member);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -230,10 +235,10 @@ namespace Aurora.Design.Views.Party
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
public void OnPartyMemberLeft(object sender, PartyMemberLeftEventArgs args)
|
||||
public void OnPartyMemberLeft(BaseEvent e)
|
||||
{
|
||||
var found = Members.Where(x => x.Id == args.Event.Member.Id);
|
||||
foreach (PartyMember member in found)
|
||||
var found = Members.Where(x => x.Name == e.MemberDeletedEvent.MemberName);
|
||||
foreach (Member member in found)
|
||||
{
|
||||
_members.Remove(member);
|
||||
}
|
||||
@ -245,14 +250,14 @@ namespace Aurora.Design.Views.Party
|
||||
private async void OnJoinCommandExecute()
|
||||
{
|
||||
SetState(PartyState.Connecting);
|
||||
_client.Start(_hostname, SettingsService.Instance.DefaultPort.ToString());
|
||||
_clientService.Start(_hostname, this._settingsService.DefaultPort.ToString());
|
||||
await JoinParty(false);
|
||||
|
||||
//TODO add cancellation token
|
||||
try
|
||||
{
|
||||
SetState(PartyState.InParty);
|
||||
await _client.GetEvents().ConfigureAwait(true);
|
||||
await GetEvents().ConfigureAwait(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -269,10 +274,9 @@ namespace Aurora.Design.Views.Party
|
||||
{
|
||||
//Change state
|
||||
SetState(PartyState.Connecting);
|
||||
ServerService.Instance.Start();
|
||||
_serverService.Start("test", "asdf");
|
||||
string localHost = ServerService.GetLocalIPAddress();
|
||||
_client.IsHost = true;
|
||||
_client.Start(localHost, SettingsService.Instance.DefaultPort.ToString());
|
||||
_clientService.Start(localHost, this._settingsService.DefaultPort.ToString());
|
||||
await JoinParty(true);
|
||||
|
||||
|
||||
@ -280,7 +284,7 @@ namespace Aurora.Design.Views.Party
|
||||
try
|
||||
{
|
||||
SetState(PartyState.Hosting);
|
||||
await _client.GetEvents().ConfigureAwait(true);
|
||||
await GetEvents().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -295,7 +299,10 @@ namespace Aurora.Design.Views.Party
|
||||
|
||||
private async void OnLeavePartyCommandExecute()
|
||||
{
|
||||
await _client.RemotePartyClient.LeavePartyAsync(new LeavePartyRequest());
|
||||
await _clientService.RemotePartyServiceClient.DeleteMemberAsync(new DeleteMemberRequest()
|
||||
{
|
||||
Name = _settingsService.ClientName
|
||||
});
|
||||
}
|
||||
|
||||
private bool CanLeavePartyCommandExecute()
|
||||
@ -311,7 +318,7 @@ namespace Aurora.Design.Views.Party
|
||||
AudioMetadata meta = _selectedMedia.Metadata as AudioMetadata;
|
||||
MediaPausedEvent mediaPaused = new MediaPausedEvent();
|
||||
|
||||
EventManager.Instance.FireEvent(new BaseEvent()
|
||||
_eventManager.FireEvent(new BaseEvent()
|
||||
{
|
||||
MediaPausedEvent = mediaPaused
|
||||
});
|
||||
@ -322,7 +329,7 @@ namespace Aurora.Design.Views.Party
|
||||
AudioMetadata meta = _selectedMedia.Metadata as AudioMetadata;
|
||||
MediaResumedEvent mediaResumed = new MediaResumedEvent();
|
||||
|
||||
EventManager.Instance.FireEvent(new BaseEvent()
|
||||
_eventManager.FireEvent(new BaseEvent()
|
||||
{
|
||||
MediaResumedEvent = mediaResumed
|
||||
});
|
||||
@ -353,16 +360,17 @@ namespace Aurora.Design.Views.Party
|
||||
AudioMetadata meta = _selectedMedia.Metadata as AudioMetadata;
|
||||
NewMediaPlayingEvent mediaPlaying = new NewMediaPlayingEvent()
|
||||
{
|
||||
Media = new RemoteMediaData()
|
||||
Media = new Media()
|
||||
{
|
||||
Id = _selectedMedia.Id,
|
||||
//TODO need full resource name
|
||||
Name = _selectedMedia.Id,
|
||||
Title = meta.Title,
|
||||
Artist = meta.Artist,
|
||||
Album = meta.Album,
|
||||
}
|
||||
};
|
||||
|
||||
EventManager.Instance.FireEvent(new BaseEvent()
|
||||
_eventManager.FireEvent(new BaseEvent()
|
||||
{
|
||||
NewMediaPlayingEvent = mediaPlaying
|
||||
});
|
||||
@ -385,24 +393,31 @@ namespace Aurora.Design.Views.Party
|
||||
{
|
||||
try
|
||||
{
|
||||
JoinPartyResponse resp = await _client.RemotePartyClient.JoinPartyAsync(new JoinPartyRequest
|
||||
Member resp = await _clientService.RemotePartyServiceClient.CreateMemberAsync(new CreateMemberRequest
|
||||
{
|
||||
UserName = SettingsService.Instance.Username,
|
||||
Member = new Member()
|
||||
{
|
||||
UserName = this._settingsService.Username,
|
||||
}
|
||||
});
|
||||
|
||||
SettingsService.Instance.ClientId = resp.ClientId;
|
||||
this._settingsService.ClientName = resp.Name;
|
||||
|
||||
RefreshMembers();
|
||||
await RefreshMembers();
|
||||
|
||||
//Subscribe to events
|
||||
await SubscribeToEvents();
|
||||
|
||||
Queue.Clear();
|
||||
QueueResponse queueResponse = _client.RemotePartyClient.GetQueue(new Empty());
|
||||
|
||||
ListMediaResponse mediaResponse = await _clientService.RemotePartyServiceClient.ListMediaAsync(new ListMediaRequest()
|
||||
{
|
||||
PageSize = 50,
|
||||
Parent = "TODO"
|
||||
});
|
||||
|
||||
//Convert received data to remote audio models
|
||||
foreach (RemoteMediaData data in queueResponse.MediaList)
|
||||
foreach (Media data in mediaResponse.Media)
|
||||
{
|
||||
//Assign received metadata (since this can't be aquired from a file)
|
||||
AudioMetadata meta = new AudioMetadata();
|
||||
@ -411,11 +426,10 @@ namespace Aurora.Design.Views.Party
|
||||
meta.Artist = data.Artist;
|
||||
meta.Duration = data.Duration;
|
||||
|
||||
RemoteAudio remote = new RemoteAudio(data.Id,
|
||||
RemoteAudio remote = new RemoteAudio(data.Name,
|
||||
asHost,
|
||||
meta,
|
||||
_client.RemotePlaybackClient,
|
||||
_client.RemoteSyncClient);
|
||||
_clientService.RemotePartyServiceClient);
|
||||
|
||||
Queue.Add(remote);
|
||||
OnPropertyChanged("Queue");
|
||||
@ -430,47 +444,52 @@ namespace Aurora.Design.Views.Party
|
||||
private async Task LeaveParty()
|
||||
{
|
||||
//Stop receiving events
|
||||
_client.StopEvents();
|
||||
// _client.StopEvents();
|
||||
|
||||
//Unsubscribe
|
||||
await UnsubscribeFromEvents();
|
||||
|
||||
//Leave party
|
||||
LeavePartyRequest leaveReq = new LeavePartyRequest();
|
||||
await _client.RemotePartyClient.LeavePartyAsync(leaveReq);
|
||||
DeleteMemberRequest req = new DeleteMemberRequest()
|
||||
{
|
||||
Name = _settingsService.ClientName
|
||||
};
|
||||
|
||||
await _clientService.RemotePartyServiceClient.DeleteMemberAsync(req);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
CreateEventSubscriptionListRequest req = new CreateEventSubscriptionListRequest();
|
||||
req.Parent = this._settingsService.ClientName;
|
||||
req.EventSubscriptions.Add(new EventSubscription() { Type = EventType.MemberCreated });
|
||||
req.EventSubscriptions.Add(new EventSubscription() { Type = EventType.MemberDeleted });
|
||||
req.EventSubscriptions.Add(new EventSubscription() { Type = EventType.MediaPlaying });
|
||||
req.EventSubscriptions.Add(new EventSubscription() { Type = EventType.MediaStopped });
|
||||
|
||||
|
||||
Console.WriteLine(string.Format("CLIENT {0} - SubscribeToEvents called from client with id", SettingsService.Instance.ClientId));
|
||||
await _client.RemoteEventClient.SubscribeToEventsAsync(req);
|
||||
Console.WriteLine(string.Format("CLIENT {0} - SubscribeToEvents called from client with id", this._settingsService.ClientName));
|
||||
await _clientService.RemotePartyServiceClient.CreateEventSubscriptionListAsync(req);
|
||||
}
|
||||
private async Task UnsubscribeFromEvents()
|
||||
{
|
||||
UnsubscribeAllRequest unsubscribeReq = new UnsubscribeAllRequest();
|
||||
await _client.RemoteEventClient.UnsubscribeFromAllAsync(unsubscribeReq);
|
||||
DeleteAllEventSubscriptionsRequest unsubscribeReq = new DeleteAllEventSubscriptionsRequest();
|
||||
await _clientService.RemotePartyServiceClient.DeleteAllEventSubscriptionsAsync(unsubscribeReq);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refresh members list.
|
||||
/// </summary>
|
||||
private void RefreshMembers()
|
||||
private async Task RefreshMembers()
|
||||
{
|
||||
Members.Clear();
|
||||
MembersResponse response = _client.RemotePartyClient.GetPartyMembers(new Empty());
|
||||
ListMembersResponse response = await _clientService.RemotePartyServiceClient.ListMembersAsync(
|
||||
new ListMembersRequest()
|
||||
{
|
||||
Parent = "TODO",
|
||||
PageSize = 50,
|
||||
});
|
||||
//Add members
|
||||
foreach (PartyMember member in response.Members)
|
||||
foreach (Member member in response.Members)
|
||||
{
|
||||
Members.Add(member);
|
||||
}
|
||||
@ -510,6 +529,47 @@ namespace Aurora.Design.Views.Party
|
||||
base.ChangePlayerState(null, Main.PlayAction.Pause);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronous function for processing events off of the event stream.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task GetEvents()
|
||||
{
|
||||
_eventCancellationTokenSource = new CancellationTokenSource();
|
||||
string clientName = this._settingsService.ClientName;
|
||||
Console.WriteLine(string.Format("CLIENT {0} - GetEvents called from client with id", clientName));
|
||||
using (AsyncServerStreamingCall<BaseEvent> eventStream = _clientService.RemotePartyServiceClient
|
||||
.GetEvents(new GetEventsRequest { Parent = this._settingsService.ClientName }))
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!_eventCancellationTokenSource.Token.IsCancellationRequested &&
|
||||
await eventStream.ResponseStream.MoveNext(_eventCancellationTokenSource.Token))
|
||||
{
|
||||
try
|
||||
{
|
||||
BaseEvent e = new BaseEvent(eventStream.ResponseStream.Current);
|
||||
|
||||
_eventHandlers.TryGetValue(e.DerivedEventCase, out EventHandler handler);
|
||||
|
||||
if (handler != null && handler != null)
|
||||
{
|
||||
handler.Invoke(e);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Exception while parsing event ---" + ex.Message);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(string.Format("EXCEPTION while parsing events --- " + ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
@ -22,6 +22,14 @@
|
||||
<Entry
|
||||
Text="{Binding Port}"/>
|
||||
</StackLayout>
|
||||
<StackLayout
|
||||
Orientation="Horizontal">
|
||||
<Label
|
||||
VerticalOptions="Center"
|
||||
Text="Path to Library"/>
|
||||
<Entry
|
||||
Text="{Binding LibraryPath}"/>
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</ContentView.Content>
|
||||
</ContentView>
|
@ -1,34 +1,46 @@
|
||||
using System;
|
||||
using Aurora.Services;
|
||||
using Aurora.Services.Settings;
|
||||
|
||||
namespace Aurora.Design.Views.Profile
|
||||
{
|
||||
public class ProfileViewModel : BaseViewModel
|
||||
{
|
||||
private ISettingsService _settingsService;
|
||||
|
||||
public ProfileViewModel()
|
||||
public ProfileViewModel(ISettingsService settingsService)
|
||||
{
|
||||
this._settingsService = settingsService;
|
||||
}
|
||||
|
||||
public string Username
|
||||
{
|
||||
get { return SettingsService.Instance.Username; }
|
||||
get { return this._settingsService.Username; }
|
||||
set
|
||||
{
|
||||
SettingsService.Instance.Username = value;
|
||||
this._settingsService.Username = value;
|
||||
OnPropertyChanged("Username");
|
||||
}
|
||||
}
|
||||
|
||||
public string Port
|
||||
{
|
||||
get { return SettingsService.Instance.DefaultPort.ToString(); }
|
||||
get { return this._settingsService.DefaultPort.ToString(); }
|
||||
set
|
||||
{
|
||||
Int32.TryParse(value, out int portNum);
|
||||
SettingsService.Instance.DefaultPort = portNum;
|
||||
this._settingsService.DefaultPort = portNum;
|
||||
OnPropertyChanged("Port");
|
||||
}
|
||||
}
|
||||
|
||||
public string LibraryPath
|
||||
{
|
||||
get { return this._settingsService.LibraryLocation; }
|
||||
set
|
||||
{
|
||||
this._settingsService.LibraryLocation = value;
|
||||
OnPropertyChanged("LibraryPath");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Aurora.Models.Media;
|
||||
using Aurora.Services;
|
||||
using Aurora.Services.Library;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Aurora.Design.Views.Songs
|
||||
@ -10,15 +10,18 @@ namespace Aurora.Design.Views.Songs
|
||||
#region Fields
|
||||
private ObservableCollection<BaseMedia> _songsList;
|
||||
private BaseMedia _selectedSong;
|
||||
private ILibraryService _libraryService;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Constructor
|
||||
public SongsViewModel()
|
||||
public SongsViewModel(ILibraryService libraryService)
|
||||
{
|
||||
_songsList = new ObservableCollection<BaseMedia>();
|
||||
DoubleClickCommand = new Command(OnDoubleClickExecute, OnDoubleClickCanExecute);
|
||||
|
||||
this._libraryService = libraryService;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
@ -45,7 +48,7 @@ namespace Aurora.Design.Views.Songs
|
||||
public void Initialize()
|
||||
{
|
||||
|
||||
SongsList = LibraryService.Instance.GetLibrary();
|
||||
SongsList = this._libraryService.GetLibrary();
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
|
@ -10,6 +10,7 @@ namespace Aurora.Models.Media
|
||||
|
||||
public BaseMedia()
|
||||
{
|
||||
//TODO need to make sure this is unique
|
||||
Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
|
@ -3,27 +3,23 @@ using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Aurora.Proto.General;
|
||||
using Aurora.Proto.Playback;
|
||||
using Aurora.Proto.Sync;
|
||||
using Aurora.Proto.Party;
|
||||
|
||||
namespace Aurora.Models.Media
|
||||
{
|
||||
public class RemoteAudio : BaseMedia
|
||||
{
|
||||
private RemotePlaybackService.RemotePlaybackServiceClient _remotePlaybackClient;
|
||||
private RemoteSyncService.RemoteSyncServiceClient _remoteSyncClient;
|
||||
private RemotePartyService.RemotePartyServiceClient _remotePartyService;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
#region Constructor
|
||||
public RemoteAudio(string id,
|
||||
bool fromHost,
|
||||
AudioMetadata metadata,
|
||||
RemotePlaybackService.RemotePlaybackServiceClient playbackClient,
|
||||
RemoteSyncService.RemoteSyncServiceClient syncClient)
|
||||
RemotePartyService.RemotePartyServiceClient partyClient)
|
||||
{
|
||||
this.Id = id;
|
||||
this._remotePlaybackClient = playbackClient;
|
||||
this._remoteSyncClient = syncClient;
|
||||
this._remotePartyService = partyClient;
|
||||
this.Metadata = metadata;
|
||||
this.FromHost = fromHost;
|
||||
|
||||
@ -41,11 +37,11 @@ namespace Aurora.Models.Media
|
||||
get { return MediaTypeEnum.Audio; }
|
||||
}
|
||||
|
||||
public RemoteSyncService.RemoteSyncServiceClient RemoteSyncClient
|
||||
public RemotePartyService.RemotePartyServiceClient RemotePartyServiceClient
|
||||
{
|
||||
get
|
||||
{
|
||||
return _remoteSyncClient;
|
||||
return _remotePartyService;
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +54,7 @@ namespace Aurora.Models.Media
|
||||
public override async Task Load()
|
||||
{
|
||||
this.DataStream = new MemoryStream();
|
||||
using (var call = _remotePlaybackClient.GetSongStream(new SongRequest() { Id = this.Id }))
|
||||
using (var call = _remotePartyService.StreamMedia(new StreamMediaRequest { Name = this.Id }))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -5,17 +5,17 @@ namespace Aurora.Proto.Party
|
||||
/// <summary>
|
||||
/// Partial PartyMember class with a constructor that generates a new id
|
||||
/// </summary>
|
||||
public partial class PartyMember
|
||||
public partial class Member
|
||||
{
|
||||
public PartyMember(string id)
|
||||
public Member(string id)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
Id = id;
|
||||
Name = id;
|
||||
}
|
||||
else
|
||||
{
|
||||
Id = Guid.NewGuid().ToString();
|
||||
Name = Guid.NewGuid().ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,77 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package Aurora.Proto.Events;
|
||||
|
||||
import "Proto/general.proto";
|
||||
import "Proto/party.proto";
|
||||
|
||||
service RemoteEventService {
|
||||
//Party Service
|
||||
rpc GetEvents(EventsRequest) returns (stream BaseEvent) {};
|
||||
rpc SubscribeToEvents(SubscribeRequest) returns(SubscriptionResponse);
|
||||
rpc UnsubscribeFromEvents(UnsubscribeRequest) returns (SubscriptionResponse);
|
||||
rpc UnsubscribeFromAll(UnsubscribeAllRequest) returns (SubscriptionResponse);
|
||||
}
|
||||
|
||||
message EventsRequest {
|
||||
string clientId = 1;
|
||||
}
|
||||
|
||||
/* Subscription messages */
|
||||
message SubscribeRequest {
|
||||
repeated EventType eventTypes = 1;
|
||||
string clientId = 2;
|
||||
}
|
||||
|
||||
message UnsubscribeRequest {
|
||||
repeated EventType eventTypes = 1;
|
||||
string clientId = 2;
|
||||
}
|
||||
|
||||
message UnsubscribeAllRequest {
|
||||
string clientId = 1;
|
||||
}
|
||||
|
||||
message SubscriptionResponse {
|
||||
bool successful = 1;
|
||||
}
|
||||
|
||||
/* Event Types */
|
||||
enum EventType {
|
||||
PartyMemberJoined = 0;
|
||||
PartyMemberLeft = 1;
|
||||
MediaPlaying = 2;
|
||||
MediaStopped = 3;
|
||||
}
|
||||
message BaseEvent {
|
||||
EventType eventType = 1;
|
||||
string clientKey = 2;
|
||||
|
||||
oneof derivedEvent {
|
||||
PartyMemberJoinedEvent partyMemberJoinedEvent = 3;
|
||||
PartyMemberLeftEvent partyMemberLeftEvent = 4;
|
||||
NewMediaPlayingEvent newMediaPlayingEvent = 5;
|
||||
MediaPausedEvent mediaPausedEvent = 6;
|
||||
MediaResumedEvent mediaResumedEvent = 7;
|
||||
}
|
||||
}
|
||||
|
||||
message NewMediaPlayingEvent {
|
||||
Aurora.Proto.Party.RemoteMediaData media = 1;
|
||||
}
|
||||
|
||||
message MediaResumedEvent {
|
||||
Aurora.Proto.General.Empty empty = 1;
|
||||
}
|
||||
|
||||
message MediaPausedEvent {
|
||||
Aurora.Proto.General.Empty empty = 1;
|
||||
}
|
||||
|
||||
message PartyMemberJoinedEvent {
|
||||
Aurora.Proto.Party.PartyMember member = 1;
|
||||
}
|
||||
|
||||
message PartyMemberLeftEvent {
|
||||
Aurora.Proto.Party.PartyMember member = 1;
|
||||
}
|
@ -3,55 +3,287 @@ syntax = "proto3";
|
||||
package Aurora.Proto.Party;
|
||||
|
||||
import "Proto/general.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "google/protobuf/field_mask.proto";
|
||||
|
||||
//Party
|
||||
//Members
|
||||
//EventSubscriptions
|
||||
//Events
|
||||
//Media
|
||||
|
||||
service RemotePartyService {
|
||||
//Party Service
|
||||
rpc JoinParty(JoinPartyRequest) returns (JoinPartyResponse);
|
||||
rpc LeaveParty(LeavePartyRequest) returns (LeavePartyResponse);
|
||||
rpc GetPartyMembers(Aurora.Proto.General.Empty) returns (MembersResponse);
|
||||
rpc GetQueue(Aurora.Proto.General.Empty) returns (QueueResponse);
|
||||
//**************
|
||||
//Party Resource
|
||||
//**************
|
||||
//Get Party
|
||||
rpc GetParty(Aurora.Proto.General.Empty) returns (Party);
|
||||
|
||||
//***************
|
||||
//Member Resource
|
||||
//***************
|
||||
//List
|
||||
rpc ListMembers(ListMembersRequest) returns (ListMembersResponse);
|
||||
|
||||
//Get
|
||||
rpc GetMember(GetMemberRequest) returns (Member);
|
||||
|
||||
//Update
|
||||
rpc UpdateMember(UpdateMemberRequest) returns (Member);
|
||||
|
||||
//Create
|
||||
rpc CreateMember(CreateMemberRequest) returns (Member);
|
||||
|
||||
//Delete
|
||||
rpc DeleteMember(DeleteMemberRequest) returns (Aurora.Proto.General.Empty) {};
|
||||
|
||||
//**************
|
||||
//Media Resource
|
||||
//**************
|
||||
//List
|
||||
rpc ListMedia(ListMediaRequest) returns (ListMediaResponse);
|
||||
|
||||
//Get
|
||||
rpc GetMedia(GetMediaRequest) returns (Media);
|
||||
|
||||
//Create
|
||||
rpc CreateMedia(CreateMediaRequest) returns (Media);
|
||||
|
||||
//Delete
|
||||
rpc DeleteMedia(DeleteMediaRequest) returns (Aurora.Proto.General.Empty) {};
|
||||
|
||||
//CUSTOM: Stream
|
||||
rpc StreamMedia(StreamMediaRequest) returns (stream Aurora.Proto.General.Chunk) {};
|
||||
|
||||
//CUSTOM: Sync
|
||||
rpc SyncMedia(SyncMediaRequest) returns (stream Sync) {};
|
||||
|
||||
//***************************
|
||||
//EventSubscriptions Resource
|
||||
//***************************
|
||||
//List
|
||||
rpc ListEventSubscriptions(ListEventSubscriptionsRequest) returns (ListEventSubscriptionsResponse);
|
||||
|
||||
//Create
|
||||
rpc CreateEventSubscription(CreateEventSubscriptionRequest) returns (EventSubscription);
|
||||
|
||||
//Delete
|
||||
rpc DeleteEventSubscription(DeleteEventSubscriptionRequest) returns (Aurora.Proto.General.Empty);
|
||||
|
||||
//CUSTOM: Create EventSubscription List
|
||||
rpc CreateEventSubscriptionList(CreateEventSubscriptionListRequest) returns (CreateEventSubscriptionListResponse);
|
||||
|
||||
//CUSTOM: Delete all
|
||||
rpc DeleteAllEventSubscriptions(DeleteAllEventSubscriptionsRequest) returns (Aurora.Proto.General.Empty);
|
||||
|
||||
//*****
|
||||
//Event
|
||||
//*****
|
||||
//Get
|
||||
rpc GetEvents(GetEventsRequest) returns (stream BaseEvent) {};
|
||||
}
|
||||
|
||||
message JoinPartyRequest {
|
||||
string userName = 2;
|
||||
message Party {
|
||||
//The resource name of the party
|
||||
string name = 1;
|
||||
string displayName = 2;
|
||||
string description = 3;
|
||||
string hostIp = 4;
|
||||
Member hostMember = 5;
|
||||
google.protobuf.Timestamp createdOn = 6;
|
||||
}
|
||||
|
||||
message JoinPartyResponse {
|
||||
PartyJoinedStatusEnum status = 1;
|
||||
string clientId = 2;
|
||||
}
|
||||
|
||||
message LeavePartyRequest {
|
||||
string clientId = 1;
|
||||
enum PartyJoinedStatusEnum {
|
||||
InParty = 0;
|
||||
NotInParty = 1;
|
||||
}
|
||||
|
||||
message LeavePartyResponse {
|
||||
PartyJoinedStatusEnum status = 1;
|
||||
}
|
||||
|
||||
message PartyMember {
|
||||
string userName = 1;
|
||||
string id = 2;
|
||||
message Member {
|
||||
//Resource name of the party member to be returned (Added by server)
|
||||
string name = 1;
|
||||
string userName = 2;
|
||||
|
||||
//Added by server
|
||||
string ipAddress = 3;
|
||||
int32 port = 4;
|
||||
}
|
||||
message MembersResponse {
|
||||
repeated PartyMember members = 1;
|
||||
|
||||
//Added by server
|
||||
google.protobuf.Timestamp addedOn = 4;
|
||||
}
|
||||
|
||||
enum PartyJoinedStatusEnum {
|
||||
Connected = 0;
|
||||
Disconnected = 1;
|
||||
message ListMembersRequest {
|
||||
//Resource name of the parent of the members collection to be returned (The party)
|
||||
string parent = 1;
|
||||
int32 pageSize = 2;
|
||||
string pageToken = 3;
|
||||
}
|
||||
|
||||
message QueueResponse{
|
||||
repeated RemoteMediaData mediaList = 1;
|
||||
message ListMembersResponse {
|
||||
repeated Member members = 1;
|
||||
string nextPageToken = 2;
|
||||
}
|
||||
message GetMemberRequest {
|
||||
//Resource name of the member to be returned
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message RemoteMediaData {
|
||||
string id = 1;
|
||||
message CreateMemberRequest {
|
||||
//Resource name of the parent collection of the member to be created (The party)
|
||||
string parent = 1;
|
||||
Member member = 2;
|
||||
}
|
||||
|
||||
message UpdateMemberRequest {
|
||||
Member member = 1;
|
||||
google.protobuf.FieldMask updateMask = 2;
|
||||
}
|
||||
|
||||
message DeleteMemberRequest {
|
||||
//Resource name of the member to be deleted
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message Media {
|
||||
//Resource name of the remote media object
|
||||
string name = 1;
|
||||
string title = 2;
|
||||
string artist = 3;
|
||||
string album = 4;
|
||||
string duration = 5;
|
||||
}
|
||||
|
||||
message ListMediaRequest {
|
||||
//Resource name of the parent of the media collection to be listed (The party)
|
||||
string parent = 1;
|
||||
int32 pageSize = 2;
|
||||
string pageToken = 3;
|
||||
}
|
||||
|
||||
message ListMediaResponse {
|
||||
repeated Media media = 1;
|
||||
string nextPageToken = 3;
|
||||
}
|
||||
|
||||
message GetMediaRequest {
|
||||
//Resource name of the media requested
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message CreateMediaRequest {
|
||||
//Resource name of the parent collection of the member to be created (The party)
|
||||
string parent = 1;
|
||||
Media media = 2;
|
||||
}
|
||||
|
||||
message DeleteMediaRequest {
|
||||
//Resource name of the member to be deleted
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message StreamMediaRequest {
|
||||
//Resource name of the media requested
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message SyncMediaRequest {
|
||||
//Resource name of the media to sync with
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message Sync {
|
||||
int64 serverTimeTicks = 1;
|
||||
float trackPosition= 2;
|
||||
}
|
||||
|
||||
/* Event Types */
|
||||
enum EventType {
|
||||
MemberCreated = 0;
|
||||
MemberDeleted = 1;
|
||||
MediaPlaying = 2;
|
||||
MediaStopped = 3;
|
||||
}
|
||||
|
||||
message BaseEvent {
|
||||
//Resource name of the event ?
|
||||
string name = 1;
|
||||
EventType eventType = 2;
|
||||
string clientKey = 3;
|
||||
|
||||
oneof derivedEvent {
|
||||
MemberCreatedEvent memberCreatedEvent = 4;
|
||||
MemberDeletedEvent memberDeletedEvent = 5;
|
||||
NewMediaPlayingEvent newMediaPlayingEvent = 6;
|
||||
MediaPausedEvent mediaPausedEvent = 7;
|
||||
MediaResumedEvent mediaResumedEvent = 8;
|
||||
}
|
||||
}
|
||||
|
||||
message NewMediaPlayingEvent {
|
||||
Media media = 1;
|
||||
}
|
||||
|
||||
message MediaResumedEvent {
|
||||
Aurora.Proto.General.Empty empty = 1;
|
||||
}
|
||||
|
||||
message MediaPausedEvent {
|
||||
Aurora.Proto.General.Empty empty = 1;
|
||||
}
|
||||
|
||||
message MemberCreatedEvent {
|
||||
Member member = 1;
|
||||
}
|
||||
|
||||
message MemberDeletedEvent {
|
||||
string memberName = 1;
|
||||
}
|
||||
|
||||
message EventSubscription {
|
||||
EventType type = 2;
|
||||
}
|
||||
|
||||
message ListEventSubscriptionsRequest {
|
||||
//Resource name of parent to the subscription list (The member)
|
||||
string parent = 1;
|
||||
int32 pageSize = 2;
|
||||
string pageToken = 3;
|
||||
}
|
||||
|
||||
message ListEventSubscriptionsResponse {
|
||||
repeated EventSubscription subscriptions = 1;
|
||||
}
|
||||
|
||||
message CreateEventSubscriptionRequest {
|
||||
//Resource name of the parent to the subscription list (The member)
|
||||
string parent = 1;
|
||||
EventSubscription eventSubscription = 2;
|
||||
}
|
||||
|
||||
message DeleteEventSubscriptionRequest {
|
||||
//Resource name of the subscription to delete
|
||||
string parent = 1;
|
||||
EventType type = 2;
|
||||
}
|
||||
|
||||
message CreateEventSubscriptionListRequest {
|
||||
//Resource name of the parent to the subscription list (The member)
|
||||
string parent = 1;
|
||||
repeated EventSubscription eventSubscriptions = 2;
|
||||
}
|
||||
|
||||
message CreateEventSubscriptionListResponse {
|
||||
repeated EventSubscription eventSubscriptions = 1;
|
||||
}
|
||||
|
||||
message DeleteAllEventSubscriptionsRequest {
|
||||
//Resource name of the parent to the subscription list (the member)
|
||||
string parent = 1;
|
||||
}
|
||||
|
||||
message GetEventsRequest {
|
||||
//Resource name of the parent to the event stream (the member)
|
||||
string parent = 1;
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package Aurora.Proto.Playback;
|
||||
|
||||
import "Proto/general.proto";
|
||||
|
||||
service RemotePlaybackService {
|
||||
rpc GetSongStream(SongRequest) returns (stream Aurora.Proto.General.Chunk) {};
|
||||
}
|
||||
|
||||
message SongRequest {
|
||||
string id = 1;
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package Aurora.Proto.Sync;
|
||||
|
||||
import "Proto/general.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
service RemoteSyncService {
|
||||
rpc GetMediaSync(Aurora.Proto.General.Empty) returns (stream Sync) {};
|
||||
}
|
||||
|
||||
message Sync {
|
||||
int64 serverTimeTicks = 1;
|
||||
float trackPosition= 2;
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using Aurora.Services;
|
||||
using Aurora.Utils;
|
||||
using System.Linq;
|
||||
using Aurora.Services.EventManager;
|
||||
using Aurora.Proto.Events;
|
||||
|
||||
namespace Aurora.RemoteImpl
|
||||
{
|
||||
public class RemoteEventServiceImpl : RemoteEventService.RemoteEventServiceBase
|
||||
{
|
||||
public RemoteEventServiceImpl()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RPC for getting event stream for a particular client.
|
||||
/// </summary>
|
||||
/// <param name="request">Empty</param>
|
||||
/// <param name="responseStream">The response stream</param>
|
||||
/// <param name="context">gRPC client context</param>
|
||||
/// <returns></returns>
|
||||
public override Task GetEvents(EventsRequest request,
|
||||
Grpc.Core.IServerStreamWriter<BaseEvent> responseStream,
|
||||
Grpc.Core.ServerCallContext context)
|
||||
{
|
||||
string peerId = Misc.Combine(new string[] { context.Peer, request.ClientId });
|
||||
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();
|
||||
};
|
||||
|
||||
EventManager.Instance.AddEventHandler(callback, cancelled, Misc.Combine(new string[] { context.Peer, request.ClientId }));
|
||||
are.WaitOne();
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RPC for subscribing to remote events
|
||||
/// </summary>
|
||||
/// <param name="request">SubscribeRequest</param>
|
||||
/// <param name="context">gRPC client context</param>
|
||||
/// <returns></returns>
|
||||
public override Task<SubscriptionResponse> SubscribeToEvents(SubscribeRequest request, Grpc.Core.ServerCallContext context)
|
||||
{
|
||||
Console.WriteLine(string.Format("SERVER - Subscription from client with id: {0}", request.ClientId));
|
||||
EventManager.Instance.AddSubscriptionList(Misc.Combine(new string[] { context.Peer, request.ClientId }), request.EventTypes.ToList());
|
||||
|
||||
return Task.FromResult(new SubscriptionResponse { Successful = true });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RPC for unsubscibing from events
|
||||
/// </summary>
|
||||
/// <param name="request">UnsubscribeRequest</param>
|
||||
/// <param name="context">gRPC client context</param>
|
||||
/// <returns></returns>
|
||||
public override Task<SubscriptionResponse> UnsubscribeFromEvents(UnsubscribeRequest request, Grpc.Core.ServerCallContext context)
|
||||
{
|
||||
EventType[] eventTypes = null;
|
||||
request.EventTypes.CopyTo(eventTypes, 0);
|
||||
|
||||
EventManager.Instance.RemoveSubscriptionList(Misc.Combine(new string[] { context.Peer, request.ClientId }), eventTypes.ToList());
|
||||
|
||||
return Task.FromResult(new SubscriptionResponse { Successful = true });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RPC for unsubscribing from all events
|
||||
/// </summary>
|
||||
/// <param name="request">UnsubscribeAllRequest</param>
|
||||
/// <param name="context">gRPC client context</param>
|
||||
/// <returns></returns>
|
||||
public override Task<SubscriptionResponse> UnsubscribeFromAll(UnsubscribeAllRequest request, Grpc.Core.ServerCallContext context)
|
||||
{
|
||||
EventManager.Instance.RemoveAllSubscriptions(Misc.Combine(new string[] { context.Peer, request.ClientId }));
|
||||
|
||||
return Task.FromResult(new SubscriptionResponse { Successful = true });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,140 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Aurora.Utils;
|
||||
using Aurora.Proto.Party;
|
||||
using Aurora.Proto.Events;
|
||||
using Aurora.Services.EventManager;
|
||||
using Aurora.Services;
|
||||
using Aurora.Models.Media;
|
||||
|
||||
namespace Aurora.RemoteImpl
|
||||
{
|
||||
public class RemotePartyServiceImpl : RemotePartyService.RemotePartyServiceBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictionary of party members. Key -> ClientId
|
||||
/// </summary>
|
||||
private ObservableCollection<PartyMember> _partyMembers;
|
||||
|
||||
public RemotePartyServiceImpl()
|
||||
{
|
||||
_partyMembers = new ObservableCollection<PartyMember>();
|
||||
}
|
||||
|
||||
public ObservableCollection<PartyMember> PartyMembers
|
||||
{
|
||||
get
|
||||
{
|
||||
return _partyMembers;
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<JoinPartyResponse> JoinParty(JoinPartyRequest request, Grpc.Core.ServerCallContext context)
|
||||
{
|
||||
PartyMember partyMember = new PartyMember("")
|
||||
{
|
||||
UserName = request.UserName,
|
||||
IpAddress = context.Host,
|
||||
};
|
||||
|
||||
Console.WriteLine("SERVER - Client joined party: " + partyMember.Id);
|
||||
|
||||
_partyMembers.Add(partyMember);
|
||||
|
||||
BaseEvent e = new BaseEvent
|
||||
{
|
||||
EventType = EventType.PartyMemberJoined,
|
||||
PartyMemberJoinedEvent = new PartyMemberJoinedEvent
|
||||
{
|
||||
Member = partyMember,
|
||||
}
|
||||
};
|
||||
|
||||
EventManager.Instance.FireEvent(e);
|
||||
|
||||
JoinPartyResponse response = new JoinPartyResponse() { Status = PartyJoinedStatusEnum.Connected, ClientId = partyMember.Id };
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
public override Task<LeavePartyResponse> LeaveParty(LeavePartyRequest request, Grpc.Core.ServerCallContext context)
|
||||
{
|
||||
PartyMember partyMember = _partyMembers.Where(e => e.Id == request.ClientId).Single();
|
||||
|
||||
_partyMembers.Remove(partyMember);
|
||||
|
||||
BaseEvent bv = new BaseEvent
|
||||
{
|
||||
EventType = EventType.PartyMemberJoined,
|
||||
PartyMemberLeftEvent = new PartyMemberLeftEvent
|
||||
{
|
||||
Member = partyMember,
|
||||
}
|
||||
};
|
||||
|
||||
EventManager.Instance.FireEvent(bv);
|
||||
EventManager.Instance.RemoveAllSubscriptions(Misc.Combine(new string[] { context.Peer, request.ClientId }));
|
||||
EventManager.Instance.CancelEventStream(Misc.Combine(new string[] { context.Peer, request.ClientId }));
|
||||
|
||||
LeavePartyResponse response = new LeavePartyResponse() { Status = PartyJoinedStatusEnum.Disconnected };
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
public override Task<MembersResponse> GetPartyMembers(Proto.General.Empty empty, Grpc.Core.ServerCallContext context)
|
||||
{
|
||||
MembersResponse response = new MembersResponse();
|
||||
response.Members.AddRange(_partyMembers);
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
public override Task<QueueResponse> GetQueue(Proto.General.Empty empty, Grpc.Core.ServerCallContext context)
|
||||
{
|
||||
//This will change as queuing operation gets solidified
|
||||
//Simply return the hosts library
|
||||
|
||||
ObservableCollection<BaseMedia> queue = LibraryService.Instance.GetLibrary();
|
||||
|
||||
QueueResponse mediaList = new QueueResponse();
|
||||
foreach (BaseMedia media in queue)
|
||||
{
|
||||
AudioMetadata metadata = new AudioMetadata();
|
||||
try
|
||||
{
|
||||
if (media.Metadata is AudioMetadata)
|
||||
{
|
||||
metadata = media.Metadata as AudioMetadata;
|
||||
|
||||
RemoteMediaData data = new RemoteMediaData();
|
||||
data.Id = 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.MediaList.Add(data);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(string.Format("Error preparing queue: {0}", ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(mediaList);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using Aurora.Services;
|
||||
using Aurora.Proto.Playback;
|
||||
using Aurora.Proto.General;
|
||||
using Aurora.Models.Media;
|
||||
|
||||
namespace Aurora.RemoteImpl
|
||||
{
|
||||
public class RemotePlaybackServiceImpl : RemotePlaybackService.RemotePlaybackServiceBase
|
||||
{
|
||||
public override async Task GetSongStream(SongRequest request,
|
||||
Grpc.Core.IServerStreamWriter<Chunk> responseStream,
|
||||
Grpc.Core.ServerCallContext context)
|
||||
{
|
||||
BaseMedia originalSong = LibraryService.Instance.GetSong(request.Id);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Aurora.Proto.Sync;
|
||||
using Aurora.Proto.General;
|
||||
using Aurora.Services.PlayerService;
|
||||
|
||||
namespace Aurora.RemoteImpl
|
||||
{
|
||||
public class RemoteSyncServiceImpl : RemoteSyncService.RemoteSyncServiceBase
|
||||
{
|
||||
/// <summary>
|
||||
/// RPC for getting a stream of media syncs
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="responseStream"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public override async Task GetMediaSync(Empty request,
|
||||
Grpc.Core.IServerStreamWriter<Sync> responseStream,
|
||||
Grpc.Core.ServerCallContext context)
|
||||
{
|
||||
bool continueSync = true;
|
||||
string currentId = PlayerService.Instance.CurrentMedia.Id;
|
||||
MediaChangedEventHandler mediaChanged = (sender, e) =>
|
||||
{
|
||||
if (e.NewId != currentId)
|
||||
{
|
||||
continueSync = false;
|
||||
}
|
||||
};
|
||||
|
||||
PlayerService.Instance.MediaChanged += mediaChanged;
|
||||
|
||||
while (continueSync)
|
||||
{
|
||||
float length = PlayerService.Instance.CurrentMediaLength;
|
||||
|
||||
Sync sync = new Sync()
|
||||
{
|
||||
TrackPosition = PlayerService.Instance.CurrentMediaPosition,
|
||||
ServerTimeTicks = Utils.TimeUtils.GetNetworkTime().DateTime.Ticks
|
||||
};
|
||||
await responseStream.WriteAsync(sync);
|
||||
Console.WriteLine("Sent Sync");
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Aurora.Services
|
||||
{
|
||||
public abstract class BaseService<T> where T : class
|
||||
{
|
||||
private static volatile Lazy<T> _instance = new Lazy<T>(() => CreateInstanceOfT());
|
||||
|
||||
public static T Instance
|
||||
{
|
||||
get { return _instance.Value; }
|
||||
}
|
||||
|
||||
private static T CreateInstanceOfT()
|
||||
{
|
||||
|
||||
return Activator.CreateInstance(typeof(T), true) as T;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
46
Aurora/Services/Client/ClientService.cs
Normal file
46
Aurora/Services/Client/ClientService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
15
Aurora/Services/Client/IClientService.cs
Normal file
15
Aurora/Services/Client/IClientService.cs
Normal 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();
|
||||
}
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
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 ClientService : BaseService<ClientService>
|
||||
{
|
||||
private RemotePartyService.RemotePartyServiceClient _remotePartyClient;
|
||||
private RemoteEventService.RemoteEventServiceClient _remoteEventsClient;
|
||||
private RemotePlaybackService.RemotePlaybackServiceClient _remotePlaybackClient;
|
||||
private RemoteSyncService.RemoteSyncServiceClient _remoteSyncClient;
|
||||
|
||||
private Channel _channel;
|
||||
CancellationTokenSource _eventCancellationTokenSource;
|
||||
|
||||
public ClientService()
|
||||
{
|
||||
}
|
||||
|
||||
public MediaPausedEventHandler OnMediaPaused;
|
||||
public NewMediaPlayingEventHandler OnNewMediaPlaying;
|
||||
public PartyMemberJoinedEventHandler OnPartyMemberJoined;
|
||||
public PartyMemberLeftEventHandler OnPartyMemberLeft;
|
||||
public MediaResumedEventHandler OnMediaResumed;
|
||||
|
||||
public RemotePartyService.RemotePartyServiceClient RemotePartyClient
|
||||
{
|
||||
get
|
||||
{
|
||||
return _remotePartyClient;
|
||||
}
|
||||
}
|
||||
|
||||
public RemoteEventService.RemoteEventServiceClient RemoteEventClient
|
||||
{
|
||||
get { return _remoteEventsClient; }
|
||||
}
|
||||
|
||||
public RemotePlaybackService.RemotePlaybackServiceClient RemotePlaybackClient
|
||||
{
|
||||
get { return _remotePlaybackClient; }
|
||||
}
|
||||
|
||||
public RemoteSyncService.RemoteSyncServiceClient RemoteSyncClient
|
||||
{
|
||||
get { return _remoteSyncClient; }
|
||||
}
|
||||
|
||||
public bool IsStarted
|
||||
{
|
||||
get
|
||||
{
|
||||
return _remoteEventsClient != null &&
|
||||
_remotePartyClient != null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsHost { get; set; }
|
||||
|
||||
public void Start(string hostname, string port)
|
||||
{
|
||||
_channel = new Channel(string.Format("{0}:{1}", hostname, port), ChannelCredentials.Insecure);
|
||||
|
||||
_remotePartyClient = new RemotePartyService.RemotePartyServiceClient(_channel);
|
||||
_remoteEventsClient = new RemoteEventService.RemoteEventServiceClient(_channel);
|
||||
_remotePlaybackClient = new RemotePlaybackService.RemotePlaybackServiceClient(_channel);
|
||||
_remoteSyncClient = new RemoteSyncService.RemoteSyncServiceClient(_channel);
|
||||
}
|
||||
|
||||
public async void Close()
|
||||
{
|
||||
_eventCancellationTokenSource.Cancel();
|
||||
await _channel.ShutdownAsync();
|
||||
|
||||
_remotePartyClient = null;
|
||||
_remoteEventsClient = null;
|
||||
_remotePlaybackClient = null;
|
||||
_remoteSyncClient = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronous function for processing events off of the event stream.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task GetEvents()
|
||||
{
|
||||
_eventCancellationTokenSource = new CancellationTokenSource();
|
||||
string clientId = SettingsService.Instance.ClientId;
|
||||
Console.WriteLine(string.Format("CLIENT {0} - GetEvents called from client with id", clientId));
|
||||
using (AsyncServerStreamingCall<BaseEvent> eventStream = _remoteEventsClient
|
||||
.GetEvents(new EventsRequest { ClientId = SettingsService.Instance.ClientId }))
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!_eventCancellationTokenSource.Token.IsCancellationRequested &&
|
||||
await eventStream.ResponseStream.MoveNext(_eventCancellationTokenSource.Token))
|
||||
{
|
||||
try
|
||||
{
|
||||
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("Exception while parsing event ---" + ex.Message);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(string.Format("EXCEPTION while parsing events --- " + ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void StopEvents()
|
||||
{
|
||||
if (_eventCancellationTokenSource != null && !_eventCancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
_eventCancellationTokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
19
Aurora/Services/EventManager/EventAction.cs
Normal file
19
Aurora/Services/EventManager/EventAction.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -1,23 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Aurora.Proto.Events;
|
||||
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; }
|
||||
}
|
||||
public class EventManager : BaseService<EventManager>
|
||||
public class EventManager : IEventManager
|
||||
{
|
||||
#region Fields
|
||||
private Dictionary<string, List<EventType>> _subscriptionList;
|
||||
@ -38,32 +27,32 @@ namespace Aurora.Services.EventManager
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// Get the list of event type subscriptions for a given session id.
|
||||
/// Get the list of event type subscriptions for a given sessionIdentifier id.
|
||||
/// </summary>
|
||||
/// <param name="session">Session Id</param>
|
||||
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
|
||||
/// <returns></returns>
|
||||
public List<EventType> GetSubscriptionList(string session)
|
||||
public List<EventType> GetSubscriptionList(string sessionIdentifier)
|
||||
{
|
||||
List<EventType> eventList = new List<EventType>();
|
||||
if (_subscriptionList.ContainsKey(session))
|
||||
if (_subscriptionList.ContainsKey(sessionIdentifier))
|
||||
{
|
||||
_subscriptionList.TryGetValue(session, out eventList);
|
||||
_subscriptionList.TryGetValue(sessionIdentifier, out eventList);
|
||||
}
|
||||
|
||||
return eventList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of event subscriptions for a given session
|
||||
/// Get the number of event subscriptions for a given sessionIdentifier
|
||||
/// </summary>
|
||||
/// <param name="session">Session Id</param>
|
||||
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
|
||||
/// <returns></returns>
|
||||
public int GetSubscriptionCount(string session)
|
||||
public int GetSubscriptionCount(string sessionIdentifier)
|
||||
{
|
||||
List<EventType> eventList = new List<EventType>();
|
||||
if (_subscriptionList.ContainsKey(session))
|
||||
if (_subscriptionList.ContainsKey(sessionIdentifier))
|
||||
{
|
||||
_subscriptionList.TryGetValue(session, out eventList);
|
||||
_subscriptionList.TryGetValue(sessionIdentifier, out eventList);
|
||||
}
|
||||
|
||||
return eventList.Count();
|
||||
@ -72,24 +61,24 @@ namespace Aurora.Services.EventManager
|
||||
/// <summary>
|
||||
/// Add a new subscription
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
/// <param name="sessionIdentifier"></param>
|
||||
/// <param name="type"></param>
|
||||
public bool AddSubscription(string session, EventType type)
|
||||
public bool AddSubscription(string sessionIdentifier, EventType type)
|
||||
{
|
||||
bool success = false;
|
||||
lock (_subscriptionList)
|
||||
{
|
||||
if (!_subscriptionList.ContainsKey(session))
|
||||
if (!_subscriptionList.ContainsKey(sessionIdentifier))
|
||||
{
|
||||
//Add session to subscription list
|
||||
//Add sessionIdentifier to subscription list
|
||||
List<EventType> eventList = new List<EventType>();
|
||||
eventList.Add(type);
|
||||
_subscriptionList.Add(session, eventList);
|
||||
_subscriptionList.Add(sessionIdentifier, eventList);
|
||||
success = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_subscriptionList.TryGetValue(session, out List<EventType> eventList);
|
||||
_subscriptionList.TryGetValue(sessionIdentifier, out List<EventType> eventList);
|
||||
if (eventList != null)
|
||||
{
|
||||
eventList.Add(type);
|
||||
@ -104,79 +93,82 @@ namespace Aurora.Services.EventManager
|
||||
/// <summary>
|
||||
/// Add a list of subscriptions. This unsubscribes from unused events.
|
||||
/// </summary>
|
||||
/// <param name="session">The browser session id.</param>
|
||||
/// <param name="sessionIdentifier">The browser sessionIdentifier id.</param>
|
||||
/// <param name="types">The list of event types to subscribe to.</param>
|
||||
public void AddSubscriptionList(string session, List<EventType> types)
|
||||
public void AddSubscriptionList(string sessionIdentifier, List<EventType> types)
|
||||
{
|
||||
RemoveAllSubscriptions(session);
|
||||
RemoveAllSubscriptions(sessionIdentifier);
|
||||
|
||||
foreach (EventType e in types)
|
||||
{
|
||||
AddSubscription(session, e);
|
||||
AddSubscription(sessionIdentifier, e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribe from a given event type.
|
||||
/// </summary>
|
||||
/// <param name="session">Session Id</param>
|
||||
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
|
||||
/// <param name="type">Event Type to be removed</param>
|
||||
public void RemoveSubscription(string session, EventType type)
|
||||
public void RemoveSubscription(string sessionIdentifier, EventType type)
|
||||
{
|
||||
lock (_subscriptionList)
|
||||
{
|
||||
if (_subscriptionList.ContainsKey(session))
|
||||
if (_subscriptionList.ContainsKey(sessionIdentifier))
|
||||
{
|
||||
List<EventType> eventTypeList;
|
||||
_subscriptionList.TryGetValue(session, out 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 session {1}", type.ToString(), session));
|
||||
//base.LogInformation(string.Format("Subscription removed for event type {0} subscription on sessionIdentifier {1}", type.ToString(), sessionIdentifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveSubscriptionList(string session, List<EventType> types)
|
||||
public void RemoveSubscriptionList(string sessionIdentifier, List<EventType> types)
|
||||
{
|
||||
foreach (EventType e in types)
|
||||
{
|
||||
RemoveSubscription(session, e);
|
||||
RemoveSubscription(sessionIdentifier, e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all subscriptons for a given session.
|
||||
/// Remove all subscriptons for a given sessionIdentifier.
|
||||
/// </summary>
|
||||
/// <param name="session">Session Id</param>
|
||||
public void RemoveAllSubscriptions(string session)
|
||||
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
|
||||
public void RemoveAllSubscriptions(string sessionIdentifier)
|
||||
{
|
||||
if (_subscriptionList.ContainsKey(session))
|
||||
if (_subscriptionList.ContainsKey(sessionIdentifier))
|
||||
{
|
||||
_subscriptionList.Remove(session);
|
||||
_subscriptionList.Remove(sessionIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddEventHandler(Action<BaseEvent> action, Action cancel, string sessionId)
|
||||
public void AddEventHandler(Action<BaseEvent> action, Action cancel, string sessionIdentifierId)
|
||||
{
|
||||
lock (_actionList)
|
||||
{
|
||||
_actionList.Add(sessionId, new EventAction(action, cancel));
|
||||
_actionList.Add(sessionIdentifierId, new EventAction(action, cancel));
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveEventHandler(string sessionId)
|
||||
public void RemoveEventHandler(string sessionIdentifierId)
|
||||
{
|
||||
_actionList.Remove(sessionId);
|
||||
_actionList.Remove(sessionIdentifierId);
|
||||
}
|
||||
|
||||
public void CancelEventStream(string sessionId)
|
||||
public void CancelEventStream(string sessionIdentifierId)
|
||||
{
|
||||
_actionList.TryGetValue(sessionId, out EventAction value);
|
||||
value.Cancel();
|
||||
_actionList.TryGetValue(sessionIdentifierId, out EventAction value);
|
||||
if (value != null)
|
||||
{
|
||||
value.Cancel();
|
||||
}
|
||||
|
||||
RemoveEventHandler(sessionId);
|
||||
RemoveEventHandler(sessionIdentifierId);
|
||||
}
|
||||
|
||||
public void FireEvent(BaseEvent bEvent)
|
||||
|
60
Aurora/Services/EventManager/IEventManager.cs
Normal file
60
Aurora/Services/EventManager/IEventManager.cs
Normal 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);
|
||||
}
|
||||
}
|
12
Aurora/Services/Library/ILibraryService.cs
Normal file
12
Aurora/Services/Library/ILibraryService.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -3,22 +3,24 @@ 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
|
||||
namespace Aurora.Services.Library
|
||||
{
|
||||
public class LibraryService : BaseService<LibraryService>
|
||||
public class LibraryService : ILibraryService
|
||||
{
|
||||
#region Fields
|
||||
private string _pathName = "/Users/brandonwatson/Music/iTunes/iTunes Media/Music";
|
||||
private string _pathName;
|
||||
private string _extensions = ".wav,.mp3,.aiff,.flac,.m4a,.m4b,.wma";
|
||||
private Dictionary<string, BaseMedia> _library;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
public LibraryService()
|
||||
public LibraryService(ISettingsService settingsService)
|
||||
{
|
||||
_library = new Dictionary<string, BaseMedia>();
|
||||
this._pathName = settingsService.LibraryLocation;
|
||||
LoadLibrary();
|
||||
}
|
||||
|
62
Aurora/Services/Player/IPlayer.cs
Normal file
62
Aurora/Services/Player/IPlayer.cs
Normal 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);
|
||||
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using Aurora.Models.Media;
|
||||
|
||||
namespace Aurora.Services.PlayerService
|
||||
namespace Aurora.Services.Player
|
||||
{
|
||||
public delegate void MediaChangedEventHandler(object source, MediaChangedEventArgs e);
|
||||
|
12
Aurora/Services/Player/PlaybackState.cs
Normal file
12
Aurora/Services/Player/PlaybackState.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Aurora.Services.Player
|
||||
{
|
||||
public enum PlaybackState
|
||||
{
|
||||
Playing,
|
||||
Stopped,
|
||||
Buffering,
|
||||
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using Aurora.Models.Media;
|
||||
|
||||
namespace Aurora.Services.PlayerService
|
||||
namespace Aurora.Services.Player
|
||||
{
|
||||
public delegate void PlaybackStateChangedEventHandler(object source, PlaybackStateChangedEventArgs e);
|
||||
|
@ -3,13 +3,13 @@ using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Grpc.Core;
|
||||
using Aurora.Models.Media;
|
||||
using Aurora.Proto.Sync;
|
||||
using Aurora.Proto.Party;
|
||||
using LibVLCSharp.Shared;
|
||||
|
||||
namespace Aurora.Services.PlayerService
|
||||
namespace Aurora.Services.Player
|
||||
{
|
||||
|
||||
public class PlayerService : BaseService<PlayerService>
|
||||
public class PlayerService : IPlayer
|
||||
{
|
||||
private const long _ticksPerMillisecond = 10000;
|
||||
private BaseMedia _currentMedia;
|
||||
@ -85,7 +85,7 @@ namespace Aurora.Services.PlayerService
|
||||
}
|
||||
_currentMedia = media;
|
||||
await _currentMedia.Load();
|
||||
var md = new Media(_libvlc, _currentMedia.DataStream);
|
||||
var md = new LibVLCSharp.Shared.Media(_libvlc, _currentMedia.DataStream);
|
||||
_mediaPlayer = new MediaPlayer(md);
|
||||
_mediaPlayer.Stopped += OnStopped;
|
||||
md.Dispose();
|
||||
@ -112,15 +112,15 @@ namespace Aurora.Services.PlayerService
|
||||
RemoteAudio media = _currentMedia as RemoteAudio;
|
||||
if (!media.FromHost)
|
||||
{
|
||||
RemoteSyncService.RemoteSyncServiceClient remoteSyncClient = media.RemoteSyncClient;
|
||||
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 () =>
|
||||
{
|
||||
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||
using (AsyncServerStreamingCall<Sync> syncStream = remoteSyncClient
|
||||
.GetMediaSync(new Proto.General.Empty()))
|
||||
using (AsyncServerStreamingCall<Sync> syncStream = remotePartyServiceClient
|
||||
.SyncMedia(new SyncMediaRequest() { }))
|
||||
{
|
||||
try
|
||||
{
|
@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
public enum PlaybackState
|
||||
{
|
||||
Playing,
|
||||
Stopped,
|
||||
Buffering,
|
||||
|
||||
}
|
88
Aurora/Services/Server/Controllers/Constructor.cs
Normal file
88
Aurora/Services/Server/Controllers/Constructor.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
Aurora/Services/Server/Controllers/EventController.cs
Normal file
35
Aurora/Services/Server/Controllers/EventController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
143
Aurora/Services/Server/Controllers/MediaController.cs
Normal file
143
Aurora/Services/Server/Controllers/MediaController.cs
Normal file
@ -0,0 +1,143 @@
|
||||
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())
|
||||
{
|
||||
IPlayer player = scope.Resolve<IPlayer>();
|
||||
|
||||
string currentId = player.CurrentMedia.Id;
|
||||
MediaChangedEventHandler mediaChanged = (sender, e) =>
|
||||
{
|
||||
if (e.NewId != currentId)
|
||||
{
|
||||
continueSync = false;
|
||||
}
|
||||
};
|
||||
|
||||
player.MediaChanged += mediaChanged;
|
||||
|
||||
while (continueSync)
|
||||
{
|
||||
float length = player.CurrentMediaLength;
|
||||
|
||||
Sync sync = new Sync()
|
||||
{
|
||||
TrackPosition = player.CurrentMediaPosition,
|
||||
ServerTimeTicks = Utils.TimeUtils.GetNetworkTime().DateTime.Ticks
|
||||
};
|
||||
await responseStream.WriteAsync(sync);
|
||||
Console.WriteLine("Sent Sync");
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
129
Aurora/Services/Server/Controllers/MemberController.cs
Normal file
129
Aurora/Services/Server/Controllers/MemberController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
33
Aurora/Services/Server/Controllers/PartyController.cs
Normal file
33
Aurora/Services/Server/Controllers/PartyController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
27
Aurora/Services/Server/Server/IServerService.cs
Normal file
27
Aurora/Services/Server/Server/IServerService.cs
Normal 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();
|
||||
}
|
||||
}
|
@ -3,45 +3,42 @@ using System.Threading.Tasks;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Grpc.Core;
|
||||
using Aurora.RemoteImpl;
|
||||
using Aurora.Proto.Events;
|
||||
using Aurora.Services.Server.Controllers;
|
||||
using Aurora.Services.Settings;
|
||||
using Aurora.Services.Library;
|
||||
using Aurora.Services.EventManager;
|
||||
using Aurora.Proto.Party;
|
||||
using Aurora.Proto.Playback;
|
||||
using Aurora.Proto.Sync;
|
||||
|
||||
|
||||
namespace Aurora.Services
|
||||
namespace Aurora.Services.Server
|
||||
{
|
||||
public class ServerService : BaseService<ServerService>
|
||||
public class ServerService : IServerService
|
||||
{
|
||||
private int _port = SettingsService.Instance.DefaultPort;
|
||||
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 RemotePartyServiceImpl _remotePartyServiceImpl;
|
||||
private RemoteEventServiceImpl _remoteEventImpl;
|
||||
private RemotePlaybackServiceImpl _remotePlaybackImpl;
|
||||
private RemoteSyncServiceImpl _remoteSyncImpl;
|
||||
private RemotePartyController _remotePartyController;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor. Registers GRPC service implementations.
|
||||
/// </summary>
|
||||
public ServerService()
|
||||
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;
|
||||
|
||||
_server = new Grpc.Core.Server
|
||||
{
|
||||
Ports = { new ServerPort(_hostname, _port, ServerCredentials.Insecure) }
|
||||
};
|
||||
}
|
||||
|
||||
public int Port
|
||||
@ -58,8 +55,7 @@ namespace Aurora.Services
|
||||
{
|
||||
get
|
||||
{
|
||||
return (_remoteEventImpl != null &&
|
||||
_remotePartyServiceImpl != null &&
|
||||
return (_remotePartyController != null &&
|
||||
_server != null);
|
||||
}
|
||||
}
|
||||
@ -68,27 +64,30 @@ namespace Aurora.Services
|
||||
/// <summary>
|
||||
/// Start Server
|
||||
/// </summary>
|
||||
public void Start()
|
||||
public void Start(string partyName, string description)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
Console.WriteLine(string.Format("Starting gRPC server at hostname: {0}, port: {1}", _hostname, _port));
|
||||
|
||||
if (!Initialized)
|
||||
_server = new Grpc.Core.Server
|
||||
{
|
||||
//Construct implementations
|
||||
_remotePartyServiceImpl = new RemotePartyServiceImpl();
|
||||
_remoteEventImpl = new RemoteEventServiceImpl();
|
||||
_remotePlaybackImpl = new RemotePlaybackServiceImpl();
|
||||
_remoteSyncImpl = new RemoteSyncServiceImpl();
|
||||
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));
|
||||
|
||||
|
||||
// Register grpc RemoteService with singleton server service
|
||||
RegisterService(RemotePartyService.BindService(_remotePartyServiceImpl));
|
||||
RegisterService(RemoteEventService.BindService(_remoteEventImpl));
|
||||
RegisterService(RemotePlaybackService.BindService(_remotePlaybackImpl));
|
||||
RegisterService(RemoteSyncService.BindService(_remoteSyncImpl));
|
||||
}
|
||||
_server.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -103,7 +102,15 @@ namespace Aurora.Services
|
||||
/// <returns>Task</returns>
|
||||
public async Task Stop()
|
||||
{
|
||||
await _server.ShutdownAsync();
|
||||
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()
|
29
Aurora/Services/Settings/ISettingsService.cs
Normal file
29
Aurora/Services/Settings/ISettingsService.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -3,14 +3,16 @@ using System.Threading;
|
||||
using Plugin.Settings;
|
||||
using Plugin.Settings.Abstractions;
|
||||
|
||||
namespace Aurora.Services
|
||||
namespace Aurora.Services.Settings
|
||||
{
|
||||
public class SettingsService : BaseService<SettingsService>
|
||||
public class SettingsService : ISettingsService
|
||||
{
|
||||
private Lazy<ISettings> _appSettings;
|
||||
private string _usernameKey = "username";
|
||||
private string _defaultPortKey = "port";
|
||||
|
||||
private string _libraryLocationKey = "libraryLocation";
|
||||
|
||||
public SettingsService()
|
||||
{
|
||||
}
|
||||
@ -55,11 +57,17 @@ namespace Aurora.Services
|
||||
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 ClientId
|
||||
public string ClientName
|
||||
{
|
||||
get; set;
|
||||
}
|
25
Aurora/Utils/HashUtil.cs
Normal file
25
Aurora/Utils/HashUtil.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System;
|
||||
|
||||
namespace Aurora.Utils
|
||||
{
|
||||
public class HashUtil
|
||||
{
|
||||
public static Guid GetHash(string[] inputs)
|
||||
{
|
||||
string input = "";
|
||||
foreach (string str in inputs)
|
||||
{
|
||||
input += str;
|
||||
}
|
||||
|
||||
byte[] stringbytes = Encoding.UTF8.GetBytes(input);
|
||||
byte[] hashedBytes = new System.Security.Cryptography
|
||||
.SHA1CryptoServiceProvider()
|
||||
.ComputeHash(stringbytes);
|
||||
Array.Resize(ref hashedBytes, 16);
|
||||
return new Guid(hashedBytes);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,9 @@
|
||||
"**/bin": true,
|
||||
"**/obj": true,
|
||||
"**/packages": true
|
||||
}
|
||||
},
|
||||
"nxunitExplorer.nunit": "/Users/brandonwatson/Documents/Gitlab/Aurora/aurora-sharp-desktop/Aurora.test/bin/Debug/nunit.framework.dll",
|
||||
"nxunitExplorer.logpanel": true,
|
||||
"dotnet-test-explorer.testProjectPath": "./Aurora.test"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user