How you can add custom ringtones to your Windows Phone 7 from your app (w/out Marketplace)

http://dotnet.dzone.com/articles/how-you-can-add-custom

We all know that Windows Phone 7 doesn't carry support ringtone modifications (not by default). This can be a bit frustrating for people who don't like any of the 30 pre-loaded tunes (although it's probably hard to find one). In this article I am going to show you how it is possible to upload your own ringtone to your WP7 device. If you are not interested in the code part of the problem and detailed explanations related to it, I would recommend going to Chris Walshie's blog and downloading the ringtone installerright away.

First thing you need to know is that this method is not official. Using it in your app will most likely cause the app to be rejected from the Windows Phone Marketplace. So use this at your own risk.

The easiest method is to use the Microsoft.Phone.Media.Extended library - it can be downloaded directly from this place. I will also show how to do the same thing via reflection, so that you won't have to add an extra dependency, but that's for later.

Before I start coding, prepare a bunch of ringtones that you want to copy to the phone. It should be aWMA (Windows Media Audio) file. There are no restrictions on the bitrate. I used Expression Encoder 4 to encode a MP3 file to WMA starting at 48kbps and ending with 320kbps and uploaded them on the phone - every single one played correctly.

Once you added a reference to Microsoft.Phone.Media.Extended, you will need to instantiate the RingtoneLibrary class. It is a singleton, therefore you need to use the current instance:

RingtoneLibrary lib = RingtoneLibrary.Instance;

 

NOTE: As pointed by keyboardP, "ID_CAP_RINGTONE_ADD" is a capability that must be declared in WMAppManifest.xml.

This class has one method that is needed to perform the operation - AddRingtone. It accepts two parameters - a Stream instance (ringtone contents) and a string (the name of the ringtone). The name of the ringtone must contain the wma extension in order to be registered.

So for example, here is how I can call  it for a ringtone that I want to call Morning:

lib.AddRingtone(stream, "Morning.wma");

The WMA extension will not be shown in the ringtone list. The stream for the WMA file can be obtained in a multitude of ways. For example, I can download it:

WebClient client = new WebClient();
client.OpenReadCompleted += new OpenReadCompletedEventHandler(client_OpenReadCompleted);
client.OpenReadAsync(new Uri("http://dennisdel.com/someFile.wma"));

Once the data is downloaded:

void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
MemoryStream st = new MemoryStream();
byte[] buffer = new byte[1024];

int count;

while ((count = e.Result.Read(buffer, 0, buffer.Length)) > 0)
{
  st.Write(buffer, 0, count);
}

byte[] b = st.ToArray();

MemoryStream s = new MemoryStream(b);

RingtoneLibrary lib = RingtoneLibrary.Instance;

lib.AddRingtone(s, "Morning.wma");

}

You might be wondering - why I am using two MemoryStream instances here. For some reason, when I am passing the MemoryStream instance that was created from based on the existing Stream (e.Result), the track isn't played (although the length of the byte array remains the same). When MemoryStream is created from a byte array directly, the audio is correctly registered and played later on.

This method also works on the emulator and if you have the debugging console enabled, you will see how the ringtone addition process is logged.

Now let's take a look at the code snippet that will do the same thing without adding an extra DLL reference:

Assembly extendedAssembly = Assembly.Load("Microsoft.Phone.Media.Extended, Version=7.0.0.0, Culture=neutral, PublicKeyToken=24eec0d8c86cda1e");
           
 
Type ringtoneLib = extendedAssembly.GetType("Microsoft.Phone.RingtoneLibrary");
FieldInfo instanceF = ringtoneLib.GetField("Instance");

 

object instance = instanceF.GetValue(null);

 

MethodInfo m = ringtoneLib.GetMethod("AddRingtone");

m.Invoke(instance, new object[] { s, "main.wma" });

That's it - not that long as one would expect. Given that I already knew the types and methods used, it is fairly easy to go the Reflection way. The only thing to remember here is that there is a singleton present and in order to get the current class instance I need to get the value of the Instance field.

posted @ 2012-12-25 17:56  hack25  阅读(238)  评论(2编辑  收藏  举报