aurora/Aurora/Services/PlayerService/PlayerService.cs

278 lines
9.5 KiB
C#
Raw Normal View History

using System;
using System.Threading.Tasks;
using System.Threading;
using Grpc.Core;
2019-07-05 18:17:09 +00:00
using Aurora.Models.Media;
using Aurora.Proto.Sync;
using Aurora.Proto.Events;
using Aurora.Proto.Party;
using LibVLCSharp.Shared;
2019-07-05 18:17:09 +00:00
namespace Aurora.Services.PlayerService
{
public class PlayerService : BaseService<PlayerService>
{
private BaseMedia _currentMedia;
private MediaPlayer _mediaPlayer;
private LibVLC _libvlc;
private PlaybackState _state;
public PlayerService()
{
_libvlc = new LibVLC();
_state = PlaybackState.Stopped;
}
/// <summary>
/// Event handler for changing playback states.
/// </summary>
public event PlaybackStateChangedEventHandler PlaybackStateChanged;
public event MediaChangedEventHandler MediaChanged;
/// <summary>
/// The state of playback
/// </summary>
/// <value></value>
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;
}
}
/// <summary>
/// Load media into the media player.
/// </summary>
/// <param name="media">Media to load</param>
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));
}
}
/// <summary>
/// Play currently loaded media.
/// </summary>
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<Sync> 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 newPosition = (sync.TrackTime + offset) / 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("Audio synced");
}
}
}
}
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));
}
}
/// <summary>
/// Async method to synchronize music playback with host
/// </summary>
/// <param name="remoteSyncClient"></param>
/// <returns></returns>
private async Task Sync(RemoteSyncService.RemoteSyncServiceClient remoteSyncClient)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
using (AsyncServerStreamingCall<Sync> 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 newPosition = (sync.TrackTime + offset) / 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("Audio synced");
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Exception caught while attempting to sync: " + ex.Message);
}
}
}
/// <summary>
/// Pause currently loaded media.
/// </summary>
public void Pause()
{
PlaybackState oldState = _state;
_state = PlaybackState.Buffering;
_mediaPlayer.Pause();
if (PlaybackStateChanged != null)
{
PlaybackStateChanged.Invoke(this, new PlaybackStateChangedEventArgs(oldState, _state));
}
}
/// <summary>
/// Stop currently loaded media.
/// </summary>
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();
}
/// <summary>
/// Unload currently loaded media.
/// </summary>
private void Unload()
{
_currentMedia.Unload();
_currentMedia = null;
_mediaPlayer.Media = null;
_mediaPlayer = null;
}
/// <summary>
/// Event fired when currently loaded media player stops.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void OnStopped(object sender, EventArgs args)
{
PlaybackStateChanged.Invoke(this, new PlaybackStateChangedEventArgs(_state, PlaybackState.Stopped));
_state = PlaybackState.Stopped;
this.Unload();
}
}
}