Difference between revisions of "LlSetPrimMediaParams/Tricks"

From Second Life Wiki
Jump to navigation Jump to search
(Replaced <source> with <syntaxhighlight>, did some polishing (mostly inserting spaces according to good scripting style guidelines) and added this to a nicer category, too)
 
(5 intermediate revisions by 2 users not shown)
Line 2: Line 2:
{{Warning|While these tricks are still useful, they are largely unneeded now with [[llSetContentType]] and [[CONTENT_TYPE_HTML]]}}
{{Warning|While these tricks are still useful, they are largely unneeded now with [[llSetContentType]] and [[CONTENT_TYPE_HTML]]}}


Pre-Viewer 2.0 interfacing with an LSL script is a jumble of llDialog, chats, touches and possibly external web sites.  With Shared Media that all changes.
Pre-Viewer 2.0 interfacing with an LSL script is a jumble of [[llDialog]], chats, touches and possibly external web sites.  With [[Shared Media]] that all changes.


One step at a time .......
One step at a time .......
=== llSetPrimMediaParams ===
=== llSetPrimMediaParams ===
* Use llSetPrimMediaParams to set the url and various parameters on the face of a prim via LSL.
* Use [[llSetPrimMediaParams]] to set the URL and various parameters on the face of a prim via LSL.
<source lang="lsl2">  
<syntaxhighlight lang="lsl2">  
default
default
{
{
    state_entry()
state_entry()
    {
{
        llSetPrimMediaParams(0,                             // Side to display the media on.
llSetPrimMediaParams(0, // Side to display the media on.
            [PRIM_MEDIA_AUTO_PLAY,TRUE,                     // Show this page immediately
[PRIM_MEDIA_AUTO_PLAY, TRUE, // Show this page immediately
            PRIM_MEDIA_CURRENT_URL,"http://google.com",   // The url currently showing
PRIM_MEDIA_CURRENT_URL, "https://google.com", // The URL currently showing
            PRIM_MEDIA_HOME_URL,"http://google.com",       // The url if they hit 'home'
PRIM_MEDIA_HOME_URL, "https://google.com", // The URL if they hit 'home'
            PRIM_MEDIA_HEIGHT_PIXELS,512,                 // Height/width of media texture will be
PRIM_MEDIA_HEIGHT_PIXELS, 512, // Height/width of media texture will be
            PRIM_MEDIA_WIDTH_PIXELS,512]);                 //  rounded up to nearest power of 2.
PRIM_MEDIA_WIDTH_PIXELS, 512]); //  rounded up to nearest power of 2.
    }
}
}
}
</source>
</syntaxhighlight>


=== data: urls ===
=== "data:" URLs ===
There is a special url type: "data:" that lets you send the html ''in the url''.  Paste the below into your browser's address bar.
There is a special URL type: "data:" that lets you send the HTML ''in the URL''.  Paste the below into your browser's address bar.
<pre>
<syntaxhighlight lang="html">
data:text/html,<h1>This is a test</h1><h2>This is a test</h2><h3>This is a test</h3>
data:text/html,<h1>This is a test</h1><h2>This is a test</h2><h3>This is a test</h3>
</pre>
</syntaxhighlight>


=== llSetPrimMediaParams for data: urls ===
=== llSetPrimMediaParams for "data:" URLs ===
* Thus you can build arbitrary html in your LSL script and display it on the face of the prim
* Thus, you can build arbitrary HTML in your LSL script and display it on the face of the prim
<source lang="lsl2">
<syntaxhighlight lang="lsl2">
show(string html)
show(string html)
{
{
    html = "data:text/html," + llEscapeURL(html);
html = "data:text/html," + llEscapeURL(html);
    llSetPrimMediaParams(0,                 // Side to display the media on.
llSetPrimMediaParams(0, // Side to display the media on.
            [PRIM_MEDIA_AUTO_PLAY,TRUE,     // Show this page immediately
[PRIM_MEDIA_AUTO_PLAY, TRUE, // Show this page immediately
            PRIM_MEDIA_CURRENT_URL,html,   // The url currently showing
PRIM_MEDIA_CURRENT_URL, html, // The url currently showing
            PRIM_MEDIA_HOME_URL,html,       // The url if they hit 'home'
PRIM_MEDIA_HOME_URL, html, // The url if they hit 'home'
            PRIM_MEDIA_HEIGHT_PIXELS,512,   // Height/width of media texture will be
PRIM_MEDIA_HEIGHT_PIXELS, 512, // Height/width of media texture will be
            PRIM_MEDIA_WIDTH_PIXELS,512]); //  rounded up to nearest power of 2.
PRIM_MEDIA_WIDTH_PIXELS, 512]); //  rounded up to nearest power of 2.
}
}
default
default
{
{
    state_entry()
state_entry()
    {
{
        show("<h1>This is a test</h1><h2>This is a test</h2><h3>This is a test</h3>");
show("<h1>This is a test</h1><h2>This is a test</h2><h3>This is a test</h3>");
    }
}
}
}
</source>
</syntaxhighlight>


