//Cruxy Player code is released under the GPL: http://www.gnu.org/copyleft/gpl.html //contact nathan@cruxy.com aka Nat Mandelbrot with questions //version 2.0 string playerName = "Cruxy Player 2.0"; integer maxTracks = 20; //to move automatically to the next track float DEFAULT_TIMER = -1;//60.0 (one minute) //300.00 (five minutes) //leave this -its the magic to xspf in SL! string XSPFZIP_SERVICE = "http://cruxy.com/sl/xspfzip_jdom.jsp?url="; //the name of the notecard for setting static playlists string sSettingsNotecard = "playlists"; //the private chat channel for voice commands integer PRIVATE_CHANNEL = 7231; //for the web-provisioned players, a simple get/put service string GET_PLAYLIST_URL = "http://cruxy.com/sl/slmx?key=playlist"; //menu stuff integer MENU_CHANNEL = 42; // dialog channel list MENU_MAIN = ["playlist info", "track info", "reload","<<", ">", ">>"]; // the main menu //the key used in cruxy.com playlists URLs to indicate pre-compressed formatting string XSPF_LITE_KEY = "xspf-lite"; //current playlist information string currentPlaylist = ""; string playlistTitle = ""; string playlistLink; string currentTrackTitle = ""; integer lastTrackNum = 0; //last track played integer listLength = 0; //total tracks loaded key requestid; //used for HTTP requests key requestidGetPlaylist; //for retrieving playlist first time integer playing = 0; //which track is playing list tracks; //stores loaded tracks integer TRACK_LIST_LENGTH = 5; //number of elements per track //for parsing notecards integer iNotecardIndex; integer iNotecardCount; integer iNoteCardLine; key kCurrentDataRequest; //the basic function for loading a XPSF url - attempt to detect whether proxying is needed or not loadXSPFPlaylist (string xspfUrl) { string reqUrl; if (llSubStringIndex(xspfUrl,XSPF_LITE_KEY)!=-1) // on cruxy reqUrl = xspfUrl; else reqUrl = XSPFZIP_SERVICE + llEscapeURL(xspfUrl); llOwnerSay("loading playlist..."); llSetText( "loading playlist...", <0.5,0.5,0.5>, 1.0 ); playlistTitle = xspfUrl; playlistLink = xspfUrl; // llOwnerSay("loading " + reqUrl); requestid = llHTTPRequest(reqUrl,[HTTP_METHOD,"GET"],""); } //parses compressed XSPF input parseXSPFZip (string body) { // llOwnerSay("parsing xspf..."); integer x; list lines = llParseString2List(body, ["|"], [""]); body = ""; string token; string contentTitle; string contentCreator; string contentLocation; string contentId; string contentInfo; tracks = []; integer trackCount = 0; // llOwnerSay("track length is: " + (string)length); while (llGetListLength(lines) > 0 && trackCount < maxTracks) { token = llList2String(lines, x); lines = llDeleteSubList (lines, x, x); if (token == "pt") //playlist title { playlistTitle = llList2String(lines, x); lines = llDeleteSubList (lines, x, x); llOwnerSay("playlist title: " + playlistTitle); } else if (token == "pl") //playlist link { playlistLink = llList2String(lines, x); lines = llDeleteSubList (lines, x, x); } else if (token == "l")//track link { contentLocation = llList2String(lines, x); lines = llDeleteSubList (lines, x, x); } else if (token == "id")//track identifier { contentId = llList2String(lines, x); lines = llDeleteSubList (lines, x, x); } else if (token == "t")//track title { contentTitle = llList2String(lines, x); lines = llDeleteSubList (lines, x, x); } else if (token == "info")//track info link { contentInfo = llList2String(lines, x); lines = llDeleteSubList (lines, x, x); } else if (token == "cre")//track creator name { contentCreator = llList2String(lines, x); lines = llDeleteSubList (lines, x, x); } else if (token == "^")//EOL token { if (llSubStringIndex(contentLocation,".mp3")!=-1) { //now add this to the track list trackCount++; llOwnerSay ("loaded track " + ((string)trackCount) + ") " + contentTitle); tracks = (tracks=[]) + tracks + [contentId, contentTitle, contentLocation, contentInfo, contentCreator]; contentId = ""; contentTitle = ""; contentLocation = ""; contentInfo = ""; contentCreator = ""; } } } //and we are done listLength = llGetListLength(tracks); if (listLength > 0) { listLength = listLength/TRACK_LIST_LENGTH; llWhisper (0, ((string)listLength) + " tracks found. say \"track \" to play."); playTrack(1); } else { //otherwise try again! loadXSPFPlaylist(currentPlaylist); } } //show the playlist info page showPlaylistPage(key id) { llLoadURL(id, "Learn more about '" + playlistTitle + "'", playlistLink); } //show a track info page showTrackPage(key id, integer trackNum) { integer trackIdx = (trackNum - 1) * TRACK_LIST_LENGTH; string title = llList2String(tracks, trackIdx + 1); string info = llList2String(tracks, trackIdx + 3); llLoadURL(id, "Learn more about '" + title + "'", info); } //play track based on number playTrack (integer trackNum) { lastTrackNum = trackNum; integer trackIdx = (trackNum - 1) * TRACK_LIST_LENGTH; string location = llList2String(tracks, trackIdx + 2); string image = llList2String(tracks, trackIdx + 4); currentTrackTitle = llList2String(tracks, trackIdx + 1); llSay(0, "now playing \"" + currentTrackTitle + "\""); llSetText("now playing\n\"" + currentTrackTitle + "\"",<1,1,1>,.5); llSetParcelMusicURL (location); if (DEFAULT_TIMER != -1) llSetTimerEvent(DEFAULT_TIMER); } //stop playback stop () { llSetParcelMusicURL (""); llSay(0, "playback stopped. touch me to start."); playing = 0; } //move one track back prevTrack () { playing = 1; lastTrackNum--; if (lastTrackNum == 0) lastTrackNum = listLength; playTrack(lastTrackNum); } //move one track forward nextTrack () { playing = 1; lastTrackNum++; if (lastTrackNum > listLength) lastTrackNum = 1; playTrack(lastTrackNum); } integer StringLeftICompare( string sLeftMatch, string sLongString ) { integer iLength; iLength = llStringLength( sLeftMatch ) - 1; if( llToLower(llGetSubString( sLongString, 0, iLength ) ) == llToLower(sLeftMatch) ) return( TRUE ); return( FALSE ); } string GetValue( string sString ) { integer iStart; string sValue = ""; string sbValue = ""; iStart = llSubStringIndex( sString, "=" ) + 1; if( iStart ) { sValue = llGetSubString( sString, iStart, llStringLength(sString) - 1 ); if( sValue ) { sbValue = llToLower( sValue ); if( sbValue == "true" ) sValue = "1"; if( sbValue == "false" ) sValue = "0"; return( sValue ); } } return( NULL_KEY ); } //player bootup code for loading XSPF from notecards and elsewhere default { on_rez( integer param ) { llResetScript(); } state_entry() { integer iii; llSetText( "booting player...", <0.5,0.5,0.5>, 1.0 ); if (llGetInventoryKey(sSettingsNotecard) != NULL_KEY) { iNotecardIndex = 0; llOwnerSay("found playlist notecard..."); iNoteCardLine = 0; kCurrentDataRequest = llGetNotecardLine( sSettingsNotecard, iNoteCardLine ); iNotecardIndex++; } else { //get playlist from name string currentPlayerName = llGetObjectDesc(); integer startIdx = llStringLength(currentPlayerName)+(integer)2; if (startIdx > 2) { currentPlaylist = llGetSubString(currentPlayerName, startIdx, -1); } else { //llSetText( "", <0.5,0.5,0.5>, 1.0 ); //llSay(0, "no playlists to load"); } state run_object; } } dataserver( key kQuery, string sData ) { list lSetting; kCurrentDataRequest = ""; if( sData != EOF ) { // you can string several of these tests for whatever values you may want. if( StringLeftICompare( "playlist=", sData ) ) { currentPlaylist = (string)GetValue( sData ); llOwnerSay("found playlist in notecard: " + currentPlaylist); } //llOwnerSay("got card data: " + currentPlaylist); kCurrentDataRequest = llGetNotecardLine( sSettingsNotecard, ++iNoteCardLine ); } else { state run_object; } } } state run_object { on_rez( integer param ) { llOwnerSay("resetting the player..."); llResetScript(); } state_entry() { llListen( PRIVATE_CHANNEL, "", NULL_KEY, "" ); llListen(MENU_CHANNEL, "", NULL_KEY, ""); // listen for dialog answers (from multiple users) if(currentPlaylist) { //locally specified playlist found loadXSPFPlaylist(currentPlaylist); } else { //check the server for a stored playlist requestidGetPlaylist = llHTTPRequest(GET_PLAYLIST_URL,[HTTP_METHOD,"GET"],""); } } //if an autonext timer is used, here is where it is called timer() { if (playing == 1) { lastTrackNum++; if (lastTrackNum > listLength) lastTrackNum = 1; playTrack(lastTrackNum); } } //show the popup dialog touch (integer num) { llDialog(llDetectedKey(0), "You are listening to the track '" + currentTrackTitle + "' from the playlist '" + playlistTitle + "'", MENU_MAIN, MENU_CHANNEL); // present dialog on click } //handle chat and menu commands listen(integer channel, string name, key id, string message) { if (channel == MENU_CHANNEL) { if (llListFindList(MENU_MAIN, [message]) != -1) // verify dialog choice { // llSay(0, name + " picked the option '" + message + "'."); // output the answer if (message == "playlist info") { showPlaylistPage(id); } else if (message == "track info") { showTrackPage(id,lastTrackNum); } else if (message == "<<") { prevTrack(); } else if (message == ">>") { nextTrack(); } else if (message == ">") { playTrack(lastTrackNum); } else if (message == "[]") { stop(); } else if (message == "reload") { currentPlaylist = ""; state default; } } } else if (channel == PRIVATE_CHANNEL) { list commands = llParseString2List(message, [" "], []); string cmd1 = llToLower(llList2String(commands, 0)); key owner = llGetOwner( ); if (cmd1 == "xspf") { string xspfUrl = llList2String(commands,1); llSay(channel, "loading playlist " + xspfUrl); loadXSPFPlaylist (xspfUrl); } else if (cmd1 == "artist") { string artist = llList2String(commands,1); llSay(channel, "loading artist " + artist); string xspfUrl = "http://cruxy.com/store/" + artist + "/xspf-lite"; loadXSPFPlaylist (xspfUrl); } else if (cmd1 == "track") { integer trackNum = (integer)llList2String(commands,1); playTrack (trackNum); } else if (cmd1 == "info") { integer trackNum = (integer)llList2String(commands,1); showTrackPage(id, trackNum); } else if (cmd1 == "prev") { prevTrack(); } else if (cmd1 == "next") { nextTrack(); } else if (cmd1 == "stop") { stop (); } else if (cmd1 == "play") { playTrack(lastTrackNum); } else if (cmd1 == "playlist") { showPlaylistPage(id); } } } http_response(key request_id, integer status, list metadata, string body) { if (requestid == request_id) parseXSPFZip (body); else if (request_id == requestidGetPlaylist && llStringLength(body)>0) { currentPlaylist = llGetSubString(body,0,llStringLength(body)-(integer)2); loadXSPFPlaylist(currentPlaylist); } } }