245 lines
8.5 KiB
C#
245 lines
8.5 KiB
C#
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<PlayerService>
|
|
{
|
|
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;
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
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));
|
|
}
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
if (PlaybackStateChanged != null)
|
|
{
|
|
PlaybackStateChanged.Invoke(this, new PlaybackStateChangedEventArgs(_state, PlaybackState.Stopped));
|
|
}
|
|
_state = PlaybackState.Stopped;
|
|
this.Unload();
|
|
}
|
|
|
|
}
|
|
}
|