=== HTTP-In ===
=== HTTP-In ===
* Scripts can become web servers via HTTP-In
* Scripts can become web servers via HTTP-In
<lsl>
<syntaxhighlight lang="lsl2">
default
default
{
{
    state_entry()
state_entry()
    {
{
        llRequestURL();
llRequestURL();
    }
}
   
    http_request(key id, string method, string body)
http_request(key id, string method, string body)
    {
{
        if (method == URL_REQUEST_GRANTED)
if (method == URL_REQUEST_GRANTED)
        {
{
            llSay(0,"URL is: " + body);
llSay(0, "URL is: " + body);
        }
}
        else if (method == "GET")
else if (method == "GET")
        {
{
            llSay(0,"Received page request.");
llSay(0, "Received page request.");
            llHTTPResponse(id,200,"OK");
llHTTPResponse(id, 200, "OK");
        }
}
    }
}
}  
}
</lsl>
</syntaxhighlight>


=== llSetPrimMediaParams for data: urls that link back to HTTP-In ===
=== llSetPrimMediaParams for "data:" URLs that link back to HTTP-In ===
* Which means you can make web pages that trigger other page views via http-in links
* Which means you can make web pages that trigger other page views via HTTP-in links
<source lang="lsl2">
<syntaxhighlight lang="lsl2">
show(string html)
show(string html)
{
{
    html = "data:text/html," + llEscapeURL(html);
html = "data:text/html," + llEscapeURL(html);
    llSetPrimMediaParams(0,                 // Side to display the media on.
llSetPrimMediaParams(0, // Side to display the media on.
            [PRIM_MEDIA_AUTO_PLAY,TRUE,     // Show this page immediately
[PRIM_MEDIA_AUTO_PLAY, TRUE, // Show this page immediately
            PRIM_MEDIA_CURRENT_URL,html,   // The url currently showing
PRIM_MEDIA_CURRENT_URL, html, // The URL currently showing
            PRIM_MEDIA_HOME_URL,html,       // The url if they hit 'home'
PRIM_MEDIA_HOME_URL, html, // The URL if they hit 'home'
            PRIM_MEDIA_HEIGHT_PIXELS,512,   // Height/width of media texture will be
PRIM_MEDIA_HEIGHT_PIXELS, 512, // Height/width of media texture will be
            PRIM_MEDIA_WIDTH_PIXELS,512]); //  rounded up to nearest power of 2.
PRIM_MEDIA_WIDTH_PIXELS, 512]); //  rounded up to nearest power of 2.
}
}


string replace_all(string src, string target, string replace)
string replace_all(string src, string target, string replace)
{
{
    return llDumpList2String(llParseString2List(src,[target],[]),replace);
return llDumpList2String(llParseString2List(src, [target], []), replace);
}
}


string get_query(key id, string name)
string get_query(key id, string name)
{
{
    string query = llGetHTTPHeader(id,"x-query-string");
string query = llGetHTTPHeader(id, "x-query-string");
    query = replace_all(query,"+"," ");
query = replace_all(query, "+", " ");
    query = llUnescapeURL(query);
query = llUnescapeURL(query);
    list q = llParseString2List(query,["=","&",";"],[]);
list q = llParseString2List(query, ["=", "&", ";"], []);
    integer i = llListFindList(q,[name]);
integer i = llListFindList(q, [name]);
    if (i != -1)
if (i != -1)
    {
{
        return llList2String(q,i+1);
return llList2String(q, i+1);
    }
}
   
    return "";
return "";
}
}


default
default
{
{
    state_entry()
state_entry()
    {
{
        show("<h1>This is a test</h1><h2>This is a test</h2><h3>This is a test</h3>");
show("<h1>This is a test</h1><h2>This is a test</h2><h3>This is a test</h3>");
        llRequestURL();
llRequestURL();
    }
}
   
    http_request(key id, string method, string body)
http_request(key id, string method, string body)
    {
{
        if (method == URL_REQUEST_GRANTED)
if (method == URL_REQUEST_GRANTED)
        {
{
            show("<h1><a href='" + body + "/?get=owner'>Owner ID</a><br><br><a href='" + body + "/?get=object'>Object ID</a></h1>");
show("<h1><a href='" + body + "/?get=owner'>Owner ID</a><br><br><a href='"
        }
+ body + "/?get=object'>Object ID</a></h1>");
        else if (method == "GET")
}
        {
else if (method == "GET")
            string get = get_query(id,"get");
{
            if (get == "owner")
string get = get_query(id, "get");
            {
if (get == "owner")
                llHTTPResponse(id,200,"Owner is: " + (string)llGetOwner());
{
            }
llHTTPResponse(id, 200, "Owner is: " + (string)llGetOwner());
            else if (get == "object")
}
            {
else if (get == "object")
                llHTTPResponse(id,200,"Object is: " + (string)llGetKey());
{
            }
llHTTPResponse(id, 200, "Object is: " + (string)llGetKey());
            else
}
            {          
else
                llHTTPResponse(id,400,"huh?");
{
            }
llHTTPResponse(id, 400, "huh?");
        }
}
    }
}
}
}
}
</source>
</syntaxhighlight>


