Archives pour l'étiquette javafx

Jouer avec JavaFX, JSON, HTTP et Jiwa

Bijour,

Au commencement de mon blog Dieu créa la terre, le ciel, linux, javaFX venait de sortir et j’avais publié quelques liens intéressant sur la bestiole, mais j’avais pas fait grand chose avec.

Aujourd’hui … j’ai essayé de faire un player musical version Desktop du genre Spotify en JavaFX (parce que Deezer ou Jiwa font pas mal ramer les machines utilisant flash sous linux), une appli qui devait se fournir sympatiquement en musique chez Jiwa.

Plantage du décors et quelques avancement du soft :

La recherche de titres, d’albums ou artistes se fait via une requête en post à l’url http://www.jiwa.fr/track/search, et la réponse est de type :

{"page":[
{"trackId":133337,
"songId":1337,
"songName":"les kikoolols attaquent!!",
"artistId":51337,
"artistName":"KikoololMaster",
"secArtistsNames":null,
"albumId":7331,
"albumName":"Les Kikoolols vaincront",
"songPopularity":69,
"itunesTrackUrl":null,
"albumReleaseDate":"1969-01-01",
"duration":"404",
"hidden":"0",
"sourceId":null}],
"total":1,
"min":0,
"max":1,
"pageSize":25,
"success":true}

Il faut donc faire la requête http POST, avec le paramètre Q = notre recherche :

var connexion : HttpRequest = HttpRequest {
        method: HttpRequest.POST
        location: locationSearch
        onOutput: function(os: java.io.OutputStream) {
            var urlConverter = URLConverter{};
            var pair = Pair {name: "q",value: "Les kikoo roxe !!"};
            var encodedMessage = urlConverter.encodeParameters([pair]);
            os.write(encodedMessage.getBytes());
            os.close();
        }
        onInput: function(is: java.io.InputStream) {
            //récupération du JSON
        }
    };connexion.start();

Rien de complexe jusque-là … on récupère donc notre JSON, grâce à l’objet PullParser :

try {
                json.input = is;
                var tmpElmt = "";
                var i=0;
                json.onEvent = function(event: Event) : Void{
                    if( event.type == PullParser.TEXT ){

                        if(event.name == "artistName"){tmpElmt.concat(event.text);}
                        else if (event.name == "artistName"){tmpElmt=tmpElmt.concat("Artiste : {event.text.trim()} ");i++;}
                        else if (event.name == "albumName"){tmpElmt=tmpElmt.concat("Album : {event.text.trim()} ");i++;}
                        else if (event.name == "songName"){tmpElmt=tmpElmt.concat("Titre : {event.text.trim()} ");i++;}
                        if(i==2){insert tmpElmt.replaceAll("\n", "").trim() into item;i=0;tmpElmt="";}
                    }
                };
                json.parse();
            } finally {is.close();}

On récupère donc le nom de l’album, du chanteur et le titre de la chanson (les autres paramètres sont pour plus tard) et on l’insert dans un tableau nommé « mus » ….

Le problème pour la suite, c’est la récupération de la chanson, qui se passe en deux étapes : la récupération des informations liées au songId et la récupération du mp3.

Grâce au songId l’on vient de choper dans le JSON, on a accès à l’étape suivante via une requête POST à l’url http://m.jiwa.fm/token.php où l’on envoit le paramètre s avec l’id « songId » retourné dans le JSON plus haut, ce qui donne comme réponse un string de la forme : « unMD5=8 chiffres=un zero=7 chiffres=3 chiffres », ce qui permet apparement de crée l’url de téléchargement de la chanson :

http://m.jiwa.fm/play.php?r=8 chiffres&s=le songId&t=un md5&m=7 chiffres&from=0, qui renvoit direct le mp3 ou -1 si les paramètres ne sont pas bon :s

Le gros problème étant que le md5 retourné par token.php, n’est pas celui à fournir dans l’url du play.php.

Il faut donc ouvrir le player swf ( ou se baser sur iJaw pour les flemmards comme moi ) pour choper la « clé », et récupérer le flux.

