Xbox Music Service: Ajouter de la musique dans votre application Windows Phone ou Windows Store


Xbox Music Platform API

Xbox Music, c’est avant tout une plateforme musicale qui permet d’écouter gratuitement en streaming votre musique (en mode web et sur Windows 8) ou d’emporter votre musique avec vous sur l’ensemble des plateformes en s’abonnant au Xbox Music Pass (vous pouvez télécharger les musiques localement sur votre périphérique). Des applications dédiées sont disponibles sur Windows 8, Windows Phone, Xbox et également sur l’App Store et Google Play.

image

La bonne nouvelle, c’est que les équipes de développement viennent d’ouvrir le catalogue aux développeurs sous forme de web services. Vous pouvez donc donner une nouvelle dimension à votre application ou votre site web en consommant les millions de titres, d’albums et d’artistes disponibles sur Xbox Music. En résumé, une base de données musicale facile d’accès ! Les APIs vont s’étoffer progressivement dans les prochaines semaines, je vous propose toutefois de découvrir les éléments proposés dans cette première mouture:

  • Obtenez tous les détails d'un album, d’un artiste ou d’une chanson si vous connaissez son identifiant (ID).
  • Rechercher par mot clés n'importe quel album, artiste, ou titre, par exemple, "Madonna hang out".
  • Obtenez les meilleurs titres (top tracks) et les albums les plus récents d’un artiste.
  • Obtenez un deep link qui redirige l’utilisateur vers les applications Xbox Music, que ce soit sur ​​le web, sur Windows Phone, Windows 8.x ou d'une autre plateforme.
  • Récupérez les couvertures d'albums et les images de l’artiste en haute définition.

En complément, si votre application redirige vers l’application Xbox Music et permet de générer une vente (Xbox Music Pass ou achat d’un titre .mp3), vous pouvez également gagner des revenus en profitant d’un programme d’affiliation.


Comment utiliser les API ?

Avant de s’attaquer au code, il est nécessaire de s’inscrire sur Windows Azure Marketplace pour récupérer un identifiant (ID client) et une clé (Secret du client) qui permettra à votre application (ou site web) de s’authentifier auprès du service. A noter que ce service est totalement gratuit et le nombre de transactions est illimité.

La procédure complète pour s’enregistrer est disponible depuis l’article Obtaining a Developer Access Token mais voici les différentes étapes:

  1. Connectez-vous sur https://datamarket.azure.com avec votre compte Microsoft.
  2. Ajoutez les informations (nom et prénom) sur votre nouveau compte Windows Azure Marketplace, puis acceptez les conditions d’utilisation.
  3. Enregistrez ensuite votre nouvelle application en passant par l’interface (depuis Mon compte, dans la section Développeurs) ou en allant directement sur https://datamarket.azure.com/developer/applications.

    image

    Le formulaire génère automatiquement une clé, conservez cette information ainsi que l’identifiant client que vous aurez saisi.

  4. Et, dernier point des plus importants, abonnez-vous à Xbox Music Plaform API pour accéder au catalogue de données.

Et le code dans tout ca?

Je vous propose une Portable Class Library pour intégrer facilement Xbox Music API dans vos applications Windows Phone et Windows Store. Et en cadeau l’application Windows Phone 8 qui utilise la librairie. Ok, l’idéal serait un petit package nugget, je vais m’y atteler prochainement 🙂 Vous pouvez télécharger la solution depuis le lien suivant.

Si vous souhaitez utiliser directement l’exemple, ajoutez simplement votre ClientId/ClientSecret dans la classe App.


public partial class App : Application
{
   public static string ClientId { get { return "MyClientId"; } }
   public static string ClientSecret { get { return "MyClientSecret"; } }
 
   [...]
}

Les différentes fonctionnalités

1. Récupérer le token d’identification.

GetAccessToken utilise simplement votre ClientId et de votre ClientSecret pour générer un token d’authentification , pas grand-chose de bien méchant. Attention, ce token a une validité de 10 minutes, vous devez donc rafraichir régulièrement celui-ci ^^