=== Forms ===
=== Forms ===
* You can put forms in data: urls
* You can put forms in "data:" URLs:
<pre>
<syntaxhighlight lang="HTML">
data:text/html,<form action="http://google.com/search" method="GET">Search:<input type="text" name="q"><input type="submit" value="Submit"></form>
data:text/html,<form action="https://google.com/search" method="GET">Search:<input type="text" name="q"><input type="submit" value="Submit"></form>
</pre>
</syntaxhighlight>


=== The Magic ===
=== The Magic ===
* You can set the script HTTP-In url as the form action to get the results of the form sent back to the script
* You can set the script HTTP-In URL as the form action to get the results of the form sent back to the script
<syntaxhighlight lang="lsl2">
<syntaxhighlight lang="lsl2">
string html_base =  
string html_base =  
Line 163: Line 164:
show(string html)
show(string html)
{
{
    html = "data:text/html," + llEscapeURL(html) + "<span " + (string)((++r) % 10) + "/>";
html = "data:text/html," + llEscapeURL(html) + "<span " + (string)((++r) % 10) + "/>";
   
    llSetPrimMediaParams(0,                 // Side to display the media on.
llSetPrimMediaParams(0, // Side to display the media on.
            [PRIM_MEDIA_AUTO_PLAY,TRUE,     // Show this page immediately
[PRIM_MEDIA_AUTO_PLAY, TRUE, // Show this page immediately
            PRIM_MEDIA_CURRENT_URL,html,   // The url currently showing
PRIM_MEDIA_CURRENT_URL, html, // The url currently showing
            PRIM_MEDIA_HOME_URL,html,       // The url if they hit 'home'
PRIM_MEDIA_HOME_URL, html, // The url if they hit 'home'
            PRIM_MEDIA_HEIGHT_PIXELS,512,   // Height/width of media texture will be
PRIM_MEDIA_HEIGHT_PIXELS, 512, // Height/width of media texture will be
            PRIM_MEDIA_WIDTH_PIXELS,512]); //  rounded up to nearest power of 2.
PRIM_MEDIA_WIDTH_PIXELS, 512]); //  rounded up to nearest power of 2.
}
}


string replace_all(string src, string target, string replace)
string replace_all(string src, string target, string replace)
{
{
    return llDumpList2String(llParseString2List(src,[target],[]),replace);
return llDumpList2String(llParseString2List(src,[target],[]),replace);
}
}


string get_query(key id, string name)
string get_query(key id, string name)
{
{
    string query = llGetHTTPHeader(id,"x-query-string");
string query = llGetHTTPHeader(id, "x-query-string");
    query = replace_all(query,"+"," ");
query = replace_all(query, "+", " ");
    query = llUnescapeURL(query);
query = llUnescapeURL(query);
    list q = llParseString2List(query,["=","&",";"],[]);
list q = llParseString2List(query, ["=", "&", ";"], []);
    integer i = llListFindList(q,[name]);
integer i = llListFindList(q, [name]);
    if (i != -1)
if (i != -1)
    {
{
        return llList2String(q,i+1);
return llList2String(q, i + 1);
    }
}
   
    return "";
return "";
}
}


default
default
{
{
    state_entry()
state_entry()
    {
{
        llRequestURL();
llRequestURL();
    }
}
   
    http_request(key id, string method, string body)
http_request(key id, string method, string body)
    {
{
        if (method == URL_REQUEST_GRANTED)
if (method == URL_REQUEST_GRANTED)
        {
{
            url = body + "/";
url = body + "/";
           
            show(replace_all(html_base,"%url%",url));
show(replace_all(html_base, "%url%", url));
        }
}
        else if (method == "GET")
else if (method == "GET")
        {
{
            llSetText(get_query(id,"text"),<1,1,0>,1);
llSetText(get_query(id, "text"), <1.0, 1.0, 0.0>, 1);
            show(replace_all(html_base,"%url%",url));
show(replace_all(html_base, "%url%", url));
            llHTTPResponse(id,200,"Loading....");
llHTTPResponse(id, 200, "Loading....");
        }
}
    }
}
}
}
</source>
</syntaxhighlight>


=== The Limits ===
=== The Limits ===
* 1024 bytes per url
* 1024 bytes per URL
* llEscapeURLs means non-alphanumeric characters are 3 bytes (ouch)
* [[llEscapeURL]]s means non-alphanumeric characters are 3 bytes (ouch)
** I'm actually not clear on if this is really needed.  I seem to sometimes get blank pages if I don't but some pages render fine with out it.  Maybe there is a subset of symbols that must be escaped and escaping only those would ease some of the space constraints?
** I'm actually not clear on if this is really needed.  I seem to sometimes get blank pages if I don't, but some pages render fine without it.  Maybe there is a subset of symbols that must be escaped and escaping only those would ease some of the space constraints?
* Must force another page view after form submission since HTTP-In can't generate HTML
* Must force another page view after form submission since HTTP-In can't generate HTML
* No real way of knowing which avatar is interacting with the HTML
* No real way of knowing which avatar is interacting with the HTML


