Skip to content

Commit

Permalink
[OVR Toolkit] Fix connection issues and image encoding.
Browse files Browse the repository at this point in the history
[Spotify] Fix null playlists being returned from Spotify api.
[NotificationView] Add test notification button
  • Loading branch information
Soapwood committed Dec 9, 2024
1 parent ce78755 commit cda8e36
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 114 deletions.
4 changes: 2 additions & 2 deletions VXMusic/Connections/Spotify/SpotifyPlaylistManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ public static string GetPlaylistIdByNameIfExists(string playlistName, IList<Full
if (SpotifyClientBuilder.Instance == null)
throw new Exception("Not authenticated with Spotify API."); // TODO Handle this, and output to UI

foreach(var playlist in playlists)
foreach(var playlist in playlists.Where(pl => pl != null))
{
if(playlist.Name == playlistName)
if (playlist.Name == playlistName)
{
return playlist.Id.ToString();
}
Expand Down
75 changes: 0 additions & 75 deletions VXMusic/Connections/Websocket/WebSocketInteractor.cs

This file was deleted.

Binary file modified VXMusic/Images/VXLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
121 changes: 87 additions & 34 deletions VXMusic/Notifications/OVRToolkit/OvrToolkitNotificationClient.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
using System.Net;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Text;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using VXMusic.Connections.Websocket;
using VXMusic.OVRToolkit;
using VXMusic.Utils;

namespace VXMusic.OVRToolkit;

Expand All @@ -15,12 +14,11 @@ public class OvrToolkitNotificationClient : INotificationClient

private static readonly Uri OvrToolkitServerUrl = new Uri("ws://127.0.0.1:11450/api");

private WebSocketInteractor _webSocket;
private readonly ClientWebSocket _webSocket;

public static bool IsReady;
public static bool IsInitialising;
public static bool IsInitialised;

private static string DefaultVxLogo;
private static byte[] DefaultVxLogoBytes;

public OvrToolkitNotificationClient(IServiceProvider serviceProvider)
{
Expand All @@ -31,14 +29,11 @@ as ILogger<OvrToolkitNotificationClient> ??

_logger.LogDebug("Creating OvrToolkitNotificationClient.");

_webSocket = new WebSocketInteractor();
_webSocket = new ClientWebSocket();

IsInitialising = true;
IsReady = IsOvrToolkitNotificationEndpointReady();
IsInitialising = false;
IsInitialised = ConnectToOvrToolKitEndpoint();

DefaultVxLogo = File.ReadAllText(
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Overlay", "Images", "VXLogoSmallBase64.txt"));
DefaultVxLogoBytes = BitmapUtils.ConvertPngToByteArray(Path.Combine( "Overlay", "Images", "VXLogo.png"));
}

/// <summary>
Expand All @@ -50,16 +45,21 @@ as ILogger<OvrToolkitNotificationClient> ??
/// <param name="timeout"></param>
public void SendNotification(NotificationLevel level, string title, string content, int timeout)
{
SendNotificationInternal(title, content, timeout, DefaultVxLogo);
SendNotificationInternal(title, content, timeout, DefaultVxLogoBytes);
}

public void SendNotification(NotificationLevel level, string title, string content, int timeout, string image)
{
SendNotificationInternal(title, content, timeout, image);
// Unless default, images are base64 encoded
byte[] imageBytes = BitmapUtils.ConvertBase64ToBitmapByteArray(image);
SendNotificationInternal(title, content, timeout, imageBytes);
}

private void SendNotificationInternal(string title, string content, int timeout, string image = "")
private void SendNotificationInternal(string title, string content, int timeout, byte[] image)
{
if (!IsConnected())
IsInitialised = ConnectToOvrToolKitEndpoint();

// OVRToolkit doesn't like when either the title or content is empty
// Need to sanitise this
if (string.IsNullOrEmpty(content) && !string.IsNullOrEmpty(title))
Expand All @@ -68,56 +68,109 @@ private void SendNotificationInternal(string title, string content, int timeout,
title = "VXMusic";
}

try
{
_webSocket.ConnectAsync(OvrToolkitServerUrl);
}
catch (Exception e)
{
Console.WriteLine($"This broke: {e}");
}

OVRToolkitNotificationBody jsonBody = new OVRToolkitNotificationBody()
{
title = title,
body = content,
icon = null
icon = image
};

OVRToolkitNotification notification = new OVRToolkitNotification()
{
messageType = "SendNotification",
json = JsonConvert.SerializeObject(jsonBody)
};

var contentsToSend = JsonConvert.SerializeObject(notification);
_webSocket.SendAsync(contentsToSend);
SendAsync(contentsToSend);
}

private bool IsOvrToolkitNotificationEndpointReady()
private bool ConnectToOvrToolKitEndpoint()
{
try
{
_webSocket.ConnectAsync(OvrToolkitServerUrl);
_logger.LogDebug("Connecting to OVRToolkit Websocket Server...");
ConnectAsync(OvrToolkitServerUrl);
_logger.LogDebug("Connected OVRToolkit Websocket Server to server.");
return true;
}
catch (SocketException se)
{
_logger.LogError("Error connecting to server: {ex.Message}");
return false;
}
}

public bool IsConnected()
{
return _webSocket.State == WebSocketState.Open;
}

public async Task ConnectAsync(Uri serverUri)
{
try
{
Console.WriteLine("Connecting to OVRToolkit Websocket Server...");
await _webSocket.ConnectAsync(OvrToolkitServerUrl);
Console.WriteLine("Connected OVRToolkit Websocket Server to server.");
_logger.LogDebug("Connecting to server...");
await _webSocket.ConnectAsync(serverUri, CancellationToken.None);
_logger.LogDebug("Connected to server.");
}
catch (Exception ex)
{
Console.WriteLine($"Error connecting to server: {ex.Message}");
_logger.LogError($"Error connecting to server: {ex.Message}");
}
}

public async Task SendAsync(string message)
{
if (_webSocket.State == WebSocketState.Open)
{
var bytes = Encoding.UTF8.GetBytes(message);
var buffer = new ArraySegment<byte>(bytes);
_logger.LogTrace("Sending message to websocket.");
await _webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
}
else
{
_logger.LogError("WebSocket is not open.");
}
}

public async Task ReceiveAsync()
{
var buffer = new byte[1024];
var segment = new ArraySegment<byte>(buffer);

while (_webSocket.State == WebSocketState.Open)
{
try
{
var result = await _webSocket.ReceiveAsync(segment, CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
_logger.LogTrace("Server requested close. Closing connection...");
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
}
else
{
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
_logger.LogTrace("Message received: " + message);
}
}
catch (Exception ex)
{
_logger.LogError($"Error receiving data: {ex.Message}");
}
}
}

public async Task CloseAsync()
{
if (_webSocket.State == WebSocketState.Open)
{
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client closing", CancellationToken.None);
Console.WriteLine("WebSocket connection closed.");
}
}


}
25 changes: 25 additions & 0 deletions VXMusic/Utils/BitmapUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,29 @@ public static NotificationBitmap_t NotificationBitmapFromBitmapData(BitmapData T
notification_icon.m_nBytesPerPixel = 4;
return notification_icon;
}

public static byte[] ConvertPngToByteArray(string filePath)
{
using (Bitmap bitmap = new Bitmap(filePath))
using (MemoryStream memoryStream = new MemoryStream())
{
bitmap.Save(memoryStream, ImageFormat.Png);
return memoryStream.ToArray();
}
}

public static byte[] ConvertBase64ToBitmapByteArray(string base64String)
{
// Convert Base64 to byte array
return Convert.FromBase64String(base64String);
}

static byte[] ConvertBitmapToByteArray(Bitmap bitmap, ImageFormat format)
{
using (MemoryStream ms = new MemoryStream())
{
bitmap.Save(ms, format);
return ms.ToArray();
}
}
}
6 changes: 6 additions & 0 deletions VXMusic/VXMusic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,11 @@
<ItemGroup>
<Folder Include="Games\VRChat\VRChatOSCLib\" />
</ItemGroup>

<ItemGroup>
<Resource Include="Images\VXLogo.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
</ItemGroup>

</Project>
23 changes: 22 additions & 1 deletion VXMusicDesktop/MVVM/View/NotificationsView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,27 @@
</Button>
</StackPanel>
</StackPanel>
<Button x:Name="TestNotificationButton"
Content="Test Notification"
Command="{Binding TestNotificationCommand}"
Padding="0"
Margin="0,15,0,0"
Cursor="Hand"
Background="{StaticResource SecondaryColor}"
Foreground="{StaticResource TextBasic}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="120" Height="40">
<Button.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="5" />
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="{StaticResource SecondaryColor}" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Resources>
</Button>
</StackPanel>

</UserControl>
9 changes: 8 additions & 1 deletion VXMusicDesktop/MVVM/ViewModel/NotificationsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ public class NotificationsViewModel : INotifyPropertyChanged
private bool _isVRChatNotificationServiceEnabled;

public ICommand NotificationViewLoaded => notificationViewLoaded ??= new RelayCommand(OnNotificationViewLoaded);


private RelayCommand _testNotificationClick;
public ICommand TestNotificationCommand => _testNotificationClick ??= new RelayCommand(FireTestNotification);

private bool _isNotificationServiceReady = true;

Expand All @@ -62,6 +64,11 @@ public void Initialise()

ProcessNotificationServiceState();
}

private void FireTestNotification(object commandParameter)
{
VXMusicSession.NotificationClient.SendNotification(NotificationLevel.Success, "VXMusic", "Howdy! Test Notification.", 5);
}

public bool IsNotificationServiceReady
{
Expand Down
Binary file added VXMusicDesktop/Overlay/Images/VXLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions VXMusicDesktop/Theme/ColourSchemeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public enum PlaylistSaveSettings
public class UIImageManager
{
public static readonly BitmapImage VXLogoTinyButton = new BitmapImage(new Uri($"pack://application:,,,/Images/VXLogoTinyButton.png", UriKind.Absolute));
public static readonly BitmapImage VXLogo = new BitmapImage(new Uri($"pack://application:,,,/Images/VXLogo.png", UriKind.Absolute));

public static readonly BitmapImage ShazamLogo = new BitmapImage(new Uri($"pack://application:,,,/Images/ShazamLogo.png", UriKind.Absolute));
public static readonly BitmapImage AudDLogo = new BitmapImage(new Uri($"pack://application:,,,/Images/AudDLogo.jpg", UriKind.Absolute));
Expand Down
Loading

0 comments on commit cda8e36

Please sign in to comment.