Migrate aurora-sharp-desktop
This commit is contained in:
62
aurora-sharp-desktop/Aurora/Services/Player/IPlayer.cs
Normal file
62
aurora-sharp-desktop/Aurora/Services/Player/IPlayer.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Grpc.Core;
|
||||
using Aurora.Models.Media;
|
||||
|
||||
namespace Aurora.Services.Player
|
||||
{
|
||||
|
||||
public interface IPlayer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Event handler for changing playback states.
|
||||
/// </summary>
|
||||
event PlaybackStateChangedEventHandler PlaybackStateChanged;
|
||||
|
||||
event MediaChangedEventHandler MediaChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The state of playback
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
PlaybackState PlaybackState { get; }
|
||||
|
||||
bool IsLoaded { get; }
|
||||
|
||||
bool IsMediaLoaded(BaseMedia media);
|
||||
|
||||
BaseMedia CurrentMedia { get; }
|
||||
|
||||
float CurrentMediaPosition { get; }
|
||||
|
||||
long CurrentMediaLength { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Load media into the media player.
|
||||
/// </summary>
|
||||
/// <param name="media">Media to load</param>
|
||||
Task LoadMedia(BaseMedia media);
|
||||
|
||||
/// <summary>
|
||||
/// Play currently loaded media.
|
||||
/// </summary>
|
||||
void Play();
|
||||
|
||||
/// <summary>
|
||||
/// Pause currently loaded media.
|
||||
/// </summary>
|
||||
void Pause();
|
||||
|
||||
/// <summary>
|
||||
/// Stop currently loaded media.
|
||||
/// </summary>
|
||||
void Stop();
|
||||
|
||||
void Enqueue(BaseMedia song);
|
||||
|
||||
void Dequeue(BaseMedia song);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Aurora.Models.Media;
|
||||
|
||||
namespace Aurora.Services.Player
|
||||
{
|
||||
public delegate void MediaChangedEventHandler(object source, MediaChangedEventArgs e);
|
||||
|
||||
public class MediaChangedEventArgs : EventArgs
|
||||
{
|
||||
public BaseMetadata NewMetadata { get; private set; }
|
||||
public string NewId { get; private set; }
|
||||
|
||||
public MediaChangedEventArgs(string id, BaseMetadata metadata)
|
||||
{
|
||||
NewMetadata = metadata;
|
||||
NewId = id;
|
||||
}
|
||||
}
|
||||
}
|
12
aurora-sharp-desktop/Aurora/Services/Player/PlaybackState.cs
Normal file
12
aurora-sharp-desktop/Aurora/Services/Player/PlaybackState.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Aurora.Services.Player
|
||||
{
|
||||
public enum PlaybackState
|
||||
{
|
||||
Playing,
|
||||
Stopped,
|
||||
Buffering,
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Aurora.Models.Media;
|
||||
|
||||
namespace Aurora.Services.Player
|
||||
{
|
||||
public delegate void PlaybackStateChangedEventHandler(object source, PlaybackStateChangedEventArgs e);
|
||||
|
||||
public class PlaybackStateChangedEventArgs : EventArgs
|
||||
{
|
||||
public PlaybackState OldState { get; }
|
||||
public PlaybackState NewState { get; }
|
||||
|
||||
public PlaybackStateChangedEventArgs(PlaybackState oldState, PlaybackState newState)
|
||||
{
|
||||
OldState = oldState;
|
||||
NewState = newState;
|
||||
}
|
||||
}
|
||||
}
|
263
aurora-sharp-desktop/Aurora/Services/Player/PlayerService.cs
Normal file
263
aurora-sharp-desktop/Aurora/Services/Player/PlayerService.cs
Normal file
@ -0,0 +1,263 @@
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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 != null ? _mediaPlayer.Position : -1;
|
||||
}
|
||||
}
|
||||
|
||||
public long CurrentMediaLength
|
||||
{
|
||||
get
|
||||
{
|
||||
return _mediaPlayer != null ? _mediaPlayer.Length : -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play currently loaded media.
|
||||
/// </summary>
|
||||
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<Sync> 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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <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();
|
||||
|
||||
//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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unload currently loaded media.
|
||||
/// </summary>
|
||||
private void Unload()
|
||||
{
|
||||
if (_currentMedia == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user