LSL-Shared Media Communication

From Second Life Wiki
Revision as of 19:59, 21 April 2010 by Strife Onizuka (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Introduction

A common question about Shared Media is whether or not it can communicate with Linden Script (LSL). Great question! The answer is: YES! This is great news as it opens up all sort of creative possibilities. However, it's not the easiest thing to figure out. Edelman Linden shows at least one way to do it.

This article presents the full source code for a simple example where you can send chat messages FROM Shared Media. In addition, the Shared Media displays some time of day information received from LSL.

It looks like this:

LSLp1.jpg

Notice that the chat there really is the text from the Shared Media:

LSLp2.jpg

Communication via HTTP

LSL script support receiving and replying to HTTP requests. The URL for a LSL running on a sim can be obtain using llrequestURL. It's this URL and HTTP request mechanism that are the key to communication with Share Media, in both directions!

  • Shared Media can send a message to LSL by sending the information in an HTTP request.
  • Shared Media can receive a message from LSL by asking for the information via an HTTP request. Asking repeatedly (also known as Polling) creates a way of making it seem like messages are being "sent".

But there's a catch! And this is a BIG catch!

  • Your Shared Media is served from your domain (in my case, uccellogames.com).
  • The LSL URL is from a different domain - lindenlab.com
  • The browser security model prevents your domain from looking at the data returned by a request to a different domain. (This would be a way of phishing inside of corporate firewalls, for example). Without a solution, you could send messages, but not receive responses.

As such, your domain needs to proxy requests from your Shared Media to the Linden server. The good news is that I'll show you how to do that. You can use this simple PHP script:

lslproxy.php

Takes the URL of the LSL script and the data you want to have it receive as the request body.

<php> <?php

$url = $_GET['url']; $data = $_GET['data'];

$session = curl_init($url); curl_setopt($session, CURLOPT_POST, true); curl_setopt($session, CURLOPT_POSTFIELDS, $data); curl_setopt($session, CURLOPT_HEADER, false); curl_setopt($session, CURLOPT_FOLLOWLOCATION, true); curl_setopt($session, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($session); echo $response;

curl_close($session);

?> </php>

KBtip2.png Tip: You need to host it from your domain and be able to run PHP scripts.

LSL to Shared Media is a Many-to-One Relationship

It's very important that you understand that the relationship between LSL and Shared Media is a many-to-one relationship. That is, the LSL is running just one time on the Linden simulation server. The Shared Media is running on the computer of every Resident that is encountering your object. While you're deveopling content, it's easy to forget this, as you are probably looking at your object alone.

KBcaution.png Important: Remember, the relationship between LSL and Shared Media is a many-to-one relationship!

Source Code for the Example

LSL script for a simple example

Here's the script on the object:

Key points:

  • Note how LlRequestURL is used to get the LSL URL and then passed to the HTML page as a query argument.
  • The different types of requests from the Shared Media are identified by the first character of their request.

<lsl> init(string scriptUrl) {

   string url = "http://www.uccellogames.com/media_demos/communication.html?url=" + scriptUrl;
   integer face = 4;
   llClearPrimMedia(face);
   llSetPrimMediaParams(face, [PRIM_MEDIA_CURRENT_URL, url, PRIM_MEDIA_AUTO_SCALE, TRUE, PRIM_MEDIA_AUTO_PLAY, TRUE, PRIM_MEDIA_PERMS_CONTROL, PRIM_MEDIA_PERM_NONE, PRIM_MEDIA_WIDTH_PIXELS, 512, PRIM_MEDIA_HEIGHT_PIXELS, 512, PRIM_MEDIA_FIRST_CLICK_INTERACT, 1]);

}

key gRequestURL; string gURL;

default {

   state_entry()
   {
       gRequestURL = llRequestURL();
   }
   http_request(key id, string method, string body) 
   {
       if (id == gRequestURL) {
           string arg = llEscapeURL(body);
           init(arg);
       } else {
           // it's a message from the Shared Media!
           string msgType = llGetSubString(body, 0, 0);
           string data = llGetSubString(body, 1, -1);
           
           if (msgType == "1") {
               // it's a chat message!
               llSay(0, data);
               llHTTPResponse(id,200,"");
           } else if (msgType == "2") {
               // it's a time of data request
               float tod = llGetTimeOfDay( );
               string reply = "Time since last region restart or SL midnight (based on SL 4 hour day):";
               integer hours = ((integer)tod / 3600) ;
               integer minutes = ((integer)tod / 60) - (hours * 60);
               reply += (string) tod + " seconds which is "+(string) hours+"h "+(string) minutes+"m"; 
               llHTTPResponse(id,200, reply);
           }                
       }
   }

} </lsl>

communication.html

Here's the HTML for the Shared Media page:

Key points:

  • Note the use of the "get_query_argument" Javascript function to get the URL for the LSL script.
  • "lslRequest" is a function that glues it all together. It uses the "lslproxy.php" script (above) to send data to the LSL and provide it's textural response.

Otherwise, it's pretty straight-forward.

<html4strict> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>

<script type="text/javascript"> function get_query_argument(name, href) {

 href = (href) || window.location.href;
 name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
 var regexS = "[\\?&]"+name+"=([^&#]*)";
 var regex = new RegExp( regexS );
 var results = regex.exec(href);
 return (results == null ? "" : results[1]);

};

function lslRequest(data, responseHandler) { var url = get_query_argument("url"); var xmlhttp=new XMLHttpRequest(); xmlhttp.open("GET", "http://www.uccellogames.com/media_demos/lslproxy.php?data=" + data + "&url=" + url, true); xmlhttp.responseHandler = responseHandler;

 xmlhttp.onreadystatechange=function() {

if (this.readyState == 4) { if (this.responseHandler) { this.responseHandler(this.responseText); } } }; xmlhttp.send(); }

function submit_chat() { var chat = escape(document.getElementById("chat").value); var data = "1" + chat; // 1 indicates it's incoming chat lslRequest(data, null); }

function get_message() { var data = "2"; // 2 indicates it's a request for the time of day lslRequest(data, function(responseText) { document.getElementById("lslmessage").value = responseText; } ); } </script>


</head>


<body onLoad="javascript:get_message()">

Example of Shared Media to LSL communication:

What you type and submit here will show up in SL chat:

<input name="chat" type="text" id="chat" size="46" maxlength="50" /> <input type="submit" name="submit" id="submit" value="Submit" onClick="javascript:submit_chat()"/>

Example of LSL to Shared Media communication:

Generally, this is done by polling from Shared Media to LSL - this example just asks once.

Here's what LSL reports as the "time of day":

<textarea name="lslmessage" id="lslmessage" cols="50" rows="5"></textarea>

</body> </html> </html4strict>

Additional Thoughts from Edelman

I hope you found this article useful. I tried to come up with a really simple example, but it's still pretty technical.

Note that if you wanted a way to keep a state machine that all new Shared Media instances could connect to, receive current state, and receive update notifications, you certainly could do it in LSL. However, if your Shared Media is Flash based, Flash Media Server (FMS) makes this easier. Server-side Javascript on the FMS server could be used both as your proxy (instead of the PHP script) AND as your server-side state machine that already has a way to broadcast to all clients. In this case, you could keep a one-to-one relationship with your LSL instance and your FMS instance, and the many-to-one relationshpe migrates to be between the FMS instance and the Shared Media Flash clients.

<lsl></lsl>