2
0
mirror of https://github.com/9ParsonsB/Pulsar.git synced 2025-07-01 16:33:43 -04:00

ready for testing

This commit is contained in:
Xjph
2024-01-21 13:35:03 -03:30
parent d99a190869
commit 97e981bae2
92 changed files with 3061 additions and 1186 deletions

View File

@ -28,6 +28,8 @@ namespace Observatory.Herald
public string ShortName => "Herald";
public bool OverrideAudioNotifications => true;
public string Version => typeof(HeraldNotifier).Assembly.GetName().Version.ToString();
public PluginUI PluginUI => new (PluginUI.UIType.None, null);
@ -55,7 +57,7 @@ namespace Observatory.Herald
{
var speechManager = new SpeechRequestManager(
heraldSettings, observatoryCore.HttpClient, observatoryCore.PluginStorageFolder, observatoryCore.GetPluginErrorLogger(this));
heraldSpeech = new HeraldQueue(speechManager, observatoryCore.GetPluginErrorLogger(this));
heraldSpeech = new HeraldQueue(speechManager, observatoryCore.GetPluginErrorLogger(this), observatoryCore);
heraldSettings.Test = TestVoice;
}

View File

@ -2,10 +2,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using NetCoreAudio;
using System.Threading;
using System;
using System.Diagnostics;
using Observatory.Framework.Interfaces;
namespace Observatory.Herald
{
@ -18,15 +18,15 @@ namespace Observatory.Herald
private string rate;
private byte volume;
private SpeechRequestManager speechManager;
private Player audioPlayer;
private Action<Exception, String> ErrorLogger;
private IObservatoryCore core;
public HeraldQueue(SpeechRequestManager speechManager, Action<Exception, String> errorLogger)
public HeraldQueue(SpeechRequestManager speechManager, Action<Exception, String> errorLogger, IObservatoryCore core)
{
this.speechManager = speechManager;
this.core = core;
processing = false;
notifications = new();
audioPlayer = new();
ErrorLogger = errorLogger;
}
@ -74,7 +74,7 @@ namespace Observatory.Herald
{
while (notifications.Any())
{
audioPlayer.SetVolume(volume).Wait();
// audioPlayer.SetVolume(volume).Wait();
notification = notifications.Dequeue();
Debug.WriteLine("Processing notification: {0} - {1}", notification.Title, notification.Detail);
@ -118,7 +118,7 @@ namespace Observatory.Herald
return await speechManager.GetAudioFileFromSsml(ssml, voice, style, rate);
}
private void PlayAudioRequestsSequentially(List<Task<string>> requestTasks)
private async void PlayAudioRequestsSequentially(List<Task<string>> requestTasks)
{
foreach (var request in requestTasks)
{
@ -126,19 +126,20 @@ namespace Observatory.Herald
try
{
Debug.WriteLine($"Playing audio file: {file}");
audioPlayer.Play(file).Wait();
await core.PlayAudioFile(file);
// audioPlayer.Play(file).Wait();
} catch (Exception ex)
{
Debug.WriteLine($"Failed to play {file}: {ex.Message}");
ErrorLogger(ex, $"while playing: {file}");
}
while (audioPlayer.Playing)
Thread.Sleep(50);
//while (audioPlayer.Playing)
// Thread.Sleep(50);
// Explicit stop to ensure device is ready for next file.
// ...hopefully.
audioPlayer.Stop(true).Wait();
//// Explicit stop to ensure device is ready for next file.
//// ...hopefully.
//audioPlayer.Stop(true).Wait();
}
speechManager.CommitCache();

View File

@ -1,19 +0,0 @@
using System;
using System.Threading.Tasks;
namespace NetCoreAudio.Interfaces
{
public interface IPlayer : IDisposable
{
event EventHandler PlaybackFinished;
bool Playing { get; }
bool Paused { get; }
Task Play(string fileName);
Task Pause();
Task Resume();
Task Stop(bool force);
Task SetVolume(byte percent);
}
}

View File

@ -1,98 +0,0 @@
using NetCoreAudio.Interfaces;
using NetCoreAudio.Players;
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace NetCoreAudio
{
public class Player : IPlayer
{
private readonly IPlayer _internalPlayer;
/// <summary>
/// Internally, sets Playing flag to false. Additional handlers can be attached to it to handle any custom logic.
/// </summary>
public event EventHandler PlaybackFinished;
/// <summary>
/// Indicates that the audio is currently playing.
/// </summary>
public bool Playing => _internalPlayer.Playing;
/// <summary>
/// Indicates that the audio playback is currently paused.
/// </summary>
public bool Paused => _internalPlayer.Paused;
public Player()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
_internalPlayer = new WindowsPlayer();
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
_internalPlayer = new LinuxPlayer();
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
_internalPlayer = new MacPlayer();
else
throw new Exception("No implementation exist for the current OS");
_internalPlayer.PlaybackFinished += OnPlaybackFinished;
}
/// <summary>
/// Will stop any current playback and will start playing the specified audio file. The fileName parameter can be an absolute path or a path relative to the directory where the library is located. Sets Playing flag to true. Sets Paused flag to false.
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public async Task Play(string fileName)
{
await _internalPlayer.Play(fileName);
}
/// <summary>
/// Pauses any ongong playback. Sets Paused flag to true. Doesn't modify Playing flag.
/// </summary>
/// <returns></returns>
public async Task Pause()
{
await _internalPlayer.Pause();
}
/// <summary>
/// Resumes any paused playback. Sets Paused flag to false. Doesn't modify Playing flag.
/// </summary>
/// <returns></returns>
public async Task Resume()
{
await _internalPlayer.Resume();
}
/// <summary>
/// Stops any current playback and clears the buffer. Sets Playing and Paused flags to false.
/// </summary>
/// <returns></returns>
public async Task Stop(bool force = false)
{
await _internalPlayer.Stop(force);
}
private void OnPlaybackFinished(object sender, EventArgs e)
{
PlaybackFinished?.Invoke(this, e);
}
/// <summary>
/// Sets the playing volume as percent
/// </summary>
/// <returns></returns>
public async Task SetVolume(byte percent)
{
await _internalPlayer.SetVolume(percent);
}
public void Dispose()
{
_internalPlayer.Dispose();
}
}
}

View File

@ -1,33 +0,0 @@
using System;
using System.IO;
using System.Threading.Tasks;
using NetCoreAudio.Interfaces;
namespace NetCoreAudio.Players
{
internal class LinuxPlayer : UnixPlayerBase, IPlayer
{
protected override string GetBashCommand(string fileName)
{
if (Path.GetExtension(fileName).ToLower().Equals(".mp3"))
{
return "mpg123 -q";
}
else
{
return "aplay -q";
}
}
public override Task SetVolume(byte percent)
{
if (percent > 100)
throw new ArgumentOutOfRangeException(nameof(percent), "Percent can't exceed 100");
var tempProcess = StartBashProcess($"amixer -M set 'Master' {percent}%");
tempProcess.WaitForExit();
return Task.CompletedTask;
}
}
}

View File

@ -1,25 +0,0 @@
using System;
using System.Threading.Tasks;
using NetCoreAudio.Interfaces;
namespace NetCoreAudio.Players
{
internal class MacPlayer : UnixPlayerBase, IPlayer
{
protected override string GetBashCommand(string fileName)
{
return "afplay";
}
public override Task SetVolume(byte percent)
{
if (percent > 100)
throw new ArgumentOutOfRangeException(nameof(percent), "Percent can't exceed 100");
var tempProcess = StartBashProcess($"osascript -e \"set volume output volume {percent}\"");
tempProcess.WaitForExit();
return Task.CompletedTask;
}
}
}

View File

@ -1,110 +0,0 @@
using NetCoreAudio.Interfaces;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace NetCoreAudio.Players
{
internal abstract class UnixPlayerBase : IPlayer
{
private Process _process = null;
internal const string PauseProcessCommand = "kill -STOP {0}";
internal const string ResumeProcessCommand = "kill -CONT {0}";
public event EventHandler PlaybackFinished;
public bool Playing { get; private set; }
public bool Paused { get; private set; }
protected abstract string GetBashCommand(string fileName);
public async Task Play(string fileName)
{
await Stop();
var BashToolName = GetBashCommand(fileName);
_process = StartBashProcess($"{BashToolName} '{fileName}'");
_process.EnableRaisingEvents = true;
_process.Exited += HandlePlaybackFinished;
_process.ErrorDataReceived += HandlePlaybackFinished;
_process.Disposed += HandlePlaybackFinished;
Playing = true;
}
public Task Pause()
{
if (Playing && !Paused && _process != null)
{
var tempProcess = StartBashProcess(string.Format(PauseProcessCommand, _process.Id));
tempProcess.WaitForExit();
Paused = true;
}
return Task.CompletedTask;
}
public Task Resume()
{
if (Playing && Paused && _process != null)
{
var tempProcess = StartBashProcess(string.Format(ResumeProcessCommand, _process.Id));
tempProcess.WaitForExit();
Paused = false;
}
return Task.CompletedTask;
}
public Task Stop(bool force = false)
{
if (_process != null)
{
_process.Kill();
_process.Dispose();
_process = null;
}
Playing = false;
Paused = false;
return Task.CompletedTask;
}
protected Process StartBashProcess(string command)
{
var escapedArgs = command.Replace("\"", "\\\"");
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = $"-c \"{escapedArgs}\"",
RedirectStandardOutput = true,
RedirectStandardInput = true,
UseShellExecute = false,
CreateNoWindow = true,
}
};
process.Start();
return process;
}
internal void HandlePlaybackFinished(object sender, EventArgs e)
{
if (Playing)
{
Playing = false;
PlaybackFinished?.Invoke(this, e);
}
}
public abstract Task SetVolume(byte percent);
public void Dispose()
{
Stop().Wait();
}
}
}

