// This is the URL shown when the radio is "off" string OFF_URL = "https://alifevirtual.com/images/off_air.png"; // --- SCRIPT STATE --- integer is_playing = FALSE; // State variable: FALSE means off, TRUE means on default { state_entry() { // When the script starts, turn the radio "off" // We do this by setting the media to our "off" image llSetPrimMediaParams(0, [PRIM_MEDIA_CURRENT_URL, OFF_URL, PRIM_MEDIA_CONTROLS, PRIM_MEDIA_CONTROLS_NONE]); llSetText("Radio is OFF\nTouch to Play", <1,1,1>, 1.0); } touch_start(integer total_number) { // This event fires when someone touches the prim if (is_playing == FALSE) { // If the radio is currently off, turn it ON llSay(0, "Starting radio stream..."); // Set the media URL to our radio stream llSetPrimMediaParams(0, [PRIM_MEDIA_CURRENT_URL, RADIO_STREAM_URL, PRIM_MEDIA_CONTROLS, PRIM_MEDIA_CONTROLS_STANDARD]); llSetText("Radio is ON\nTouch to Stop", <0,1,0>, 1.0); is_playing = TRUE; // Update the state } else { // If the radio is currently on, turn it OFF llSay(0, "Stopping radio stream."); // Set the media URL back to our "off" image llSetPrimMediaParams(0, [PRIM_MEDIA_CURRENT_URL, OFF_URL, PRIM_MEDIA_CONTROLS, PRIM_MEDIA_CONTROLS_NONE]); llSetText("Radio is OFF\nTouch to Play", <1,1,1>, 1.0); is_playing = FALSE; // Update the state } } }

Practical Example: A Multi-Station Radio

An on/off switch is good, but a real radio has multiple stations. To do this, we'll store our station URLs in an LSL list. We'll also need a variable to keep track of the current station index. For this, we'll need to use linked prims for the "Next" and "Previous" buttons.

Setup:

  1. Create a main box prim for the radio body.
  2. Create two smaller prims for the "Next" and "Previous" buttons.
  3. Place the buttons on the main body.
  4. Select the two buttons first, then select the main body last (the main body should have a yellow outline). Link them using Ctrl+L. The main body is now the "root" prim.
  5. Place the following script into the root prim.

// Multi-Station Radio Script
// By Sorin Todys, Alife Virtual School

// --- CONFIGURATION ---
list g_station_urls; // Global list to hold our station URLs
list g_station_names; // Global list to hold station names

// --- SCRIPT STATE ---
integer g_current_station_index = 0; // Index for the current station
integer g_is_playing = FALSE; // Radio on/off state

// --- HELPER FUNCTION ---
// This function updates the media and hover text
update_media()
{
    if (g_is_playing)
    {
        string url = llList2String(g_station_urls, g_current_station_index);
        string name = llList2String(g_station_names, g_current_station_index);
        llSetPrimMediaParams(0, [PRIM_MEDIA_CURRENT_URL, url, PRIM_MEDIA_CONTROLS, PRIM_MEDIA_CONTROLS_STANDARD]);
        llSetText("Now Playing:\n" + name + "\n(Touch main body to turn OFF)", <0,1,0>, 1.0);
    }
    else
    {
        // When off, we can show an image or a blank page
        llSetPrimMediaParams(0, [PRIM_MEDIA_CURRENT_URL, "", PRIM_MEDIA_CONTROLS, PRIM_MEDIA_CONTROLS_NONE]);
        llSetText("Radio is OFF\n(Touch main body to turn ON)", <1,1,1>, 1.0);
    }
}


default
{
    state_entry()
    {
        // --- POPULATE OUR STATION LISTS ---
        g_station_names = ["Classical Calm", "Rock Anthems", "Jazz Club"];
        g_station_urls = ["http://dradio.org:8000/1", "http://dradio.org:8000/2", "http://dradio.org:8000/3"]; // Replace with real URLs!

        // Set the link names for our buttons for easy identification
        llSetLinkPrimitiveParamsFast(2, [PRIM_NAME, "NextButton"]);
        llSetLinkPrimitiveParamsFast(3, [PRIM_NAME, "PrevButton"]);
        
        g_is_playing = FALSE;
        update_media();
    }

    link_message(integer sender_num, integer num, string str, key id)
    {
        // This event listens for messages from other prims in the linkset
        if (str == "touch")
        {
            // Check which linked prim was touched by its name
            string prim_name = llGetLinkName(num);

            if (prim_name == "NextButton")
            {
                g_current_station_index++;
                // Loop back to the beginning if we go past the end of the list
                if (g_current_station_index >= llGetListLength(g_station_urls))
                {
                    g_current_station_index = 0;
                }
                llSay(0, "Next station...");
                update_media();
            }
            else if (prim_name == "PrevButton")
            {
                g_current_station_index--;
                // Loop to the end if we go past the beginning
                if (g_current_station_index < 0)
                {
                    g_current_station_index = llGetListLength(g_station_urls) - 1;
                }
                llSay(0, "Previous station...");
                update_media();
            }
        }
    }
    
    touch_start(integer total_number)
    {
        // This event only fires for the root prim
        // We use this for the ON/OFF toggle
        if (llDetectedLinkNumber(0) == 1) // Ensure it's the root prim being touched
        {
            g_is_playing = !g_is_playing; // This is a cool shortcut to toggle a boolean
            update_media();
        }
    }
}

