using System; using System.Threading.Tasks; using System.Threading; using Grpc.Core; using Aurora.Models.Media; using Aurora.Proto.Sync; using LibVLCSharp.Shared; namespace Aurora.Services.PlayerService { public class PlayerService : BaseService { private const long _ticksPerMillisecond = 10000; 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 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); } } }); 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(); 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) { if (PlaybackStateChanged != null) { PlaybackStateChanged.Invoke(this, new PlaybackStateChangedEventArgs(_state, PlaybackState.Stopped)); } _state = PlaybackState.Stopped; this.Unload(); } } }