View File

@ -1,141 +0,0 @@
using NetCoreAudio.Interfaces;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
namespace NetCoreAudio.Players
{
internal class WindowsPlayer : IPlayer
{
[DllImport("winmm.dll")]
private static extern int mciSendString(string command, StringBuilder stringReturn, int returnLength, IntPtr hwndCallback);
[DllImport("winmm.dll")]
private static extern int mciGetErrorString(int errorCode, StringBuilder errorText, int errorTextSize);
[DllImport("winmm.dll")]
public static extern int waveOutSetVolume(IntPtr hwo, uint dwVolume);
private System.Timers.Timer _playbackTimer;
private Stopwatch _playStopwatch;
private string _fileName;
public event EventHandler PlaybackFinished;
public bool Playing { get; private set; }
public bool Paused { get; private set; }
public Task Play(string fileName)
{
_fileName = $"\"{fileName}\"";
_playbackTimer = new System.Timers.Timer
{
AutoReset = false
};
_playStopwatch = new Stopwatch();
ExecuteMciCommand($"Status {_fileName} Length");
ExecuteMciCommand($"Play {_fileName}");
Paused = false;
Playing = true;
_playbackTimer.Elapsed += HandlePlaybackFinished;
_playbackTimer.Start();
_playStopwatch.Start();
return Task.CompletedTask;
}
public Task Pause()
{
if (Playing && !Paused)
{
ExecuteMciCommand($"Pause {_fileName}");
Paused = true;
_playbackTimer.Stop();
_playStopwatch.Stop();
_playbackTimer.Interval -= _playStopwatch.ElapsedMilliseconds;
}
return Task.CompletedTask;
}
public Task Resume()
{
if (Playing && Paused)
{
ExecuteMciCommand($"Resume {_fileName}");
Paused = false;
_playbackTimer.Start();
_playStopwatch.Reset();
_playStopwatch.Start();
}
return Task.CompletedTask;
}
public Task Stop(bool force = false)
{
if (Playing || force)
{
ExecuteMciCommand($"Stop {_fileName}");
Playing = false;
Paused = false;
_playbackTimer?.Stop();
_playStopwatch?.Stop();
}
return Task.CompletedTask;
}
private void HandlePlaybackFinished(object sender, ElapsedEventArgs e)
{
Playing = false;
PlaybackFinished?.Invoke(this, e);
_playbackTimer?.Dispose();
_playbackTimer = null;
}
private Task ExecuteMciCommand(string commandString)
{
var sb = new StringBuilder();
var result = mciSendString(commandString, sb, 1024 * 1024, IntPtr.Zero);
if (result != 0)
{
var errorSb = new StringBuilder($"Error executing MCI command '{commandString}'. Error code: {result}.");
var sb2 = new StringBuilder(128);
mciGetErrorString(result, sb2, 128);
errorSb.Append($" Message: {sb2}");
throw new Exception(errorSb.ToString());
}
if (commandString.ToLower().StartsWith("status") && int.TryParse(sb.ToString(), out var length))
_playbackTimer.Interval = length + 75;
return Task.CompletedTask;
}
public Task SetVolume(byte percent)
{
// Calculate the volume that's being set
int NewVolume = ushort.MaxValue / 100 * percent;
// Set the same volume for both the left and the right channels
uint NewVolumeAllChannels = ((uint)NewVolume & 0x0000ffff) | ((uint)NewVolume << 16);
// Set the volume
waveOutSetVolume(IntPtr.Zero, NewVolumeAllChannels);
return Task.CompletedTask;
}
public void Dispose()
{
Stop().Wait();
ExecuteMciCommand("Close All");
}
}
}

View File

@ -37,12 +37,8 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Folder Include="NetCoreAudio\Players\" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Condition=" '$(OS)' == 'Windows_NT' " Command="xcopy &quot;$(TargetPath)&quot; &quot;$(ProjectDir)..\ObservatoryCore\$(OutDir)plugins\&quot; /y" />
<Exec Condition=" '$(OS)' == 'Windows_NT' " Command="xcopy &quot;$(TargetPath)&quot; &quot;$(ProjectDir)..\ObservatoryCore\$(OutDir)..\net6.0-windows\plugins\&quot; /y" />
<Exec Condition=" '$(OS)' != 'Windows_NT' " Command="[ ! -d &quot;$(ProjectDir)../ObservatoryCore/$(OutDir)plugins/deps&quot; ] &amp;&amp; mkdir -p &quot;$(ProjectDir)../ObservatoryCore/$(OutDir)plugins/deps&quot; || echo Directory already exists" />
</Target>