User:Becky Pippen/Shared Media LSL Recipes

From Second Life Wiki
< User:Becky Pippen
Revision as of 15:54, 22 March 2010 by Becky Pippen (talk | contribs) (Shared Media LSL Recipes)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

How-to's with Media-on-a-Prim

The examples here assume you're comfortable with LSL, and you just need a quick reference for the syntax of handling JavaScript in LSL for Shared Media scripting. All the examples on this page are ready to copy-n-paste and drop into a prim — no other setup needed. The key syntax in each example is highlighted. There's no error handling in these minimalist examples, so add plenty of your own.

Display plain text — XyText replacement

Plain text

default
{
    state_entry()
    {
        integer face = 4;
        string message = "Hello World";
        llSetPrimMediaParams(face,
            [PRIM_MEDIA_CURRENT_URL, "data:text/html," + message]);
    }
} 

Comments:

  • An avatar must press the Reload button on the browser to see this rendered in-world. A page will be auto-loaded if you include the parameter PRIM_MEDIA_AUTO_PLAY, TRUE, and if the avatar has auto-load enabled in preferences.
  • There's no need to add any %-hex-escaping or to use llEscapeURL(); llSetPrimMediaParams(face, [PRIM_MEDIA_CURRENT_URL, url]) will automatically escape the string for you.
  • This works by feeding a data URI to the on-prim browser. Think of it as serving the contents of the page in the URL itself. For more information, see here and here.
  • The in-world browser limits the data URI (or any address) to 1024 bytes after %-hex-escaping and encoded in UTF-8. See below for tricks using external or self-served JavaScript or CSS to leverage the data URI.
  • If the data URI contains just plain text, the prefix "data:text/html," can be abbreviated "data:,".

Force a page reload

Auto-refresh
integer face = 4;
string myURL;
integer seq = 0; // sequence number for unique URLs

default
{
    state_entry()
    {
        llRequestURL();
    }

    http_request(key id, string method, string body)
    {
        if (method == URL_REQUEST_GRANTED) {
            myURL = body;
            llSetPrimMediaParams(face,
                [PRIM_MEDIA_AUTO_PLAY, TRUE,
                 PRIM_MEDIA_CURRENT_URL, myURL]);
            llSetTimerEvent(5.0);
        } else if (method == "GET") {
            llHTTPResponse(id, 200, "Sim FPS: " + (string)llGetRegionFPS());
        }
    }

    timer()
    {
        llSetPrimMediaParams(face,
            [PRIM_MEDIA_CURRENT_URL, myURL + "/?r=" + (string)(++seq)]);
    }
} 

Comments:

  • The page will reload automatically if PRIM_MEDIA_AUTO_PLAY is enabled and if the new PRIM_MEDIA_CURRENT_URL is different. This technique appends a short dummy parameter (like "/?r=12") to the end of the URL to make it different each time, forcing a reload. The parameter is otherwise ignored.

Display text with HTML markup

HTML markup

default
{
    state_entry()
    {
        integer face = 4;
        string message = "<i>Hello</i><h2>World!</h2>";
        llSetPrimMediaParams(face,
            [PRIM_MEDIA_CURRENT_URL, "data:text/html," + message]);
    }
} 

Comments:

  • The text of the data URI will be %-hex-escaped automatically for you, so there's no need to use llEscapeURL(). All you need in the data URI is the prefix data:text/html, plus your HTML.

Change window resolution

Resized web window

default
{
    state_entry()
    {
        integer face = 4;
        string message = "Hello World";
        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, "data:text/html," + message,
                PRIM_MEDIA_WIDTH_PIXELS, 128,
                PRIM_MEDIA_HEIGHT_PIXELS, 32]);
    }
} 

Comments:

  • The browser will add scroll bars if the rendered HTML doesn't fit within the specified PRIM_MEDIA_* size.

Display an image — works with animated GIFs too