=== The Tricks ===
=== The Tricks ===
* <base href="<http-in-url>/">
* <syntaxhighlight lang="HTML" inline><base href="<http-in-url>/"></syntaxhighlight>
** HTTP-In urls are long, especially if escaped.  In a page that includes more than a single link back to the script, or more than a single form, this can save a lot of space.
** HTTP-In URLs are long, especially if escaped.  In a page that includes more than a single link back to the script, or more than a single form, this can save a lot of space.
* Tiny urls
* Tiny URLs
** You can use url shortening services to create short links to long data urls.  In theory this should let you get past the 1k limit quite nicely, however there is overhead in setting up the tiny url.
** You can use URL shortening services to create short links to long data URLs.  In theory this should let you get past the 1k limit quite nicely, however there is overhead in setting up the tiny URL.
=== The future? ===
=== The future? ===
I think there is a lot more potential here than what I have outlined .... if you have ideas or work out great new hacks let me know!  Also let me know if it is ok to share those hacks with others. :)
I think there is a lot more potential here than what I have outlined .... if you have ideas or work out great new hacks let me know!  Also let me know if it is ok to share those hacks with others. :)
Line 237: Line 238:


=== DHTML / Javascript (Tali Rosca) ===
=== DHTML / Javascript (Tali Rosca) ===
Tali Rosca sent me the code to render the output of HTTP-In directly. I've wrapped her magic into the build_url and build_response functions in the example below.  This example renders a page that is around 3.5k bytes - well past the 1k limit of urls.<br>
[[User:Tali Rosca|Tali Rosca]] sent me the code to render the output of HTTP-In directly. I've wrapped her magic into the {{code|build_url}} and {{code|build_response}} functions in the example below.  This example renders a page that is around 3.5k bytes - well past the 1k limit of URLs.<br>
'''Pros''':
'''Pros''':
* Navigation is much smoother and feels more natural
* Navigation is much smoother and feels more natural
* Only limited by script memory
* Only limited by script memory
'''Cons''':   
'''Cons''':   
* Links are extremely large: 296 bytes for a link to just the base http-in url.
* Links are extremely large: 296 bytes for a link to just the base HTTP-in URL.


<lsl>
<syntaxhighlight lang="lsl2">
string url; // Our base http-in url
string url; // Our base HTTP-in URL
integer r; // Used for uniqueness in PrimMedia urls
integer r; // Used for uniqueness in PrimMedia URLs
integer page; // The page number.
integer page; // The page number.


show(string html)
show(string html)
{
{
    html += "<span " + (string)((++r) % 10) + "/>";
html += "<span " + (string)((++r) % 10) + "/>";
   
    llSetPrimMediaParams(0,                 // Side to display the media on.
llSetPrimMediaParams(0, // Side to display the media on.
            [PRIM_MEDIA_AUTO_PLAY,TRUE,     // Show this page immediately
[PRIM_MEDIA_AUTO_PLAY, TRUE, // Show this page immediately
            PRIM_MEDIA_CURRENT_URL,html,   // The url if they hit 'home'
PRIM_MEDIA_CURRENT_URL, html, // The url if they hit 'home'
            PRIM_MEDIA_HOME_URL,html,       // The url currently showing
PRIM_MEDIA_HOME_URL, html, // The url currently showing
            PRIM_MEDIA_HEIGHT_PIXELS,512,   // Height/width of media texture will be
PRIM_MEDIA_HEIGHT_PIXELS, 512, // Height/width of media texture will be
            PRIM_MEDIA_WIDTH_PIXELS,512]); //  rounded up to nearest power of 2.
PRIM_MEDIA_WIDTH_PIXELS, 512]); //  rounded up to nearest power of 2.
}
}


// This creates a data: url that will render the output of the http-in url
// This creates a "data:" URL that will render the output of the HTTP-in URL given.
// given.
string build_url(string url)
string build_url(string url)
{
{
    return "data:text/html,"  
return "data:text/html,"  
        + llEscapeURL("<html><head><script src='" + url  
+ llEscapeURL("<html><head><script src='" + url  
        + "' type='text/javascript'></script></head><body onload='init()'></body></html>");
+ "' type='text/javascript'></script></head><body onload='init()'></body></html>");
}
}


// This wraps the html you want to display so that it will be shown from links  
// This wraps the HTML you want to display so that it will be shown from links
// made with build_url
// made with "build_url()"
string build_response(string body)
string build_response(string body)
{
{
    return "function init() {document.getElementsByTagName('body')[0].innerHTML='" + body + "';}";
return "function init() {document.getElementsByTagName('body')[0].innerHTML='" + body + "';}";
}
}