Le code complet en javaFX (pour le moment ^^, permet de récupérer le flux, et de le lire … en executant le jar!!! Via une compilation direct depuis netbeans, il ne trouve pas le mp3) :

/*
 * Main.fx
 *
 * Created on 3 oct. 2009, 13:07:02
 */

package jiwafx;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.io.http.HttpRequest;
import javafx.io.http.URLConverter;
import javafx.data.Pair;
import javafx.scene.control.ListView;
import javafx.ext.swing.SwingListItem;
import javafx.data.pull.PullParser;
import javafx.data.pull.Event;
import javafx.scene.control.Button;
import javafx.scene.control.TextBox;
import javafx.scene.layout.VBox;
import javafx.scene.layout.HBox;
import javafx.io.http.HttpHeader;
import javafx.scene.input.MouseEvent;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.Media;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.FileOutputStream;
import java.io.BufferedInputStream;
import java.io.InputStream;

/**
 * @author cipher16
 */
//Global var
var item : SwingListItem[] = [];
var mus = [];
//UI items
var scene = Scene {width: 250,height: 250};
var searchTxt = TextBox{width:bind (scene.width - 100),text: "Entrer votre recherche"};
var searchBtn = Button{text: "Rechercher",width: 100,action: function(){searchAlbum(searchTxt.text)};};
var listView : ListView = ListView{width:bind scene.width,height: bind (scene.height - searchTxt.height),items: bind item};
var SearchBox = HBox {content: [searchTxt,searchBtn],width: bind scene.width};
var player = MediaPlayer{autoPlay:true,onBuffering:function(d){println("Encore :  {d}")}};
scene.content = VBox{content:[SearchBox,listView],width: bind scene.width,height: bind scene.height}

function launchDl(url:String) : Void
{
    var ua = HttpHeader{name:"User-Agent",value:"Mozilla/5.0 (X11; U; Linux i686; fr; rv:1.9.1.3) Gecko/20091020 Ubuntu/9.10 (karmic) Firefox/3.5.3"};
    HttpRequest {
        method: HttpRequest.GET
        headers: [ua]
        location: url
        onInput: function(is: java.io.InputStream) {
            var file = new FileOutputStream('jiwa.mp3');
            while(is.available()>0){
                file.write(is.read());
            }
            is.close();
        }
        onDone:function(){player.media = Media{source:"{__DIR__}jiwa.mp3"};player.play(); }
    }.start();
}

function getToken(id:String)
{
    var ret="";
    var referer = HttpHeader{name:"Referer",value:"http://www.jiwa.fm/res/widget/LightPlayer.swf?=1255325694463"};
    var ua = HttpHeader{name:"User-Agent",value:"Mozilla/5.0 (X11; U; Linux i686; fr; rv:1.9.1.3) Gecko/20091020 Ubuntu/9.10 (karmic) Firefox/3.5.3"};
    HttpRequest {
        method: HttpRequest.POST
        headers: [referer,ua]
        location: "http://m.jiwa.fm/token.php"
        onOutput: function(os: java.io.OutputStream) {
            var urlConverter = URLConverter{};
            var pairS = Pair {name: "s",value: id};
            var encodedMessage = urlConverter.encodeParameters([pairS]);
            os.write(encodedMessage.getBytes());
            os.close();
        }
        onInput: function(is: java.io.InputStream) {
            var buff = new BufferedReader (new InputStreamReader(is));
            var line = "";
            while((line = buff.readLine())!=null){
                ret="{ret}{line}";
            }
            is.close();
        }
        onDone: function()
        {
            launchDl(getTrackUrl(ret,id));
        }

    }.start();
}

function getTrackUrl(token:String,sid:String)
{
	var t_vals = token.split("=");
        var url="";
        if(t_vals.length>0)
        {
            var a = MD5.toHex('gwqd29ydg7sqys_qsh0');
            var b = MD5.toHex("{t_vals[0]}{a}{sid}");
            url = "http://m.jiwa.fm/play.php?r={t_vals[1]}&s={sid}&t={b}&m={t_vals[3]}&from=0";
        }
        return url;
}

