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 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 float CurrentMediaTime { 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.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; 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) { //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; _mediaPlayer.Position = position; } } } 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) { PlaybackStateChanged.Invoke(this, new PlaybackStateChangedEventArgs(_state, PlaybackState.Stopped)); _state = PlaybackState.Stopped; this.Unload(); } } }