I'm developing a Windows Phone app that needs to retrieve and manipulate information about the songs played on the device.
I know it is possible to get the song that is currently playing using MediaPlayer.Queue.ActiveSong.
However, what I really need is to have access to a list of recently played tracks.
MediaHistory and MediaHistoryItem classes don't seem to provide this.
Is is really possible? How?
The current API, as #Igor has pointed out in his answer does not allow this. However, there is another way for us to reasonably assume that a particular media file has been played recently, by getting some information about the actual file.
We can use GetBasicPropertiesAsync() along with RetrievePropertiesAsync() which will give us the DateAccessed property for that file.
Here is a code snippet taken from this MSDN page:
public async void test()
{
try
{
StorageFile file = await StorageFile.GetFileFromPathAsync("Filepath");
if (file != null)
{
StringBuilder outputText = new StringBuilder();
// Get basic properties
BasicProperties basicProperties = await file.GetBasicPropertiesAsync();
outputText.AppendLine("File size: " + basicProperties.Size + " bytes");
outputText.AppendLine("Date modified: " + basicProperties.DateModified);
// Specify more properties to retrieve
string dateAccessedProperty = "System.DateAccessed";
string fileOwnerProperty = "System.FileOwner";
List<string> propertiesName = new List<string>();
propertiesName.Add(dateAccessedProperty);
propertiesName.Add(fileOwnerProperty);
// Get the specified properties through StorageFile.Properties
IDictionary<string, object> extraProperties = await file.Properties.RetrievePropertiesAsync(propertiesName);
var propValue = extraProperties[dateAccessedProperty];
if (propValue != null)
{
outputText.AppendLine("Date accessed: " + propValue);
}
propValue = extraProperties[fileOwnerProperty];
if (propValue != null)
{
outputText.AppendLine("File owner: " + propValue);
}
}
}
// Handle errors with catch blocks
catch (FileNotFoundException)
{
// For example, handle a file not found error
}
}
Once you have the DateAccessed property in a variable, we can see if it is a recent date, say, yesterday, or maybe even 2 or 3 days ago. Then we'll know that if it's been accessed within a short period of time, it could have been played.
There are some caveats to this, though. Some virus scanners change the Timestamp properties on files and folders, and they also need to open files to scan them which I would assume would change the DateAccessed property. However, many new Antivirus apps that I've seen revert the Timestamp info back to the original, as if it had never touched the file.
I believe this is the best workaround for this problem at the moment. Unless you only care about when your app recently played a file. Then the answer to that question is as simple as you managing your own recently-played lists for media files.
Update
In order to retrieve the PlayCount for a specified song, you can access that song using the MediaLibrary class:
MediaLibrary library = new MediaLibrary();
Then just access the song like this:
Int32 playCount = library.Songs[0].PlayCount;
where [0] is the index of the song you'd like to get the PlayCount for. An easier way (depending on how you're accessing songs already, might be to do something like:
Int32 playCount = library.Artists[selectedArtistIndex].Albums[selectedArtistAlbumIndex].Songs[selectedSongInAlbumIndex].PlayCount;
Not possible with the current API. MediaHistoryItem only returns last item set by your application, so it is of no use.
Related
I am attempting to get the metadata from a few music files and failing miserably. Online, there seems to be absolutely NO HOPE in finding an answer; no matter what I google. I thought it would be a great time to come and ask here because of this.
The specific error I got was: Error HRESULT E_FAIL has been returned from a call to a COM component. I really wish I could elaborate on this issue, but I'm simply getting nothing back from the COMException object. The error code was -2147467259, and it in hex is -0x7FFFBFFB, and Microsoft have not documented this specific error.
I 70% sure that its not the file's fault. My code will run through a directory full of music and convert the file into a song, hence the ConvertFileToSong name. The function would not be running if the file were to not exist is what I'm trying to say.
The only thing I can really say is that I'm using Dotnet 6, and have a massive headache.
Well, I guess I could also share another problem I had before this error showed up. Dotnet6 has top level code or whatever its called, this means that I can't add the [STAThread] attribute. To solve this, I simply added the code bellow to the top. Not sure why I have to set it to unknown, but that's what I (someone else on Stack Overflow) have to do. That solved that previous problem that the Shell32 could not start, but could that be causing my current problem? Who knows... definitely not me.
Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown);
Thread.CurrentThread.SetApartmentState(ApartmentState.STA);
Here is the code:
// Help from: https://stackoverflow.com/questions/37869388/how-to-read-extended-file-properties-file-metadata
public static Song ConvertFileToSong(FileInfo file)
{
Song song = new Song();
List<string> headers = new List<string>();
// initialise the windows shell to parse attributes from
Shell32.Shell shell = new Shell32.Shell();
Shell32.Folder objFolder = null;
try
{
objFolder = shell.NameSpace(file.FullName);
}
catch (COMException e)
{
int code = e.ErrorCode;
string hex = code.ToString();
Console.WriteLine("MESSAGE: " + e.Message + ", CODE: " + hex);
return null;
}
Shell32.FolderItem folderItem = objFolder.ParseName(file.Name);
// the rest of the code is not important, but I'll leave it there anyway
// pretty much loop infinetly with a counter better than
// while loop because we don't have to declare an int on a new
// line
for (int i = 0; i < short.MaxValue; i++)
{
string header = objFolder.GetDetailsOf(null, i);
// the header does not exist, so we must exit
if (String.IsNullOrEmpty(header)) break;
headers.Add(header);
}
// Once the code works, I'll try and get this to work
song.Title = objFolder.GetDetailsOf(folderItem, 0);
return song;
}
Good night,
Diseased Finger
Ok, so the solution isn't that hard. I used file.FullName which includes the file's name, but Shell32.NameSpace ONLY requires the directory name (discluding the file name).
This is the code that fixed it:
public static Song ConvertFileToSong(FileInfo file)
{
// .....
Shell32.Shell shell = new Shell32.Shell();
Shell32.Folder objFolder = file.DirectoryName;
Shell32.FolderItem folderItem = objFolder.ParseName(file.Name);
// .....
return something;
}
I'm creating a music player app, and I want to be able to automatically scan the user's device for audio files when launched, then create databases of songs, albums, and artists (which can then be displayed and played when clicked).
So far, I've looked at a few guides and similar questions, and based on those, I've come up with this code:
string[] columns = {
MediaStore.Audio.Media.InterfaceConsts.IsMusic,
MediaStore.Audio.Media.InterfaceConsts.RelativePath,
MediaStore.Audio.Media.InterfaceConsts.Title,
};
ICursor cursor = context?.ContentResolver?.Query(MediaStore.Audio.Media.ExternalContentUri, columns, null, null, null);
if (cursor is null)
{
return;
}
while (cursor.MoveToNext())
{
if (!Boolean.Parse(cursor.GetString(0)))
{
continue;
}
string trackPath = cursor.GetString(1);
string trackTitle = cursor.GetString(2);
}
cursor.Close();
The idea here is to just query the data I need (song file paths, titles, albums, artists, etc) and store them as my own data structures for better readability (and to access them from my classes that handle UI).
However, it doesn't seem to work, and I can't understand why.
For reference, I'm testing it out on the Xamarin Android emulator (API version 29), and I've got a few .mp3 files in the "Downloads" folder of the virtual device.
From some logging, I've figured out that cursor is not null, but it doesn't contain any data to iterate over.
The app has all the permissions it needs (read/write external storage, access media location, etc), so it should be able to read files.
That leaves MediaStore.Audio.Media.ExternalContentUri as a potential culprit, but I don't know of any other way to get the URI for files stored on the device.
What am I doing wrong?
Any help would be appreciated. Thanks.
Fixed this problem.
Turns out that even though InterfaceConsts.Data is marked as obsolete, it still works as expected and will give you the absolute path to the media file.
So using the same code as in the question:
string[] columns = {
MediaStore.Audio.Media.InterfaceConsts.IsMusic,
MediaStore.Audio.Media.InterfaceConsts.Data,
MediaStore.Audio.Media.InterfaceConsts.Title,
};
ICursor cursor = context?.ContentResolver?.Query(MediaStore.Audio.Media.ExternalContentUri, columns, null, null, null);
if (cursor is null)
{
return;
}
while (cursor.MoveToNext())
{
if (!Boolean.Parse(cursor.GetString(0)))
{
continue;
}
string trackPath = cursor.GetString(1);
string trackTitle = cursor.GetString(2);
}
cursor.Close();
That should work.
I've Vimeo PRO and I'm trying to get the download link so the end user can download the video source. However, the lack of documentation makes it really hard to figure that out.
I'm trying VimeoDotNet but I cannot authenticate, I'm doing the following:
var client = new VimeoClientFactory().GetVimeoClient(key, secret)
var downloadLink = client.GetVideo(video_id).download;
However, the call to GetVideo throws an error saying I have to authenticate first, but I don't see how!
I've also tried with another VimeoClient, but it doesn't seem to implement the download link part.
Can anyone help? Or better yet, share a working example. Thanks.
After 2 days I was finally able to do it, I'll share what I did in case someone needs it. First, download this library:
https://github.com/saeedafshari/VimeoDotNet3
Open in Visual Studio and compile it. It's pretty simple so it compiled right away.
Then reference that compiled DLL from your project and do the following:
var VimeoClient3 = Vimeo.VimeoClient.ReAuthorize(_vimeoAccessToken,
_vimeoAppConsumerKey, _vimeoAppClientSecret);
// videoId is the ID of the video as in the public URL (eg, 123874983)
var result = VimeoClient3.Request("/videos/" + videoId, null, "GET");
if (result == null)
{
throw new Exception("Video not found.");
}
if (result["download"] == null)
{
throw new Exception("Download link not available.");
}
foreach (var item in (ArrayList)result["download"])
{
var downloadLinkInfo = item as Dictionary<string, object>;
if (downloadLinkInfo == null) continue;
// For example, get the link for SD quality.
// As of today, Vimeo was returning an HD quality and a 'mobile' one
// that is for streaming.
if (string.Equals((downloadLinkInfo["quality"] as string), "sd", StringComparison.InvariantCultureIgnoreCase))
{
return downloadLinkInfo["link"] as string;
}
}
I need to create a qrreader with windows phone.
Xzing examples only print to video the qr string captured,
I need an example of how to understand if this string is a vcard and, consequently, save it in contact, or if it is a link and open it in the browser.
private void ScanPreviewBuffer()
{
try
{
_photoCamera.GetPreviewBufferY(_luminance.PreviewBufferY);
var binarizer = new HybridBinarizer(_luminance);
var binBitmap = new BinaryBitmap(binarizer);
var result = _reader.decode(binBitmap);
Dispatcher.BeginInvoke(() => CheckQr(result.Text));
}
catch { }
}
private void CheckQr(string qrString)
{
VibrateController vibrate = VibrateController.Default;
vibrate.Start(TimeSpan.FromMilliseconds(500));
MessageBox.Show(qrString);
/* CONTROLS HERE */
}
Obviously you have to start by parsing the qrString content to get what you want, i think we'll both agree on that point ;)
So the main issues are :
Determining formats (url or vcard)
Parsing them (if needed)
Using them to trigger wanted actions
1. About vCard
To determine if you qrString holds a vCard, maybe you could just try to match (with string.Contains or string.StartsWith methods) the vCard header which is BEGIN:VCARD and always seems to be the same from one version to another (see wikipedia).
For Windows Phone 7, there's no builtin features to parse vCards, so you have to do it by yourself or you could try to use the vCard library For Windows Phone. It would be used this way :
byte[] byteArray = Encoding.UTF8.GetBytes(qrString);
using (StreamReader reader = new StreamReader(new MemoryStream(byteArray)))
{
vCard card = new vCard(reader);
// access here card.PropertyFromvCard to get the information you need
}
There's not so much documentation about it, but sources are available on codeplex, so you'll probably find all the property names and samples you need.
For Windows Phone 8, the builtin ContactInformation.ParseVcardAsync method could help you to parse your qrString content (here is an official tutorial)
Then you need to finally create your contact :
If you're developping your App on Windows Phone 7, there's no way to create a contact directly from your application. You need to use the "save contact task" and pre-populate the fields you need. Here's an example :
SaveContactTask saveContactTask = new SaveContactTask();
saveContactTask.Completed += new EventHandler<SaveContactResult>(saveContactTask_Completed);
saveContactTask.FirstName = "John"; // card.PropertyFromvCard and so on...
saveContactTask.LastName = "Doe";
saveContactTask.MobilePhone = "2065550123";
saveContactTask.Show();
If you're developping on Windows Phone 8 (and it doesn't seem to be the case given your question tags), you can create a Custom contact store and write directly into it
2. About URLs
To know if you're dealing with an URL or not, i would advice you to follow suggestions coming with this SO answer. To make a long story short, here's the code you could use or at least something similar :
static bool IsValidUrl(string qrString)
{
Uri uri;
return Uri.TryCreate(urlString, UriKind.Absolute, out uri)
&& (uri.Scheme == Uri.UriSchemeHttp
|| uri.Scheme == Uri.UriSchemeHttps
|| uri.Scheme == Uri.UriSchemeFtp
|| uri.Scheme == Uri.UriSchemeMailto
/*...*/);
}
And finally to open your URL into a web browser (if it is a valid one of course), you could use the WebBrowser task or embed a true WebBrowser into your application with the WebBrowser control and make good use of it.
ZXing has a class called ResultParser with a static method parseResult.
The ResultParser supports some common content formats like vCard, vEvent, URL, etc.
It gives you as a result an instance of AddressBookParsedResult for vCard content back.
ParsedResult parsedResult = ResultParser.parseResult(result);
I am a beginner at Programming with C Sharp, and I am using the XNA SDK. I am trying to make a simple game to help my fellow classmates with studying for school, and I decided I would like it if there was some way to have them put music they want to listen to while playing the game inside of a file, and have the game automatically Load the music files, and play them in a playlist.
So far, I am able to get the game to detect whether the files are music, by detecting whether the file path name Contains(".mp3") , but I am trying to actually load the file name into a list of type Song, using a for Loop. The code looks like this.
(Declaration)
List<Song> songsToPlay;
string[] fileNames
(Initialize)
fileNames[] = Directory.GetFiles(".\Music")
(LoadContent)
for (int i = 0; i < fileNames.Count(); i++)
{
if (fileNames[i].Contains(".mp3")
{
songsToPlay.Add(fileNames[i]);
}
}
I have been trying to find a way to add a whole folder to the Content Directory, and have it do something more like
for (int i = 0; i < fileNames.Count(); i++)
{
songsToPlay.Add(Content.Load<Song>("fileNames[i]")
}
I have been unable to find some way to do this... Does anyone know how to make this work, or a better way to do this?
If you have your files in your project content, you should use the ContentManager class. It gives you more than just file loading. For example you can use Content.Unload to unload all your data when you're no longer using it.
There is no need to avoid that class. This page has an example showing exactly what you're trying to do:
public static Dictionary<string, T> LoadContent<T>(
this ContentManager contentManager, string contentFolder)
{
var dir = new DirectoryInfo(contentManager.RootDirectory
+ "\\" + contentFolder);
if (!dir.Exists)
throw new DirectoryNotFoundException();
var result = new Dictionary<string, T>();
foreach (FileInfo file in dir.GetFiles("*.mp3"))
{
string key = Path.GetFileNameWithoutExtension(file.Name);
result[key] = contentManager.Load<T>(
contentManager.RootDirectory + "/" + contentFolder + "/" + key);
}
return result;
}
You can use it like this:
var songs = Content.LoadContent<Song>("Songs");
Slight improvement to this code...
Once you get the above code working, I also suggest you make a slight change:
var dir = new DirectoryInfo(
System.IO.Path.Combine(contentManager.RootDirectory, contentFolder));
You shouldn't manually build paths via string concatenation when you can possibly avoid it. I don't know that you can do the same for ContentManager paths tho, so you might have to stick with string concatenation for that case.
Edit: Too many constructs you haven't used in class yet
Since you haven't used extension methods or the static keyword in your class yet, and probably haven't used dictionaries, here's a simpler way to do this:
string contentFolder = "Music";
var dir = new DirectoryInfo(Content.RootDirectory + "\\" + contentFolder);
if (!dir.Exists)
{
// Todo: Display a message to the user instead?
throw new DirectoryNotFoundException();
}
string[] files = dir.GetFiles("*.mp3");
for (int i = 0; i < files.Count(); ++i)
{
FileInfo file = files[i];
string key = System.IO.Path.GetFileNameWithoutExtension(file.Name);
var song = Content.Load<Song>(
Content.RootDirectory + "/" + contentFolder + "/" + key);
songsToPlay.Add(song);
}
Edit2: Some explanation of this second code sample
The DirectoryInfo class lets you load up a directory so you can enumerate all the files in it.
The GetFiles method on DirectoryInfo lets you enumerate files using a wildcard style pattern matching. Wildcard pattern matching for files means that when given these patterns:
*.* - you are looking for files named <anything>.<anything>
*.mp3 - you are looking for <anything>.mp3
throw means throwing an exception. This will deliberately stop executing code and display a good error message ("directory not found") and a line number. There is a lot to learn about exception handling, so I won't try to do it justice with a description here.
GetFileNameWithoutExtension should be obvious because it is well named.
Content.RootDirectory + "/" + contentFolder + "/" + key
That last little bit of code will build up a string containing the content root directory, the sub-directory of your songs, and each file name, using a name it can understand (since it doesn't know about filename extensions).
var means "whatever type I assign to it". It is a short-cut. For example, instead of typing:
List<string> someList = new List<string>();
You type:
var someList = new List<string>();
var has to know what type is on the right-hand-side of the assignment. It is useful because you can avoid repeating yourself.
Using var doesn't bestow any magical abilities to the variable though. You won't be able to assign a variable of a different type once you've declared the variable. It is just a short-cut for the exact same functionality.
Use the Song.FromUri method:
Song.FromUri("audio name", new Uri(#"C:\audio.mp3"));
The filepath can't contain spaces!
See here for a workaround: XNA 4 Song.fromUri containing Spaces