using System; using System.Threading.Tasks; using System.Threading; using Grpc.Core; using Aurora.Models.Media; using Aurora.Proto.Sync; using Aurora.Proto.Events; using Aurora.Proto.Party; using LibVLCSharp.Shared; namespace Aurora.Services.PlayerService { public class PlayerService : BaseService { private BaseMedia _currentMedia; private MediaPlayer _mediaPlayer; private LibVLC _libvlc; private PlaybackState _state; public PlayerService() { _libvlc = new LibVLC(); _state = PlaybackState.Stopped; } /// /// Event handler for changing playback states. /// public event PlaybackStateChangedEventHandler PlaybackStateChanged; public event MediaChangedEventHandler MediaChanged; /// /// The state of playback /// /// public PlaybackState PlaybackState { get { return _state; } } public bool IsLoaded { get { return this._currentMedia != null; } } public bool IsMediaLoaded(BaseMedia media) { return _currentMedia == media; } public BaseMedia CurrentMedia { get { return _currentMedia; } } public float CurrentMediaPosition { get { return _mediaPlayer.Position; } } public long CurrentMediaLength { get { return _mediaPlayer.Length; } } /// /// Load media into the media player. /// /// Media to load public async Task LoadMedia(BaseMedia media) { if (_state == PlaybackState.Playing || _state == PlaybackState.Buffering) { Unload(); } _currentMedia = media; await _currentMedia.Load(); var md = new Media(_libvlc, _currentMedia.DataStream); _mediaPlayer = new MediaPlayer(md); _mediaPlayer.Stopped += OnStopped; md.Dispose(); if (MediaChanged != null) { MediaChanged.Invoke(this, new MediaChangedEventArgs(_currentMedia.Id, _currentMedia.Metadata)); } } /// /// Play currently loaded media. /// public void Play() { PlaybackState oldState = _state; _state = PlaybackState.Playing; _mediaPlayer.Play(); //Use sync RPC for remote audio if (_currentMedia is RemoteAudio) { RemoteAudio media = _currentMedia as RemoteAudio; if (!media.FromHost) { RemoteSyncService.RemoteSyncServiceClient remoteSyncClient = media.RemoteSyncClient; //Sync playback in a separate task //Task completes when host stops syncing (when a song is complete) Task syncTask = new Task(async () => { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); using (AsyncServerStreamingCall syncStream = remoteSyncClient .GetMediaSync(new Proto.General.Empty())) { try { while (await syncStream.ResponseStream.MoveNext(cancellationTokenSource.Token)) { Sync sync = new Sync(syncStream.ResponseStream.Current); if (sync != null) { Utils.Time time = Utils.TimeUtils.GetNetworkTime(); //Adjust position based on sync DateTime localTime = time.DateTime; //Get offset - elapsed time converted to milliseconds float offset = (((localTime.Ticks - sync.ServerTime) * 100) / (1000 * 1000)); float length = CurrentMediaLength; float newPosition = (sync.TrackTime + (offset + time.Elapsed.Milliseconds)) / length; //Adjust position if greater than 10 percent difference float oldPosition = _mediaPlayer.Position; if (newPosition - oldPosition > 0.001 || newPosition - oldPosition < -0.001) { _mediaPlayer.Position = newPosition; Console.WriteLine(string.Format("Audio synced: oldPosition: {0} newPosition: {1}", oldPosition, newPosition)); } } } } catch (Exception ex) { Console.WriteLine("Exception caught while attempting to sync: " + ex.Message); } } }); syncTask.Start(); // Task syncTask = Task.Run(() => Sync(remoteSyncClient)); } } if (PlaybackStateChanged != null) { PlaybackStateChanged.Invoke(this, new PlaybackStateChangedEventArgs(oldState, _state)); } } /// /// Pause currently loaded media. /// public void Pause() { PlaybackState oldState = _state; _state = PlaybackState.Buffering; _mediaPlayer.Pause(); if (PlaybackStateChanged != null) { PlaybackStateChanged.Invoke(this, new PlaybackStateChangedEventArgs(oldState, _state)); } } /// /// Stop currently loaded media. /// public void Stop() { PlaybackState oldState = _state; _state = PlaybackState.Stopped; _mediaPlayer.Stop(); if (PlaybackStateChanged != null) { PlaybackStateChanged.Invoke(this, new PlaybackStateChangedEventArgs(oldState, _state)); } } public void Enqueue(BaseMedia song) { throw new NotImplementedException(); } public void Dequeue(BaseMedia song) { throw new NotImplementedException(); } /// /// Unload currently loaded media. /// private void Unload() { _currentMedia.Unload(); _currentMedia = null; _mediaPlayer.Media = null; _mediaPlayer = null; } /// /// Event fired when currently loaded media player stops. /// /// /// private void OnStopped(object sender, EventArgs args) { PlaybackStateChanged.Invoke(this, new PlaybackStateChangedEventArgs(_state, PlaybackState.Stopped)); _state = PlaybackState.Stopped; this.Unload(); } } }