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