/// <summary>
/// Get a developer authentication Access Token obtained from Azure Data Market.
/// Used to identify the third-party application using the Xbox Music RESTful API.
/// </summary>
/// <param name="clientId"></param>
/// <param name="clientSecret"></param>
/// <returns></returns>
public async Task<string> GetAccessToken(string clientId, string clientSecret)
{
    string token = string.Empty;
 
    const string service = "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13";
    const string scope = "http://music.xboxlive.com";
    const string grantType = "client_credentials";
 
    using (HttpClient proxy = new HttpClient())
    {
        var postData = new Dictionary<string, string>();
        postData["client_id"] = clientId;
        postData["client_secret"] = clientSecret;
        postData["scope"] = scope;
        postData["grant_type"] = grantType;
 
        HttpContent content = new FormUrlEncodedContent(postData);
 
        // Authentication Request
        HttpResponseMessage response = await proxy.PostAsync(new Uri(service), content);
        string responseContent = await response.Content.ReadAsStringAsync();
 
        if (response.IsSuccessStatusCode)
        {
            // Parsing content to extract the token
            token = ExtractTokenFromJson(responseContent);
        }
    }
 
    return token;
}
 
private string ExtractTokenFromJson(string json)
{
    string token = string.Empty;
 
    Match match = Regex.Match(json, ".*\"access_token\":\"(?<token>.*?)\".*", RegexOptions.IgnoreCase);
    if (match.Success)
    {
        token = match.Groups["token"].Value;
    }
 
    return token;
}

Utilisation de cette méthode:

MusicHelper helper = new MusicHelper();
string token = await helper.GetAccessToken(App.ClientId, App.ClientSecret);

2. Récupération du catalogue (recherche par mots clés).

SearchMediaCatalog permet de rechercher par mots clés dans le catalogue. Par défaut, la recherche est effectuée dans tous les éléments. Un filtre en argument permet donc de préciser si vous souhaitez filtrer sur une ou des sous-sections comme Albums, Artists, Tracks.

/// <summary>
/// Search for a potentially large number of items from a media catalog.
/// </summary>
/// <param name="token">Required. A valid developer authentication Access Token obtained from Azure Data Market, used to identify the third-party application using the Xbox Music RESTful API.</param>
/// <param name="query">Required. The search query.</param>
/// <param name="maxitems">Optional. Positive integer from 1 to 25, inclusive. The maximum number of results that should be returned in the response. If this parameter is not set, the response will be limited to a maximum of 25 results. There is no guarantee that all search results will be returned in a single response; the response may contain a truncated list of responses and a continuation token.</param>
/// <param name="filters">A subcategory of item types, in case the client is interested in only one or more specific types of items. If this parameter is not provided, the search will be performed in all categories.</param>
/// <param name="culture">Optional. The standard two-letter code that identifies the country/region of the user. If not specified, the value defaults to the geolocated country/region of the client's IP address. Responses will be filtered to provide only those that match the user's country/region.</param>
/// <returns></returns>
public async Task<Music> SearchMediaCatalog(string token, string query, uint maxitems = 25, Filters[] filters = null, Culture culture = null)
{
    const string scope = "https://music.xboxlive.com/1/content/music/search";
    Music search = null;
 
    // Formatting filters argument
    StringBuilder filtersbuilder = new StringBuilder();
    if (filters != null)
    {
        for (int cpt = 0; cpt < filters.Length; cpt++)
        {
            if (cpt != 0) filtersbuilder.Append("+"); filtersbuilder.Append(filters[cpt].ToString().ToLower());
        }
    }
    else
    {
        filtersbuilder.Append("music");
    }
 
    using (HttpClient proxy = new HttpClient())
    {
        StringBuilder service = new StringBuilder();
        service.Append(scope);
        service.Append("?q=");
        service.Append(WebUtility.UrlEncode(query));
        service.Append("&maxitems=");
        service.Append(maxitems.ToString());
        service.Append("&filter=");
        service.Append(filtersbuilder.ToString());
        service.Append("&accessToken=Bearer+");
        service.Append(WebUtility.UrlEncode(token));
 
        if (culture != null)
        {
            service.Append(string.Format("&language={0}", culture.Language));
            service.Append(string.Format("&country={0}", culture.Country));
        }
 
        // Authentication Request
        HttpResponseMessage response = await proxy.GetAsync(new Uri(service.ToString()));
        string responseContent = await response.Content.ReadAsStringAsync();
 
        if (response.IsSuccessStatusCode)
        {
            // Parsing Content to populate Music object
            search = Music.PopulateObject(responseContent);
        }
    }
 
    return search;
}

Utilisation de cette méthode:

MusicHelper helper = new MusicHelper();
string token = await helper.GetAccessToken(App.ClientId, App.ClientSecret);

Filters[] filters = new Filters[] { Filters.Music };
string keywords = "Daft Punk";
Music music = await helper.SearchMediaCatalog(token, keywords, 25, filters, new Culture("fr", "FR"));
Toutes les données json sont sérialisées dans des classes respectives Artist, Album et Track. Et la classe Music regroupe l’ensemble des entités récupérées. A noter que chaque appel retourne au maximum 25 éléments. Si d’autres éléments sont disponibles, un ContinuationToken est mis à disposition pour récupérer les éléments suivants (j’aborde ce point plus bas dans le post).
public class Music
{
// A paginated list of Artists that matched the request criteria.
public Artists Artists { get; set; }
// A paginated list of Albums that matched the request criteria.
public Albums Albums { get; set; }
// A paginated list of Tracks that matched the request criteria.
public Tracks Tracks { get; set; }
}

3. Recherche par Id.

Chaque élément du catalogue est identifié par un Id unique. Vous pouvez donc récupérer le détail de ces éléments par l’appel de cette méthode LookupMediaCatalog.
/// <summary>
/// Access a small number of items from a media catalog.
/// </summary>
/// <param name="token">Required. A valid developer authentication Access Token obtained from Azure Data Market, used to identify the third-party application using the Xbox Music RESTful API.</param>
/// <param name="namespaceids">Required. The ID or IDs to be looked up. Each ID is prefixed by a namespace and ".". You can specify from 1 to 10 IDs in your request.</param>
/// <param name="extras">Optional. List of extra fields that can be optionally requested (at the cost of performance).</param>
/// <param name="culture">Optional. The standard two-letter code that identifies the country/region of the user. If not specified, the value defaults to the geolocated country/region of the client's IP address. Responses will be filtered to provide only those that match the user's country/region.</param>
/// <returns></returns>
public async Task<Music> LookupMediaCatalog(string token, string[] namespaceids, Extras[] extras = null, Culture culture = null)
{
    const string scope = "https://music.xboxlive.com/1/content/{0}/lookup";
    Music lookup = null;
    
    // Formatting namespace ids argument
    StringBuilder idsbuilder = new StringBuilder();
    for(int cpt = 0; cpt < namespaceids.Length; cpt++)
    {
        if (cpt != 0) idsbuilder.Append("+"); idsbuilder.Append(namespaceids[0]);
    }
 
    // Formatting extras argument
    StringBuilder extrasbuilder = new StringBuilder();
    if (extras != null)
    {
        for (int cpt = 0; cpt < extras.Length; cpt++)
        {
            if (cpt != 0) extrasbuilder.Append("+"); extrasbuilder.Append(extras[cpt].ToString());
        }
    }
 
    // Formatting lookup request Uri
    StringBuilder service = new StringBuilder();
    service.Append(string.Format(scope, idsbuilder.ToString()));
 
    service.Append("?accessToken=Bearer+");
    service.Append(WebUtility.UrlEncode(token));
 
    if (extras != null) 
    {
        service.Append(string.Format("&extras={0}", extrasbuilder.ToString()));
    }
 
    if (culture != null)
    {
        service.Append(string.Format("&language={0}", culture.Language));
        service.Append(string.Format("&country={0}", culture.Country));
    }
 
    using (HttpClient proxy = new HttpClient())
    {
        // Lookup Request - Look up a small number of items from a media catalog.
        HttpResponseMessage response = await proxy.GetAsync(new Uri(service.ToString()));
        string responseContent = await response.Content.ReadAsStringAsync();
 
        if (response.IsSuccessStatusCode)
        {
            // Parsing Content to populate Music object
            lookup = Music.PopulateObject(responseContent);
        }
    }
 
    return lookup;
}

Utilisation de cette méthode:

MusicHelper helper = new MusicHelper();
string token = await helper.GetAccessToken(App.ClientId, App.ClientSecret);

string[] namespaceids = new string[] { "music.C61C0000-0200-11DB-89CA-0019B92A3933" };
Extras[] extras = new Extras[] { Extras.Tracks };

Music music = await helper.LookupMediaCatalog(token, namespaceids, extras, new Culture("fr", "FR"));
Pour éviter de surcharger le fichier json transmis, les collections liées (par exemple la classe Album propose une collection liée Tracks) ne sont pas inclus par défaut. Il est nécessaire de préciser, via l’argument extras, si vous souhaitez obtenir ces informations additionnelles. L’ensemble des déclarations possibles pour extras est définie ci-dessous:

Item type "Extras" value Corresponding extra information
Artist albums The artist's paginated list of albums.
topTracks The artist's paginated list of top tracks.
Album tracks The album's paginated list of tracks.
artistDetails Extra fields in the album's artist list (level of detail equivalent to a Lookup call on the artist).
Track albumDetails Extra fields in the track's album (level of detail equivalent to a Lookup call on the album).
artistDetails Extra fields in the track's artist (level of detail equivalent to a Lookup call on the artist).

4. Obtenir les éléments additionnels.

LoadNext permet de charger des éléments additionnels à l’aide du ContinuationToken.

/// <summary>
/// Request the continuation of an incomplete list of content.
/// </summary>
/// <param name="token">Required. A valid developer authentication Access Token obtained from Azure Data Market, used to identify the third-party application using the Xbox Music RESTful API.</param>
/// <param name="continuationToken">Required. A Continuation Token provided in an earlier service response and optionally passed back to the service to request the continuation of an incomplete list of content.</param>
/// <param name="namespaceids">Optional. The ID or IDs to be looked up. Each ID is prefixed by a namespace and ".". You can specify from 1 to 10 IDs in your request.</param>
/// <returns></returns>
public async Task<Music> LoadNext(string token, string continuationToken, string[] namespaceids = null)
{
    const string scopesearch = "https://music.xboxlive.com/1/content/music/search";
    const string scopelookup = "https://music.xboxlive.com/1/content/{0}/lookup";
 
    string scope = string.Empty;
    string jsonContent = string.Empty;
 
    Music next = null;
 
    // Formatting namespace ids
    if (namespaceids != null)
    {
        StringBuilder ids = new StringBuilder();
        for (int cpt = 0; cpt < namespaceids.Length; cpt++)
        {
            if (cpt != 0) ids.Append("+"); ids.Append(namespaceids[0]);
        }
 
        scope = string.Format(scopelookup, ids.ToString());
    }
    else
    {
        scope = scopesearch;
    }
 
    using (HttpClient proxy = new HttpClient())
    {
        StringBuilder service = new StringBuilder();
        service.Append(scope);
        service.Append("?continuationToken=");
        service.Append(continuationToken);
        service.Append("&accessToken=Bearer+");
        service.Append(WebUtility.UrlEncode(token));
 
        // Authentication Request
        HttpResponseMessage response = await proxy.GetAsync(new Uri(service.ToString()));
        string responseContent = await response.Content.ReadAsStringAsync();
 
        if (response.IsSuccessStatusCode)
        {
            // Parsing Content to populate Music object
            next = Music.PopulateObject(responseContent);
        }
    }
 
    return next;
}

Utilisation de cette méthode (lors de l’évènement ItemRealized sur le LongListSelector):

private async void AlbumsList_ItemRealized(object sender, ItemRealizationEventArgs e)
{
int offset = 25;
if (AlbumsList.ItemsSource != null && AlbumsList.ItemsSource.Count >= offset)
{
if (e.ItemKind == LongListSelectorItemKind.Item)
{
if ((e.Container.Content as Album).Equals(AlbumsList.ItemsSource[AlbumsList.ItemsSource.Count
- offset]))
{
// Refresh token (in case of expiration)
string token = await helper.GetAccessToken(App.ClientId, App.ClientSecret);
string continuationToken = music.Albums.ContinuationToken;

Music result = await helper.LoadNext(token, continuationToken);
foreach (Album item in result.Albums.Items)
{
music.Albums.Items.Add(item);
}
}
}
}
}

DeepLink et ImageUrl

Les classes Track et Album contiennent un DeepLink pour rediriger vers les applications natives Xbox Music de Windows Phone et Windows Store. Ce DeepLink peut être paramétré pour jouer, acheter ou ajouter le titre ou l’album à la collection de l’utilisateur. A noter que le programme d’affiliation vous permet également de modifier ce DeepLink pour un partage des revenus quand votre application déclenche une vente d’un morceau ou d’un abonnement (cf. Affiliate Program).
// Launches playback of the media content.
public string DeepLinkPlay
{
get { return this.Link + "?action=play"; }
}