function searchAlbum(search: String)
{
    item = [];//on reset à la barbare 😉
    HttpRequest {
        method: HttpRequest.POST
        location: "http://www.jiwa.fr/track/search"
        onOutput: function(os: java.io.OutputStream) {
            var urlConverter = URLConverter{};
            var pair = Pair {name: "q",value: search};
            var encodedMessage = urlConverter.encodeParameters([pair]);
            os.write(encodedMessage.getBytes());
            os.close();
        }
        onInput: function(is: java.io.InputStream) {
            try {
                var json : PullParser = PullParser {documentType: PullParser.JSON,input:is};
                var tmpElmt = "";
                var i=0;
                var it = SwingListItem{};
                json.onEvent = function(event: Event) : Void{
                    if( event.type == PullParser.TEXT or event.type == PullParser.INTEGER or event.type == PullParser.NUMBER ){
                        if (event.name == "artistName"){tmpElmt=tmpElmt.concat("Artiste : {event.text.trim()} ");i++;}
                        else if (event.name == "albumName"){tmpElmt=tmpElmt.concat("Album : {event.text.trim()} ");i++;}
                        else if (event.name == "songName"){tmpElmt=tmpElmt.concat("Titre : {event.text.trim()}");i++;}
                        else if (event.name == "songId"){i++;it.value="{it.value};{event.integerValue}"}
                        else if (event.name == "artistId"){i++;}//Lol xD Da Kikoolol Attack!!!
                        else if (event.name == "trackId"){i++;it.value="{event.integerValue}"}
                        if(i>=6){ it.text=tmpElmt.replaceAll("\n", "").trim(); insert it into item;i=0;tmpElmt="";it=SwingListItem{};}
                    }
                };
                json.parse();
            } finally {is.close();}
        }
    }.start();
}

listView.onMouseClicked = function(e:MouseEvent){
    player.stop();
    player.media.source = null;
    player.media = null;
    var ids = item[listView.selectedIndex].value.toString().split(";");
    getToken(ids[1]);
}

Stage {title: "JiwaFX",scene: scene}

Et tire cette tête là une fois executé (avec une recherche sérieuse) :

CaptureCe qui est pas trop mal pour un début … j’essaierais de faire la même chose pour deezer, avec la clé de l’API cette fois … histoire de réussir un truc plus abouti^^. J’ai juste posté le code pour aider les nouveaux en JavaFX (pour l’utilisation de requêtes POST en HTTP, très mal documenté par SUN et sur le parsing JSON, encore plus mal documenté …).

Le code est assez crade, car on est obligé d’attendre la fin (probable) de deux évènements (le chargement des deux pages token et play) pour lancer la musique.

L’un des gros problèmes actuel, c’est que Jiwa rejette les User-Agent en Java, ce qui fait que l’on ne peut pas directement filer l’url retourné par la fonction à la source du media, d’où l’obligation de passer par un fichier temporaire.

Voilà, amusez-vous bien

try {
json.input = is;
var tmpElmt = «  »;
var i=0;
json.onEvent = function(event: Event) : Void{
if( event.type == PullParser.TEXT ){

if(event.name == « artistName »){tmpElmt.concat(event.text);}
else if (event.name == « artistName »){tmpElmt=tmpElmt.concat(« Artiste : {event.text.trim()} « );i++;}
else if (event.name == « albumName »){tmpElmt=tmpElmt.concat(« Album : {event.text.trim()} « );i++;}
else if (event.name == « songName »){tmpElmt=tmpElmt.concat(« Titre : {event.text.trim()} « );i++;}
if(i==2){insert tmpElmt.replaceAll(« \n », «  »).trim() into item;i=0;tmpElmt= » »;}
}
};
json.parse();
} finally {is.close();}

JavaFX … quelque liens utile

Je ne vais pas dire que je suis fan de java … mais bon javafx a du potentiel … d’autant plus que c’est plutôt bien géré par linux … déjà mieux que flex ou encore silverlight ^^.

Alors bon … quelque liens rapide : (histoire que je les ai sous la main le jour où j’en ai besoin ^^)

Le Wiki

La page de résumé

L’adresse de la doc

Le plugin de dev de javafx pour netbeans

La page de l’API

… A partir de la … on peut déjà faire pas mal. (rien qu’avec la doc de l’api…)