Image on a prim

default
{
    state_entry()
    {
        integer face = 4;
        string imageURL =
          "http://upload.wikimedia.org/wikipedia/commons/7/70/Rotating_earth_(small).gif";
        string dataURI = "data:text/html,<object data='" + imageURL + "'></object>";
        llSetPrimMediaParams(face,
            [PRIM_MEDIA_CURRENT_URL, dataURI,
             PRIM_MEDIA_WIDTH_PIXELS, 256,
             PRIM_MEDIA_HEIGHT_PIXELS, 256]);
    }
} 

Comments:

  • If attempting to display YouTube video full-frame, see this discussion.
  • For images, you can also use <image src= > like this:
string dataURI="data:text/html,<img src='" + imageURL + "'>";

Display a background image, tiled

Method #1 — using CSS in a style element in <head>

Tiled background image

default
{
    state_entry()
    {
        integer face = 4;
        string imageURL =
            "http://www.google.com/intl/en_ALL/images/logo.gif";
        string dataURI = "data:text/html,<head>"
            + "<style type='text/css'>body{background-image:url(\""
            + imageURL + "\");}</style></head><body>Hello World</body>";
        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
    }
} 

Method #2 — using background attribute in <body>

default
{
    state_entry()
    {
        integer face = 4;
        string imageURL = "http://www.google.com/intl/en_ALL/images/logo.gif";
        string dataURI = "data:text/html,<body background='" + imageURL + "'>"
            + "Hello World"
            + "</body>";
        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
    }
} 

Method #3 — using style attribute in <body>

default
{
    state_entry()
    {
        integer face = 4;
        string imageURL = "http://www.google.com/intl/en_ALL/images/logo.gif";
        string dataURI = "data:text/html,<body style='background-image:url(\"" + imageURL + "\")'>"
            + "Hello World"
            + "</body>";
        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
    }
} 

Text colors — using <font> element

Font color

default
{
    state_entry()
    {
        integer face = 4;
        string message = "<font color='Red'>Hello World</font>";
        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, "data:text/html," + message]);
    }
}  

Comments:

  • You can also specify color and other CSS rules in a style attribute in another element. For example, you can replace message in the example above with:
string message = "<h2 style='color:#FF0000'>Hello World</h2>";

Link to external CSS file (forms example)

External CSS file

default
{
    state_entry()
    {
        integer face = 4;
        string externalCSS =
            "http://www.google.com/css/modules/buttons/g-button-chocobo.css";

        string dataURI = "data:text/html,<head><link href='" +
                externalCSS + "' rel='stylesheet' type='text/css' /></head>" +
                "<form><input type='button' value='Click Me' class='g-button' /></form>";

        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
    }
}  

Comments:

  • This is one way to work around the 1024-byte limitation of data URIs. The external CSS can be as large as the browser permits. Also see the examples below for putting JavaScript in an external file.
  • The element <head> and the attribute type='text/css' may be omitted to make the data URI a few bytes shorter.

Calling external JavaScript functions (JQuery example)

Calling external JQuery

integer face = 4;
string externalJavascript =
    "http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js";
default
{
    state_entry()
    {
        string dataURI = "data:text/html," +
            "<head><script src='" + externalJavascript + "'></script></head>" +
            "<button>Toggle</button>" +
            "<p>Hello<br />World</p>" +
            "<script>$('button').click(function () " +
            "{$('p').slideToggle('slow');});</script>";

        llSetPrimMediaParams(face,
            [PRIM_MEDIA_CURRENT_URL, dataURI]);
    }
} 

