diff --git a/Aurora/Design/Views/Party/PartyViewModel.cs b/Aurora/Design/Views/Party/PartyViewModel.cs index d6359a8..a7db796 100644 --- a/Aurora/Design/Views/Party/PartyViewModel.cs +++ b/Aurora/Design/Views/Party/PartyViewModel.cs @@ -225,7 +225,7 @@ namespace Aurora.Design.Views.Party { SetState(PartyState.Connecting); _client.Start(Hostname, SettingsService.Instance.DefaultPort.ToString()); - await JoinParty(); + await JoinParty(false); //TODO add cancellation token try @@ -252,7 +252,7 @@ namespace Aurora.Design.Views.Party string localHost = ServerService.GetLocalIPAddress(); _client.IsHost = true; _client.Start(localHost, SettingsService.Instance.DefaultPort.ToString()); - await JoinParty(); + await JoinParty(true); //TODO add cancellation token @@ -341,7 +341,7 @@ namespace Aurora.Design.Views.Party /// Join the remote party. /// /// - private async Task JoinParty() + private async Task JoinParty(bool asHost) { try { @@ -383,6 +383,7 @@ namespace Aurora.Design.Views.Party meta.Duration = data.Duration; RemoteAudio remote = new RemoteAudio(data.Id, + asHost, meta, _client.RemotePlaybackClient, _client.RemoteSyncClient); diff --git a/Aurora/Models/Media/BaseMedia.cs b/Aurora/Models/Media/BaseMedia.cs index 0e31597..c3a22e8 100644 --- a/Aurora/Models/Media/BaseMedia.cs +++ b/Aurora/Models/Media/BaseMedia.cs @@ -6,35 +6,39 @@ namespace Aurora.Models.Media { public abstract class BaseMedia { - private bool _loaded; private Stream _stream; public BaseMedia() { - _loaded = false; Id = Guid.NewGuid().ToString(); } #region Properties public string Id { get; protected set; } + public abstract MediaTypeEnum MediaType { get; } + + public abstract BaseMetadata Metadata { get; protected set; } + + public bool IsLoaded + { + get + { + return DataStream != null; + } + } + #endregion Properties public virtual Task Load() { - _loaded = true; return Task.FromResult(default(object)); } public virtual void Unload() { - _loaded = false; } - public abstract MediaTypeEnum MediaType { get; } - - public abstract BaseMetadata Metadata { get; protected set; } - /// /// Gets or sets the data stream that holds the song. diff --git a/Aurora/Models/Media/LocalAudio.cs b/Aurora/Models/Media/LocalAudio.cs index 6b6ec7b..3d7bd0f 100644 --- a/Aurora/Models/Media/LocalAudio.cs +++ b/Aurora/Models/Media/LocalAudio.cs @@ -12,6 +12,12 @@ namespace Aurora.Models.Media LoadMetadata(); } + public LocalAudio(LocalAudio copy) + { + File = copy.File; + LoadMetadata(); + } + #region Properties public FileInfo File { get; private set; } diff --git a/Aurora/Models/Media/RemoteAudio.cs b/Aurora/Models/Media/RemoteAudio.cs index 6b2ce4e..679b57e 100644 --- a/Aurora/Models/Media/RemoteAudio.cs +++ b/Aurora/Models/Media/RemoteAudio.cs @@ -15,7 +15,9 @@ namespace Aurora.Models.Media private CancellationTokenSource _cancellationTokenSource; #region Constructor - public RemoteAudio(string id, AudioMetadata metadata, + public RemoteAudio(string id, + bool fromHost, + AudioMetadata metadata, RemotePlaybackService.RemotePlaybackServiceClient playbackClient, RemoteSyncService.RemoteSyncServiceClient syncClient) { @@ -23,6 +25,7 @@ namespace Aurora.Models.Media this._remotePlaybackClient = playbackClient; this._remoteSyncClient = syncClient; this.Metadata = metadata; + this.FromHost = fromHost; _cancellationTokenSource = new CancellationTokenSource(); } @@ -46,6 +49,7 @@ namespace Aurora.Models.Media } } + public bool FromHost { get; private set; } #endregion Properties /// @@ -56,15 +60,21 @@ namespace Aurora.Models.Media this.DataStream = new MemoryStream(); using (var call = _remotePlaybackClient.GetSongStream(new SongRequest() { Id = this.Id })) { - while (await call.ResponseStream.MoveNext(_cancellationTokenSource.Token)) + try { - Chunk chunk = call.ResponseStream.Current; - byte[] buffer = chunk.Content.ToByteArray(); + while (await call.ResponseStream.MoveNext(_cancellationTokenSource.Token)) + { + Chunk chunk = call.ResponseStream.Current; + byte[] buffer = chunk.Content.ToByteArray(); - await this.DataStream.WriteAsync(buffer, 0, buffer.Length); + await this.DataStream.WriteAsync(buffer, 0, buffer.Length); + } + Console.WriteLine("Done receiving stream"); + } + catch (Exception ex) + { + Console.WriteLine("Exception caught while loading remote audio:" + ex.Message); } - Console.WriteLine("Done receiving stream"); - } await base.Load(); } diff --git a/Aurora/RemoteImpl/RemotePlaybackImpl.cs b/Aurora/RemoteImpl/RemotePlaybackImpl.cs index 63d0fa0..17da449 100644 --- a/Aurora/RemoteImpl/RemotePlaybackImpl.cs +++ b/Aurora/RemoteImpl/RemotePlaybackImpl.cs @@ -15,19 +15,38 @@ namespace Aurora.RemoteImpl Grpc.Core.IServerStreamWriter responseStream, Grpc.Core.ServerCallContext context) { - BaseMedia song = LibraryService.Instance.GetSong(request.Id); - await song.Load(); - - //Send stream - Console.WriteLine("Begin sending file"); - byte[] buffer = new byte[2048]; // read in chunks of 2KB - int bytesRead; - while ((bytesRead = song.DataStream.Read(buffer, 0, buffer.Length)) > 0) + BaseMedia originalSong = LibraryService.Instance.GetSong(request.Id); + if (!(originalSong is LocalAudio)) { - Google.Protobuf.ByteString bufferByteString = Google.Protobuf.ByteString.CopyFrom(buffer); - await responseStream.WriteAsync(new Chunk { Content = bufferByteString }); + 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); } - Console.WriteLine("Done sending file"); } } } \ No newline at end of file diff --git a/Aurora/RemoteImpl/RemoteSyncImpl.cs b/Aurora/RemoteImpl/RemoteSyncImpl.cs index 0a8f7d9..faa6a0f 100644 --- a/Aurora/RemoteImpl/RemoteSyncImpl.cs +++ b/Aurora/RemoteImpl/RemoteSyncImpl.cs @@ -19,18 +19,22 @@ namespace Aurora.RemoteImpl Grpc.Core.IServerStreamWriter responseStream, Grpc.Core.ServerCallContext context) { - bool songIsPlaying = true; - PlaybackStateChangedEventHandler playbackStateChanged = (sender, e) => + bool continueSync = true; + string currentId = PlayerService.Instance.CurrentMedia.Id; + MediaChangedEventHandler mediaChanged = (sender, e) => { - songIsPlaying = false; + if (e.NewId != currentId) + { + continueSync = false; + } }; - PlayerService.Instance.PlaybackStateChanged += playbackStateChanged; + PlayerService.Instance.MediaChanged += mediaChanged; - while (songIsPlaying) + while (continueSync) { DateTime time = Utils.TimeUtils.GetNetworkTime(); - float position = PlayerService.Instance.CurrentMediaTime; + float position = PlayerService.Instance.CurrentMediaPosition; float length = PlayerService.Instance.CurrentMediaLength; float trackTime = length * position; @@ -41,7 +45,8 @@ namespace Aurora.RemoteImpl ServerTime = time.Ticks }; await responseStream.WriteAsync(sync); - await Task.Delay(10000); + Console.WriteLine("Sent Sync"); + await Task.Delay(5000); } } } diff --git a/Aurora/Services/PlayerService/MediaChangedEvent.cs b/Aurora/Services/PlayerService/MediaChangedEvent.cs index 868edf3..e78ce14 100644 --- a/Aurora/Services/PlayerService/MediaChangedEvent.cs +++ b/Aurora/Services/PlayerService/MediaChangedEvent.cs @@ -8,10 +8,12 @@ namespace Aurora.Services.PlayerService public class MediaChangedEventArgs : EventArgs { public BaseMetadata NewMetadata { get; private set; } + public string NewId { get; private set; } - public MediaChangedEventArgs(BaseMetadata metadata) + public MediaChangedEventArgs(string id, BaseMetadata metadata) { NewMetadata = metadata; + NewId = id; } } } \ No newline at end of file diff --git a/Aurora/Services/PlayerService/PlayerService.cs b/Aurora/Services/PlayerService/PlayerService.cs index 6e12539..baf6d7a 100644 --- a/Aurora/Services/PlayerService/PlayerService.cs +++ b/Aurora/Services/PlayerService/PlayerService.cs @@ -41,7 +41,7 @@ namespace Aurora.Services.PlayerService { get { - return this._currentMedia == null; + return this._currentMedia != null; } } @@ -50,7 +50,12 @@ namespace Aurora.Services.PlayerService return _currentMedia == media; } - public float CurrentMediaTime + public BaseMedia CurrentMedia + { + get { return _currentMedia; } + } + + public float CurrentMediaPosition { get { @@ -85,7 +90,7 @@ namespace Aurora.Services.PlayerService if (MediaChanged != null) { - MediaChanged.Invoke(this, new MediaChangedEventArgs(_currentMedia.Metadata)); + MediaChanged.Invoke(this, new MediaChangedEventArgs(_currentMedia.Id, _currentMedia.Metadata)); } } @@ -103,43 +108,53 @@ namespace Aurora.Services.PlayerService if (_currentMedia is RemoteAudio) { RemoteAudio media = _currentMedia as RemoteAudio; - RemoteSyncService.RemoteSyncServiceClient _remoteSyncClient = media.RemoteSyncClient; - - //Sync playback in a separate task - //Task completes when host stops syncing (when a song is complete) - Task syncTask = new Task(async () => + if (!media.FromHost) { - CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - using (AsyncServerStreamingCall syncStream = _remoteSyncClient - .GetMediaSync(new Proto.General.Empty())) + RemoteSyncService.RemoteSyncServiceClient _remoteSyncClient = media.RemoteSyncClient; + + //Sync playback in a separate task + //Task completes when host stops syncing (when a song is complete) + Task syncTask = new Task(async () => { - try + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + using (AsyncServerStreamingCall syncStream = _remoteSyncClient + .GetMediaSync(new Proto.General.Empty())) { - while (await syncStream.ResponseStream.MoveNext(cancellationTokenSource.Token)) + try { - Sync sync = new Sync(syncStream.ResponseStream.Current); - if (sync != null) + while (await syncStream.ResponseStream.MoveNext(cancellationTokenSource.Token)) { - //Adjust position based on sync - DateTime localTime = Utils.TimeUtils.GetNetworkTime(); - //Get offset converted to milliseconds - float offset = ((localTime.Ticks - sync.ServerTime) * 100) / (1000 * 1000); + Sync sync = new Sync(syncStream.ResponseStream.Current); + if (sync != null) + { + //Adjust position based on sync + DateTime localTime = Utils.TimeUtils.GetNetworkTime(); + //Get offset converted to milliseconds + float offset = ((localTime.Ticks - sync.ServerTime) * 100) / (1000 * 1000); - float length = CurrentMediaLength; - float position = (sync.TrackTime + offset) / length; + float length = CurrentMediaLength; + float newPosition = (sync.TrackTime + offset) / length; - _mediaPlayer.Position = position; + //Adjust position if greater than 10 percent difference + float oldPosition = _mediaPlayer.Position; + if (newPosition - oldPosition > 0.001 || + newPosition - oldPosition < -0.001) + { + _mediaPlayer.Position = newPosition; + Console.WriteLine("Audio synced"); + } + } } } + catch (Exception ex) + { + Console.WriteLine("Exception caught while attempting to sync: " + ex.Message); + } } - catch (Exception ex) - { - Console.WriteLine("Exception caught while attempting to sync: " + ex.Message); - } - } - }); + }); - syncTask.Start(); + syncTask.Start(); + } } if (PlaybackStateChanged != null)