default
default
{
{
    state_entry()
state_entry()
    {
{
        llRequestURL();
llRequestURL();
    }
}
   
    http_request(key id, string method, string body)
http_request(key id, string method, string body)
    {
{
        if (method == URL_REQUEST_GRANTED)
if (method == URL_REQUEST_GRANTED)
        {
{
            url = body + "/";
url = body + "/";
           
            show(build_url(url + "page" + (string)(page)));
show(build_url(url + "page" + (string)(page)));
        }
}
        else if (method == "GET")
else if (method == "GET")
        {
{
            string path = llGetHTTPHeader(id,"x-path-info");
string path = llGetHTTPHeader(id, "x-path-info");
            string content = "<h1>path: " + path + "</h1>";
string content = "<h1>path: " + path + "</h1>";
            page = (integer)llGetSubString(path,5,5);
page = (integer)llGetSubString(path, 5, 5);
            if (page > 0) content += "<a href=\"" + build_url(url + "page" + (string)(page - 1)) + "\">Previous</a> ";
if (page > 0) content += "<a href=\"" + build_url(url + "page" + (string)(page - 1)) + "\">Previous</a> ";
            if (page < 9) content += "<a href=\"" + build_url(url + "page" + (string)(page + 1)) + "\">Next</a><br>";
if (page < 9) content += "<a href=\"" + build_url(url + "page" + (string)(page + 1)) + "\">Next</a><br>";
            integer k;
integer k;
            for(;k<10;++k)
for(; k<10; ++k)
            {
{
                if (k == page)
if (k == page)
                    content += "<br>Page " + (string)k;
content += "<br>Page " + (string)k;
                else
else
                    content += "<br><a href=\"" + build_url(url + "page" + (string)(k)) + "\">Page " + (string)k + "</a>";
content += "<br><a href=\"" + build_url(url + "page" + (string)(k)) + "\">Page " + (string)k + "</a>";
            }
}
            content += "<br><br>This page is " + (string)(llStringLength(build_response(content)) + 37) + " bytes long.";
content += "<br><br>This page is " + (string)(llStringLength(build_response(content)) + 37) + " bytes long.";
            llHTTPResponse(id,200,build_response(content));
llHTTPResponse(id, 200, build_response(content));
        }
}
    }
}
}
}
</lsl>
</syntaxhighlight>
* I'm not quite sure if it is possible to hook up a form using this method. Ideas?
* I'm not quite sure if it is possible to hook up a form using this method. Ideas?


=== Another javascript boot strapping method (Vegas Silverweb) ===
=== Another JavaScript bootstrapping method ([[User:Vegas Silverweb|Vegas Silverweb]]) ===
This one makes it pretty easy to build out some arbitrary length web pages, using http-in's output directly. Loading between pages is kinda slow.
This one makes it pretty easy to build out some arbitrary-length web pages, using HTTP-in's output directly. Loading between pages is kinda slow.
<lsl>
<syntaxhighlight lang="lsl2">
string URL;
string url;


string get(string page)
string get(string page)
{
{
    if (page == "index")
if (page == "index")
    {
{
        return "<h1>Index</h1>This is the index page.<br><br><a href='page1.html'>Link to page 1</a>";
return "<h1>Index</h1>This is the index page.<br><br><a href='page1.html'>Link to page 1</a>";
    }
}
    else if (page == "page1")
else if (page == "page1")
    {
{
        return "<h1>Page 1</h1>This is page 1!.<br><br><a href ='index.html'>Link to the index page.</a>";   
return "<h1>Page 1</h1>This is page 1!.<br><br><a href ='index.html'>Link to the index page.</a>";   
    }
}
    else return "Unknown page.";
else return "Unknown page.";
}
}


string serve(string page)
string serve(string page)
{
{
    return "document.write(atob('"+llStringToBase64(get(page))+"'));";
return "document.write(atob('" + llStringToBase64(get(page)) + "'));";
}
}


integer R;
integer r;
load(string page)
load(string page)
{
{
    string content="data:text/html;base64,"
string content = "data:text/html;base64,"
        +llStringToBase64("<html><head><base href='"+URL+"/'></head><body>"+
+ llStringToBase64("<html><head><base href='" + url + "/'></head><body>"
                        "<script src='"+page+".js' type='text/javascript'>"+
+ "<script src='" + page + ".js' type='text/javascript'>"
                        "</script><span r='"+(string)(++R%10)+"</body></html>");
+ "</script><span r='" + (string)(++r % 10) + "</body></html>");
   
    llSetPrimMediaParams(0,[
llSetPrimMediaParams(0,[
                    PRIM_MEDIA_CURRENT_URL,content,
PRIM_MEDIA_CURRENT_URL, content,
                    PRIM_MEDIA_HOME_URL,content,
PRIM_MEDIA_HOME_URL, content,
                    PRIM_MEDIA_AUTO_ZOOM,FALSE,
PRIM_MEDIA_AUTO_ZOOM, FALSE,
                    PRIM_MEDIA_FIRST_CLICK_INTERACT,TRUE,
PRIM_MEDIA_FIRST_CLICK_INTERACT, TRUE,
                    PRIM_MEDIA_PERMS_INTERACT,PRIM_MEDIA_PERM_ANYONE,
PRIM_MEDIA_PERMS_INTERACT, PRIM_MEDIA_PERM_ANYONE,
                    PRIM_MEDIA_PERMS_CONTROL,PRIM_MEDIA_PERM_NONE,
PRIM_MEDIA_PERMS_CONTROL, PRIM_MEDIA_PERM_NONE,
                    PRIM_MEDIA_AUTO_PLAY,TRUE
PRIM_MEDIA_AUTO_PLAY, TRUE
                    ]);
]);
}
}