// --- SCRIPT FOR BUTTONS ---
// Place this tiny script in EACH of the button prims (Next and Previous)
default
{
    touch_start(integer total_number)
    {
        // When touched, send a message to the root prim
        llMessageLinked(LINK_ROOT, 0, "touch", NULL_KEY);
    }
}

4. LESSON 3: Advanced Broadcasting: TV Stations, Security, and User Experience

Advanced Application: Creating a TV Station

You might be surprised to learn that a TV is functionally identical to the radio we just built. The only difference is the media URL and a few extra parameters. Instead of an audio stream, you provide a video stream URL. This could be a link to a .mp4 file or a live video feed.

The llSetPrimMediaParams function can take many more arguments to control the video playback. Here are some of the most useful:

Example: Scripting a Simple Movie Screen

This script will play a public domain movie on a loop. We'll use a URL from the Internet Archive.

// Simple Looping Movie Player
// By Sorin Todys, Alife Virtual School

// Public domain movie: "Night of the Living Dead" from archive.org
string MOVIE_URL = "http://www.archive.org/download/night_of_the_living_dead/night_of_the_living_dead_512kb.mp4";

default
{
    state_entry()
    {
        // Set the media on face 0.
        // We specify resolution, auto play, and looping.
        llSetPrimMediaParams(0, [
            PRIM_MEDIA_CURRENT_URL, MOVIE_URL,
            PRIM_MEDIA_WIDTH, 640,
            PRIM_MEDIA_HEIGHT, 480,
            PRIM_MEDIA_AUTO_PLAY, TRUE,
            PRIM_MEDIA_AUTO_LOOP, TRUE,
            PRIM_MEDIA_CONTROLS, PRIM_MEDIA_CONTROLS_MINI
        ]);
        
        llSetText("Now Showing:\nNight of the Living Dead", <1,1,0>, 1.0);
    }
}

Real-World Scenario: Security and Access Control

Your media player is amazing, but what if you don't want just anyone changing the station at your club? You need to add security. We can easily modify our touch events to check who is clicking the prim.

To do this, we'll use llDetectedKey(0) to get the unique key of the avatar who touched the prim, and llGetOwner() to get the key of the object's owner. We can also check if the user is in the same group as the object.

Example: Owner-Only Control

Let's modify the touch_start event from our simple On/Off radio to be owner-only.

touch_start(integer total_number)
{
    // Check if the person who touched is the owner of the object
    if (llDetectedKey(0) == llGetOwner())
    {
        // All the on/off logic from before goes inside this 'if' block
        if (is_playing == FALSE)
        {
            // ... turn on radio ...
            is_playing = TRUE;
        }
        else
        {
            // ... turn off radio ...
            is_playing = FALSE;
        }
    }
    else
    {
        // If it's not the owner, send them a message
        llInstantMessage(llDetectedKey(0), "Sorry, only the owner can operate this radio.");
    }
}

Example: Group-Only Control

This is extremely useful for clubs and businesses. First, you must set the object to the correct group and "Share" it. Then, use llDetectedGroup(0).

touch_start(integer total_number)
{
    // llDetectedGroup returns TRUE if the toucher is in the same group as the prim
    if (llDetectedGroup(0) == TRUE)
    {
        // ... all your radio logic here ...
    }
    else
    {
        llInstantMessage(llDetectedKey(0), "Sorry, only group members can operate this radio.");
    }
}

Best Practices for a Great User Experience (UX)

5. HANDS-ON EXERCISES

Time to put your knowledge into practice. These exercises are designed to be completed on your own land or in a sandbox area where you have permission to rez and run scripts.

  1. Exercise 1: Set Your Parcel Mood Music.
    Find a royalty-free internet radio stream online (a good search is "free public domain radio stream"). Go to your personal parcel, open World > Parcel Details > Sound, and set the stream. Invite