Playing Music files in OpenSim
From OpenSimulator
Introduction
Web-hosted audio files are the simplest way to play Music in OpenSimulator. Entering the url of an audio file in About Land > Sound > Music instructs the viewer to play this file. The only software you need is a web server.
Unlike streaming, where the content is choosen by the broadcaster, the songs to play may be choosen in-world using a script.
Making a jukebox
First, prepare a set of music files on a web directory. Make this directory listable (Apache directive Options +Indexes). The result is as follow :
Next, write a script that will read and parse this page to build a list of songs. Finally, present this list to the user (this example use dynamic textures) and provide a selection mechanism (click on the song name). One the selected song name is obtained, call llSetParcelMusicURL
A more sophisticated script can browse multiple web directories, allowing for a unlimited number of songs.
// // Sample jukebox script // string musicDirectoryUrl = "http://my.web.server/music/wildfire/"; list Songs; // // Get song list from the HTTP server // QueryMusicDirectory (string url) { // Default MAXLENGTH of 2048 probably too small // Extend to max OpenSim default of 16384 // This can be further extended in the [Network] // section of OpenSim.ini editing HttpBodyMaxLenMAX llHTTPRequest (url, [HTTP_BODY_MAXLENGTH, 16384], ""); } list ParseMusicDirectory (string html) { list result; list lines = llParseString2List (html, ["\n"], [ ]); for (integer l=0; l<llGetListLength(lines); l++) { string line = llList2String (lines, l); // Crude HTML parsing integer p = llSubStringIndex (line, "<a href="); if (p != -1) line = llDeleteSubString (line, 0, p+7); p = llSubStringIndex (line, ">"); if (p != -1) line = llDeleteSubString (line, p, -1); // Remove leading and trailing quotes if (llGetSubString (line, 0,0) == "\"") line = llDeleteSubString (line, 0,0); if (llGetSubString (line, -1,-1) == "\"") line = llDeleteSubString (line, -1,-1); if (llGetSubString (line, -4,-1) == ".mp3") { line = llDeleteSubString (line, -4,-1); result += [ line ]; } } return result; } // // Matrix drawing stuff // integer DISPLAY_SIDE = 1; // Active side integer TEXTURE_SIZE = 512; integer FONT_SIZE = 10; integer COLUMNS = 3; // Holds 3x16 items integer ROWS = 16; // Extend if necessary string cellBorderColor = "FFFFFFFF"; string backgroundColor = "FF555555"; string cellBackgroundColor = "FF000055"; string cellForegroundColor = "FFFFFFFF"; string drawList; displayBegin() { drawList = ""; } displayEnd() { osSetDynamicTextureDataFace ( "", "vector", drawList, "width:"+(string)TEXTURE_SIZE+",height:"+(string)TEXTURE_SIZE, 0, DISPLAY_SIDE); } drawCell (integer x, integer y, string text) { integer CELL_HEIGHT = TEXTURE_SIZE / ROWS; integer CELL_WIDHT = TEXTURE_SIZE / COLUMNS; integer xTopLeft = x*CELL_WIDHT; integer yTopLeft = y*CELL_HEIGHT; // Draw border drawList = osSetPenColor (drawList, cellBorderColor); drawList = osMovePen (drawList, xTopLeft, yTopLeft); drawList = osDrawRectangle (drawList, CELL_WIDHT, CELL_HEIGHT); // Fill background drawList = osSetPenColor (drawList, cellBackgroundColor); drawList = osMovePen (drawList, xTopLeft+2, yTopLeft+2); drawList = osDrawFilledRectangle (drawList, CELL_WIDHT-3, CELL_HEIGHT-3); // Draw text xTopLeft += 10; // Center text in cell yTopLeft += 08; // Center text in cell drawList = osSetPenColor (drawList, cellForegroundColor); drawList = osMovePen (drawList, xTopLeft, yTopLeft); drawList = osDrawText (drawList, text); } drawTable(list items) { displayBegin(); drawList = osSetPenSize (drawList, 1); drawList = osSetFontSize (drawList, FONT_SIZE); drawList = osMovePen (drawList, 0, 0); drawList = osSetPenColor (drawList, backgroundColor); drawList = osDrawFilledRectangle (drawList, TEXTURE_SIZE, TEXTURE_SIZE); integer x; integer y; for (x=0; x<COLUMNS; x++) for (y=0; y<ROWS; y++) drawCell (x, y, llList2String (items, y+x*ROWS)); displayEnd(); } integer getCellIndex(vector point) { integer y = (ROWS-1) - llFloor(point.y*ROWS); // Top to bottom integer x = llFloor(point.x*COLUMNS); // Left to right integer index = (y+x*ROWS); return index; } // // Click action // // To use llSetParcelMusicURL, you need to own the parcel // The object owner must also be the land owner. If the land // is deeded to a group the object will need to be deeded to // the same group for this function to work. HandleClick (vector point) { integer index = getCellIndex(point); MoveIndex (index); string song = llList2String (Songs, index); if (song == "") return; // Blank cell clicked if (song == "STOP") { llWhisper (0, "Stopping music"); llSetParcelMusicURL (""); } else { llWhisper (0, "Now playing "+song); llSetParcelMusicURL (musicDirectoryUrl+song+".mp3"); } } integer INDEX_PRIM = 2; // Optional colored, semi-transparent prim // to place in front of selection MoveIndex(integer index) { integer x = index / ROWS; integer y = index % ROWS; vector size = llGetScale(); float xIndex = - (size.x/2) + (x+0.5) * size.x / COLUMNS; float yIndex = (size.y/2) - (y+0.5) * size.y / ROWS; vector indexPos = < xIndex, yIndex, size.z/2 >; llSetLinkPrimitiveParamsFast (INDEX_PRIM, [PRIM_POS_LOCAL, indexPos]); } default { state_entry() { QueryMusicDirectory (musicDirectoryUrl); } http_response (key request_id, integer status, list metadata, string body) { if (status == 200) { Songs = ParseMusicDirectory (body); Songs += [ "STOP" ]; drawTable (Songs); } else llOwnerSay ("HTTP error "+(string)status); } touch_start (integer n) { vector point = llDetectedTouchST(0); integer face = llDetectedTouchFace(0); integer link = llDetectedLinkNumber(0); if (face != DISPLAY_SIDE) return; if (point == TOUCH_INVALID_TEXCOORD) return; HandleClick (point); } }