default
default
{
{
    state_entry()
state_entry()
    {
{
        llRequestURL();
llRequestURL();
    }
}


    http_request(key id, string method, string body)
http_request(key id, string method, string body)
    {
{
        if (method == URL_REQUEST_GRANTED)
if (method == URL_REQUEST_GRANTED)
        {
{
            URL = body;
url = body;
            load("index");
load("index");
        }
}
        else if (method == URL_REQUEST_DENIED)
else if (method == URL_REQUEST_DENIED)
        {
{
            llOwnerSay("Unable to get url!");
llOwnerSay("Unable to get URL!");
        }
}
        else if (method == "GET")
else if (method == "GET")
        {
{
            string path_raw = llGetHTTPHeader(id,"x-path-info");
string path_raw = llGetHTTPHeader(id, "x-path-info");
            if (path_raw == "")
if (path_raw == "")
            {
{
                // No path, assume index.html
// No path, assume index.html
                path_raw = "/index.html";
path_raw = "/index.html";
            }
}
           
            list path = llParseString2List(llGetSubString(path_raw,1,-1),["."],[]);
list path = llParseString2List(llGetSubString(path_raw, 1, -1), ["."], []);
            string page = llList2String(path,0);
string page = llList2String(path, 0);
            string type = llList2String(path,1);
string type = llList2String(path, 1);
           
            if (type == "js")
if (type == "js")
            {
{
                llHTTPResponse(id,200,serve(page));
llHTTPResponse(id, 200, serve(page));
            }
}
            else if (type == "html")
else if (type == "html")
            {
{
                llHTTPResponse(id,100,"");
llHTTPResponse(id, 100, "");
                load(page);
load(page);
            }
}
        }
}
    }
}
}
}
</lsl>
</syntaxhighlight>
[[Category:Shared Media]]
[[Category:Shared Media]]
[[Category:LSL Examples]]

Latest revision as of 08:34, 29 April 2024

Shared Media and HTTP-In

Warning!

While these tricks are still useful, they are largely unneeded now with llSetContentType and CONTENT_TYPE_HTML


Pre-Viewer 2.0 interfacing with an LSL script is a jumble of llDialog, chats, touches and possibly external web sites. With Shared Media that all changes.

One step at a time .......

llSetPrimMediaParams

 
default
{
	state_entry()
	{
		llSetPrimMediaParams(0,								// Side to display the media on.
			[PRIM_MEDIA_AUTO_PLAY, TRUE, 					// Show this page immediately
			 PRIM_MEDIA_CURRENT_URL, "https://google.com",	// The URL currently showing
			 PRIM_MEDIA_HOME_URL, "https://google.com",		// The URL if they hit 'home'
			 PRIM_MEDIA_HEIGHT_PIXELS, 512,					// Height/width of media texture will be
			 PRIM_MEDIA_WIDTH_PIXELS, 512]);				//   rounded up to nearest power of 2.
	}
}

"data:" URLs

There is a special URL type: "data:" that lets you send the HTML in the URL. Paste the below into your browser's address bar.

data:text/html,<h1>This is a test</h1><h2>This is a test</h2><h3>This is a test</h3>

llSetPrimMediaParams for "data:" URLs

  • Thus, you can build arbitrary HTML in your LSL script and display it on the face of the prim
show(string html)
{
	html = "data:text/html," + llEscapeURL(html);
	llSetPrimMediaParams(0,						// Side to display the media on.
			[PRIM_MEDIA_AUTO_PLAY, TRUE,		// Show this page immediately
			 PRIM_MEDIA_CURRENT_URL, html,		// The url currently showing
			 PRIM_MEDIA_HOME_URL, html,			// The url if they hit 'home'
			 PRIM_MEDIA_HEIGHT_PIXELS, 512,		// Height/width of media texture will be
			 PRIM_MEDIA_WIDTH_PIXELS, 512]);	//   rounded up to nearest power of 2.
}
default
{
	state_entry()
	{
		show("<h1>This is a test</h1><h2>This is a test</h2><h3>This is a test</h3>");
	}
}

HTTP-In

  • Scripts can become web servers via HTTP-In
default
{
	state_entry()
	{
		llRequestURL();
	}
	
	http_request(key id, string method, string body)
	{
		if (method == URL_REQUEST_GRANTED)
		{
			llSay(0, "URL is: " + body);
		}
		else if (method == "GET")
		{
			llSay(0, "Received page request.");
			llHTTPResponse(id, 200, "OK");
		}
	}
}

llSetPrimMediaParams for "data:" URLs that link back to HTTP-In

  • Which means you can make web pages that trigger other page views via HTTP-in links
show(string html)
{
	html = "data:text/html," + llEscapeURL(html);
	llSetPrimMediaParams(0,						// Side to display the media on.
			[PRIM_MEDIA_AUTO_PLAY, TRUE,		// Show this page immediately
			 PRIM_MEDIA_CURRENT_URL, html,		// The URL currently showing
			 PRIM_MEDIA_HOME_URL, html,			// The URL if they hit 'home'
			 PRIM_MEDIA_HEIGHT_PIXELS, 512,		// Height/width of media texture will be
			 PRIM_MEDIA_WIDTH_PIXELS, 512]);	//   rounded up to nearest power of 2.
}

