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:
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -37,12 +37,8 @@
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="NetCoreAudio\Players\" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Condition=" '$(OS)' == 'Windows_NT' " Command="xcopy "$(TargetPath)" "$(ProjectDir)..\ObservatoryCore\$(OutDir)plugins\" /y" />
|
||||
<Exec Condition=" '$(OS)' == 'Windows_NT' " Command="xcopy "$(TargetPath)" "$(ProjectDir)..\ObservatoryCore\$(OutDir)..\net6.0-windows\plugins\" /y" />
|
||||
<Exec Condition=" '$(OS)' != 'Windows_NT' " Command="[ ! -d "$(ProjectDir)../ObservatoryCore/$(OutDir)plugins/deps" ] && mkdir -p "$(ProjectDir)../ObservatoryCore/$(OutDir)plugins/deps" || echo Directory already exists" />
|
||||
</Target>
|
||||
|
||||
|
Reference in New Issue
Block a user