User:Dz/ShoutCast
From OpenSimulator
(Created page with "== dzShoutCast == As with all OpenSimian projects posted here, You are free to copy/modify/and IMPROVE the functionality to fit your needs. If you have feedback on script...") |
(Added Useful section titles and filled in some of the details.... Beta script added and checked copyright notices) |
||
Line 8: | Line 8: | ||
− | === | + | === Feature List === |
− | + | Note card configuration. | |
− | + | MOAP style information display with Texture slide show option for non MOAP viewers. | |
+ | Configurable Access by owner, group, or individuals (DJs) | ||
+ | Default Texture override. | ||
+ | 10 user defined GENRE Categories. | ||
+ | Unlimited URLs per GENRE. | ||
+ | Usable on Group owned land. | ||
+ | |||
+ | === Building the Board === | ||
+ | |||
+ | You are free to design your own container for this script but there are implications of using M.O.A.P. as a display driver. | ||
+ | Obviously.. if you expect users who do not have MOAP enabled viewers ( really?? still???) this may be less than useful. | ||
+ | |||
+ | I use a 2 prim object for my display boards. the display itself I size in a 1:2 (Height : Width) size. This allows | ||
+ | The other thing to remember is that one face of your base prim is going to be turned into a web display, this will disable the ability to click on the display to generate a touch event to trigger a menu. | ||
+ | |||
+ | === The BETA script === | ||
+ | |||
+ | I will attempt to document the changes to the script as I upgrade from BETA release status. This is a copy of a board i have been using in SL for a couple years... it is not bug free but it has been useful and reliable. | ||
<source lang = "lsl"> | <source lang = "lsl"> | ||
+ | // Viewer 2 Dynamic HTML Shoutcast stream controller | ||
+ | |||
+ | // version 2.5 (c) dz questi 4/25/2011 | ||
+ | |||
+ | // Converted existing script to use Floating Text and dynamic HTML display | ||
+ | // Corrected multiple menu display feature | ||
+ | // Added UUID access list feature | ||
+ | // This script is a modification of the script described below | ||
+ | // It is released in accordance to the license granted, and is released under the same license | ||
+ | |||
+ | // Script: Shoutcast - radio controller | ||
+ | // Version: 0.3 - released 10-2-2011 | ||
+ | // Logic Scripts (Flennan Roffo) | ||
+ | // (c) 2010 - Flennan Roffo (Logic Scripts) | ||
+ | |||
+ | // LICENCE INFO | ||
+ | // | ||
+ | // This program is free software: you can redistribute it and/or modify | ||
+ | // it under the terms of the GNU General Public License as published by | ||
+ | // the Free Software Foundation, either version 3 of the License, or | ||
+ | // (at your option) any later version. | ||
+ | // | ||
+ | // This program is distributed in the hope that it will be useful, | ||
+ | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
+ | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
+ | // GNU General Public License for more details. | ||
+ | // | ||
+ | // You should have received a copy of the GNU General Public License | ||
+ | // along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
+ | /////////////////////////////////////////////////////////////////////////////////////////// | ||
+ | |||
+ | float update_time=30.0; /////// EDITABLE \\\\\\ | ||
+ | |||
+ | string info_notecard="Radio Control info"; /////// EDITABLE \\\\\\ | ||
+ | string config_notecard="Radio Control config"; /////// EDITABLE \\\\\\ | ||
+ | string comment_char="#"; /////// EDITABLE \\\\\\ | ||
+ | list sep_char_list= ["|"]; /////// EDITABLE \\\\\\ | ||
+ | string no_title_info="(no title info available)"; /////// EDITABLE \\\\\\ | ||
+ | |||
+ | // Buttons | ||
+ | |||
+ | string button_MAIN = "MAIN"; /////// EDITABLE \\\\\\ | ||
+ | string button_HELP = "HELP"; /////// EDITABLE \\\\\\ | ||
+ | string button_NEXT = ">>"; /////// EDITABLE \\\\\\ | ||
+ | string button_PREV = "<<"; /////// EDITABLE \\\\\\ | ||
+ | string button_ON = "ON"; /////// EDITABLE \\\\\\ | ||
+ | string button_OFF = "OFF"; /////// EDITABLE \\\\\\ | ||
+ | |||
+ | // Profile picture particle parameters | ||
+ | float PictSize = 1.0; | ||
+ | |||
+ | key ppReqID = NULL_KEY; | ||
+ | key ProfilePic = NULL_KEY; | ||
+ | key lastWho = NULL_KEY; | ||
+ | |||
+ | key MyOwner = NULL_KEY; | ||
+ | |||
+ | // List of categories (=genres) | ||
+ | |||
+ | list category_list=[]; | ||
+ | |||
+ | // List of stations. KEEP THESE LISTS IN SYNCH! | ||
+ | |||
+ | list station_category=[]; | ||
+ | list station_name=[]; | ||
+ | list station_desc=[]; | ||
+ | list station_url=[]; | ||
+ | list dj_UUID = []; | ||
+ | list UUID_access = []; | ||
+ | |||
+ | |||
+ | string StationId = ""; | ||
+ | string GenreType = ""; | ||
+ | string ThisTitle = ""; | ||
+ | string LastTitle = ""; | ||
+ | |||
+ | integer radio_status=0; // 0 - OFF 1 - ON | ||
+ | string parcel_url=""; | ||
+ | integer lineno=0; | ||
+ | key reqid=NULL_KEY; | ||
+ | key httpreq_id=NULL_KEY; | ||
+ | integer config_error=FALSE; | ||
+ | integer flag; | ||
+ | integer section=0; | ||
+ | |||
+ | integer displayface = 4; | ||
+ | integer imagesfound = 0; | ||
+ | integer whichimage = 0; | ||
+ | |||
+ | // Access values. Note that users who are banned can not access the device even when access is public | ||
+ | integer dj_access=TRUE; | ||
+ | integer group_access=TRUE; | ||
+ | integer public_access=FALSE; | ||
+ | list banned_keys=[]; | ||
+ | |||
+ | // Channels for menu and user input | ||
+ | integer menu_channel; | ||
+ | integer listen_handle; | ||
+ | |||
+ | // Channel for deeded object | ||
+ | integer DeedChannel = -142356; | ||
+ | |||
+ | string ParcelName = ""; | ||
+ | |||
+ | // Menu | ||
+ | integer menu_type=0; // 0 - Main menu (genres) 1 - Station menu (stations) | ||
+ | integer menu_num=0; // When more menu options need to be selectable then can be displayed on a menu (12), this is the menu number - menu number 0 is the first menu. | ||
+ | |||
+ | // Genres and stations | ||
+ | |||
+ | integer category_index=0; // Current index in category_list (genre) | ||
+ | integer station_index=0; // Current index in station_* (station) | ||
+ | |||
+ | integer num_categories=0; | ||
+ | integer num_stations=0; | ||
+ | |||
+ | ///// DEFINE the script Functions ///////// | ||
+ | |||
+ | // Make request for title info using HTTP request | ||
+ | retrieve_titelinfo() | ||
+ | { | ||
+ | string url=llList2String(station_url,station_index); | ||
+ | // httpreq_id=llHTTPRequest(url + "/7.html HTTP/1.0\nUser-Agent: LSL Script (Mozilla Compatible)\n\n",[],""); | ||
+ | // llSay(0," send to : " + url + "/7.html"); | ||
+ | httpreq_id=llHTTPRequest(url + "/7.html HTTP/1.0\nUser-Agent: LSL Script (Mozilla Compatible)\n\n",[],""); | ||
+ | // httpreq_id=llHTTPRequest(url + "/7.html",[],""); | ||
+ | } | ||
+ | |||
+ | // Make request for profile picture using metatag info | ||
+ | getProfilePic(key AvatarKey) | ||
+ | { | ||
+ | ppReqID = llHTTPRequest( "http://world.secondlife.com/resident/" + (string)AvatarKey,[HTTP_METHOD,"GET"],""); | ||
+ | } | ||
+ | |||
+ | // Set particle system | ||
+ | setProfilePic(key Texture) | ||
+ | { | ||
+ | if (Texture != NULL_KEY) | ||
+ | { | ||
+ | vector resizer = llGetScale(); | ||
+ | PictSize = resizer.z / 0.75; | ||
+ | |||
+ | llParticleSystem([PSYS_PART_FLAGS, PSYS_PART_EMISSIVE_MASK, | ||
+ | PSYS_SRC_PATTERN, 4, | ||
+ | PSYS_PART_START_ALPHA, 0.5, | ||
+ | PSYS_PART_END_ALPHA, 0.5, | ||
+ | PSYS_PART_START_COLOR, <1.0, 1.0, 1.0>, | ||
+ | PSYS_PART_END_COLOR, <1.0, 1.0, 1.0>, | ||
+ | PSYS_PART_START_SCALE, <PictSize, PictSize, 0.0>, | ||
+ | PSYS_PART_END_SCALE, <PictSize, PictSize, 0.0>, | ||
+ | PSYS_PART_MAX_AGE, 1.2, | ||
+ | PSYS_SRC_MAX_AGE, 0.0, | ||
+ | PSYS_SRC_ACCEL, <0.0, 0.0, 0.0>, | ||
+ | PSYS_SRC_ANGLE_BEGIN, 0.0, | ||
+ | PSYS_SRC_ANGLE_END, 0.0, | ||
+ | PSYS_SRC_BURST_PART_COUNT, 8, | ||
+ | PSYS_SRC_BURST_RADIUS, PictSize, | ||
+ | PSYS_SRC_BURST_RATE, 0.1, | ||
+ | PSYS_SRC_BURST_SPEED_MIN, 0.0, | ||
+ | PSYS_SRC_BURST_SPEED_MAX, 0.0, | ||
+ | PSYS_SRC_OMEGA, <0.0, 0.0, 0.0>, | ||
+ | PSYS_SRC_TEXTURE, Texture]); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | // Update text displays with new info | ||
+ | updateText(string line, string message) | ||
+ | { | ||
+ | |||
+ | if(line == "1") | ||
+ | StationId = message; | ||
+ | |||
+ | if (line == "2") | ||
+ | GenreType = message; | ||
+ | |||
+ | if (line == "3") | ||
+ | { | ||
+ | ThisTitle = message; | ||
+ | |||
+ | // llSay(0," height " + (string) llGetPrimMediaParams(displayface,[PRIM_MEDIA_HEIGHT_PIXELS] )); | ||
+ | // llSay(0," width " + (string) llGetPrimMediaParams(displayface,[PRIM_MEDIA_WIDTH_PIXELS] )); | ||
+ | |||
+ | llSetText( StationId + " \n(" + GenreType + ") \nNow Playing :" + ThisTitle + "\n \nLast Song: " + LastTitle, <0.0, 1.0, 0.0>, 1.0); | ||
+ | |||
+ | string WebText = "<body bgcolor='black'><div style='text-align: center;'><font size='7'><font color ='blue'><i><h1>" + StationId +"</i> ( " + GenreType + " )</h1><p><i><h3>On NOW</h3></i><p><h1>"+ ThisTitle + "<p><p></h1><h3><i>Last</i></h3><p><h1>" + LastTitle + "</h1></div></body>"; | ||
+ | |||
+ | llSetPrimMediaParams(displayface,[PRIM_MEDIA_HEIGHT_PIXELS, 1024, PRIM_MEDIA_WIDTH_PIXELS, 2048, PRIM_MEDIA_CURRENT_URL, "data:text/html," + WebText]); | ||
+ | |||
+ | |||
+ | LastTitle = ThisTitle; | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | // Display a line | ||
+ | display_line(string line, string message) | ||
+ | { | ||
+ | |||
+ | // llSay(0,message); | ||
+ | updateText(line, message); | ||
+ | } | ||
+ | |||
+ | // Set the text display to default | ||
+ | clear_display() | ||
+ | { | ||
+ | // Clears the display | ||
+ | display_line("1","Radio Station ID"); | ||
+ | display_line("2","Music Genre...."); | ||
+ | display_line("3","Now Playing...."); | ||
+ | } | ||
+ | |||
+ | // Make a menu / dialog | ||
+ | make_menu(key id) | ||
+ | { | ||
+ | menu_channel=random_channel(); | ||
+ | |||
+ | if (radio_status == 0) | ||
+ | { | ||
+ | menu_type=0; | ||
+ | menu_num=0; | ||
+ | llDialog(id,"Menu: Status\n\nRadio is OFF", [ "ON", "HELP" ],menu_channel); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | if (menu_type ==0) | ||
+ | { | ||
+ | llDialog(id,"Menu: Genres", category_menu(menu_num),menu_channel); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | llDialog(id,"Menu: Stations\nGenre: " + llList2String(category_list,category_index), station_menu(menu_num),menu_channel); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | if (listen_handle != 0) llListenRemove(listen_handle); | ||
+ | listen_handle=llListen(menu_channel,"",id,""); | ||
+ | } | ||
+ | |||
+ | // Make the menu option list for menu: catagories (genres) | ||
+ | list category_menu(integer num) | ||
+ | { | ||
+ | integer len=llGetListLength(category_list); | ||
+ | list menu=[]; | ||
+ | |||
+ | if (len > 9) // If more then 9 items (12 minus the 3 buttons for MAIN/HELP and PREV, NEXT) | ||
+ | { | ||
+ | integer last_sub=(len-1)/9; // submenus start at 0. 9th entry is in submenu 0, 10th in 1, etc. | ||
+ | |||
+ | if (num > last_sub) | ||
+ | { | ||
+ | llWhisper(0,"error: wrong submenu number: " + (string) num + "."); | ||
+ | return [ "MAIN" ]; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | integer first=9*num; | ||
+ | |||
+ | while (--len >= first) | ||
+ | menu+=(list)llList2String(category_list,len); | ||
+ | |||
+ | if (num == 0) | ||
+ | menu+=(list)button_HELP; | ||
+ | else | ||
+ | menu+=(list)button_MAIN; | ||
+ | |||
+ | if (num == 0) | ||
+ | menu+=(list)button_OFF; | ||
+ | else | ||
+ | menu+=(list)button_PREV; | ||
+ | |||
+ | if (num != last_sub) | ||
+ | menu+=(list)button_NEXT; | ||
+ | } | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | while (--len >= 0) | ||
+ | menu+=(list)llList2String(category_list,len); | ||
+ | |||
+ | menu+=(list)button_OFF; | ||
+ | menu+=(list)button_HELP; | ||
+ | } | ||
+ | |||
+ | return menu; // order_buttons(menu); | ||
+ | } | ||
+ | |||
+ | // Returns the number of stations in a certain category | ||
+ | integer stations_in_category(integer cat) | ||
+ | { | ||
+ | integer count=0; | ||
+ | integer i; | ||
+ | integer len=llGetListLength(station_category); | ||
+ | string category=llList2String(category_list,cat); | ||
+ | |||
+ | for (i=0; i < len; i++) | ||
+ | if (category == llList2String(category_list,i)) | ||
+ | count++; | ||
+ | |||
+ | return count; | ||
+ | } | ||
+ | |||
+ | |||
+ | // Returns a list of station names in a certain category (genre) | ||
+ | list station_list(integer category) | ||
+ | { | ||
+ | list s=[]; | ||
+ | integer i; | ||
+ | integer catcnt = llGetListLength(station_name); | ||
+ | string cname=llList2String(category_list,category_index); | ||
+ | |||
+ | for (i = 0; i < catcnt; i++) | ||
+ | if (llList2String(station_category,i) == cname) | ||
+ | s+=(list)llList2String(station_name,i); | ||
+ | |||
+ | return s; | ||
+ | |||
+ | } | ||
+ | |||
+ | // Returns the list of stations for the station menu, depending on the submenu number | ||
+ | list station_menu(integer num) | ||
+ | { | ||
+ | list stations=station_list(category_index); | ||
+ | integer len=llGetListLength(stations); | ||
+ | list menu=[]; | ||
+ | |||
+ | if (len > 11) // 12 - 1 for MAIN menu | ||
+ | { | ||
+ | integer last_sub=(len-1)/9; | ||
+ | |||
+ | if (num > last_sub) | ||
+ | { | ||
+ | llWhisper(0,"error: wrong submenu number: " + (string) num + "."); | ||
+ | return [ "MAIN" ]; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | integer first=9*num; | ||
+ | integer last=9*num+8; | ||
+ | |||
+ | // llSay(0,"delivering menu for stations" + (string) first + " to " + (string) last); | ||
+ | |||
+ | menu+=(list)button_MAIN; | ||
+ | |||
+ | if (num > 0) | ||
+ | menu+=(list)button_PREV; | ||
+ | |||
+ | if (num < last_sub) | ||
+ | menu+=(list)button_NEXT; | ||
+ | |||
+ | if (len > last) | ||
+ | len =last; | ||
+ | |||
+ | while (--len >= first) | ||
+ | menu+=(list)llList2String(stations,len); | ||
+ | } | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | menu+=(list)button_MAIN; | ||
+ | |||
+ | while (--len >= 0) | ||
+ | menu+=(list)llList2String(stations,len); | ||
+ | } | ||
+ | |||
+ | return menu; // order_buttons(menu); | ||
+ | } | ||
+ | |||
+ | // Returns whether av with key id has access | ||
+ | integer has_access(key id) | ||
+ | { | ||
+ | if (id == MyOwner) | ||
+ | return TRUE; | ||
+ | |||
+ | if (llListFindList(banned_keys,(list)id) != -1) | ||
+ | return FALSE; | ||
+ | |||
+ | if (llListFindList(UUID_access,(list)id) != -1) | ||
+ | return TRUE; | ||
+ | |||
+ | if (dj_access && llListFindList(dj_UUID,(list)id) != -1) | ||
+ | return TRUE; | ||
+ | |||
+ | if (group_access && llSameGroup(id)) | ||
+ | return TRUE; | ||
+ | |||
+ | if (public_access) | ||
+ | return TRUE; | ||
+ | |||
+ | return FALSE; | ||
+ | } | ||
+ | |||
+ | // Gets a random channel -- uses a wide range of big negative channel numbers seldomly used | ||
+ | integer random_channel() | ||
+ | { | ||
+ | integer min=-2147483647; | ||
+ | integer max=-1000; | ||
+ | |||
+ | return (integer) (min + llFrand(max-min)); | ||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | // Returns a true value depending on the first character in input - anything else is assumed false. | ||
+ | integer true_value(string input) | ||
+ | { | ||
+ | string value=llToLower(llGetSubString(input,0,0)); | ||
+ | |||
+ | if (value == "y" || value == "t" || value =="1") | ||
+ | return TRUE; | ||
+ | |||
+ | return FALSE; | ||
+ | } | ||
+ | |||
+ | // Return if more input should be processed (if not at EOF) - sets ConfigError if any config error found. Reading config card stops at the first error. | ||
+ | integer process_line(string dataline) | ||
+ | { | ||
+ | string line=llStringTrim(dataline,STRING_TRIM); | ||
+ | integer index=llSubStringIndex(line,comment_char); | ||
+ | |||
+ | if (index==0) // line starts with comment - ignore line | ||
+ | return TRUE; | ||
+ | |||
+ | if (index!=-1) | ||
+ | line=llStringTrim(llGetSubString(line,0,index-1),STRING_TRIM_TAIL); // skip everything after comment_char and trim tail | ||
+ | |||
+ | if (line=="") // Ignore blank lines | ||
+ | return TRUE; | ||
+ | |||
+ | if (llToLower(line) == "[access]") | ||
+ | { | ||
+ | section = 1; | ||
+ | return TRUE; | ||
+ | } | ||
+ | else if (llToLower(line) == "[banned]") | ||
+ | { | ||
+ | section = 2; | ||
+ | return TRUE; | ||
+ | } | ||
+ | else if (llToLower(line) == "[genre]") | ||
+ | { | ||
+ | section = 3; | ||
+ | return TRUE; | ||
+ | } | ||
+ | else if (llToLower(line) == "[station]") | ||
+ | { | ||
+ | section = 4; | ||
+ | return TRUE; | ||
+ | } | ||
+ | else if (llGetSubString(line,0,0) == "[" && llGetSubString(line,-1,-1) == "]") | ||
+ | { | ||
+ | llWhisper(0,"error: malformed section found at line " + (string)lineno + ".\n" + dataline); | ||
+ | config_error=TRUE; | ||
+ | return FALSE; | ||
+ | } | ||
+ | |||
+ | if (section == 0) | ||
+ | { | ||
+ | llWhisper(0,"error: no section found on line: " + (string) lineno); | ||
+ | config_error = TRUE; | ||
+ | return FALSE; | ||
+ | } | ||
+ | |||
+ | list breakup=llParseString2List(line,["="],[]); | ||
+ | |||
+ | string field=llStringTrim(llList2String(breakup,0),STRING_TRIM); | ||
+ | |||
+ | string values=llStringTrim(llList2String(breakup,1),STRING_TRIM); | ||
+ | |||
+ | if (section == 1) // access | ||
+ | { | ||
+ | field=llToLower(field); | ||
+ | |||
+ | if (field=="dj") | ||
+ | { | ||
+ | dj_access=true_value(values); | ||
+ | return TRUE; | ||
+ | } | ||
+ | else if (field=="group") | ||
+ | { | ||
+ | group_access=true_value(values); | ||
+ | return TRUE; | ||
+ | } | ||
+ | else if (field=="public") | ||
+ | { | ||
+ | public_access=true_value(values); | ||
+ | return TRUE; | ||
+ | } | ||
+ | else if (llStringLength(field) == 36 ) | ||
+ | { | ||
+ | UUID_access += [(key)field]; | ||
+ | return TRUE; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | llWhisper(0,"error: invalid option on line: " + (string)lineno + ".\n" + dataline); | ||
+ | config_error=TRUE; | ||
+ | return FALSE; | ||
+ | } | ||
+ | |||
+ | } | ||
+ | else if (section == 2) // ban list | ||
+ | { | ||
+ | key try=(key) field; | ||
+ | |||
+ | if (try) | ||
+ | { | ||
+ | banned_keys+=(list)((key) field); | ||
+ | return TRUE; | ||
+ | } | ||
+ | else | ||
+ | return FALSE; | ||
+ | } | ||
+ | else if (section == 3) // categories | ||
+ | { | ||
+ | if (llListFindList(category_list,(list)field) == -1) | ||
+ | { | ||
+ | category_list+=(list)field; | ||
+ | } | ||
+ | else | ||
+ | llWhisper(0,"genre: '" + field + "' already entered; double entry skipped."); | ||
+ | |||
+ | return TRUE; | ||
+ | } | ||
+ | else if (section == 4) // stations | ||
+ | { | ||
+ | list parse=llParseString2List(line,sep_char_list, []); | ||
+ | string category=llStringTrim(llList2String(parse,0),STRING_TRIM); | ||
+ | string name=llStringTrim(llList2String(parse,1),STRING_TRIM); | ||
+ | string desc=llStringTrim(llList2String(parse,2),STRING_TRIM); | ||
+ | string url=llStringTrim(llToLower(llList2String(parse,3)),STRING_TRIM); | ||
+ | string AviUUID=llStringTrim(llToLower(llList2String(parse,4)),STRING_TRIM); | ||
+ | |||
+ | if (!available_category(category)) | ||
+ | { | ||
+ | llWhisper(0,"error: unknown genre on line: " + (string)lineno + ".\n" + dataline); | ||
+ | config_error=TRUE; | ||
+ | return FALSE; | ||
+ | } | ||
+ | |||
+ | |||
+ | if (llListFindList(station_url,(list)url) == -1 || llListFindList(station_category,(list)category) == -1 || llListFindList(station_name,(list)name) == -1) | ||
+ | { | ||
+ | num_stations++; | ||
+ | station_category+=(list)category; | ||
+ | station_name+=(list)name; | ||
+ | station_desc+=(list)desc; | ||
+ | station_url+=(list)url; | ||
+ | dj_UUID += [(key)AviUUID]; | ||
+ | return TRUE; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | llWhisper(0,"This station is already entered under the same genre and same url and is skipped.\nStation: " + name + "\nGenre: '" + category + "'\nURL: " + url); | ||
+ | return TRUE; | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | return FALSE; | ||
+ | } | ||
+ | |||
+ | // Sets the parcel URL and updates the display | ||
+ | set_parcel_url(string url) | ||
+ | { | ||
+ | parcel_url=url; | ||
+ | llSetParcelMusicURL(parcel_url); | ||
+ | |||
+ | llRegionSay(DeedChannel,ParcelName + "|" + parcel_url); | ||
+ | |||
+ | llSay(0," changinf url for " + ParcelName); | ||
+ | |||
+ | if (parcel_url=="") | ||
+ | { | ||
+ | clear_display(); | ||
+ | display_line("1","Radio is OFF"); | ||
+ | display_line("2",""); | ||
+ | display_line("3",""); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | llWhisper(0,"station now set to " + llList2String(station_desc,station_index) + "."); | ||
+ | display_line("1","Station: " + llList2String(station_desc,station_index)); | ||
+ | display_line("2", llList2String(category_list,category_index)); | ||
+ | display_line("3","Now playing....."); | ||
+ | llSetTimerEvent(update_time); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Returns if a category (genre) exists. | ||
+ | integer available_category(string category) | ||
+ | { | ||
+ | integer i; | ||
+ | integer len=llGetListLength(category_list); | ||
+ | |||
+ | for (i=0;i<len;i++) | ||
+ | if (llToLower(category) == llToLower(llList2String(category_list,i))) | ||
+ | return TRUE; | ||
+ | |||
+ | return FALSE; | ||
+ | } | ||
+ | |||
+ | // Returns if a category (genre) is empty (i.e. there are no stations for this catagory (genre)) | ||
+ | integer empty_category(string category) | ||
+ | { | ||
+ | integer i; | ||
+ | integer len=llGetListLength(station_category); | ||
+ | |||
+ | for (i=0; i < len; i++) | ||
+ | if (llToLower(category) == llToLower(llList2String(station_category,i))) | ||
+ | return FALSE; | ||
+ | |||
+ | return TRUE; | ||
+ | } | ||
+ | |||
+ | // Removes categories (genres) for which no station is known. | ||
+ | skip_empty_categories() | ||
+ | { | ||
+ | integer i=0; | ||
+ | |||
+ | while (i<llGetListLength(category_list)) | ||
+ | { | ||
+ | if (empty_category(llList2String(category_list,i))) | ||
+ | { | ||
+ | llWhisper(0,"Warning: Genre '" + llList2String(category_list,i) + "' contains no stations and is deleted."); | ||
+ | category_list=llDeleteSubList(category_list,i,i); | ||
+ | } | ||
+ | else | ||
+ | i++; | ||
+ | } | ||
+ | |||
+ | num_categories=llGetListLength(category_list); | ||
+ | } | ||
+ | |||
+ | ///////////////////////////////////////////// | ||
+ | // state default | ||
+ | //////////////////////////////////////////// | ||
+ | |||
+ | default | ||
+ | { | ||
+ | state_entry() | ||
+ | { | ||
+ | flag=FALSE; | ||
+ | lineno=0; | ||
+ | config_error=FALSE; | ||
+ | num_stations=0; | ||
+ | num_categories=0; | ||
+ | radio_status=0; | ||
+ | menu_num=0; | ||
+ | menu_type=0; | ||
+ | |||
+ | MyOwner = llGetOwner(); | ||
+ | |||
+ | list details = llGetParcelDetails(llGetPos(), [PARCEL_DETAILS_NAME]); | ||
+ | ParcelName = llList2String(details ,0); | ||
+ | |||
+ | imagesfound = llGetInventoryNumber(INVENTORY_TEXTURE); | ||
+ | |||
+ | llParticleSystem([]); | ||
+ | |||
+ | if (imagesfound < 1) | ||
+ | { | ||
+ | |||
+ | llSetPrimitiveParams([PRIM_FULLBRIGHT,displayface,TRUE, PRIM_TEXTURE, displayface, "adbff0cd-09c0-54b1-aeaa-ffd6eabfcab09",<1.0,1.0,0.0>, <0.0,0.0,0.0>, 0.0]); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | |||
+ | llSetPrimitiveParams([PRIM_FULLBRIGHT,displayface,TRUE, PRIM_TEXTURE, displayface, llGetInventoryName(INVENTORY_TEXTURE,0),<1.0,1.0,0.0>, <0.0,0.0,0.0>, 0.0]); | ||
+ | } | ||
+ | |||
+ | llSetPrimMediaParams(displayface,[PRIM_MEDIA_HEIGHT_PIXELS, 1024, PRIM_MEDIA_WIDTH_PIXELS, 2048, PRIM_MEDIA_PERMS_CONTROL , PRIM_MEDIA_PERM_NONE]); | ||
+ | |||
+ | if (llGetInventoryType(config_notecard) == INVENTORY_NOTECARD) | ||
+ | { | ||
+ | reqid=llGetNotecardLine(config_notecard,lineno++); | ||
+ | llWhisper(0, "Reading config notecard..."); | ||
+ | display_line("1","Reading configuration."); | ||
+ | display_line("2","Wait...."); | ||
+ | display_line("3",""); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | llWhisper(0,"No config notecard '" + config_notecard + "' present."); | ||
+ | state offline; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | on_rez(integer param) | ||
+ | { | ||
+ | llResetScript(); | ||
+ | } | ||
+ | |||
+ | dataserver(key id, string data) | ||
+ | { | ||
+ | if (reqid==id) | ||
+ | { | ||
+ | if (data==EOF) | ||
+ | { | ||
+ | skip_empty_categories(); | ||
+ | llWhisper(0,"Configuration ok.\n" + (string)num_categories + " genres and " + (string)num_stations + " stations."); | ||
+ | display_line("1","Configuration OK"); | ||
+ | display_line("2","Genres : " + (string)num_categories); | ||
+ | display_line("3","Stations: " + (string)num_stations); | ||
+ | state menu; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | if (process_line(data)) | ||
+ | reqid=llGetNotecardLine(config_notecard,lineno++); | ||
+ | else if (config_error) | ||
+ | { | ||
+ | llWhisper(0,"errors found in configuration. please correct them."); | ||
+ | state offline; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | changed(integer ch) | ||
+ | { | ||
+ | if (ch & CHANGED_INVENTORY) | ||
+ | llResetScript(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | ///////////////////////////////////////////// | ||
+ | // state offline | ||
+ | //////////////////////////////////////////// | ||
+ | |||
+ | state offline | ||
+ | { | ||
+ | state_entry() | ||
+ | { | ||
+ | llWhisper(0,"Reset on owner touch or when notecard updated."); | ||
+ | } | ||
+ | |||
+ | touch_start(integer t) | ||
+ | { | ||
+ | if (llDetectedKey(0)==llGetOwner()) | ||
+ | llResetScript(); | ||
+ | } | ||
+ | |||
+ | changed(integer ch) | ||
+ | { | ||
+ | if (ch & CHANGED_INVENTORY) | ||
+ | llResetScript(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | ///////////////////////////////////////////// | ||
+ | // state menu | ||
+ | //////////////////////////////////////////// | ||
+ | |||
+ | state menu | ||
+ | { | ||
+ | state_entry() | ||
+ | { | ||
+ | menu_type=0; | ||
+ | menu_num=0; | ||
+ | listen_handle=0; | ||
+ | } | ||
+ | |||
+ | on_rez(integer param) | ||
+ | { | ||
+ | llResetScript(); | ||
+ | } | ||
+ | |||
+ | touch_start(integer total_num) | ||
+ | { | ||
+ | key toucher=llDetectedKey(0); | ||
+ | |||
+ | if (has_access(toucher)) | ||
+ | { | ||
+ | make_menu(toucher); | ||
+ | } | ||
+ | else | ||
+ | llWhisper(0,"sorry, no access."); | ||
+ | } | ||
+ | |||
+ | listen(integer chan, string name,key id,string msg) | ||
+ | { | ||
+ | integer index; | ||
+ | |||
+ | if (menu_type == 0) // main menu | ||
+ | { | ||
+ | if (msg == button_MAIN) | ||
+ | { | ||
+ | menu_type=0; | ||
+ | menu_num =0; | ||
+ | make_menu(id); | ||
+ | } | ||
+ | else if (msg == button_NEXT) | ||
+ | { | ||
+ | menu_num++; | ||
+ | make_menu(id); | ||
+ | } | ||
+ | else if (msg == button_PREV) | ||
+ | { | ||
+ | menu_num--; | ||
+ | make_menu(id); | ||
+ | } | ||
+ | else if (msg == button_ON) | ||
+ | { | ||
+ | radio_status=1; | ||
+ | display_line("1","Radio is ON"); | ||
+ | menu_num=0; | ||
+ | llWhisper(0,"Radio now turned on."); | ||
+ | make_menu(id); | ||
+ | } | ||
+ | else if (msg == button_OFF) | ||
+ | { | ||
+ | radio_status=0; | ||
+ | set_parcel_url(""); | ||
+ | llSetTimerEvent(0.0); | ||
+ | llWhisper(0,"Radio now turned off."); | ||
+ | } | ||
+ | else if (msg == button_HELP) | ||
+ | { | ||
+ | if (llGetInventoryType(info_notecard) == INVENTORY_NOTECARD) | ||
+ | { | ||
+ | llGiveInventory(id,info_notecard); | ||
+ | } | ||
+ | else | ||
+ | llWhisper(0,"sorry, help not available."); | ||
+ | } | ||
+ | else if (radio_status == 1) | ||
+ | { | ||
+ | index = llListFindList(category_list, (list)msg); | ||
+ | |||
+ | if (index == -1) | ||
+ | llWhisper(0,"error: genre not found: " + msg); | ||
+ | else | ||
+ | { | ||
+ | category_index=index; | ||
+ | llWhisper(0,"Genre now set to " + llList2String(category_list,category_index) + "."); | ||
+ | menu_type=1; | ||
+ | menu_num=0; | ||
+ | make_menu(id); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | else if (menu_type == 1 && radio_status == 1) // station menu | ||
+ | { | ||
+ | if (msg == button_MAIN) | ||
+ | { | ||
+ | menu_type=0; | ||
+ | menu_num =0; | ||
+ | make_menu(id); | ||
+ | } | ||
+ | else if (msg == button_NEXT) | ||
+ | { | ||
+ | menu_num++; | ||
+ | make_menu(id); | ||
+ | } | ||
+ | else if (msg == button_PREV) | ||
+ | { | ||
+ | menu_num--; | ||
+ | make_menu(id); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | index = llListFindList(station_name, (list)msg); | ||
+ | |||
+ | if (index == -1) | ||
+ | llWhisper(0,"error: station not found: " + msg); | ||
+ | else | ||
+ | { | ||
+ | station_index=index; | ||
+ | string new_url=llList2String(station_url,station_index); | ||
+ | string new_avi=llList2String(dj_UUID,station_index); | ||
+ | |||
+ | llSay(0, llList2String(category_list,category_index) + " - found profile ID " + new_avi); | ||
+ | |||
+ | if (new_url != parcel_url) | ||
+ | { | ||
+ | if (llList2String(category_list,category_index) == "DJ") | ||
+ | { | ||
+ | // getProfilePic(new_avi); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | llParticleSystem([]); | ||
+ | } | ||
+ | |||
+ | set_parcel_url(new_url); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | timer() | ||
+ | { | ||
+ | retrieve_titelinfo(); | ||
+ | |||
+ | if (imagesfound > 0 ) | ||
+ | { | ||
+ | llSetPrimitiveParams([PRIM_COLOR,displayface,<1.0,1.0,1.0>, 0.0]); | ||
+ | |||
+ | llSetPrimitiveParams([PRIM_FULLBRIGHT,displayface,TRUE, PRIM_TEXTURE, displayface, llGetInventoryName(INVENTORY_TEXTURE,whichimage),<1.0,1.0,0.0>, <0.0,0.0,0.0>, 0.0]); | ||
+ | llSleep(1.5); | ||
+ | |||
+ | llSetPrimitiveParams([PRIM_COLOR,displayface,<1.0,1.0,1.0>, 1.0]); | ||
+ | |||
+ | whichimage +=1; | ||
+ | if (whichimage == imagesfound) | ||
+ | whichimage = 0; | ||
+ | } | ||
+ | |||
+ | llSetTimerEvent(update_time); | ||
+ | } | ||
+ | |||
+ | http_response(key id, integer status, list meta, string body) | ||
+ | { | ||
+ | if (id == httpreq_id) | ||
+ | { | ||
+ | if (status == 200) | ||
+ | { | ||
+ | // llSay(0," http response : " + body); | ||
+ | string feed = llGetSubString(body,llSubStringIndex(body, "<body>") + llStringLength("<body>"), llSubStringIndex(body,"</body>") - 1); | ||
+ | list feed_list = llParseString2List(feed,[","],[]); | ||
+ | string current_title_info= llList2String(feed_list,6); | ||
+ | integer length = llGetListLength(feed_list); | ||
+ | |||
+ | if(llList2String(feed_list,7)) | ||
+ | { | ||
+ | integer a = 7; | ||
+ | for(; a<length; ++a) | ||
+ | { | ||
+ | current_title_info += ", " + llList2String(feed_list,a); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | if (current_title_info != LastTitle) | ||
+ | { | ||
+ | display_line("3", current_title_info); | ||
+ | } | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | display_line("3", no_title_info); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | if (id == ppReqID) | ||
+ | { | ||
+ | llSay(0,"profile pic request returned"); | ||
+ | |||
+ | integer s1 = llSubStringIndex(body,"<meta name=\"imageid\" content=\""); | ||
+ | integer s1l = llStringLength("<meta name=\"imageid\" content=\""); | ||
+ | integer s2 = llSubStringIndex(body,"\" /> \n <meta name=\"agentid\"") - 1; | ||
+ | |||
+ | if(s1 == -1) | ||
+ | { | ||
+ | ProfilePic = NULL_KEY; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | ProfilePic = (key)llGetSubString(body,s1+s1l,s2); | ||
+ | |||
+ | if ( ProfilePic != "00000000-0000-0000-0000-000000000000") | ||
+ | { | ||
+ | if (lastWho == ProfilePic) | ||
+ | { | ||
+ | setProfilePic("a2769d9c-5733-dec8-ef86-a1bf27334de9"); | ||
+ | lastWho = NULL_KEY; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | setProfilePic(ProfilePic); | ||
+ | lastWho = ProfilePic; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | changed(integer ch) | ||
+ | { | ||
+ | if (ch & CHANGED_INVENTORY) | ||
+ | llResetScript(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | ////////////////////////////// | ||
+ | // end of script | ||
+ | ////////////////////////////// | ||
Revision as of 13:23, 31 August 2015
Contents |
dzShoutCast
As with all OpenSimian projects posted here, You are free to copy/modify/and IMPROVE the functionality to fit your needs.
If you have feedback on script errors, please post it to the page discussion User_talk:Dz/ShoutCast
Feature List
Note card configuration. MOAP style information display with Texture slide show option for non MOAP viewers. Configurable Access by owner, group, or individuals (DJs) Default Texture override. 10 user defined GENRE Categories. Unlimited URLs per GENRE. Usable on Group owned land.
Building the Board
You are free to design your own container for this script but there are implications of using M.O.A.P. as a display driver. Obviously.. if you expect users who do not have MOAP enabled viewers ( really?? still???) this may be less than useful.
I use a 2 prim object for my display boards. the display itself I size in a 1:2 (Height : Width) size. This allows The other thing to remember is that one face of your base prim is going to be turned into a web display, this will disable the ability to click on the display to generate a touch event to trigger a menu.
The BETA script
I will attempt to document the changes to the script as I upgrade from BETA release status. This is a copy of a board i have been using in SL for a couple years... it is not bug free but it has been useful and reliable.
// Viewer 2 Dynamic HTML Shoutcast stream controller // version 2.5 (c) dz questi 4/25/2011 // Converted existing script to use Floating Text and dynamic HTML display // Corrected multiple menu display feature // Added UUID access list feature // This script is a modification of the script described below // It is released in accordance to the license granted, and is released under the same license // Script: Shoutcast - radio controller // Version: 0.3 - released 10-2-2011 // Logic Scripts (Flennan Roffo) // (c) 2010 - Flennan Roffo (Logic Scripts) // LICENCE INFO // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. /////////////////////////////////////////////////////////////////////////////////////////// float update_time=30.0; /////// EDITABLE \\\\\\ string info_notecard="Radio Control info"; /////// EDITABLE \\\\\\ string config_notecard="Radio Control config"; /////// EDITABLE \\\\\\ string comment_char="#"; /////// EDITABLE \\\\\\ list sep_char_list= ["|"]; /////// EDITABLE \\\\\\ string no_title_info="(no title info available)"; /////// EDITABLE \\\\\\ // Buttons string button_MAIN = "MAIN"; /////// EDITABLE \\\\\\ string button_HELP = "HELP"; /////// EDITABLE \\\\\\ string button_NEXT = ">>"; /////// EDITABLE \\\\\\ string button_PREV = "<<"; /////// EDITABLE \\\\\\ string button_ON = "ON"; /////// EDITABLE \\\\\\ string button_OFF = "OFF"; /////// EDITABLE \\\\\\ // Profile picture particle parameters float PictSize = 1.0; key ppReqID = NULL_KEY; key ProfilePic = NULL_KEY; key lastWho = NULL_KEY; key MyOwner = NULL_KEY; // List of categories (=genres) list category_list=[]; // List of stations. KEEP THESE LISTS IN SYNCH! list station_category=[]; list station_name=[]; list station_desc=[]; list station_url=[]; list dj_UUID = []; list UUID_access = []; string StationId = ""; string GenreType = ""; string ThisTitle = ""; string LastTitle = ""; integer radio_status=0; // 0 - OFF 1 - ON string parcel_url=""; integer lineno=0; key reqid=NULL_KEY; key httpreq_id=NULL_KEY; integer config_error=FALSE; integer flag; integer section=0; integer displayface = 4; integer imagesfound = 0; integer whichimage = 0; // Access values. Note that users who are banned can not access the device even when access is public integer dj_access=TRUE; integer group_access=TRUE; integer public_access=FALSE; list banned_keys=[]; // Channels for menu and user input integer menu_channel; integer listen_handle; // Channel for deeded object integer DeedChannel = -142356; string ParcelName = ""; // Menu integer menu_type=0; // 0 - Main menu (genres) 1 - Station menu (stations) integer menu_num=0; // When more menu options need to be selectable then can be displayed on a menu (12), this is the menu number - menu number 0 is the first menu. // Genres and stations integer category_index=0; // Current index in category_list (genre) integer station_index=0; // Current index in station_* (station) integer num_categories=0; integer num_stations=0; ///// DEFINE the script Functions ///////// // Make request for title info using HTTP request retrieve_titelinfo() { string url=llList2String(station_url,station_index); // httpreq_id=llHTTPRequest(url + "/7.html HTTP/1.0\nUser-Agent: LSL Script (Mozilla Compatible)\n\n",[],""); // llSay(0," send to : " + url + "/7.html"); httpreq_id=llHTTPRequest(url + "/7.html HTTP/1.0\nUser-Agent: LSL Script (Mozilla Compatible)\n\n",[],""); // httpreq_id=llHTTPRequest(url + "/7.html",[],""); } // Make request for profile picture using metatag info getProfilePic(key AvatarKey) { ppReqID = llHTTPRequest( "http://world.secondlife.com/resident/" + (string)AvatarKey,[HTTP_METHOD,"GET"],""); } // Set particle system setProfilePic(key Texture) { if (Texture != NULL_KEY) { vector resizer = llGetScale(); PictSize = resizer.z / 0.75; llParticleSystem([PSYS_PART_FLAGS, PSYS_PART_EMISSIVE_MASK, PSYS_SRC_PATTERN, 4, PSYS_PART_START_ALPHA, 0.5, PSYS_PART_END_ALPHA, 0.5, PSYS_PART_START_COLOR, <1.0, 1.0, 1.0>, PSYS_PART_END_COLOR, <1.0, 1.0, 1.0>, PSYS_PART_START_SCALE, <PictSize, PictSize, 0.0>, PSYS_PART_END_SCALE, <PictSize, PictSize, 0.0>, PSYS_PART_MAX_AGE, 1.2, PSYS_SRC_MAX_AGE, 0.0, PSYS_SRC_ACCEL, <0.0, 0.0, 0.0>, PSYS_SRC_ANGLE_BEGIN, 0.0, PSYS_SRC_ANGLE_END, 0.0, PSYS_SRC_BURST_PART_COUNT, 8, PSYS_SRC_BURST_RADIUS, PictSize, PSYS_SRC_BURST_RATE, 0.1, PSYS_SRC_BURST_SPEED_MIN, 0.0, PSYS_SRC_BURST_SPEED_MAX, 0.0, PSYS_SRC_OMEGA, <0.0, 0.0, 0.0>, PSYS_SRC_TEXTURE, Texture]); } } // Update text displays with new info updateText(string line, string message) { if(line == "1") StationId = message; if (line == "2") GenreType = message; if (line == "3") { ThisTitle = message; // llSay(0," height " + (string) llGetPrimMediaParams(displayface,[PRIM_MEDIA_HEIGHT_PIXELS] )); // llSay(0," width " + (string) llGetPrimMediaParams(displayface,[PRIM_MEDIA_WIDTH_PIXELS] )); llSetText( StationId + " \n(" + GenreType + ") \nNow Playing :" + ThisTitle + "\n \nLast Song: " + LastTitle, <0.0, 1.0, 0.0>, 1.0); string WebText = "<body bgcolor='black'><div style='text-align: center;'><font size='7'><font color ='blue'><i><h1>" + StationId +"</i> ( " + GenreType + " )</h1><p><i><h3>On NOW</h3></i><p><h1>"+ ThisTitle + "<p><p></h1><h3><i>Last</i></h3><p><h1>" + LastTitle + "</h1></div></body>"; llSetPrimMediaParams(displayface,[PRIM_MEDIA_HEIGHT_PIXELS, 1024, PRIM_MEDIA_WIDTH_PIXELS, 2048, PRIM_MEDIA_CURRENT_URL, "data:text/html," + WebText]); LastTitle = ThisTitle; } } // Display a line display_line(string line, string message) { // llSay(0,message); updateText(line, message); } // Set the text display to default clear_display() { // Clears the display display_line("1","Radio Station ID"); display_line("2","Music Genre...."); display_line("3","Now Playing...."); } // Make a menu / dialog make_menu(key id) { menu_channel=random_channel(); if (radio_status == 0) { menu_type=0; menu_num=0; llDialog(id,"Menu: Status\n\nRadio is OFF", [ "ON", "HELP" ],menu_channel); } else { if (menu_type ==0) { llDialog(id,"Menu: Genres", category_menu(menu_num),menu_channel); } else { llDialog(id,"Menu: Stations\nGenre: " + llList2String(category_list,category_index), station_menu(menu_num),menu_channel); } } if (listen_handle != 0) llListenRemove(listen_handle); listen_handle=llListen(menu_channel,"",id,""); } // Make the menu option list for menu: catagories (genres) list category_menu(integer num) { integer len=llGetListLength(category_list); list menu=[]; if (len > 9) // If more then 9 items (12 minus the 3 buttons for MAIN/HELP and PREV, NEXT) { integer last_sub=(len-1)/9; // submenus start at 0. 9th entry is in submenu 0, 10th in 1, etc. if (num > last_sub) { llWhisper(0,"error: wrong submenu number: " + (string) num + "."); return [ "MAIN" ]; } else { integer first=9*num; while (--len >= first) menu+=(list)llList2String(category_list,len); if (num == 0) menu+=(list)button_HELP; else menu+=(list)button_MAIN; if (num == 0) menu+=(list)button_OFF; else menu+=(list)button_PREV; if (num != last_sub) menu+=(list)button_NEXT; } } else { while (--len >= 0) menu+=(list)llList2String(category_list,len); menu+=(list)button_OFF; menu+=(list)button_HELP; } return menu; // order_buttons(menu); } // Returns the number of stations in a certain category integer stations_in_category(integer cat) { integer count=0; integer i; integer len=llGetListLength(station_category); string category=llList2String(category_list,cat); for (i=0; i < len; i++) if (category == llList2String(category_list,i)) count++; return count; } // Returns a list of station names in a certain category (genre) list station_list(integer category) { list s=[]; integer i; integer catcnt = llGetListLength(station_name); string cname=llList2String(category_list,category_index); for (i = 0; i < catcnt; i++) if (llList2String(station_category,i) == cname) s+=(list)llList2String(station_name,i); return s; } // Returns the list of stations for the station menu, depending on the submenu number list station_menu(integer num) { list stations=station_list(category_index); integer len=llGetListLength(stations); list menu=[]; if (len > 11) // 12 - 1 for MAIN menu { integer last_sub=(len-1)/9; if (num > last_sub) { llWhisper(0,"error: wrong submenu number: " + (string) num + "."); return [ "MAIN" ]; } else { integer first=9*num; integer last=9*num+8; // llSay(0,"delivering menu for stations" + (string) first + " to " + (string) last); menu+=(list)button_MAIN; if (num > 0) menu+=(list)button_PREV; if (num < last_sub) menu+=(list)button_NEXT; if (len > last) len =last; while (--len >= first) menu+=(list)llList2String(stations,len); } } else { menu+=(list)button_MAIN; while (--len >= 0) menu+=(list)llList2String(stations,len); } return menu; // order_buttons(menu); } // Returns whether av with key id has access integer has_access(key id) { if (id == MyOwner) return TRUE; if (llListFindList(banned_keys,(list)id) != -1) return FALSE; if (llListFindList(UUID_access,(list)id) != -1) return TRUE; if (dj_access && llListFindList(dj_UUID,(list)id) != -1) return TRUE; if (group_access && llSameGroup(id)) return TRUE; if (public_access) return TRUE; return FALSE; } // Gets a random channel -- uses a wide range of big negative channel numbers seldomly used integer random_channel() { integer min=-2147483647; integer max=-1000; return (integer) (min + llFrand(max-min)); } // Returns a true value depending on the first character in input - anything else is assumed false. integer true_value(string input) { string value=llToLower(llGetSubString(input,0,0)); if (value == "y" || value == "t" || value =="1") return TRUE; return FALSE; } // Return if more input should be processed (if not at EOF) - sets ConfigError if any config error found. Reading config card stops at the first error. integer process_line(string dataline) { string line=llStringTrim(dataline,STRING_TRIM); integer index=llSubStringIndex(line,comment_char); if (index==0) // line starts with comment - ignore line return TRUE; if (index!=-1) line=llStringTrim(llGetSubString(line,0,index-1),STRING_TRIM_TAIL); // skip everything after comment_char and trim tail if (line=="") // Ignore blank lines return TRUE; if (llToLower(line) == "[access]") { section = 1; return TRUE; } else if (llToLower(line) == "[banned]") { section = 2; return TRUE; } else if (llToLower(line) == "[genre]") { section = 3; return TRUE; } else if (llToLower(line) == "[station]") { section = 4; return TRUE; } else if (llGetSubString(line,0,0) == "[" && llGetSubString(line,-1,-1) == "]") { llWhisper(0,"error: malformed section found at line " + (string)lineno + ".\n" + dataline); config_error=TRUE; return FALSE; } if (section == 0) { llWhisper(0,"error: no section found on line: " + (string) lineno); config_error = TRUE; return FALSE; } list breakup=llParseString2List(line,["="],[]); string field=llStringTrim(llList2String(breakup,0),STRING_TRIM); string values=llStringTrim(llList2String(breakup,1),STRING_TRIM); if (section == 1) // access { field=llToLower(field); if (field=="dj") { dj_access=true_value(values); return TRUE; } else if (field=="group") { group_access=true_value(values); return TRUE; } else if (field=="public") { public_access=true_value(values); return TRUE; } else if (llStringLength(field) == 36 ) { UUID_access += [(key)field]; return TRUE; } else { llWhisper(0,"error: invalid option on line: " + (string)lineno + ".\n" + dataline); config_error=TRUE; return FALSE; } } else if (section == 2) // ban list { key try=(key) field; if (try) { banned_keys+=(list)((key) field); return TRUE; } else return FALSE; } else if (section == 3) // categories { if (llListFindList(category_list,(list)field) == -1) { category_list+=(list)field; } else llWhisper(0,"genre: '" + field + "' already entered; double entry skipped."); return TRUE; } else if (section == 4) // stations { list parse=llParseString2List(line,sep_char_list, []); string category=llStringTrim(llList2String(parse,0),STRING_TRIM); string name=llStringTrim(llList2String(parse,1),STRING_TRIM); string desc=llStringTrim(llList2String(parse,2),STRING_TRIM); string url=llStringTrim(llToLower(llList2String(parse,3)),STRING_TRIM); string AviUUID=llStringTrim(llToLower(llList2String(parse,4)),STRING_TRIM); if (!available_category(category)) { llWhisper(0,"error: unknown genre on line: " + (string)lineno + ".\n" + dataline); config_error=TRUE; return FALSE; } if (llListFindList(station_url,(list)url) == -1 || llListFindList(station_category,(list)category) == -1 || llListFindList(station_name,(list)name) == -1) { num_stations++; station_category+=(list)category; station_name+=(list)name; station_desc+=(list)desc; station_url+=(list)url; dj_UUID += [(key)AviUUID]; return TRUE; } else { llWhisper(0,"This station is already entered under the same genre and same url and is skipped.\nStation: " + name + "\nGenre: '" + category + "'\nURL: " + url); return TRUE; } } return FALSE; } // Sets the parcel URL and updates the display set_parcel_url(string url) { parcel_url=url; llSetParcelMusicURL(parcel_url); llRegionSay(DeedChannel,ParcelName + "|" + parcel_url); llSay(0," changinf url for " + ParcelName); if (parcel_url=="") { clear_display(); display_line("1","Radio is OFF"); display_line("2",""); display_line("3",""); } else { llWhisper(0,"station now set to " + llList2String(station_desc,station_index) + "."); display_line("1","Station: " + llList2String(station_desc,station_index)); display_line("2", llList2String(category_list,category_index)); display_line("3","Now playing....."); llSetTimerEvent(update_time); } } // Returns if a category (genre) exists. integer available_category(string category) { integer i; integer len=llGetListLength(category_list); for (i=0;i<len;i++) if (llToLower(category) == llToLower(llList2String(category_list,i))) return TRUE; return FALSE; } // Returns if a category (genre) is empty (i.e. there are no stations for this catagory (genre)) integer empty_category(string category) { integer i; integer len=llGetListLength(station_category); for (i=0; i < len; i++) if (llToLower(category) == llToLower(llList2String(station_category,i))) return FALSE; return TRUE; } // Removes categories (genres) for which no station is known. skip_empty_categories() { integer i=0; while (i<llGetListLength(category_list)) { if (empty_category(llList2String(category_list,i))) { llWhisper(0,"Warning: Genre '" + llList2String(category_list,i) + "' contains no stations and is deleted."); category_list=llDeleteSubList(category_list,i,i); } else i++; } num_categories=llGetListLength(category_list); } ///////////////////////////////////////////// // state default //////////////////////////////////////////// default { state_entry() { flag=FALSE; lineno=0; config_error=FALSE; num_stations=0; num_categories=0; radio_status=0; menu_num=0; menu_type=0; MyOwner = llGetOwner(); list details = llGetParcelDetails(llGetPos(), [PARCEL_DETAILS_NAME]); ParcelName = llList2String(details ,0); imagesfound = llGetInventoryNumber(INVENTORY_TEXTURE); llParticleSystem([]); if (imagesfound < 1) { llSetPrimitiveParams([PRIM_FULLBRIGHT,displayface,TRUE, PRIM_TEXTURE, displayface, "adbff0cd-09c0-54b1-aeaa-ffd6eabfcab09",<1.0,1.0,0.0>, <0.0,0.0,0.0>, 0.0]); } else { llSetPrimitiveParams([PRIM_FULLBRIGHT,displayface,TRUE, PRIM_TEXTURE, displayface, llGetInventoryName(INVENTORY_TEXTURE,0),<1.0,1.0,0.0>, <0.0,0.0,0.0>, 0.0]); } llSetPrimMediaParams(displayface,[PRIM_MEDIA_HEIGHT_PIXELS, 1024, PRIM_MEDIA_WIDTH_PIXELS, 2048, PRIM_MEDIA_PERMS_CONTROL , PRIM_MEDIA_PERM_NONE]); if (llGetInventoryType(config_notecard) == INVENTORY_NOTECARD) { reqid=llGetNotecardLine(config_notecard,lineno++); llWhisper(0, "Reading config notecard..."); display_line("1","Reading configuration."); display_line("2","Wait...."); display_line("3",""); } else { llWhisper(0,"No config notecard '" + config_notecard + "' present."); state offline; } } on_rez(integer param) { llResetScript(); } dataserver(key id, string data) { if (reqid==id) { if (data==EOF) { skip_empty_categories(); llWhisper(0,"Configuration ok.\n" + (string)num_categories + " genres and " + (string)num_stations + " stations."); display_line("1","Configuration OK"); display_line("2","Genres : " + (string)num_categories); display_line("3","Stations: " + (string)num_stations); state menu; } else { if (process_line(data)) reqid=llGetNotecardLine(config_notecard,lineno++); else if (config_error) { llWhisper(0,"errors found in configuration. please correct them."); state offline; } } } } changed(integer ch) { if (ch & CHANGED_INVENTORY) llResetScript(); } } ///////////////////////////////////////////// // state offline //////////////////////////////////////////// state offline { state_entry() { llWhisper(0,"Reset on owner touch or when notecard updated."); } touch_start(integer t) { if (llDetectedKey(0)==llGetOwner()) llResetScript(); } changed(integer ch) { if (ch & CHANGED_INVENTORY) llResetScript(); } } ///////////////////////////////////////////// // state menu //////////////////////////////////////////// state menu { state_entry() { menu_type=0; menu_num=0; listen_handle=0; } on_rez(integer param) { llResetScript(); } touch_start(integer total_num) { key toucher=llDetectedKey(0); if (has_access(toucher)) { make_menu(toucher); } else llWhisper(0,"sorry, no access."); } listen(integer chan, string name,key id,string msg) { integer index; if (menu_type == 0) // main menu { if (msg == button_MAIN) { menu_type=0; menu_num =0; make_menu(id); } else if (msg == button_NEXT) { menu_num++; make_menu(id); } else if (msg == button_PREV) { menu_num--; make_menu(id); } else if (msg == button_ON) { radio_status=1; display_line("1","Radio is ON"); menu_num=0; llWhisper(0,"Radio now turned on."); make_menu(id); } else if (msg == button_OFF) { radio_status=0; set_parcel_url(""); llSetTimerEvent(0.0); llWhisper(0,"Radio now turned off."); } else if (msg == button_HELP) { if (llGetInventoryType(info_notecard) == INVENTORY_NOTECARD) { llGiveInventory(id,info_notecard); } else llWhisper(0,"sorry, help not available."); } else if (radio_status == 1) { index = llListFindList(category_list, (list)msg); if (index == -1) llWhisper(0,"error: genre not found: " + msg); else { category_index=index; llWhisper(0,"Genre now set to " + llList2String(category_list,category_index) + "."); menu_type=1; menu_num=0; make_menu(id); } } } else if (menu_type == 1 && radio_status == 1) // station menu { if (msg == button_MAIN) { menu_type=0; menu_num =0; make_menu(id); } else if (msg == button_NEXT) { menu_num++; make_menu(id); } else if (msg == button_PREV) { menu_num--; make_menu(id); } else { index = llListFindList(station_name, (list)msg); if (index == -1) llWhisper(0,"error: station not found: " + msg); else { station_index=index; string new_url=llList2String(station_url,station_index); string new_avi=llList2String(dj_UUID,station_index); llSay(0, llList2String(category_list,category_index) + " - found profile ID " + new_avi); if (new_url != parcel_url) { if (llList2String(category_list,category_index) == "DJ") { // getProfilePic(new_avi); } else { llParticleSystem([]); } set_parcel_url(new_url); } } } } } timer() { retrieve_titelinfo(); if (imagesfound > 0 ) { llSetPrimitiveParams([PRIM_COLOR,displayface,<1.0,1.0,1.0>, 0.0]); llSetPrimitiveParams([PRIM_FULLBRIGHT,displayface,TRUE, PRIM_TEXTURE, displayface, llGetInventoryName(INVENTORY_TEXTURE,whichimage),<1.0,1.0,0.0>, <0.0,0.0,0.0>, 0.0]); llSleep(1.5); llSetPrimitiveParams([PRIM_COLOR,displayface,<1.0,1.0,1.0>, 1.0]); whichimage +=1; if (whichimage == imagesfound) whichimage = 0; } llSetTimerEvent(update_time); } http_response(key id, integer status, list meta, string body) { if (id == httpreq_id) { if (status == 200) { // llSay(0," http response : " + body); string feed = llGetSubString(body,llSubStringIndex(body, "<body>") + llStringLength("<body>"), llSubStringIndex(body,"</body>") - 1); list feed_list = llParseString2List(feed,[","],[]); string current_title_info= llList2String(feed_list,6); integer length = llGetListLength(feed_list); if(llList2String(feed_list,7)) { integer a = 7; for(; a<length; ++a) { current_title_info += ", " + llList2String(feed_list,a); } } if (current_title_info != LastTitle) { display_line("3", current_title_info); } } else { display_line("3", no_title_info); } } if (id == ppReqID) { llSay(0,"profile pic request returned"); integer s1 = llSubStringIndex(body,"<meta name=\"imageid\" content=\""); integer s1l = llStringLength("<meta name=\"imageid\" content=\""); integer s2 = llSubStringIndex(body,"\" /> \n <meta name=\"agentid\"") - 1; if(s1 == -1) { ProfilePic = NULL_KEY; } else { ProfilePic = (key)llGetSubString(body,s1+s1l,s2); if ( ProfilePic != "00000000-0000-0000-0000-000000000000") { if (lastWho == ProfilePic) { setProfilePic("a2769d9c-5733-dec8-ef86-a1bf27334de9"); lastWho = NULL_KEY; } else { setProfilePic(ProfilePic); lastWho = ProfilePic; } } } } } changed(integer ch) { if (ch & CHANGED_INVENTORY) llResetScript(); } } ////////////////////////////// // end of script //////////////////////////////
Group Land add-on
When you try and change the URL on group owned land, you need to use an object deeded to the group.. If you deed the ShoutCast board, you will probably lose the ability to edit the config note card...
Place this script in a small prim somewhere on the parcel and then deed the prim to the proper group. It will relay the original ShoutCast board message....
// dzShoutCast group land add on BETA // Doug Osborn MOSES grid 9/17/2014 // Copyright (c) 2014, 2015 Douglas Osborn // All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // 1. Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // This scipt is designed to listen for a shoutcast board on the current parcel // to send a "Change URL" message. It is useful on group owned land. // Instead of deeding the shoutcast board to the group,.. Place this script in a prim, // leave the prim somewhere on the parcel, and then deed the prim to the group. integer listen_handle = 0; // Initialize handler to register listener string ParcelName = ""; integer listen_channel = -142356; default { state_entry() // Set the parcel Name and start listening for messages on the designated channel { ParcelName = llList2String(llGetParcelDetails(llGetPos(), [PARCEL_DETAILS_NAME]),0); listen_handle = llListen( listen_channel, "", "", ""); } listen( integer channel, string name, key id, string message ) // Wait for incoming messages... { if(llGetSubString(name, 0, 6) == "dzShout") // Check to see they are from an object name we expect { list msg_list = llParseString2List(message,["|"],[""]); // parse the listen message string msgParcel = llList2String(msg_list ,0); // Looking for the parcel name it was sent from string msgHttp = llList2String(msg_list ,1); // and double checking it sent us an URL if (llGetSubString(msgHttp, 0, 3) == "http" && msgParcel == ParcelName) { llSetParcelMusicURL(msgHttp); // if everyhting looks good, set the new URL // llSay(0,"Setting url to : " + msgHttp); // and squawk about it Or not } } } on_rez(integer param) //Triggered when the object is rezed { llResetScript(); // By resetting the script on rez it forces the listen to re-register. } changed(integer mask) { if(mask & CHANGED_OWNER) // Triggered when the object containing this script changes owner. { llResetScript(); // should reset when deeded to group in place. } } }
Configuration Notecard
This is how URLs are added to the menus and grouped into Genres.