string replace_all(string src, string target, string replace)
{
	return llDumpList2String(llParseString2List(src, [target], []), replace);
}

string get_query(key id, string name)
{
	string query = llGetHTTPHeader(id, "x-query-string");
	query = replace_all(query, "+", " ");
	query = llUnescapeURL(query);
	list q = llParseString2List(query, ["=", "&", ";"], []);
	integer i = llListFindList(q, [name]);
	if (i != -1)
	{
		return llList2String(q, i+1);
	}
	
	return "";
}

default
{
	state_entry()
	{
		show("<h1>This is a test</h1><h2>This is a test</h2><h3>This is a test</h3>");
		llRequestURL();
	}
	
	http_request(key id, string method, string body)
	{
		if (method == URL_REQUEST_GRANTED)
		{
			show("<h1><a href='" + body + "/?get=owner'>Owner ID</a><br><br><a href='"
				+ body + "/?get=object'>Object ID</a></h1>");
		}
		else if (method == "GET")
		{
			string get = get_query(id, "get");
			if (get == "owner")
			{
				llHTTPResponse(id, 200, "Owner is: " + (string)llGetOwner());
			}
			else if (get == "object")
			{
				llHTTPResponse(id, 200, "Object is: " + (string)llGetKey());
			}
			else
			{			
				llHTTPResponse(id, 400, "huh?");
			}
		}
	}
}

Forms

  • You can put forms in "data:" URLs:
data:text/html,<form action="https://google.com/search" method="GET">Search:<input type="text" name="q"><input type="submit" value="Submit"></form>

The Magic

  • You can set the script HTTP-In URL as the form action to get the results of the form sent back to the script
string html_base = 
"<h1><form action='%url%' method='GET'>
Floating Text:<input type='text' name='text'><br>
<input type='submit' value='Set'>
</form></h1>";

string url;

integer r;
show(string html)
{
	html = "data:text/html," + llEscapeURL(html) + "<span " + (string)((++r) % 10) + "/>";
	
	llSetPrimMediaParams(0,						// Side to display the media on.
			[PRIM_MEDIA_AUTO_PLAY, TRUE,		// Show this page immediately
			 PRIM_MEDIA_CURRENT_URL, html,		// The url currently showing
			 PRIM_MEDIA_HOME_URL, html,			// The url if they hit 'home'
			 PRIM_MEDIA_HEIGHT_PIXELS, 512,		// Height/width of media texture will be
			 PRIM_MEDIA_WIDTH_PIXELS, 512]);	//   rounded up to nearest power of 2.
}

string replace_all(string src, string target, string replace)
{
	return llDumpList2String(llParseString2List(src,[target],[]),replace);
}

string get_query(key id, string name)
{
	string query = llGetHTTPHeader(id, "x-query-string");
	query = replace_all(query, "+", " ");
	query = llUnescapeURL(query);
	list q = llParseString2List(query, ["=", "&", ";"], []);
	integer i = llListFindList(q, [name]);
	if (i != -1)
	{
		return llList2String(q, i + 1);
	}
	
	return "";
}

default
{
	state_entry()
	{
		llRequestURL();
	}
	
	http_request(key id, string method, string body)
	{
		if (method == URL_REQUEST_GRANTED)
		{
			url = body + "/";
			
			show(replace_all(html_base, "%url%", url));
		}
		else if (method == "GET")
		{
			llSetText(get_query(id, "text"), <1.0, 1.0, 0.0>, 1);
			show(replace_all(html_base, "%url%", url));
			llHTTPResponse(id, 200, "Loading....");
		}
	}
}

The Limits

  • 1024 bytes per URL
  • llEscapeURLs means non-alphanumeric characters are 3 bytes (ouch)
    • I'm actually not clear on if this is really needed. I seem to sometimes get blank pages if I don't, but some pages render fine without it. Maybe there is a subset of symbols that must be escaped and escaping only those would ease some of the space constraints?
  • Must force another page view after form submission since HTTP-In can't generate HTML
  • No real way of knowing which avatar is interacting with the HTML

The Tricks

  • <base href="<http-in-url>/">
    • HTTP-In URLs are long, especially if escaped. In a page that includes more than a single link back to the script, or more than a single form, this can save a lot of space.
  • Tiny URLs
    • You can use URL shortening services to create short links to long data URLs. In theory this should let you get past the 1k limit quite nicely, however there is overhead in setting up the tiny URL.

The future?

I think there is a lot more potential here than what I have outlined .... if you have ideas or work out great new hacks let me know! Also let me know if it is ok to share those hacks with others. :)

  • svg?
  • javascript?
  • ajax?

DHTML / Javascript (Tali Rosca)

Tali Rosca sent me the code to render the output of HTTP-In directly. I've wrapped her magic into the build_url and build_response functions in the example below. This example renders a page that is around 3.5k bytes - well past the 1k limit of URLs.
Pros:

  • Navigation is much smoother and feels more natural
  • Only limited by script memory

