I totally like the idea of making API.
What about me, the only thing I want to do with the API - to create my own simplififed skin for listening with ability of switching to the full skin (MusicBee has a lot of functions, but many of them are not for everyday use).
The problem is the entry level. Markup languages like xml are enough for creating skins and also much simplier than C++ or VB. So I hope that knowledge of xml would be enough for making skins.
for 2, can you give a specific example you have in mindI try to develop small plugin for coping one tag to another and for swapping 2 tags for selected files. Suppose that the only sense to use such plugin is to invoke its functions from menu/hotkey/toolbar button (mostly important is menu).
I'm probably just too blind, but is there a complete documentation for the API somewhere? Such as what gets fired when, what the more obscure API calls actually do, and so on?Download code examples at the beginning of this topic. Examples contain MusicBeeInterface.cs (and for other languages too) with full API, but its mostly self-documenting (using meaningful names) and almost without comments.
Download code examples at the beginning of this topic.Examples are great, but this is not what I'd call documentation... besides, it should be kinda obvious that I've looked at these files before, no? :-)
I'm not aware that it's even possible to handle direct Post messages in C# (Windows' window messages are handled deep inside the Windows Forms library)...Do you mean from my (autohotkey) side? Since they do make it possible to send post messages to any process or window. (Dunno if they used C# or not.)
for a working open source plugin example seeIs there any way the API or functions of musicbee directly can be called via COM or Dll Call or Windows PostMessage?
http://emonic.sourceforge.net/html/usage.html - Emonic is a C#/.NET 2.0 plugin for Eclipse, the link explains how it's set up.Finally, after many hours, I got it to work and the (example) plugin works in MB! Eureka!
Sure, but how do I write html on this forum? I'd prefer not to do everything in html and BBcodeAPI Docs, HowTo's etc.. (http://getmusicbee.com/forum/index.php?topic=3864.0) <- Like this OK?
api.TrackChanged += (MusicBeeEventArgs args) => { Console.WriteLine(args.RelatedFile.AlbumArtist); };
public void ReceiveNotification(string sourceFileUrl, NotificationType type) {
switch(type) {
case NotificationType.TrackChanged:
Console.WriteLine(mbApiInterface.Library_GetFileTag(relatedFile, MetaDataType.AlbumArtist);
break;
}
}
Would something like this make life easier for you, R U Bn?It would! Great! Reminds me of AS 3 (which follows the ECMA standard). More developer-friendly.
using System;
using System.Windows.Forms;
using MusicBeePlugin;
namespace ExamplePlugin
{
[PluginFrontend]
class Example : IPluginFrontend
{
void IPluginFrontend.Initialize(IMusicBeeAPI api, out int configPanelHeight) {
configPanelHeight = 0;
api.PluginStartup += PluginStartup;
}
private void PluginStartup(NotificationEventArgs args) {
args.API.TrackChanged += TrackChanged;
}
private void TrackChanged(NotificationEventArgs args) {
Console.WriteLine(args.RelatedFile.TrackTitle);
}
bool IPluginFrontend.PluginConfigure(Panel configPanel) {
return false;
}
void IDisposable.Dispose() { }
}
}
there is already TrackChanged so you could grab the tags at that pointMB changes play count and skip count in very smart way. This tags may be changed in the middle of track playback.
but i will add a PlayCountersChanged event (which includes SkipCount changing)Thanks.
1) its not a good idea to access data in the form controls from a background threadThey weren't background threads initially! They became such in latest beta version. :)
If you mean double clicking then it sounds the same issue as 2No, I meant even single-clicking.
2) double clicking on status bar tries to open the parent form. I'm not sure what its doing as i dont provide any way for you to pass the parent form so i need to check that out.You can get object of delegate via delegate.Target, but you should implement some common class for background processes (maybe Form or subclass of Form). Or Mb should ignore double-clicking on statusbar at least (but certainly shouldn't hang up)
To handle the user clicking X you can capture the ThreadAbortException and do the cleanup as appropriateThanks. I'll probably try this but I'm mostly feel uncomfortable about terminating thread than actually need to do some cleanup.
i dont understand what you mean by "second click"Click twice (probably with some delay) on 'save' button (in docked tag editor) or click 'save' button and reopen floating tag editor and click 'save' again.
it should be sending TagsChanging immediately when you click save in the tag editor. There isnt any logic to determine if any tags actually changed or not.Yes, its quite right for TagsChanging event, but I spoke about TagsChanged in this case. BTW I can grab only not yet changed tags on TagsChanging event, so it's useless in my case.
if the file is not playing the TagsChanged should be immediate, otherwise if the file is being played then when the song finishesI said that I have no problem with tracks that are not played when tags are changed. Please reread first part of my previous post about currently played tracks.
i've done a very simple test using the API and i am finding the TagsChanging/TagsChanged messages are consistent with the above. Could you give me some steps to follow via your plugin so i can reproduce this?I'm doubtful that you want to test my plugin. This is simple modification of your example plugin (http://www.mediafire.com/?udlknhaohve0rjk) and this is source code (c#) (http://www.mediafire.com/?58mc4ne1mbumcb1) for it. This plugin just shows message box with 'Track title' value displayed if it receives TagsChanged notification. Please repeat steps 1-2 from my previous post with this plugin enabled.
What i could do is ensure the cached values are made available from the API as soon as TagsChanging is generatedThat's what I really would want.
can you confirm its not working when clicking a rating in the main panel - i see no reason for it not to work, unless you are refering to using the tag editor docked in the main panel
i've updated the API as follows:
- Rating change now notified when changed in the tag editor
- you can now set tags on http:// files (still need to call Library_CommitTagsToFile but it just updates the internal MB cache)
- MB_AddMenuItem now returns the ToolStripItem object that was created
the updated interface definition file is in the first post
updated MusicBee.exe:
http://www.mediafire.com/?d3vv22moj53b6g3
Is Rating change also notified when done from player control panel as well? Because, usually I rate the tracks while listening (by clicking the stars in control panel or in main panel, Album and tracks layout), I don't open explicitly Tag editor.its anywhere the rating is changed
Steven, I was updating my "speak back" plugin so now it could also say "from" which rating you are changing. E.g. "Rating changed from 1 to 4".Bump?
So, logically, I used NotificationType.TagsChanging to get old value, but I discovered that doesn't work on keyboard shortcuts. (It does work with context menu editing.)
So is there another way to get the old rating from the track you are changing?
MB 2.0.4467
Bump. Any fixes or insights on this?Steven, I was updating my "speak back" plugin so now it could also say "from" which rating you are changing. E.g. "Rating changed from 1 to 4".Bump?
So, logically, I used NotificationType.TagsChanging to get old value, but I discovered that doesn't work on keyboard shortcuts. (It does work with context menu editing.)
So is there another way to get the old rating from the track you are changing?
MB 2.0.4467
i have addedI have finally tried it, but it still doesn't work. It also gives me the rating AFTER having changed it, not BEFORE. (Capitals not mean as shouting, just highlight.).
PluginNotifyType.RatingChanging
public void ReceiveNotification(string sourceFileUrl, NotificationType type)
{
string debug = "", rating ="";
// perform some action depending on the notification type
rating = mbAPI.Library_GetFileTag(sourceFileUrl, MetaDataType.Rating);
rating = rating=="0,0"?"0":rating==""?"norating":rating;
switch (type)
{
case NotificationType.PluginStartup:
// perform startup initialisation
myPlay(about.Name+" launched");
break;
case NotificationType.TagsChanging:
debug = " - Rating: " + rating;
myPlay("tagschanging");
break;
case NotificationType.RatingChanging:
debug = " - Rating: " + rating;
//myPlay("ratingchanging");
break;
case NotificationType.RatingChanged:
debug = " - Rating: " + rating;
myPlay("ratingchangedfrom");
myPlay("2"); //function not as the numbern but the word, "to" :-)
myPlay(rating);
break;
case NotificationType.TagsChanged:
//debug = " - Rating: " + rating;
myPlay("changessaved");
break;
}
if (debug!="") Out(type + debug); //debug
}
Steven, I believe PluginNotifyType.RatingChanging is still not working properly (MB 2.0.4663).Bump?
Library_QueryFiles("field=XXXX")
for multiple "and" conditions, separate each one by a null character '\0'
Library_QueryFiles("artist=XXXX" + '\0' + "album=YYYY")
there is no "or" syntax currently supported
I really feel ignored :'(Steven, I believe PluginNotifyType.RatingChanging is still not working properly (MB 2.0.4663).Bump?
Or am I doing something wrong? (Details in my post above.)
Library_QueryFiles(query) has been enhanced so the query parameter can now be an xml string using the same syntax as the xml for auto-playlist files (suggest you create an auto-playlist to get the xml you want to generate) - however you only really need the Conditions and Condition elements. I forgot that auto-playlists can already search all fields for text by setting the field to "Any field" (=None in the xml) and the comparison type to "Is" or "Is any of"Library_QueryFiles("field=XXXX")
for multiple "and" conditions, separate each one by a null character '\0'
Library_QueryFiles("artist=XXXX" + '\0' + "album=YYYY")
there is no "or" syntax currently supported
That! But i need something like the quicksearch that is in the main window, it uses "all fields" as default, is there a metafield for "all fields"?
must i escape the query text anyway?
The Playlist_QueryFiles(filename) is not usefull because I must generate the xml on runtime so it will not be (and must not be) cached nor shown in the ui
Thanks a lot for your responses and your hard work!
i didnt think anyone was using it, so i changed it to return a url as the picture can be very big for an encoded string.
If the API revision in the plugin interface file mbApiInterface.ApiRevision is <= 25 then its using the old way
mbApiInterface = (MusicBeeApiInterface)Marshal.PtrToStructure(apiInterfacePtr, typeof(MusicBeeApiInterface));
the reason the rating is not the old value is because the notifications are asynchronous, so by the time you receive it, any call backs to retrieve the current rating will have in likelyhood already changed. Can you let me know what you are trying to achieve
check out boroda74's "Advanced Tagging Tools" plugin which has c# source code posted in the first post of that plugin topicMy plugin uses only NotificationType.PluginStartup, NotificationType.PlayCountersChanged, NotificationType.TagsChanged and NotificationType.RatingChanged events.
// register the event that is fired after the key press.
hook.KeyPressed += new EventHandler<MusicBeePlugin.KeyboardHook.KeyPressedEventArgs>(hook_KeyPressed);
// register the control + alt + F12 combination as hot key.
hook.RegisterHotKey(MusicBeePlugin.KeyboardHook.ModifierKeys.Win, Keys.W);
about.ReceiveNotifications = ReceiveNotificationFlags.StartupOnly
mbApiInterface.MB_RegisterCommand("Playback: Delete Current Track and Play Next", AddressOf functionToDoThis)
yes, its already in the interface definition file:Thank you
public enum MetaDataType
RatingLove = 76
with returns "L", "B" or blank
2. My fail, I used MB2 instead of MB2.1i looks like you have an up-to-date MusicBeeInterface file but using the old method for loading the interface (i expect because thats how the old lyrics plugin did it). See the example file included with the download
for 1, having a quick look at the code you should move the initialisation logic to NotificationType.PluginStartup in ReceiveNotification(...)I doubt there is any error - doing it the way you have done it means your initialise function wont be called the 2nd and subsequent re-enables.
the Initialise(...) function only gets called once whereas NotificationType.PluginStartup is called each time the plugin is enabled and wont be called if the plugin is disabled.
this.logger = new Logger(this.pluginDirectory + Path.DirectorySeparatorChar + this.name + ".log");
This wont work on vista/7 until you run MB as administrator:CodeUse Setting_GetPersistentStoragePath() to get writable application directory.this.logger = new Logger(this.pluginDirectory + Path.DirectorySeparatorChar + this.name + ".log");
@Steven: Just provide more information when a plugin fails to enable.Use "try {...} catch (Exception e) {MessageBox.Show(e.Message)}" in the Initialise() function.
A question about lyrics: How does MusicBee differ between synchronized and not synchronized lyrics? GetProvider() knows what kind is preferred, but that is only a preference, how does MusicBee know of what kind the returned lyrics are?disregard that parameter as it was never implemented. I recommend you return synchronised lyrics if there is a choice.
As far as I can see, the old plugin doesn't even load synchronized lyrics.
Can you please incorporate a new function Playlist_AddFiles(playlistUrl string, string[] filenames) ?that has been included in the next v2.2 update. I will make it available in a day or two
It would be ideal if the function automatically avoid duplication.
That's great, thank you very much.Quote from: PakoCan you please incorporate a new function Playlist_AddFiles(playlistUrl string, string[] filenames) ?that has been included in the next v2.2 update. I will make it available in a day or two
It would be ideal if the function automatically avoid duplication.
According to the log file (which is part of the EventGhost plugin), I see that API revision = 31 and Interface version = 27.Can you please incorporate a new function Playlist_AddFiles(playlistUrl string, string[] filenames) ?that has been included in the next v2.2 update. I will make it available in a day or two
It would be ideal if the function automatically avoid duplication.
there isnt a direct way but you could construct it yourself using:Yes, in that case it would be relatively easy.
FilePropertyType.Kind
FilePropertyType.Bitrate
there isnt a direct way but you could construct it yourself using:So as I now realized it is not as easy as it seemed.
FilePropertyType.Kind
FilePropertyType.Bitrate
Something like GetLocalisedString("Main.msg.UnBitrate#") ?+1
mbApiInterface.NowPlaying_GetDownloadedArtwork()
Steven, is there any way of getting currently displayed rotating artist picture? NowPlaying_GetArtistPicture() returns only 1st artist picture from rotating ones.use NowPlaying_GetArtistPictureUrls(true, urls[]) to get them all the locally stored ones but there is no way to get the current active one
Over time I will add APIs to stream music data, control GUI aspects so you can create new views or possibly entirely skin MusicBee.
In my opinion there is missing a LibraryChanged event.thats done for the next v2.4 update
thats done for the next v2.4 updateI finally had a chance to try.
it will be notified via
PluginNotifyType.LibrarySwitched = 29
I think I have found 2 bugs in the API or in MusicBee.
When I do "mbApiInterface.Player_SetRepeat(RepeatMode.One);" MusicBee will set repeat to All instead of One.
public delegate bool Library_GetFileTagsDelegate(string sourceFileUrl, MetaDataType[] fields, ref string[] results);sourceFileUrl is a file path, e.g C:\music\track1.mp3
What exactly is the sourceFileUrl parameter asking for? I can understand that the second parameter wants the array of data/tag types I want and the results are passed by reference in the third parameter. If I want to query the MusicBee Library - what exactly do I need to pass to sourceFileUrl.
public delegate System.Windows.Forms.ToolStripItem MB_AddMenuItemDelegate(string menuPath, string hotkeyDescription, EventHandler handler);
public delegate bool MB_AddTreeNodeDelegate(string treePath, string name, System.Drawing.Bitmap icon, EventHandler openHandler, EventHandler closeHandler);
private void SaveLyrics(string lyrics)
{
string filePath = mbApiInterface.NowPlaying_GetFileUrl();
string dirPath = Path.GetDirectoryName(filePath);
string fileName = Path.GetFileNameWithoutExtension(filePath) + ".lrc";
string targetPath = Path.Combine(dirPath, fileName);
try
{
using (var stream = new StreamWriter(targetPath, false))
{
stream.Write(lyrics);
stream.Flush();
}
mbApiInterface.Library_SetFileTag(mbApiInterface.NowPlaying_GetFileUrl(), Plugin.MetaDataType.HasLyrics, "Y [synched]");
}
catch (IOException)
{
MessageBox.Show("Failed to write", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
_mbApiInterface.Playlist_CreatePlaylist(playlistDir, playlist.Name, mbPlaylistSongFiles);
working fine here. Can you expand upon "it doesnt work"
playlistDir = "C:\\Users\\Leo\\Music\\MusicBee\\Playlists\\"
playlist.Name = "Music I Like 2"
mbPlaylistSongFiles = {string[11]}
Where the string array is an array of things that look something like this:
[9] = "D:\\Music\\Music-15\\Criolo - Convoque Seu Buda\\10-Fio De Prumo (Padê Onã).mp3"
_mbApiInterface.Playlist_CreatePlaylist(playlistDir, playlist.Name, mbPlaylistSongFiles);
Steven is British, so he uses British spellings across his codebase.But the redesigned website, per Steven's request, will use US English rather than UK English. As it gets closer to going live, I will be proofreading it to accommodate Steven's request.
System.ComponentModel.TypeConverter tc = System.ComponentModel.TypeDescriptor.GetConverter(typeof(Bitmap));
Bitmap pic = (Bitmap)tc.ConvertFrom(Convert.FromBase64String(value));
byte[] imageData = (byte[])tc.ConvertTo(pic, typeof(byte[]));
return MbApiInterface.Library_SetArtworkEx(sourceFileUrl, 0, imageData);
public Player_GetEqualiserBandsDelegate Player_GetEqualiserBands;
public Player_SetEqualiserBandsDelegate Player_SetEqualiserBands;
public delegate bool Player_GetEqualiserBands(ref float[] bands);
public delegate bool Player_SetEqualiserBands(float[] bands);
Just found out NowPlaying_GetSpectrumData can't return low to high frequency range on 3.1, it return centered frequency range now.https://getmusicbee.com/forum/index.php?topic=22822.0
Anyway to fix it? :'(
Hello. I can't find the API for v3.1.i have updated the first post and it includes the new sort tags
Can I use the API to set sort tags?
In fact I have some custom texts in my plugins which does not exist in MusicBee definitions. I want to get language setting in MusicBee and enable the localization files for my plugin.you will need to have your own localisation implementation
for 3: use Library_SetFileTag(sourceFileUrl, MetaDataType.Rating, "xx"), where xx is in range 0-100 (%), i.e. Library_SetFileTag(sourceFileUrl, MetaDataType.Rating, "20") for 1 star.
// receive event notifications from MusicBee
// you need to set about.ReceiveNotificationFlags = PlayerEvents to receive all notifications, and not just the startup event
public void ReceiveNotification(string sourceFileUrl, NotificationType type)
{
// perform some action depending on the notification type
switch (type)
{
case NotificationType.RatingChanged:
//perform some action here
break;
}
}
Library_GetFileTag(sourceFileUrl, MetaDataType.Rating)
1) Save Playlists. There are currently GetPlaylists() and GetPlaylistFiles() which are already working, but is there a call I can use for saving them?i will look at this is a couple of weeks time
2) Get Podcasts. Is there a method that gets called for that through the API, that I can use to implement?
1) Save Playlists. There are currently GetPlaylists() and GetPlaylistFiles() which are already working, but is there a call I can use for saving them?actually all the functions you need are there eg.
2) Get Podcasts. Is there a method that gets called for that through the API, that I can use to implement?can you tell me what you want to do with this? There is a hierarchical structure of subscriptions and episodes to consider
2) The actual request from the users was: if there is a way to listen to podcasts that I have on my libresonic server.Its not clear to me what you are asking for, especially with this statement "I know how to get them from the server, but I don't know how to integrate and make that work with MusicBee."
I know how to get them from the server, but I don't know how to integrate and make that work with MusicBee.
1) Save Playlists. There are currently GetPlaylists() and GetPlaylistFiles() which are already working, but is there a call I can use for saving them?actually all the functions you need are there eg.
Playlist_CreatePlaylist(string folderName, string playlistName, string[] filenames)
and various other Playlist_xxxx functions to adjust the playlist files
Coming back to this again:
I can instantiate a Playlist_CreatePlaylist delegate, but isn't that used for Creating a new Playlist inside MusicBee? What I'm looking for is to get a notification when a MusicBee Playlist is created, so I can send the same playlist to Subsonic.
Is there any notification triggered when the user creates a new Playlist from MusicBee? I couldn't find one in the list of Notification Types at least.
Or is there another way that I'm missing to handle this?
PlaylistCreated = 37
PlaylistUpdated = 38
PlaylistDeleted = 39
PlaylistMoved = 40
i will add these notifications to the API for the next v3.3 updateCodePlaylistCreated = 37
PlaylistUpdated = 38
PlaylistDeleted = 39
PlaylistMoved = 40
this patch version has the change if you want to test it:
https://getmusicbee.com/patches/MusicBee33_Patched.zip
unzip and replace the existing musicbee files
List<string> tracks;
...
tracks[0] = "file://D:\\Music\\Music\\Compilations\\Metal Hammer #200 A Tribute to Dimebag Darrell\\04 Walk.mp3";
mbApiInterface.NowPlayingList_QueueFilesNext(tracks.ToArray());
D:\Music\Music\Compilations\Metal Hammer #200 A Tribute to Dimebag Darrell\04 Walk.mp3
var rating = Subsonic.GetFileTag(sourceFileUrl, Interfaces.Plugin.MetaDataType.Rating);
public static Interfaces.Plugin.Library_GetFileTagDelegate GetFileTag;
var starred = Subsonic.GetFileTag(sourceFileUrl, Interfaces.Plugin.MetaDataType.RatingLove);
public void ReceiveNotification(string sourceFileUrl, Interfaces.Plugin.NotificationType type)
{
switch (type)
{
...
case Interfaces.Plugin.NotificationType.TagsChanged:
var rating = Subsonic.GetFileTag(sourceFileUrl, Interfaces.Plugin.MetaDataType.Rating);
var starred = Subsonic.GetFileTag(sourceFileUrl, Interfaces.Plugin.MetaDataType.RatingLove);
...
I was trying to finally get myself set up with this in Visual Studio, and noticed on the main downloads page that it's still the 3.0 API. Should that be updated?that is an old link - where did you get it from?
this should fix the issue. I will include in the next official release which should be soon
https://getmusicbee.com/patches/MusicBee33_Patched.zip
unzip and replace the existing musicbee application files
MB_SetBackgroundTaskMessage
With the background tasks is it possible to set the taskbar progress indicator using the MB API?The api call updates the text on the MusicBee status bar, not the windows taskbar. I do recall though that if the player controls panel shows a wavebar, the text for the playing track takes precedence.
private enum TBATFLAG
{
TBATF_USEMDITHUMBNAIL = 0x1,
TBATF_USEMDILIVEPREVIEW = 0x2
}
private enum TBPFLAG
{
TBPF_NOPROGRESS = 0,
TBPF_INDETERMINATE = 0x1,
TBPF_NORMAL = 0x2,
TBPF_ERROR = 0x4,
TBPF_PAUSED = 0x8
}
private enum THBMASK
{
THB_BITMAP = 0x1,
THB_ICON = 0x2,
THB_TOOLTIP = 0x4,
THB_FLAGS = 0x8
}
private enum THBFLAGS
{
THBF_ENABLED = 0,
THBF_DISABLED = 0x1,
THBF_DISMISSONCLICK = 0x2,
THBF_NOBACKGROUND = 0x4,
THBF_HIDDEN = 0x8
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct THUMBBUTTON
{
[MarshalAs(UnmanagedType.U4)]
public THBMASK dwMask;
public uint iId;
public uint iBitmap;
public IntPtr hIcon;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szTip;
[MarshalAs(UnmanagedType.U4)]
public THBFLAGS dwFlags;
}
[StructLayout(LayoutKind.Sequential)]
private struct NATIVERECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[ComImport()]
[Guid("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface ITaskbarList3
{
[PreserveSig()]
void HrInit();
[PreserveSig()]
void AddTab(IntPtr hwnd);
[PreserveSig()]
void DeleteTab(IntPtr hwnd);
[PreserveSig()]
void ActivateTab(IntPtr hwnd);
[PreserveSig()]
void SetActiveAlt(IntPtr hwnd);
[PreserveSig()]
void MarkFullscreenWindow(IntPtr hwnd, [MarshalAs(UnmanagedType.Bool)] bool fFullscreen);
void SetProgressValue(IntPtr hwnd, UInt64 ullCompleted, UInt64 ullTotal);
void SetProgressState(IntPtr hwnd, TBPFLAG tbpFlags);
void RegisterTab(IntPtr hwndTab, IntPtr hwndMDI);
void UnregisterTab(IntPtr hwndTab);
void SetTabOrder(IntPtr hwndTab, IntPtr hwndInsertBefore);
void SetTabActive(IntPtr hwndTab, IntPtr hwndMDI, TBATFLAG tbatFlags);
void ThumbBarAddButtons(IntPtr hwnd, uint cButtons, [MarshalAs(UnmanagedType.LPArray)] THUMBBUTTON[] pButtons);
void ThumbBarUpdateButtons(IntPtr hwnd, uint cButtons, [MarshalAs(UnmanagedType.LPArray)] THUMBBUTTON[] pButtons);
void ThumbBarSetImageList(IntPtr hwnd, IntPtr himl);
void SetOverlayIcon(IntPtr hwnd, IntPtr hIcon, [MarshalAs(UnmanagedType.LPWStr)] string pszDescription);
void SetThumbnailToolTip(IntPtr hwnd, [MarshalAs(UnmanagedType.LPWStr)] string pszTip);
void SetThumbnailClip(IntPtr hwnd, ref NATIVERECT prcClip);
}
[Guid("56FDF344-FD6D-11d0-958A-006097C9A090")]
[ClassInterface(ClassInterfaceType.None)]
[ComImport()]
private class TaskbarListClass
{
}
taskBarInterface = (ITaskbarList3)new TaskbarListClass();
taskBarInterface.HrInit();
NowPlayingList_GetCurrentIndex();
NowPlayingList_MoveFilesDelegate(int[] fromIndices, int toIndex);
NowPlayingList_QueryFiles(null);
NowPlayingList_MoveFiles uses the same internal functions as manually drag/dropping files, however i do see one inconsistency.
Does this version work better?
https://getmusicbee.com/patches/MusicBee34_Patched.zip
If not, are you saying that you are comparing NowPlayingList_QueryFiles(null) before and after the operation that its shuffled? If so, I dont see how that would have been case so could you explain how you determined the list is re-shuffled?
public void RequestNowPlayingListOrdered(string clientId, int offset = 0, int limit = 100)
{
_api.NowPlayingList_QueryFiles(null);
var tracks = new List<NowPlaying>();
var position = 1;
var itemIndex = _api.NowPlayingList_GetCurrentIndex();
while (position <= limit)
{
var trackPath = _api.NowPlayingList_GetListFileUrl(itemIndex);
var track = getFileMetadata(trackPath, itemIndex);
tracks.Add(track);
itemIndex = _api.NowPlayingList_GetNextIndex(position);
position++;
}
sendDataToClient(clientId, tracks);
}
public void RequestNowPlayingMove(string clientId, int from, int to)
{
bool result;
int[] aFrom = {from};
int dIn;
if (from > to)
{
dIn = to - 1;
}
else
{
dIn = to;
}
result = _api.NowPlayingList_MoveFiles(aFrom, dIn);
sendDataToClient(clientId, result);
}
https://getmusicbee.com/patches/MusicBee34_Patched.zip
Initial list - "position":3939,"position":115,"position":178,"position":3693,"position":2517...
3939 is Now Playing track, so I'm not touching it.
Move "from":178,"to":115
Result "position":3939,"position":116,"position":114,"position":3693,"position":2517...
115 properly moved to 116, but 114 is a new track instead of 178 (from the same album as 116). 117 is not shown anymore.
Move "from":3693,"to":114
"position":3939,"position":117,"position":115,"position":113,"position":2518...
116 moved to 117, 114 moved to 115, but 113 is a new track instead of 3693 (from the same album as 114 and 116), 2517 moved to 2518
Move in different direction "from":117,"to":115
"position":3939,"position":114,"position":116,"position":113,"position":2518...
Where previous 3693 appears back as 114 (instead of 115), 117 moved to 116
Move "from":114,"to":116
"position":3939,"position":115,"position":116,"position":113,"position":2518...
Action consistent - everything worked as expected! (116->115, 114->116)
that will teach me for making last minute optimisations.
https://getmusicbee.com/patches/MusicBee34_Patched.zip
When using the API calls, the only issue i can reproduce is a minor GUI issue where MB displays the playing icon against the wrong file but is fine for subsequent files. That only happens when moving files via the API and not manual drag/drop.
Using your RequestNowPlayingListOrdered() function the list always looks fine to me after trying various move operations. I dont think I will spend more time on this.
Just in case its a misunderstanding of what NowPlayingList_MoveFiles() does - it changes the physical ordering of files in the playing tracks list which means that MB internally adjusts the indices of the upcoming tracks to match the new physical ordering of the files but it is not moving files up or down the next in sequence order
Thats an example of what i am saying. There is no api call to move files ordering within the shuffle list, just move files physical displayed ordering
It does mirror the drag/drop functionality. When you have the shuffle button enabled and showing Playing Tracks, drag/dropping just changes the displayed order. The shuffle order is unchanged. Thats what the api does.
I am happy to add something for v3.5
You are showing the "upcoming tracks" which is the order within the shuffle list
StringBuilder query = new StringBuilder();
query.Append("<SmartPlaylist");
query.Append("<Source Type=\"1\">");
query.Append(" <Description />");
query.Append(" <Conditions CombineMethod=\"All\">");
query.Append(" <Condition Field=\"None\" Comparison=\"Is \" Value=\"Someone Like You\" />");
query.Append(" </Conditions>");
query.Append("</Source>");
query.Append("</SmartPlaylist>");
pApi.Library_QueryFilesEx(query.ToString().Trim() + "", out var results);
string query = " <SmartPlaylist>\n"
+"<Source Type=\"1\">\n"
+ "<Conditions CombineMethod=\"All\"> \n"
+ "<Condition Field=\"Title\" Comparison=\"Is\" Value=\"Mysteria Tale\" />\n"
+ "</Conditions>\n"
+ "</Source>\n"
+ "</SmartPlaylist>\n";
StringBuilder query = new StringBuilder();
query.Append("<SmartPlaylist>\n");
query.Append("<Source Type=\"1\">\n");
query.Append("<Conditions CombineMethod=\"All\">\n");
query.Append("<Condition Field=\"Title\" Comparison=\"Is \" Value=\"Mysteria Tale\" />\n");
query.Append("</Conditions>\n");
query.Append("</Source>\n");
query.Append("</SmartPlaylist>\n");
StringBuilder query = new StringBuilder();
query.Append("<SmartPlaylist>\n");
I am puzzled.
string[] songsList = null;
_api.Library_QueryFilesEx(null, ref songsList);
string searchText= "SomeTitleToSearch"
_api.Library_QueryFiles(XmlFilter(new[] {"Title"}, searchText, false);
private static string XmlFilter(string[] tags, string query, bool isStrict,
SearchSource source = SearchSource.None)
{
short src;
if (source != SearchSource.None)
{
src = (short) source;
}
else
{
var userDefaults = UserSettings.Instance.Source != SearchSource.None;
src = (short)
(userDefaults
? UserSettings.Instance.Source
: SearchSource.Library);
}
var filter = new XElement("Source",
new XAttribute("Type", src));
var conditions = new XElement("Conditions",
new XAttribute("CombineMethod", "Any"));
foreach (var tag in tags)
{
var condition = new XElement("Condition",
new XAttribute("Field", tag),
new XAttribute("Comparison", isStrict ? "Is" : "Contains"),
new XAttribute("Value", query));
conditions.Add(condition);
}
filter.Add(conditions);
return filter.ToString();
}
public void RequestNowPlayingListOrdered(string clientId, int offset = 0, int limit = 100)
{
_api.NowPlayingList_QueryFiles(null);
var tracks = new List<NowPlaying>();
var position = 1;
var itemIndex = _api.NowPlayingList_GetCurrentIndex();
while (position <= limit)
{
var trackPath = _api.NowPlayingList_GetListFileUrl(itemIndex);
var track = getFileMetadata(trackPath, itemIndex);
tracks.Add(track);
itemIndex = _api.NowPlayingList_GetNextIndex(position);
position++;
}
sendDataToClient(clientId, tracks);
}
Are you still trying to change the play order of existing files for a shuffled playlist?
You wont be able to do it until i add API support for that.
NowPlayingList_QueueFilesNext() will add the files to the now playing list and queue them next - the equivalent of right clicking in the main panel/ Play Next.
public enum MetaDataType
{
TrackTitle = 65,
Album = 30,
AlbumUniqueId = 108,
AlbumArtist = 31, // displayed album artist
AlbumArtistRaw = 34, // stored album artist
Artist = 32, // displayed artist
MultiArtist = 33, // individual artists, separated by a null char
PrimaryArtist = 19, // first artist from multi-artist tagged file, otherwise displayed artist
Artists = 144,
ArtistsWithArtistRole = 145,
ArtistsWithPerformerRole = 146,
ArtistsWithGuestRole = 147,
ArtistsWithRemixerRole = 148,
Artwork = 40,
BeatsPerMin = 41,
Composer = 43, // displayed composer
MultiComposer = 89, // individual composers, separated by a null char
Comment = 44,
Conductor = 45,
Custom1 = 46,
Custom2 = 47,
Custom3 = 48,
Custom4 = 49,
Custom5 = 50,
Custom6 = 96,
Custom7 = 97,
Custom8 = 98,
Custom9 = 99,
Custom10 = 128,
Custom11 = 129,
Custom12 = 130,
Custom13 = 131,
Custom14 = 132,
Custom15 = 133,
Custom16 = 134,
Decade = 105,
DiscNo = 52,
DiscCount = 54,
Encoder = 55,
Genre = 59,
Genres = 143,
GenreCategory = 60,
Grouping = 61,
Keywords = 84,
HasLyrics = 63,
Lyricist = 62,
Lyrics = 114,
Mood = 64,
Occasion = 66,
Origin = 67,
Publisher = 73,
Quality = 74,
Rating = 75,
RatingLove = 76,
RatingAlbum = 104,
Tempo = 85,
TrackNo = 86,
TrackCount = 87,
Virtual1 = 109,
Virtual2 = 110,
Virtual3 = 111,
Virtual4 = 112,
Virtual5 = 113,
Virtual6 = 122,
Virtual7 = 123,
Virtual8 = 124,
Virtual9 = 125,
Virtual10 = 135,
Virtual11 = 136,
Virtual12 = 137,
Virtual13 = 138,
Virtual14 = 139,
Virtual15 = 140,
Virtual16 = 141,
Virtual17 = 149,
Virtual18 = 150,
Virtual19 = 151,
Virtual20 = 152,
Virtual21 = 153,
Virtual22 = 154,
Virtual23 = 155,
Virtual24 = 156,
Virtual25 = 157,
Virtual26 = 185,
Virtual27 = 186,
Virtual28 = 187,
Virtual29 = 188,
Virtual30 = 189,
Virtual31 = 190,
Virtual32 = 191,
Year = 88,
YearOnly = 35,
SortTitle = 163,
SortAlbum = 164,
SortAlbumArtist = 165,
SortArtist = 166,
SortComposer = 167,
Work = 168,
MovementName = 169,
MovementNo = 170,
MovementCount = 171,
ShowMovement = 172,
Language = 173,
OriginalArtist = 174,
OriginalYear = 175,
OriginalTitle = 177,
InstrumentsPerformers = 182
}
string query = "<SmartPlaylist>\n";
query += "<Source Type=\"1\">\n";
query += "<Conditions CombineMethod=\"All\">\n";
query += "<Condition Field=\"AlbumUniqueId\" Comparison=\"Is\" Value=\"" + albumUniqueId + "\" />\n";
query += "</Conditions>\n";
query += "</Source>\n";
query += "</SmartPlaylist>\n";
I've gotten by just getting matches for album name and album artist but I think AlbumUniqueId would be more accurate.thats right. I will have a look tonight
I triedthis definitely works, i expect the 2nd approach would as well. The matching files need to be in the music library, audiobooks or inbox
Library_QueryFilesEx("AlbumUniqueId=xxxxxxxxxx", out songList)
Plugin.MbApiInterface.Library_QueryFilesEx("AlbumUniqueId=" + uniqueAlbumId, out songsList);
if (songsList.Length > 0)
{
Plugin.MbApiInterface.NowPlayingList_PlayNow(songsList[0]);
if (songsList.Length > 1)
{
songsList = songsList.Skip(1).ToArray();//remove first song from array
Plugin.MbApiInterface.NowPlayingList_QueueFilesNext(songsList);
}
}
Plugin.MbApiInterface.NowPlayingList_QueueFilesNext(songsList);
foreach(string s in songsList)
{
Plugin.MbApiInterface.NowPlayingList_QueueNext(s);
}
Plugin.MbApiInterface.Library_QueryFilesEx("AlbumUniqueId=" + uniqueAlbumId, out songsList);
if (songsList.Length > 0)
{
Plugin.MbApiInterface.NowPlayingList_QueueFilesNext(songsList);
Plugin.MbApiInterface.NowPlayingList_PlayNow(songsList[0]);
}
I noticed that the `RetrieveLyrics` function always receive an empty string when called. Is this intended?That function contains multiple parameters. Do you specifically mean the first (sourceFileUrl) parameter?
If so, how to read the tags and "real track title (without content within parenthesis being stripped)" of the track being queried?As a workaround, you could probably use Library_QueryFilesEx in conjunction with an autoplaylist rule,
would it be better to leave it to the lyrics plugin developers to remove any bracketed text in the track title at their choice?My personal opinion based on the workings of Lyrics Reloaded and my library:
The reason it was done in the first case was to handle the cases where track titles contain an artist name or commentsStill valid reasons to this day, I would think.
System.InvalidCastException: Unable to cast object of type '#=zb1GcBWSkGkmiEXINZQ==' to type 'System.String'.
at #=zrbDttskFerZnvGPRu7vQSrLiIig0.#=z5dkWyXz0a43S.#=zgWcTkwjOfOtS8mGbXg==(Object #=zIOqGacA=)
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart(Object obj)
would it be better to leave it to the lyrics plugin developers to remove any bracketed text in the track title at their choice?
The reason it was done in the first case was to handle the cases where track titles contain an artist name or comments
the default behaviour of removing the brackets is unchanged. What has changed is the url field is populated so you can use that to re-query the original title tag and use as you wantwould it be better to leave it to the lyrics plugin developers to remove any bracketed text in the track title at their choice?
The reason it was done in the first case was to handle the cases where track titles contain an artist name or comments
Just make it an option during plugin initialization for compatibility, maybe there are plugins that will never be updated and changing the default behavior would affect the amount of successful results. For example, my plugins gradually broaden the query if no good results were found on the first try, it removes additional features that might be contained in tha Artist field. I don't mind updating the logic to first try as-is and later strip the parts I find unnecessary