// Opens the "add to collection" screen on the Xbox Music service.
public string DeepLinkAddToCollection
{
get { return this.Link + "?action=addtocollection"; }
}

// Opens the appropriate purchase flow on the Xbox Music service.
public string DeepLinkBuy
{
get { return this.Link + "?action=buy"; }
}

Utilisation du DeepLink (lors de l’évènement SelectionChanged sur le LongListSelector):

private void TracksList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// If selected item is null (no selection) do nothing
if (TracksList.SelectedItem == null)
return;

Track track = TracksList.SelectedItem as Track;
// Navigate to the Xbox Music application
WebBrowserTask webBrowserTask = new WebBrowserTask();
webBrowserTask.Uri = new Uri(track.DeepLinkPlay, UriKind.Absolute);
webBrowserTask.Show();

// Reset selected item to null (no selection)
TracksList.SelectedItem = null;
}

Last but not least, les classes Track, Album et Artist permettent d’accéder à des images en HD pour illustrer vos applications. Du coup, si vous récupérez dans l’état l’Url, vous avez des grandes chances de plomber la mémoire et de voir votre application s’arrêter violement (oui, ça sent un peu le vécu). Pensez donc à paramétrer l’Url pour demander les images dans une taille moins gourmande.
Pour vous aider à paramétrer ce point, j’ai ajouté une classe ImageSettings à la solution:
public enum ImageMode
{
Scale = 1,
LetterBox = 2,
Crop = 3
}

public class ImageSettings
{
public int Width { get; set; }
public int Height { get; set; }
public ImageMode Mode { get; set; }
public string Background { get; set; }

public ImageSettings(int width, int height, ImageMode mode, string background)
{
this.Width = width;
this.Height = height;
this.Mode = mode;
this.Background = background;
}
}

Cette classe vous permet de configurer la taille, le mode (Scale, Crop ou LetterBox) et la couleur du fond (uniquement dans le mode LetterBox).
/// <summary>
/// Every piece of content returned by catalog search and lookup APIs contains a field ImageUrl, which is a direct link to the content's default image, hosted on http://musicimage.xboxlive.com/. This image link generates an image with specific default properties, but it is possible to modify the link in order to change some of the image properties, such as its size. Image resolution is context-aware, so the actual image might change depending on the size parameters used. The details of the image API are shown here.
/// You should use the image URL given in the response. The URL should not be modified or altered other than with the parameters described below. Any other use of the images will be considered as a breach of the terms of use of the API (see TBD).
/// </summary>
/// <param name="width">Image width. Cannot be set without height parameter.</param>
/// <param name="height">Image height. Cannot be set without width parameter.</param>
/// <param name="mode">Image resize mode: 
/// - scale to resize to maximum size which fits dimension without changing the aspect ratio.
/// - letterbox to pad to dimension after resize if aspect ratio didn't match. 
/// - crop to get the required width and height but image is cropped. Defaults to crop if w and h are provided.</param>
/// <param name="background">HTML-compliant color for letterbox resize mode background. Cannot be specified if mode is not set to letterbox.</param>
/// <returns></returns>
public string ImageUrlWithOptions(ImageSettings settings)
{
    if (settings.Mode == ImageMode.LetterBox)
    {
        return this.ImageUrl + string.Format("&w={0}&h={1}&mode={2}&background={3}", settings.Width.ToString(), settings.Height.ToString(), settings.Mode.ToString().ToLower(), settings.Background);
    }
    else
    {
        return this.ImageUrl + string.Format("&w={0}&h={1}&mode={2}", settings.Width.ToString(), settings.Height.ToString(), settings.Mode.ToString().ToLower(), settings.Background);
    }
}
 
// Settings in order to change image format
public ImageSettings ImageSettings { get; set; }
 
public string ImageUrlEx
{
    get
    {
        if (this.ImageSettings != null)
        {
            return this.ImageUrlWithOptions(ImageSettings);
        }
        else
        {
            return this.ImageUrlWithOptions(new ImageSettings(160, 160, ImageMode.Scale, "#000000"));
        }
    }
}

Enjoy ! A très bientôt pour un prochain update des API.

N’hésitez pas à me signaler les coquilles éventuelles dans le code. On n’est jamais à l’abri ^^. Pour compléter ces informations, je vous invite à consulter la documentation Xbox Music for Developers.
Code de la solution XboxMusicDemo.
 

 

Comments (0)

Skip to main content