Cons:

  • Links are extremely large: 296 bytes for a link to just the base HTTP-in URL.
string url;		// Our base HTTP-in URL
integer r;		// Used for uniqueness in PrimMedia URLs
integer page;	// The page number.

show(string html)
{
	html += "<span " + (string)((++r) % 10) + "/>";
	
	llSetPrimMediaParams(0,						// Side to display the media on.
			[PRIM_MEDIA_AUTO_PLAY, TRUE,		// Show this page immediately
			 PRIM_MEDIA_CURRENT_URL, html,		// The url if they hit 'home'
			 PRIM_MEDIA_HOME_URL, html,			// The url currently showing
			 PRIM_MEDIA_HEIGHT_PIXELS, 512,		// Height/width of media texture will be
			 PRIM_MEDIA_WIDTH_PIXELS, 512]);	//   rounded up to nearest power of 2.
}

// This creates a "data:" URL that will render the output of the HTTP-in URL given.
string build_url(string url)
{
	return "data:text/html," 
		+ llEscapeURL("<html><head><script src='" + url 
		+ "' type='text/javascript'></script></head><body onload='init()'></body></html>");
}

// This wraps the HTML you want to display so that it will be shown from links
// made with "build_url()"
string build_response(string body)
{
	return "function init() {document.getElementsByTagName('body')[0].innerHTML='" + body + "';}";
}

default
{
	state_entry()
	{
		llRequestURL();
	}
	
	http_request(key id, string method, string body)
	{
		if (method == URL_REQUEST_GRANTED)
		{
			url = body + "/";
			
			show(build_url(url + "page" + (string)(page)));
		}
		else if (method == "GET")
		{
			string path = llGetHTTPHeader(id, "x-path-info");
			string content = "<h1>path: " + path + "</h1>";
			page = (integer)llGetSubString(path, 5, 5);
			if (page > 0) content += "<a href=\"" + build_url(url + "page" + (string)(page - 1)) + "\">Previous</a> ";
			if (page < 9) content += "<a href=\"" + build_url(url + "page" + (string)(page + 1)) + "\">Next</a><br>";
			integer k;
			for(; k<10; ++k)
			{
				if (k == page)
					content += "<br>Page " + (string)k;
				else
					content += "<br><a href=\"" + build_url(url + "page" + (string)(k)) + "\">Page " + (string)k + "</a>";
			}
			content += "<br><br>This page is " + (string)(llStringLength(build_response(content)) + 37) + " bytes long.";
			llHTTPResponse(id, 200, build_response(content));
		}
	}
}
  • I'm not quite sure if it is possible to hook up a form using this method. Ideas?

Another JavaScript bootstrapping method (Vegas Silverweb)

This one makes it pretty easy to build out some arbitrary-length web pages, using HTTP-in's output directly. Loading between pages is kinda slow.

string url;

string get(string page)
{
	if (page == "index")
	{
		return "<h1>Index</h1>This is the index page.<br><br><a href='page1.html'>Link to page 1</a>";
	}
	else if (page == "page1")
	{
		return "<h1>Page 1</h1>This is page 1!.<br><br><a href ='index.html'>Link to the index page.</a>";   
	}
	else return "Unknown page.";
}

string serve(string page)
{
	return "document.write(atob('" + llStringToBase64(get(page)) + "'));";
}

integer r;
load(string page)
{
	string content = "data:text/html;base64,"
		+ llStringToBase64("<html><head><base href='" + url + "/'></head><body>"
						+ "<script src='" + page + ".js' type='text/javascript'>"
						+ "</script><span r='" + (string)(++r % 10) + "</body></html>");
	
	llSetPrimMediaParams(0,[
						PRIM_MEDIA_CURRENT_URL, content,
						PRIM_MEDIA_HOME_URL, content,
						PRIM_MEDIA_AUTO_ZOOM, FALSE,
						PRIM_MEDIA_FIRST_CLICK_INTERACT, TRUE,
						PRIM_MEDIA_PERMS_INTERACT, PRIM_MEDIA_PERM_ANYONE,
						PRIM_MEDIA_PERMS_CONTROL, PRIM_MEDIA_PERM_NONE,
						PRIM_MEDIA_AUTO_PLAY, TRUE
						]);
}

default
{
	state_entry()
	{
		llRequestURL();
	}

	http_request(key id, string method, string body)
	{
		if (method == URL_REQUEST_GRANTED)
		{
			url = body;
			load("index");
		}
		else if (method == URL_REQUEST_DENIED)
		{
			llOwnerSay("Unable to get URL!");
		}
		else if (method == "GET")
		{
			string path_raw = llGetHTTPHeader(id, "x-path-info");
			if (path_raw == "")
			{
				// No path, assume index.html
				path_raw = "/index.html";
			}
			
			list path = llParseString2List(llGetSubString(path_raw, 1, -1), ["."], []);
			string page = llList2String(path, 0);
			string type = llList2String(path, 1);
			
			if (type == "js")
			{
				llHTTPResponse(id, 200, serve(page));
			}
			else if (type == "html")
			{
				llHTTPResponse(id, 100, "");
				load(page);
			}
		}
	}
}