d5e827112a
- Created PROTOCOL.md to outline the MusicBridge protocol v1, detailing setup, TLS, pairing, state management, and command structure. - Implemented SystemVolume class for managing system audio levels, including methods for setting, reading, changing, and muting volume. - Added unit tests for the MusicBridge agent, covering pairing, identity storage, command validation, and media state management. - Included tests for real-time media reading from Windows and ensured proper handling of artwork caching and retrieval.
44 lines
2.0 KiB
C#
44 lines
2.0 KiB
C#
using System.Security.Cryptography;
|
|
|
|
namespace MusicBridge.Agent.Media;
|
|
|
|
internal sealed record Artwork(string Id, string ContentType, byte[] Bytes);
|
|
|
|
// Only the current cover is retained. IDs are content hashes, never file paths or URLs.
|
|
internal sealed class ArtworkCache
|
|
{
|
|
public const int MaxBytes = 2 * 1024 * 1024;
|
|
private Artwork? current;
|
|
public Artwork? Current => Volatile.Read(ref current);
|
|
public Artwork? Get(string id) => Current is { } artwork && artwork.Id == id ? artwork : null;
|
|
public void Clear() => Volatile.Write(ref current, null);
|
|
|
|
public async Task<Artwork> LoadAsync(Stream stream, CancellationToken cancellationToken)
|
|
{
|
|
Clear(); // A failed/new cover must not leave the previous track's image attached.
|
|
using var output = new MemoryStream();
|
|
var buffer = new byte[16 * 1024];
|
|
while (true)
|
|
{
|
|
var count = await stream.ReadAsync(buffer, cancellationToken);
|
|
if (count == 0) break;
|
|
if (output.Length + count > MaxBytes) throw new InvalidDataException("Обложка больше 2 МиБ.");
|
|
output.Write(buffer, 0, count);
|
|
}
|
|
var bytes = output.ToArray();
|
|
var type = DetectType(bytes) ?? throw new InvalidDataException("Неподдерживаемый формат обложки.");
|
|
var artwork = new Artwork(Convert.ToHexString(SHA256.HashData(bytes)), type, bytes);
|
|
Volatile.Write(ref current, artwork);
|
|
return artwork;
|
|
}
|
|
|
|
private static string? DetectType(ReadOnlySpan<byte> data)
|
|
{
|
|
if (data.StartsWith(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 })) return "image/png";
|
|
if (data.StartsWith(new byte[] { 255, 216, 255 })) return "image/jpeg";
|
|
if (data.StartsWith("GIF87a"u8) || data.StartsWith("GIF89a"u8)) return "image/gif";
|
|
if (data.Length >= 12 && data[..4].SequenceEqual("RIFF"u8) && data.Slice(8, 4).SequenceEqual("WEBP"u8)) return "image/webp";
|
|
return null;
|
|
}
|
|
}
|