Comments:

  • This is a technique for working around the 1024-byte limit for data URIs. When you can't fit all the JavaScript you need into one data URI, just move some JavaScript functions into an external file and give the browser a data URI that references the external file with a URL. In this example above, the JavaScript for JQuery lives in an external file on Google's servers. You just need enough bytes left over in the data URI to put a JavaScript function call with its parameters inside a <script> element.
  • A data URI can be used as a springboard to generate arbitrary complex web pages by making use of these elements:
    • a reference to external CSS with <link href= ,
    • a reference to external JavaScript with <script src=, and
    • a function call with parameters to the external JavaScript using <script>someFunction(args);</script>. (In the JQuery example above, the name of the external function is "$".)
  • See the next example for more of this same technique.

Self-served JavaScript

Example #1 — using <script src= and .innerHTML=

Self-served auto-refreshed pages

integer face = 4;
string selfServedJavascriptURL;

// This can be up to 2KBytes after %-hex-escaping:
string servedJavascript = "
function checklength(i){if (i<10) {i='0'+i;} return i;}      
function clock(){ 
 var now = new Date();  
 var hours = checklength(now.getHours());  
 var minutes = checklength(now.getMinutes());  
 var seconds = checklength(now.getSeconds());  
 var format = 1; //0=24 hour format, 1=12 hour format   
 var time;  
 if (format == 1) { 
  if (hours >= 12) {  
   if (hours ==12 ) { hours = 12;
   } else { hours = hours-12; } 
   time=hours+':'+minutes+':'+seconds+' PM';   
  } else if(hours < 12) { 
   if (hours ==0) {hours=12;}   
   time=hours+':'+minutes+':'+seconds+' AM';   
  }   
 }  
 if (format == 0) {time= hours+':'+minutes+':'+seconds;}   
 document.getElementById('clock').innerHTML=time;
 setTimeout('clock();', 500); 
} "; // yes, that's one long string.

displayPage()
{
    string dataURI = "data:text/html,<script src='" +
        selfServedJavascriptURL + "'></script>" +
        "<div id='clock'><script>clock();</script></div>";
    llSetPrimMediaParams(face, [PRIM_MEDIA_CURRENT_URL, dataURI]);
}

default
{
    state_entry()
    {
        llRequestURL();
    }

    http_request(key id, string method, string body)
    {
        if (method == URL_REQUEST_GRANTED) {
            selfServedJavascriptURL = body;
            displayPage();
        } else if (method == "GET") {
            llHTTPResponse(id, 200, servedJavascript);
        }
    }
} 

Comments:

  • This is a technique for trading the 1024-byte limit of data URIs for the 2048-byte-per-page limit that the script can serve itself. As in the previous example, when you can't fit all the JavaScript into one data URI, just move the JavaScript into an external file and reference the file with <script src=URL></script>. When the URL refers to the script's own HTTP-in URL, then the page served can contain up to 2K bytes, so you get at least that many bytes plus whatever logic you can fit into the rest of the data URI. If you move that JavaScript to an external web server, the size of the external JavaScript file can be as large as the client web browser can render.
  • Your script can serve an arbitrary number of different pages through one HTTP-in URL by appending a path or parameter to the URL.
  • This works by assigning a string containing HTML to the .innerHTML contents of the <div> element with id "clock". This achieves something similar to the technique published by Tali Rosca and mentioned here. In those techniques, a string is constructed containing an <a href= tag and an href= that points to the script's HTTP-in URL. That string then replaces the <body> element using:
document.getElementsByTagName('body')[0].innerHTML = new-content

Example #2 — using <body onload= (lag graph example)

Refreshed pages

integer face = 4;
string jsURL; // where to fetch external JavaScript
list numbers;
integer numSamples = 50;

// This is self-served in this example, but can be
// moved to an external server:
//
string externalJavascript()
{
    return
        "function bar(widthPct,heightPix) {" +
        " document.writeln(\"<hr style='padding:0;margin:0;" +
        "  margin-top:-1px;text-align:left;align:left;border=0;" +
        "  width:\"+widthPct+\"%;height:\"+heightPix+\"px;" +
        "  background-color:#c22;color:#c22;'>\");}" +
        " function graphBars(arr){for(var i=0;i<arr.length;++i)" +
        "  {bar(arr[i],18);}}";
}

