diff --git a/README.md b/README.md
index 21f5d102..1a8249a8 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,15 @@ Audio Band allows you to display song information in the taskbar.
![](screenshots/custom-2.png)
+# Table of contents
+1. [Installation](#installation)
+2. [Usage](#usage)
+3. [Plugins](#audiosources)
+ 1. [Spotify](#spotify-setup)
+ 2. [iTunes](#itunes)
+ 3. [MusicBee](#musicbee)
+4. [Customization](#customization)
+
## Installation
There is currently no installer available, however there are prereleases in the [Release](https://github.com/dsafa/audio-band/releases) page that come with a script to install manually.
@@ -31,7 +40,7 @@ There is currently no installer available, however there are prereleases in the
**IMPORTANT** If nothing happens after selecting `Audio Band` from the toolbars menu or if there are no options in the `Audio Source` menu, some files are being blocked by windows. To fix it, run `unblock.ps1` with powershell. If that doesn't work you can manually fix it by right clicking the files -> properties and clicking unblock. If there are still problems, feel free to post an issue.
-## Current Supported Audio Sources (see below for setup instructions and issues)
+## Current Supported Audio Sources (see below for setup instructions and issues)
- Spotify
- iTunes
- MusicBee
@@ -49,7 +58,6 @@ There is currently no installer available, however there are prereleases in the
![](./screenshots/spotify-app-settings-callback.png)
#### Spotify Issues
-- The song progress will be out of sync if you change the song's progress. This is due to current limitations making the song progress being tracked locally. This can be fixed if you pause and play again or go to a different song.
- The first time you open up Spotify, the current song may not be displayed. This is because Spotify doesn't report any song information if you have no devices playing songs. Just start playing a song and it will start displaying.
### iTunes
diff --git a/src/AudioBand.AudioSource/AudioBand.AudioSource.csproj b/src/AudioBand.AudioSource/AudioBand.AudioSource.csproj
index 98043841..1ea6633a 100644
--- a/src/AudioBand.AudioSource/AudioBand.AudioSource.csproj
+++ b/src/AudioBand.AudioSource/AudioBand.AudioSource.csproj
@@ -21,6 +21,8 @@
prompt
4
bin\Debug\AudioBand.AudioSource.xml
+ ..\AudioBandRules.ruleset
+ true
pdbonly
@@ -30,6 +32,8 @@
prompt
4
.none
+ ..\AudioBandRules.ruleset
+ bin\Release\AudioBand.AudioSource.xml
@@ -41,10 +45,23 @@
+
+
-
+
+
+
+
+
+ stylecop.json
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AudioBand.AudioSource/AudioBand.AudioSource.nuspec b/src/AudioBand.AudioSource/AudioBand.AudioSource.nuspec
new file mode 100644
index 00000000..9aca6da7
--- /dev/null
+++ b/src/AudioBand.AudioSource/AudioBand.AudioSource.nuspec
@@ -0,0 +1,19 @@
+
+
+
+ AudioBand.AudioSource
+ $version$
+ $title$
+ Brandon Chong
+ dsafa
+ MIT
+ https://github.com/dsafa/audio-band
+ false
+ Library to develop audio sources for AudioBand
+ Copyright 2019
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AudioBand.AudioSource/AudioSourceSettingAttribute.cs b/src/AudioBand.AudioSource/AudioSourceSettingAttribute.cs
index bc7b89a6..2c3c1889 100644
--- a/src/AudioBand.AudioSource/AudioSourceSettingAttribute.cs
+++ b/src/AudioBand.AudioSource/AudioSourceSettingAttribute.cs
@@ -7,64 +7,39 @@ namespace AudioBand.AudioSource
///
///
[AttributeUsage(AttributeTargets.Property)]
+ [Serializable]
public class AudioSourceSettingAttribute : Attribute
{
///
- /// Name of the validator function
- ///
- /// The validation function should have the signature (object valueToValidate, string nameOfPropertyBeingSet) ->
+ /// Initializes a new instance of the class
+ /// with the setting name.
///
- public string ValidatorName { get; set; }
+ /// Name of the setting.
+ public AudioSourceSettingAttribute(string name)
+ {
+ Name = name;
+ }
///
- /// Name of the setting.
+ /// Gets or sets the name of the setting.
///
public string Name { get; set; }
///
- /// Gets or Sets the for the setting.
+ /// Gets or sets the flags for the setting.
///
public SettingOptions Options { get; set; }
///
- /// Priority when changing more that one setting. Higher is higher priority
+ /// Gets or sets the priority when changing more that one setting.
+ ///
+ /// A higher value is higher priority.
///
public int Priority { get; set; }
///
- /// Description of the setting.
+ /// Gets or sets the description of the setting.
///
public string Description { get; set; }
-
- ///
- /// Expose this property as a setting with a given name
- ///
- /// Name of the setting.
- public AudioSourceSettingAttribute(string name)
- {
- Name = name;
- }
- }
-
- ///
- /// Flags for audio source settings.
- ///
- [Flags]
- public enum SettingOptions
- {
- ///
- /// Setting is invisible to the user.
- ///
- Hidden = 1 << 0,
-
- ///
- /// Setting cannot be modified by the user.
- ///
- ReadOnly = 1 << 1,
-
- ///
- /// Indicates a sensitive setting such as a password, causing a warning to be given.
- ///
- Sensitive = 1 << 2,
}
}
diff --git a/src/AudioBand.AudioSource/IAudioSource.cs b/src/AudioBand.AudioSource/IAudioSource.cs
index 94165d74..519e16be 100644
--- a/src/AudioBand.AudioSource/IAudioSource.cs
+++ b/src/AudioBand.AudioSource/IAudioSource.cs
@@ -1,6 +1,5 @@
using System;
using System.ComponentModel.Composition;
-using System.Threading;
using System.Threading.Tasks;
namespace AudioBand.AudioSource
@@ -12,70 +11,114 @@ namespace AudioBand.AudioSource
public interface IAudioSource
{
///
- /// Gets the name of the audio source.
+ /// Occurs when a setting has changed internally.
///
- /// The name of the audio source.
- string Name { get; }
+ event EventHandler SettingChanged;
///
- /// Gets or sets the used for logging.
+ /// Occurs when track information has changed.
///
- /// Audio source logger that will be injected.
- IAudioSourceLogger Logger { get; set; }
+ event EventHandler TrackInfoChanged;
///
- /// Occurs when a setting has changed internally.
+ /// Occurs when the track play state has changed. if playing; otherwise;
///
- event EventHandler SettingChanged;
+ event EventHandler IsPlayingChanged;
///
- /// Occurs when track information has changed.
+ /// Occurs when the current track progress has changed.
///
- event EventHandler TrackInfoChanged;
+ event EventHandler TrackProgressChanged;
///
- /// Occurs when the playback state has changed to playing.
+ /// Occurs when the volume of the audio source changes. The range of the volume is between 0.0 and 1.0 inclusive.
///
- event EventHandler TrackPlaying;
+ event EventHandler VolumeChanged;
///
- /// Occurs when the playback state has changed to paused.
+ /// Occurs when the shuffle state changes. if shuffle is on; otherwise;
///
- event EventHandler TrackPaused;
+ event EventHandler ShuffleChanged;
///
- /// Occurs when the current track progress has changed.
+ /// Occurs when the repeat mode changes.
///
- event EventHandler TrackProgressChanged;
+ event EventHandler RepeatModeChanged;
+
+ ///
+ /// Gets the name of the audio source.
+ ///
+ /// The name of the audio source.
+ string Name { get; }
///
- /// Called when the audio source is active.
+ /// Gets or sets the used for logging.
///
- Task ActivateAsync(CancellationToken cancellationToken = default(CancellationToken));
+ /// Audio source logger that will be injected.
+ IAudioSourceLogger Logger { get; set; }
+
+ ///
+ /// Called when the audio source becomes active.
+ ///
+ /// A representing the asynchronous activate operation.
+ Task ActivateAsync();
///
/// Called when the audio source is no longer active.
///
- Task DeactivateAsync(CancellationToken cancellationToken = default(CancellationToken));
+ /// A representing the asynchronous deactivate operation.
+ Task DeactivateAsync();
///
/// Called when there is a request to start playback.
///
- Task PlayTrackAsync(CancellationToken cancellationToken = default(CancellationToken));
+ /// A representing the asynchronous play operation.
+ Task PlayTrackAsync();
///
/// Called when there is a request to stop playback.
///
- Task PauseTrackAsync(CancellationToken cancellationToken = default(CancellationToken));
+ /// A representing the asynchronous pause operation.
+ Task PauseTrackAsync();
///
/// Called when there is a request to skip to the previous track.
///
- Task PreviousTrackAsync(CancellationToken cancellationToken = default(CancellationToken));
+ /// A representing the asynchronous skip to previous track operation.
+ Task PreviousTrackAsync();
///
/// Called when there is a request to skip to the next track.
///
- Task NextTrackAsync(CancellationToken cancellationToken = default(CancellationToken));
+ /// A representing the asynchronous skip to next track operation.
+ Task NextTrackAsync();
+
+ ///
+ /// Called when there is a request to change the volume.
+ ///
+ /// The new volume to set. The range is between 0.0 and 1.0 inclusive.
+ /// A representing the asynchronous set volume operation.
+ Task SetVolumeAsync(float newVolume);
+
+ ///
+ /// Called when there is a request to change to current playback progress.
+ ///
+ /// The new time to seek to.
+ /// A representing the asynchronous set playback progress operation.
+ Task SetPlaybackProgressAsync(TimeSpan newProgress);
+
+ ///
+ /// Called when there is a request to change the shuffle state.
+ ///
+ /// The new shuffle state, if shuffle should be on; otherwise.
+ /// A representing the asynchronous set shuffle operation.
+ Task SetShuffleAsync(bool shuffleOn);
+
+ ///
+ /// Called when there is a request to change the repeat mode.
+ ///
+ /// The new .
+ /// A representing the asynchronous set repeat mode operation.
+ Task SetRepeatModeAsync(RepeatMode newRepeatMode);
}
}
diff --git a/src/AudioBand.AudioSource/ISupportsRatings.cs b/src/AudioBand.AudioSource/ISupportsRatings.cs
new file mode 100644
index 00000000..95374a6f
--- /dev/null
+++ b/src/AudioBand.AudioSource/ISupportsRatings.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Threading.Tasks;
+
+namespace AudioBand.AudioSource
+{
+ ///
+ /// Indicates that this audio source supports ratings on tracks.
+ ///
+ public interface ISupportsRatings
+ {
+ ///
+ /// Occurs when the the track's rating has changed.
+ ///
+ event EventHandler TrackRatingChanged;
+
+ ///
+ /// Called when a new rating is being set.
+ ///
+ /// The new rating of the track
+ /// A representing the asynchronous set rating operation.
+ Task SetTrackRatingAsync(TrackRating newRating);
+ }
+}
diff --git a/src/AudioBand.AudioSource/Properties/AssemblyInfo.cs b/src/AudioBand.AudioSource/Properties/AssemblyInfo.cs
index 94617118..8dcbd9db 100644
--- a/src/AudioBand.AudioSource/Properties/AssemblyInfo.cs
+++ b/src/AudioBand.AudioSource/Properties/AssemblyInfo.cs
@@ -6,7 +6,7 @@
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("AudioBand.AudioSource")]
-[assembly: AssemblyDescription("")]
+[assembly: AssemblyDescription("Library that provides the interfaces for implementing audio sources")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("AudioBand.AudioSource")]
@@ -35,4 +35,4 @@
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
-[assembly: InternalsVisibleTo("AudioBand")]
\ No newline at end of file
+[assembly: InternalsVisibleTo("AudioBand")]
diff --git a/src/AudioBand.AudioSource/RepeatMode.cs b/src/AudioBand.AudioSource/RepeatMode.cs
new file mode 100644
index 00000000..5096b77d
--- /dev/null
+++ b/src/AudioBand.AudioSource/RepeatMode.cs
@@ -0,0 +1,23 @@
+namespace AudioBand.AudioSource
+{
+ ///
+ /// Specifies the repeat mode of the audio source.
+ ///
+ public enum RepeatMode
+ {
+ ///
+ /// No repeat.
+ ///
+ Off,
+
+ ///
+ /// Repeat the current context.
+ ///
+ RepeatContext,
+
+ ///
+ /// Repeat the current track.
+ ///
+ RepeatTrack,
+ }
+}
diff --git a/src/AudioBand.AudioSource/SettingChangedEventArgs.cs b/src/AudioBand.AudioSource/SettingChangedEventArgs.cs
index 23309c7a..21e102bd 100644
--- a/src/AudioBand.AudioSource/SettingChangedEventArgs.cs
+++ b/src/AudioBand.AudioSource/SettingChangedEventArgs.cs
@@ -5,20 +5,22 @@ namespace AudioBand.AudioSource
///
/// Provides data for a event.
///
- public class SettingChangedEventArgs : EventArgs
+ [Serializable]
+ public sealed class SettingChangedEventArgs : EventArgs
{
///
- /// Name of the setting's property that changed.
+ /// Initializes a new instance of the class
+ /// with the setting's name.
///
- public string PropertyName { get; set; }
+ /// Name of the setting that changed.
+ public SettingChangedEventArgs(string settingName)
+ {
+ SettingName = settingName;
+ }
///
- /// Creates a new instance of a with the setting name.
+ /// Gets or sets the name of the setting's property that changed.
///
- /// Name of the setting's property that changed.
- public SettingChangedEventArgs(string propertyName)
- {
- PropertyName = propertyName;
- }
+ public string SettingName { get; set; }
}
}
diff --git a/src/AudioBand.AudioSource/SettingOptions.cs b/src/AudioBand.AudioSource/SettingOptions.cs
new file mode 100644
index 00000000..9f7360b4
--- /dev/null
+++ b/src/AudioBand.AudioSource/SettingOptions.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace AudioBand.AudioSource
+{
+ ///
+ /// Flags for audio source settings.
+ ///
+ [Flags]
+ public enum SettingOptions
+ {
+ ///
+ /// Setting is invisible to the user.
+ ///
+ Hidden = 1 << 0,
+
+ ///
+ /// Setting cannot be modified by the user.
+ ///
+ ReadOnly = 1 << 1,
+
+ ///
+ /// Indicates a sensitive setting such as a password, causing a warning to be given.
+ ///
+ Sensitive = 1 << 2,
+ }
+}
diff --git a/src/AudioBand.AudioSource/SettingValidationResult.cs b/src/AudioBand.AudioSource/SettingValidationResult.cs
deleted file mode 100644
index c7667df4..00000000
--- a/src/AudioBand.AudioSource/SettingValidationResult.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-namespace AudioBand.AudioSource
-{
- ///
- /// Class that represents the results of validation
- ///
- internal class SettingValidationResult
- {
- ///
- /// The value is valid
- ///
- public bool IsValid { get; }
-
- ///
- /// Associated error message if the value was not valid
- ///
- public string ErrorMessage { get; }
-
- public SettingValidationResult(bool isValid, string errorMessage = null)
- {
- IsValid = isValid;
- ErrorMessage = errorMessage;
- }
-
- public static SettingValidationResult Fail => new SettingValidationResult(false);
- public static SettingValidationResult Success => new SettingValidationResult(true);
- }
-}
diff --git a/src/AudioBand.AudioSource/TrackInfoChangedEventArgs.cs b/src/AudioBand.AudioSource/TrackInfoChangedEventArgs.cs
index 4882c4a5..d90a2edc 100644
--- a/src/AudioBand.AudioSource/TrackInfoChangedEventArgs.cs
+++ b/src/AudioBand.AudioSource/TrackInfoChangedEventArgs.cs
@@ -1,33 +1,86 @@
using System;
using System.Drawing;
+using System.Runtime.Serialization;
namespace AudioBand.AudioSource
{
- public class TrackInfoChangedEventArgs : EventArgs
+ ///
+ /// Event arguments for the event.
+ ///
+ [Serializable]
+ public sealed class TrackInfoChangedEventArgs : EventArgs, ISerializable
{
///
- /// Track Name
+ /// Initializes a new instance of the class.
+ ///
+ public TrackInfoChangedEventArgs()
+ {
+ // Empty
+ }
+
+ private TrackInfoChangedEventArgs(SerializationInfo info, StreamingContext context)
+ {
+ TrackName = info.GetString(nameof(TrackName));
+ Artist = info.GetString(nameof(Artist));
+ AlbumArt = ByteArrayToImage((byte[])info.GetValue(nameof(AlbumArt), typeof(byte[])));
+ Album = info.GetString(nameof(Album));
+ TrackLength = (TimeSpan)info.GetValue(nameof(TrackLength), typeof(TimeSpan));
+ }
+
+ ///
+ /// Gets or sets the track Name
///
public string TrackName { get; set; }
///
- /// Artist
+ /// Gets or sets the artist.
///
public string Artist { get; set; }
///
- /// Album art image. If null, a placeholder will be used
+ /// Gets or sets the album art image. If , a placeholder will be used.
///
public Image AlbumArt { get; set; }
///
- /// Album name
+ /// Gets or sets the album name.
///
public string Album { get; set; }
///
- /// Length of the track
+ /// Gets or sets the length of the track
///
public TimeSpan TrackLength { get; set; }
+
+ ///
+ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ info.AddValue(nameof(TrackName), TrackName);
+ info.AddValue(nameof(Artist), Artist);
+ info.AddValue(nameof(AlbumArt), ImageToByteArray(AlbumArt), typeof(byte[]));
+ info.AddValue(nameof(Album), Album);
+ info.AddValue(nameof(TrackLength), TrackLength, typeof(TimeSpan));
+ }
+
+ private static byte[] ImageToByteArray(Image image)
+ {
+ if (image == null)
+ {
+ return null;
+ }
+
+ var copy = new Bitmap(image);
+ return new ImageConverter().ConvertTo(copy, typeof(byte[])) as byte[];
+ }
+
+ private static Image ByteArrayToImage(byte[] data)
+ {
+ if (data == null)
+ {
+ return null;
+ }
+
+ return new ImageConverter().ConvertFrom(data) as Image;
+ }
}
}
diff --git a/src/AudioBand.AudioSource/TrackRating.cs b/src/AudioBand.AudioSource/TrackRating.cs
new file mode 100644
index 00000000..275af4b2
--- /dev/null
+++ b/src/AudioBand.AudioSource/TrackRating.cs
@@ -0,0 +1,23 @@
+namespace AudioBand.AudioSource
+{
+ ///
+ /// Options for the rating of a track.
+ ///
+ public enum TrackRating
+ {
+ ///
+ /// No rating.
+ ///
+ None,
+
+ ///
+ /// The track has a positive rating.
+ ///
+ Positive,
+
+ ///
+ /// The song has a negative rating.
+ ///
+ Negative
+ }
+}
diff --git a/src/AudioBand.AudioSource/content/AudioSource.manifest b/src/AudioBand.AudioSource/content/AudioSource.manifest
new file mode 100644
index 00000000..550c2b51
--- /dev/null
+++ b/src/AudioBand.AudioSource/content/AudioSource.manifest
@@ -0,0 +1 @@
+AudioSource = "ASSEMBLY_NAME_HERE.dll"
\ No newline at end of file
diff --git a/src/AudioBand.AudioSource/content/readme.txt b/src/AudioBand.AudioSource/content/readme.txt
new file mode 100644
index 00000000..5a5fa139
--- /dev/null
+++ b/src/AudioBand.AudioSource/content/readme.txt
@@ -0,0 +1,4 @@
+AudioBand.AudioSource readme
+
+1. Fill in the AudioSource.manifest file
+2. Deploy your audio source in a folder under the Audiosources folder of AudioBand. The folder should contain the manifest file and NOT contain this AudioBand.AudioSource dll
\ No newline at end of file
diff --git a/src/AudioBand.AudioSource/packages.config b/src/AudioBand.AudioSource/packages.config
new file mode 100644
index 00000000..119108d1
--- /dev/null
+++ b/src/AudioBand.AudioSource/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/AudioBand.Test/AudioBand.Test.csproj b/src/AudioBand.Test/AudioBand.Test.csproj
index 33eceff1..1035324d 100644
--- a/src/AudioBand.Test/AudioBand.Test.csproj
+++ b/src/AudioBand.Test/AudioBand.Test.csproj
@@ -1,6 +1,6 @@
-
+
Debug
@@ -29,6 +29,8 @@
DEBUG;TRACE
prompt
4
+
+
pdbonly
@@ -37,35 +39,41 @@
TRACE
prompt
4
+
+
..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll
- ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll
+ ..\packages\MSTest.TestFramework.1.4.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll
- ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll
+ ..\packages\MSTest.TestFramework.1.4.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll
- ..\packages\Moq.4.10.0\lib\net45\Moq.dll
+ ..\packages\Moq.4.10.1\lib\net45\Moq.dll
-
- ..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll
+
+ ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll
-
- ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll
+
+ ..\packages\System.Threading.Tasks.Extensions.4.5.2\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll
+
+
+ ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll
+
@@ -74,6 +82,10 @@
+
+ {30F2BFEA-788A-494D-88E7-F2070528EBEA}
+ AudioBand.AudioSource
+
{b69832ad-8373-47ac-a52a-183238903896}
AudioBand
@@ -85,8 +97,8 @@
This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
+
+
-
+
\ No newline at end of file
diff --git a/src/AudioBand.Test/SettingsMapping.cs b/src/AudioBand.Test/SettingsMapping.cs
index 8f690867..aa1e911b 100644
--- a/src/AudioBand.Test/SettingsMapping.cs
+++ b/src/AudioBand.Test/SettingsMapping.cs
@@ -4,7 +4,7 @@
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
-using AudioBand.Settings.Models.v2;
+using AudioBand.Settings.Models.V2;
using AudioBand.Settings;
using AudioBand.Models;
diff --git a/src/AudioBand.Test/SettingsMigrationTests.cs b/src/AudioBand.Test/SettingsMigrationTests.cs
index 7c8aa138..ed24a14a 100644
--- a/src/AudioBand.Test/SettingsMigrationTests.cs
+++ b/src/AudioBand.Test/SettingsMigrationTests.cs
@@ -2,11 +2,11 @@
using System.Drawing;
using AudioBand.Models;
using AudioBand.Settings.Migrations;
-using AudioBand.Settings.Models.v1;
-using V1Settings = AudioBand.Settings.Models.v1.AudioBandSettings;
-using V2Settings = AudioBand.Settings.Models.v2.Settings;
+using AudioBand.Settings.Models.V1;
+using V1Settings = AudioBand.Settings.Models.V1.AudioBandSettings;
+using V2Settings = AudioBand.Settings.Models.V2.Settings;
using Microsoft.VisualStudio.TestTools.UnitTesting;
-using AudioSourceSetting = AudioBand.Settings.Models.v1.AudioSourceSetting;
+using AudioSourceSetting = AudioBand.Settings.Models.V1.AudioSourceSetting;
namespace AudioBand.Test
{
diff --git a/src/AudioBand.Test/SettingsViewModelsTests.cs b/src/AudioBand.Test/SettingsViewModelsTests.cs
new file mode 100644
index 00000000..d385036f
--- /dev/null
+++ b/src/AudioBand.Test/SettingsViewModelsTests.cs
@@ -0,0 +1,133 @@
+using AudioBand.AudioSource;
+using AudioBand.Models;
+using AudioBand.ViewModels;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AudioBand.Test
+{
+ [TestClass]
+ public class SettingsViewModelsTests
+ {
+ private Mock _audioSourceMock;
+
+
+ [TestInitialize]
+ public void Init()
+ {
+ _audioSourceMock = new Mock();
+ }
+
+ [TestMethod]
+ public void NoMatchingSettings()
+ {
+ _audioSourceMock.SetupGet(s => s.Settings).Returns(new List());
+
+ var name = "test";
+ var keyVals = new List
+ {
+ new AudioSourceSetting { Name = "key1", Value = "val1" },
+ new AudioSourceSetting { Name = "key2", Value = "val2" }
+ };
+ var settings = new AudioSourceSettings { AudioSourceName = name, Settings = keyVals };
+
+ var vm = new AudioSourceSettingsVM(settings, _audioSourceMock.Object);
+
+ Assert.AreEqual(0, vm.Settings.Count);
+ Assert.AreEqual(name, vm.AudioSourceName);
+ }
+
+ [TestMethod]
+ public void MatchingSettingsShouldCreateVmsInOrder()
+ {
+ var setting1 = "Setting1";
+ var setting2 = "setting2";
+ object val1 = "val1";
+ object val2 = 2;
+
+ _audioSourceMock.SetupGet(s => s.Settings).Returns(new List
+ {
+ new AudioSourceSettingAttribute(setting1),
+ new AudioSourceSettingAttribute(setting2),
+ });
+
+ _audioSourceMock.SetupGet(s => s[It.Is(x => x == setting1)]).Returns(val1);
+ _audioSourceMock.SetupGet(s => s[It.Is(x => x == setting2)]).Returns(val2);
+
+ var settingModels = new List
+ {
+ new AudioSourceSetting { Name = setting1, Value = val1 },
+ new AudioSourceSetting { Name = setting2, Value = val2 }
+ };
+
+ var settings = new AudioSourceSettings { Settings = settingModels };
+ var vm = new AudioSourceSettingsVM(settings, _audioSourceMock.Object);
+
+ Assert.AreEqual(settingModels.Count, vm.Settings.Count);
+ Assert.AreEqual(settingModels[0].Name, vm.Settings[0].Name);
+ Assert.AreEqual(settingModels[1].Name, vm.Settings[1].Name);
+
+ Assert.AreEqual(settingModels[0].Value, vm.Settings[0].Value);
+ Assert.AreEqual(settingModels[1].Value, vm.Settings[1].Value);
+ }
+
+ [TestMethod]
+ public void AudioSourceSettingUpdatesAreHandled()
+ {
+ var setting = "setting";
+
+ _audioSourceMock.SetupGet(s => s.Settings).Returns(new List { new AudioSourceSettingAttribute(setting) });
+ var settingModel = new AudioSourceSetting { Name = setting };
+ var settings = new AudioSourceSettings
+ {
+ Settings = new List { settingModel }
+ };
+
+ var vm = new AudioSourceSettingsVM(settings, _audioSourceMock.Object);
+
+ object newSettingValue = 1;
+ _audioSourceMock.SetupGet(s => s[It.Is(x => x == setting)]).Returns(newSettingValue);
+ _audioSourceMock.Raise(s => s.SettingChanged += null, new SettingChangedEventArgs(setting));
+
+ Assert.AreEqual(settingModel.Value, newSettingValue);
+ }
+
+ [TestMethod]
+ public void AudioSourceSettingsCalledInPriority()
+ {
+ var setting1 = new AudioSourceSettingAttribute("test1") { Priority = 10 };
+ var setting2 = new AudioSourceSettingAttribute("test2") { Priority = 5 };
+ var setting3 = new AudioSourceSettingAttribute("test3") { Priority = 20 };
+
+ _audioSourceMock.SetupAllProperties();
+ _audioSourceMock.SetupGet(m => m.Settings).Returns(new List { setting1, setting2, setting3 });
+
+ var settings = new AudioSourceSettings
+ {
+ Settings = new List
+ {
+ new AudioSourceSetting {Name = setting1.Name},
+ new AudioSourceSetting {Name = setting2.Name},
+ new AudioSourceSetting {Name = setting3.Name},
+ }
+ };
+
+ var s = new MockSequence();
+ _audioSourceMock.InSequence(s).SetupSet(source => source[It.Is(x => x == setting3.Name)] = null);
+ _audioSourceMock.InSequence(s).SetupSet(source => source[It.Is(x => x == setting1.Name)] = null);
+ _audioSourceMock.InSequence(s).SetupSet(source => source[It.Is(x => x == setting2.Name)] = null);
+
+ var vm = new AudioSourceSettingsVM(settings, _audioSourceMock.Object);
+
+ _audioSourceMock.VerifySet(source => source[setting3.Name] = null);
+ _audioSourceMock.VerifySet(source => source[setting1.Name] = null);
+ _audioSourceMock.VerifySet(source => source[setting2.Name] = null);
+ }
+ }
+}
diff --git a/src/AudioBand.Test/app.config b/src/AudioBand.Test/app.config
index a18c8d72..71eae980 100644
--- a/src/AudioBand.Test/app.config
+++ b/src/AudioBand.Test/app.config
@@ -4,7 +4,15 @@
-
+
+
+
+
+
+
+
+
+
diff --git a/src/AudioBand.Test/packages.config b/src/AudioBand.Test/packages.config
index 06c17081..173da68a 100644
--- a/src/AudioBand.Test/packages.config
+++ b/src/AudioBand.Test/packages.config
@@ -1,9 +1,10 @@
-
-
-
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AudioBand.sln b/src/AudioBand.sln
index 87a0d331..dfe6926e 100644
--- a/src/AudioBand.sln
+++ b/src/AudioBand.sln
@@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iTunesAudioSource", "iTunes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MusicBeeAudioSource", "MusicBeeAudioSource\MusicBeeAudioSource.csproj", "{741DB79C-921D-4D91-85F1-CD10C746F46E}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AudioSourceHost", "AudioSourceHost\AudioSourceHost.csproj", "{D3F92C3E-E546-4A6B-ADA2-FACD95E229F7}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -45,6 +47,10 @@ Global
{741DB79C-921D-4D91-85F1-CD10C746F46E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{741DB79C-921D-4D91-85F1-CD10C746F46E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{741DB79C-921D-4D91-85F1-CD10C746F46E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D3F92C3E-E546-4A6B-ADA2-FACD95E229F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D3F92C3E-E546-4A6B-ADA2-FACD95E229F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D3F92C3E-E546-4A6B-ADA2-FACD95E229F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D3F92C3E-E546-4A6B-ADA2-FACD95E229F7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/AudioBand/AudioBand.csproj b/src/AudioBand/AudioBand.csproj
index 227a1ef1..f497f803 100644
--- a/src/AudioBand/AudioBand.csproj
+++ b/src/AudioBand/AudioBand.csproj
@@ -22,6 +22,9 @@
DEBUG;TRACE
prompt
4
+ ..\AudioBandRules.ruleset
+ true
+ bin\Debug\AudioBand.xml
pdbonly
@@ -31,6 +34,7 @@
prompt
4
.none
+ ..\AudioBandRules.ruleset
@@ -58,18 +62,17 @@
..\packages\MahApps.Metro.IconPacks.Material.2.3.0\lib\net46\MahApps.Metro.IconPacks.Material.dll
- ..\packages\Nett.0.10.0\lib\Net40\Nett.dll
+ ..\packages\Nett.0.10.1\lib\Net40\Nett.dll
..\packages\NLog.4.5.11\lib\net45\NLog.dll
-
- ..\packages\Svg.2.3.0\lib\net35\Svg.dll
+
+ ..\packages\Svg.2.4.1\lib\Svg.dll
-
@@ -78,7 +81,6 @@
-
..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll
@@ -97,15 +99,17 @@
-
+
+
-
+
+
UserControl
MainControl.cs
@@ -146,20 +150,21 @@
+
+
+
-
-
+
Component
-
@@ -173,6 +178,7 @@
AudioSourceSettingsView.xaml
+
@@ -210,8 +216,8 @@
+
-
@@ -233,7 +239,7 @@
Component
-
+
True
@@ -257,11 +263,17 @@
+
+ stylecop.json
+
UserControl
MainControl.cs
+
+ PreserveNewest
+
@@ -286,6 +298,10 @@
{30f2bfea-788a-494d-88e7-f2070528ebea}
AudioBand.AudioSource
+
+ {D3F92C3E-E546-4A6B-ADA2-FACD95E229F7}
+ AudioSourceHost
+
@@ -349,7 +365,10 @@
MSBuild:Compile
-
+
+
+
+
if "$(ConfigurationName)" == "Debug" (
diff --git a/src/AudioBand/AudioSource/AudioSourceLoader.cs b/src/AudioBand/AudioSource/AudioSourceLoader.cs
deleted file mode 100644
index 5f65c816..00000000
--- a/src/AudioBand/AudioSource/AudioSourceLoader.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.Composition;
-using System.ComponentModel.Composition.Hosting;
-using System.IO;
-using System.Linq;
-using Nett;
-using NLog;
-
-namespace AudioBand.AudioSource
-{
- internal class AudioSourceLoader
- {
- [ImportMany(AllowRecomposition = true)]
- public IEnumerable AudioSources { get; private set; } = new List();
-
- public event EventHandler AudioSourcesChanged;
-
- private const string PluginFolderName = "AudioSources";
- private const string ManifestFileName = "AudioSource.manifest";
- private static readonly string PluginFolderPath = Path.Combine(DirectoryHelper.BaseDirectory, PluginFolderName);
- private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
-
- public void LoadAudioSources()
- {
- Logger.Debug($"Searching for audio sources in path `{PluginFolderPath}`");
-
- var catalog = new AggregateCatalog();
- var directories = Directory.EnumerateDirectories(PluginFolderPath, "*", SearchOption.TopDirectoryOnly);
-
- foreach (var directory in directories)
- {
- Logger.Debug($"Searching in {directory} for {ManifestFileName}");
-
- var manifest = Directory.GetFiles(directory, ManifestFileName, SearchOption.TopDirectoryOnly).FirstOrDefault();
- if (manifest == null)
- {
- continue;
- }
-
- var manifestData = Toml.ReadFile(manifest);
- Logger.Debug($"Reading {manifest}. Audio sources: [{string.Join("," , manifestData.AudioSources)}]");
-
- foreach (var audioSourceFileName in manifestData.AudioSources)
- {
- var audioSourcePath = Path.Combine(directory, audioSourceFileName);
- catalog.Catalogs.Add(new AssemblyCatalog(audioSourcePath));
- }
- }
-
- var container = new CompositionContainer(catalog);
- container.ComposeParts(this);
-
- foreach (var audioSource in AudioSources)
- {
- audioSource.Logger = new AudioSourceLogger(audioSource.Name);
- }
- }
-
- private class Manifest
- {
- public List AudioSources { get; set; }
- }
- }
-}
diff --git a/src/AudioBand/AudioSource/AudioSourceManager.cs b/src/AudioBand/AudioSource/AudioSourceManager.cs
new file mode 100644
index 00000000..129a080f
--- /dev/null
+++ b/src/AudioBand/AudioSource/AudioSourceManager.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using NLog;
+
+namespace AudioBand.AudioSource
+{
+ ///
+ /// Detects and loads audio sources.
+ ///
+ internal class AudioSourceManager
+ {
+ private const string PluginFolderName = "AudioSources";
+ private static readonly string PluginFolderPath = Path.Combine(DirectoryHelper.BaseDirectory, PluginFolderName);
+ private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
+ private static readonly object _audioSourcesLock = new object();
+ private readonly List _uninitializedProxies = new List();
+
+ ///
+ /// Gets the list of audio sources available.
+ ///
+ public ObservableCollection AudioSources { get; private set; } = new ObservableCollection();
+
+ ///
+ /// Load all audio sources.
+ ///
+ public void LoadAudioSources()
+ {
+ Logger.Debug("Loading audio sources");
+ foreach (var dir in Directory.EnumerateDirectories(PluginFolderPath))
+ {
+ try
+ {
+ var proxy = new AudioSourceProxy(dir);
+ AudioSources.Add(proxy);
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, $"Error creating proxy for {dir}");
+ }
+ }
+ }
+ }
+}
diff --git a/src/AudioBand/AudioSource/AudioSourceProxy.cs b/src/AudioBand/AudioSource/AudioSourceProxy.cs
new file mode 100644
index 00000000..160a9316
--- /dev/null
+++ b/src/AudioBand/AudioSource/AudioSourceProxy.cs
@@ -0,0 +1,355 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using AudioSourceHost;
+using NLog;
+
+namespace AudioBand.AudioSource
+{
+ ///
+ /// Proxy class for an IAudioSource in another process.
+ ///
+ internal class AudioSourceProxy : IInternalAudioSource
+ {
+ private readonly ILogger _logger;
+ private readonly object _isActivatedLock = new object();
+ private readonly Dictionary _settingsCache = new Dictionary();
+ private readonly string _directory;
+ private bool _isActivated;
+ private AppDomain _appDomain;
+ private AudioSourceWrapper _wrapper;
+
+ ///
+ /// Initializes a new instance of the class
+ /// with the directory and the host service.
+ ///
+ /// The audio source directory.
+ public AudioSourceProxy(string directory)
+ {
+ _directory = directory;
+ var directoryName = new DirectoryInfo(directory).Name;
+ _logger = LogManager.GetLogger($"AudioSourceProxy({directoryName})");
+
+ AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
+
+ var domainSetupInfo = new AppDomainSetup
+ {
+ ApplicationName = $"AudioSourceHost({directoryName})",
+ ApplicationBase = DirectoryHelper.BaseDirectory,
+ };
+
+ _appDomain = AppDomain.CreateDomain(directoryName, null, domainSetupInfo);
+ CreateWrapper();
+ }
+
+ ///
+ public event EventHandler SettingChanged;
+
+ ///
+ public event EventHandler TrackInfoChanged;
+
+ ///
+ public event EventHandler IsPlayingChanged;
+
+ ///
+ public event EventHandler TrackProgressChanged;
+
+ ///
+ public event EventHandler VolumeChanged;
+
+ ///
+ public event EventHandler ShuffleChanged;
+
+ ///
+ public event EventHandler RepeatModeChanged;
+
+ ///
+ public string Name
+ {
+ get
+ {
+ try
+ {
+ return _wrapper.Name;
+ }
+ catch (Exception e)
+ {
+ _logger.Error(e, "Error trying to get the name");
+ throw;
+ }
+ }
+ }
+
+ ///
+ public IAudioSourceLogger Logger { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
+
+ ///
+ /// Gets the settings that the audio source has.
+ ///
+ public List Settings { get; private set; } = new List();
+
+ private bool IsActivated
+ {
+ get
+ {
+ lock (_isActivatedLock)
+ {
+ return _isActivated;
+ }
+ }
+
+ set
+ {
+ lock (_isActivatedLock)
+ {
+ _isActivated = value;
+ }
+ }
+ }
+
+ ///
+ /// Get or set a setting.
+ ///
+ /// Name of the setting
+ /// The setting value.
+ public object this[string settingName]
+ {
+ get
+ {
+ try
+ {
+ var settingValue = _wrapper.GetSettingValue(settingName);
+ _settingsCache[settingName] = settingValue;
+ return settingValue;
+ }
+ catch (Exception e)
+ {
+ _logger.Error(e, "Error trying to get setting.");
+ throw;
+ }
+ }
+
+ set
+ {
+ try
+ {
+ _settingsCache[settingName] = value;
+ _wrapper.UpdateSetting(settingName, value);
+ }
+ catch (Exception e)
+ {
+ _logger.Error(e, "Error tring to update setting.");
+ throw;
+ }
+ }
+ }
+
+ ///
+ public async Task ActivateAsync()
+ {
+ Debug.Assert(!IsActivated, "Audio source already activated");
+ if (await CallWrapperAsync(_wrapper.Activate))
+ {
+ IsActivated = true;
+ }
+ else
+ {
+ throw new InvalidOperationException("Unable to activate audiosource");
+ }
+ }
+
+ ///
+ public async Task DeactivateAsync()
+ {
+ Debug.Assert(IsActivated, "Audio source is not activated");
+ await CallWrapperAsync(_wrapper.Deactivate);
+ IsActivated = false;
+ }
+
+ ///
+ public async Task NextTrackAsync()
+ {
+ if (!IsActivated)
+ {
+ return;
+ }
+
+ await CallWrapperAsync(_wrapper.NextTrack);
+ }
+
+ ///
+ public async Task PauseTrackAsync()
+ {
+ if (!IsActivated)
+ {
+ return;
+ }
+
+ await CallWrapperAsync(_wrapper.PauseTrack);
+ }
+
+ ///
+ public async Task PlayTrackAsync()
+ {
+ if (!IsActivated)
+ {
+ return;
+ }
+
+ await CallWrapperAsync(_wrapper.PlayTrack);
+ }
+
+ ///
+ public async Task PreviousTrackAsync()
+ {
+ if (!IsActivated)
+ {
+ return;
+ }
+
+ await CallWrapperAsync(_wrapper.PreviousTrack);
+ }
+
+ ///
+ public async Task SetVolumeAsync(float newVolume)
+ {
+ if (!IsActivated)
+ {
+ return;
+ }
+
+ await CallWrapperAsync(_wrapper.SetVolume, newVolume);
+ }
+
+ ///
+ public async Task SetPlaybackProgressAsync(TimeSpan newProgress)
+ {
+ if (!IsActivated)
+ {
+ return;
+ }
+
+ await CallWrapperAsync(_wrapper.SetPlayback, newProgress);
+ }
+
+ ///
+ public async Task SetShuffleAsync(bool shuffleOn)
+ {
+ if (!IsActivated)
+ {
+ return;
+ }
+
+ await CallWrapperAsync(_wrapper.SetShuffle, shuffleOn);
+ }
+
+ ///
+ public async Task SetRepeatModeAsync(RepeatMode newRepeatMode)
+ {
+ if (!IsActivated)
+ {
+ return;
+ }
+
+ await CallWrapperAsync(_wrapper.SetRepeatMode, newRepeatMode);
+ }
+
+ ///
+ public Type GetSettingType(string settingName)
+ {
+ return _wrapper.GetSettingType(settingName);
+ }
+
+ // Use load from context
+ private static Assembly AssemblyResolve(object sender, ResolveEventArgs e)
+ {
+ string shortName = e.Name.Substring(0, e.Name.IndexOf(','));
+ string fileName = Path.Combine(DirectoryHelper.BaseDirectory, shortName + ".dll");
+ return File.Exists(fileName) ? Assembly.LoadFrom(fileName) : null;
+ }
+
+ private async Task Restart()
+ {
+ // Assuming this won't throw because it was created previously without errors
+ CreateWrapper();
+
+ // re-activate if the host was restarted
+ if (IsActivated)
+ {
+ // If activation fails then don't auto restart otherwise it will be a loop
+ await CallWrapperAsync(_wrapper.Activate, autoRestart: false);
+ }
+ }
+
+ private void LoadAudioSourceSettings()
+ {
+ try
+ {
+ Settings = _wrapper.Settings;
+
+ // apply the settings again, if host restarted, the cache should be populated
+ foreach (var keyVal in _settingsCache)
+ {
+ _wrapper.UpdateSetting(keyVal.Key, keyVal.Value);
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.Error(e, "Error loading settings");
+ }
+ }
+
+ private void CreateWrapper()
+ {
+ var wrapperType = typeof(AudioSourceWrapper);
+ var dllPath = Path.Combine(DirectoryHelper.BaseDirectory, wrapperType.Assembly.GetName().Name + ".dll");
+ _wrapper = (AudioSourceWrapper)_appDomain.CreateInstanceFromAndUnwrap(dllPath, wrapperType.FullName);
+
+ if (!_wrapper.Initialize(_directory))
+ {
+ throw new InvalidOperationException("Unable to initialize wrapper");
+ }
+
+ _wrapper.SettingChanged += new MarshaledEventHandler(e => SettingChanged?.Invoke(this, e)).Handler;
+ _wrapper.TrackInfoChanged += new MarshaledEventHandler(e => TrackInfoChanged?.Invoke(this, e)).Handler;
+ _wrapper.IsPlayingChanged += new MarshaledEventHandler(e => IsPlayingChanged?.Invoke(this, e)).Handler;
+ _wrapper.TrackProgressChanged += new MarshaledEventHandler(e => TrackProgressChanged?.Invoke(this, e)).Handler;
+ _wrapper.VolumeChanged += new MarshaledEventHandler(e => VolumeChanged?.Invoke(this, e)).Handler;
+ _wrapper.ShuffleChanged += new MarshaledEventHandler(e => ShuffleChanged?.Invoke(this, e)).Handler;
+ _wrapper.RepeatModeChanged += new MarshaledEventHandler(e => RepeatModeChanged?.Invoke(this, e)).Handler;
+
+ LoadAudioSourceSettings();
+ }
+
+ private async Task CallWrapperAsync(Action wrapperAction, bool autoRestart = true)
+ {
+ try
+ {
+ var tcs = new MarshaledTaskCompletionSource();
+ wrapperAction(tcs);
+
+ await tcs.Task.ConfigureAwait(false);
+ return true;
+ }
+ catch (Exception e)
+ {
+ _logger.Error(e, $"Unexpected error when calling the wrapper, recreating wrapper");
+ if (autoRestart)
+ {
+ await Restart();
+ }
+
+ return false;
+ }
+ }
+
+ private async Task CallWrapperAsync(Action wrapperAction, TArg arg, bool autoRestart = true)
+ {
+ return await CallWrapperAsync((tcs) => wrapperAction(arg, tcs), autoRestart);
+ }
+ }
+}
diff --git a/src/AudioBand/AudioSource/AudioSourceSettingInfo.cs b/src/AudioBand/AudioSource/AudioSourceSettingInfo.cs
deleted file mode 100644
index ab3a79a4..00000000
--- a/src/AudioBand/AudioSource/AudioSourceSettingInfo.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-using System;
-using System.ComponentModel;
-using System.Reflection;
-using NLog;
-
-namespace AudioBand.AudioSource
-{
- ///
- /// Contains information for an audio source setting
- ///
- internal class AudioSourceSettingInfo
- {
- private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
-
- ///
- /// Audio source that this setting belongs to
- ///
- public IAudioSource Source { get; }
-
- ///
- /// Property that the is applied to.
- ///
- public PropertyInfo Property { get; }
-
- ///
- /// attached to the property.
- ///
- public AudioSourceSettingAttribute Attribute { get; }
-
- ///
- /// Type of the property.
- ///
- public Type PropertyType { get; }
-
- public AudioSourceSettingInfo(IAudioSource source, PropertyInfo property, AudioSourceSettingAttribute attribute)
- {
- Source = source;
- Property = property;
- Attribute = attribute;
- PropertyType = property.PropertyType;
- }
-
- ///
- /// Validate a value for this setting.
- ///
- /// Value to validate.
- /// Result of validation.
- public SettingValidationResult ValidateSetting(object value)
- {
- var validationMethodName = Attribute.ValidatorName;
- if (validationMethodName == null)
- {
- return new SettingValidationResult(true);
- }
-
- var method = Source.GetType().GetMethod(validationMethodName);
- if (method == null)
- {
- Logger.Error($"Validation method {validationMethodName} not found for {Source}");
- return new SettingValidationResult(false, "Error with validation. See log for more details.");
- }
-
- return (SettingValidationResult)method.Invoke(Source, new[] { value, Property.Name });
- }
-
-
- ///
- /// Updates the audio source with the new value.
- ///
- /// New value.
- public void UpdateAudioSource(object value)
- {
- try
- {
- var newValue = ConvertToSettingType(value);
- Property.SetValue(Source, newValue);
- }
- catch (Exception e)
- {
- Logger.Error($"Error occured while change audio source settings. value: {value}, Exception: {e}");
- throw;
- }
- }
-
- ///
- /// Gets the current setting value from the audio source.
- ///
- ///
- public object GetValue()
- {
- return Property.GetValue(Source);
- }
-
- ///
- /// Convert the value to the type of the setting.
- ///
- /// Value to convert
- /// The value converted to the type of the setting.
- public object ConvertToSettingType(object value)
- {
- if (value != null && value.GetType() != PropertyType)
- {
- var converter = TypeDescriptor.GetConverter(value.GetType());
- if (converter.CanConvertTo(PropertyType))
- {
- return converter.ConvertTo(value, PropertyType);
- }
-
- if (TryChangeType(value, out var converted))
- {
- return converted;
- }
-
- Logger.Error($"Unable to convert value to the setting's type. setting: `{Attribute.Name}` using value `{value}`. Setting type: `{PropertyType}`, value type: `{value.GetType()}`");
- throw new ArgumentException();
- }
-
- return value;
- }
-
- private bool TryChangeType(object value, out object converted)
- {
- try
- {
- converted = Convert.ChangeType(value, PropertyType);
- return true;
- }
- catch (Exception e)
- {
- Logger.Error(e);
- converted = null;
- return false;
- }
- }
- }
-}
diff --git a/src/AudioBand/AudioSource/IInternalAudioSource.cs b/src/AudioBand/AudioSource/IInternalAudioSource.cs
new file mode 100644
index 00000000..07e83ae2
--- /dev/null
+++ b/src/AudioBand/AudioSource/IInternalAudioSource.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+
+namespace AudioBand.AudioSource
+{
+ ///
+ /// Internal extension to that exposes additional methods.
+ ///
+ internal interface IInternalAudioSource : IAudioSource
+ {
+ ///
+ /// Gets the settings that the audio source exposes.
+ ///
+ List Settings { get; }
+
+ ///
+ /// Gets or sets a setting.
+ ///
+ /// The name of the setting.
+ /// The value of the setting.
+ object this[string settingName] { get; set; }
+
+ ///
+ /// Gets the type of the setting.
+ ///
+ /// Name of the setting.
+ /// The type of the setting.
+ Type GetSettingType(string settingName);
+ }
+}
diff --git a/src/AudioBand/Commands/AsyncRelayCommand.cs b/src/AudioBand/Commands/AsyncRelayCommand.cs
index 23720310..35f011f0 100644
--- a/src/AudioBand/Commands/AsyncRelayCommand.cs
+++ b/src/AudioBand/Commands/AsyncRelayCommand.cs
@@ -7,20 +7,23 @@ namespace AudioBand.Commands
///
/// Basic async command.
///
- ///
+ /// Type of the command parameter.
internal class AsyncRelayCommand : ICommand
{
private readonly Func _execute;
private readonly Predicate _canExecute;
///
- /// Create an instance of with a delegate to execute.
+ /// Initializes a new instance of the class
+ /// with a delegate to be executed.
///
/// Action to execute.
- public AsyncRelayCommand(Func execute) : this(execute, null) { }
+ public AsyncRelayCommand(Func execute)
+ : this(execute, null) { }
///
- /// Create an instace of with a delegate to execute and a predicate to determine if it can be executed.
+ /// Initializes a new instance of the class
+ /// with a delegate to be executed and a predicate to determine if it can be executed.
///
/// Async delegate to execute.
/// Predicate to check if the commmand can execute.
@@ -30,12 +33,6 @@ public AsyncRelayCommand(Func execute, Predicate canExecute)
_canExecute = canExecute;
}
- ///
- public bool CanExecute(object parameter)
- {
- return _canExecute?.Invoke((T)parameter) ?? true;
- }
-
///
public event EventHandler CanExecuteChanged
{
@@ -43,6 +40,12 @@ public event EventHandler CanExecuteChanged
remove => CommandManager.RequerySuggested -= value;
}
+ ///
+ public bool CanExecute(object parameter)
+ {
+ return _canExecute?.Invoke((T)parameter) ?? true;
+ }
+
///
public async void Execute(object parameter)
{
diff --git a/src/AudioBand/Commands/RelayCommand.cs b/src/AudioBand/Commands/RelayCommand.cs
index 3554c235..744f6a2d 100644
--- a/src/AudioBand/Commands/RelayCommand.cs
+++ b/src/AudioBand/Commands/RelayCommand.cs
@@ -1,65 +1,25 @@
using System;
-using System.Windows.Input;
namespace AudioBand.Commands
{
- ///
- public class RelayCommand : ICommand
- {
- private readonly Action _execute;
- private readonly Predicate _canExecute;
-
- ///
- /// Create an instance of with a delegate to execute.
- ///
- /// Action to execute.
- public RelayCommand(Action execute) : this(execute, null) {}
-
- ///
- /// Create an instace of with a delegate to execute and a predicate to determine if it can be executed.
- ///
- /// Action to execute.
- /// Predicate to check if the commmand can be execute.
- public RelayCommand(Action execute, Predicate canExecute)
- {
- _execute = execute ?? throw new ArgumentNullException(nameof(execute));
- _canExecute = canExecute;
- }
-
- ///
- public bool CanExecute(object parameter)
- {
- return _canExecute?.Invoke((T)parameter) ?? true;
- }
-
- ///
- public event EventHandler CanExecuteChanged
- {
- add => CommandManager.RequerySuggested += value;
- remove => CommandManager.RequerySuggested -= value;
- }
-
- ///
- public void Execute(object parameter)
- {
- _execute((T)parameter);
- }
- }
-
///
public class RelayCommand : RelayCommand