default
{
    state_entry()
    {
        llClearPrimMedia(face);
        llRequestURL();
        integer i = numSamples;
        while (--i >= 0) {
            numbers += 0;
        }
    }

    timer()
    {
        numbers = llList2List(numbers, 1, -1) + [(integer)(6.0 * (45.0 - llGetRegionFPS()))];

        // The dataURI loads external JavaScript functions and calls one with parameters:

        string dataURI = "data:text/html," +
            "<head><script src='" + jsURL + "'></script></head>" +
            "<body onload=\"graphBars([" + llList2CSV(numbers) + "]);\"></body>";

        llSetPrimMediaParams(face, [PRIM_MEDIA_CURRENT_URL, dataURI, PRIM_MEDIA_AUTO_PLAY, TRUE]);
    }

    http_request(key id, string method, string body)
    {
        if (method == URL_REQUEST_GRANTED) {
            jsURL = body; // self-serve the JavaScript
            llSetTimerEvent(1.0);
        } else if (method == "GET") {
            llHTTPResponse(id, 200, externalJavascript());
        }
    }
} 

Comments:

  • Like the previous example, this is another variation of how to structure a data URI to serve as a springboard for arbitrarily large pages. In this example, the data URI first uses a <script src=...> tag to read a file of external JavaScript functions in the <head> element, and then the onload= attribute in <body> triggers a call to one of the functions which executes a loop creating a bunch of <hr> elements that form the bar chart. The size of HTML generated by the JavaScript can be as large as the browser permits.
  • In this example, jsURL is set to the script's own HTTP-in URL so that the example can be self-contained, but you can point jsURL to an external URL as well. If served by an external server, the JavaScript read in <head> can be as large as the browser permits.
  • <head></head> can be omitted in the data URI surrounding the <script src= tag.

Make TinyURLs by script

string myTinyURL;

default
{
    state_entry()
    {
        llRequestURL();
    }

    http_request(key id, string method, string body)
    {
        if (method == URL_REQUEST_GRANTED) {
            // Send our full URL to tinyurl.com for conversion
            // The answer will come back in http_response()
            llHTTPRequest("http://tinyurl.com/api-create.php?url=" + body, [], "");
        } else if (method == "GET") {
            llHTTPResponse(id, 200, "Hello Real World from the Virtual World");
        }
    }

    http_response(key req, integer stat, list met, string body)
    {
        myTinyURL = body;
        llOwnerSay("My HTTP-in TinyURL is: " + myTinyURL + " , Click Me!");
    }
} 

Comments:

  • Any URL or data URI can be mapped to a TinyURL. The example above reduces an assigned HTTP-in URL, which is a long one such as:
http://sim4605.agni.lindenlab.com:12046/cap/2b9f06f7-431e-5b0f-9271-2d03bd15370b

into a TinyURL this size:

http://tinyurl.com/y9etul3

Development hints

  1. You can develop your HTML and JavaScript outside of Second Life by putting your JavaScript incantations in data URIs that you feed to any web browser. For the most compatible browsers, use Safari or Google Chrome, or any other that uses the WebKit HTML rendering engine. Personally I like the open source Arora web browser for simulating Media-on-a-Prim: it's based on WebKit and has a handy inspect feature for browsing the HTML and debugging the JavaScript that you're developing.
  2. If you don't have direct access to a web server while developing this stuff, then consider installing a local copy of Apache web server on your computer. You can restrict it to local access. Then you can refer to files on your own computer with URLs that start with http://localhost/.... That lets you, for example, edit myfile.js on your own computer while working on a data URI that contains <script src="http://localhost/myfile.js">. Then when myfile.js is working, you can put the debugged JavaScript into an LSL script that will serve it through its HTTP-in port. That's easier than debugging all that in-world.

References and sources