ART-504 Advanced The School of Digital Arts
Lead Instructor

Sorin Todys - Advanced expert with 20+ years of experience in virtual worlds


All classes take place in Alife Virtual World at our dedicated Alife Virtual School region

How to Create Your Radio or TV Station

How to Create Your Radio or TV Station - Alife Virtual World School

Learn and Grow at Alife Virtual World School

Course Code: ART-504 | The School of Digital Arts


Difficulty Level: Advanced

Lead Instructor: Sorin Todys

1. COURSE OVERVIEW

Welcome, Future Broadcaster!

Welcome to one of the most exciting and dynamic courses offered at Alife Virtual School! In ART-504, we move beyond static builds and into the world of live, streaming media. Have you ever dreamed of running your own radio station, hosting a talk show, DJing at a virtual club, or creating a movie theater for your friends? This course will give you the technical foundation and scripting knowledge to make it a reality.

Broadcasting media is the lifeblood of social spaces in virtual worlds. It sets the mood, delivers information, and provides entertainment. By learning to control it, you are learning to craft immersive experiences. This advanced course, led by our Linden Scripting Language (LSL) expert, Sorin Todys, will guide you through every step, from the underlying theory of media streams to scripting complex, user-friendly devices.

Learning Objectives

Upon successful completion of this course, you will be able to:

  • Understand the difference between parcel media and prim-based media (media-on-a-prim).
  • Configure land settings to broadcast audio streams across an entire parcel.
  • Find and correctly format media URLs for use in Alife Virtual.
  • Use LSL to set and control media on a prim surface, effectively creating a TV screen or radio player.
  • Script an interactive media device with controls like On/Off, Next/Previous Station, and volume.
  • Implement security features to control who can operate your media devices.
  • Troubleshoot common issues related to in-world media playback.

What You Will Master

This isn't just about copying a script. By the end, you will have mastered the art and science of in-world media broadcasting. You'll be able to:

  • Design and Build Functional Media Players: From a simple "on-air" sign to a multi-channel television, you'll know how to build the objects and, more importantly, make them work.
  • Advanced LSL Media Scripting: You will gain proficiency with LSL's media functions, including llSetPrimMediaParams, llGetPrimMediaParams, and llSetParcelMusicURL, and learn to combine them with touch events and data storage for powerful results.
  • Experience Curation: You will understand how to use audio and video to create compelling environments, whether for a personal home, a commercial business, or a large-scale event.

Prerequisites

This is an Advanced course. To succeed, students should have a solid foundation in the following areas:

  • Basic Building: You should be comfortable creating and manipulating prims, linking them, and applying textures. (Recommended: BLD-101)
  • Introduction to LSL: You must understand LSL fundamentals, including variables, functions, events (especially touch_start), and basic state management. You should be able to create, save, and add a script to a prim. (Recommended: ART-401)

2. LESSON 1: Foundations of In-World Media: Streams, Parcels, and Prims

Theory: Understanding the Flow

Before we can control media, we must understand what it is. In Alife Virtual, all audio and video are "streams." This means the data isn't stored in the virtual world itself; instead, our viewer (Firestorm) is directed to an external internet address (a URL) to play the content. Think of it like your web browser playing a YouTube video—the video isn't on your computer, it's streaming from YouTube's servers.

There are two primary ways to deliver this media to users in-world:

  1. Parcel Media (or Parcel Audio): This method assigns an audio stream to the entire parcel of land. Anyone standing on that parcel will hear the same audio stream, managed through their viewer's controls. This is ideal for setting a consistent ambient mood for a large area like a club, a shop, or a garden. The LSL function to control this is llSetParcelMusicURL().
  2. Prim Media (Media-on-a-Prim or MOAP): This is a more powerful and flexible method. It allows you to assign a media stream (audio or video) to a specific face of a prim. This is how in-world TVs, radios, and information kiosks are made. It's localized to the object, and users must click on it to see or hear the media. The LSL function for this is llSetPrimMediaParams().

Step-by-Step: Setting Your First Parcel Stream

Let's start with the easiest method: setting the music for your land. This requires you to have rights to modify the parcel.

  1. Find a Stream URL: For this example, we'll use a public domain, royalty-free stream. A good source for streams is the Shoutcast directory, but always check the station's terms of service. Let's use a sample stream: http://dradio.org:8000/7 (This is a fictional example URL; you'll need to find a real one).
  2. Navigate to Parcel Details: Stand on your land and go to the top menu bar in your Firestorm viewer. Click World > Parcel Details.
  3. Open the SOUND Tab: In the "About Land" window that appears, find and click on the "SOUND" tab.
  4. Enter the URL: You will see a field labeled "Music URL". Paste your stream URL directly into this box. For our example, you'd paste http://dradio.org:8000/7.
  5. Apply the Changes: Click "Apply" or "OK".
  6. Listen: If the stream is valid, the music icon in the top right of your viewer should activate. You can click it to start/stop the stream and control its volume. Everyone who visits your parcel will now be able to listen to this stream!

Step-by-Step: Your First Media Prim (A "Silent" TV)

Now, let's display a webpage on a prim face. This uses the same principle as a TV, but is simpler to start with.

  1. Create a Prim: Rez a new cube (or any prim) on your land.
  2. Edit the Prim: Right-click the prim and choose "Edit".
  3. Select a Face: In the edit window, under the "Texture" tab, select the "Select Face" radio button and click on the face of the prim you want to turn into a screen.
  4. Choose Media Type: Just below the texture preview, you'll see a dropdown menu that says "Texture". Click it and change it to "Media".
  5. Add a URL: A "Choose Media" window will pop up. For now, let's just use the Alife Virtual homepage. Type https://alifevirtual.com into the URL field and click "Apply".

You've just done manually what we're about to do with a script! The front face of your prim should now display the Alife Virtual website. You can click on it and interact with it like a mini-browser. This is the core of MOAP.

3. LESSON 2: Scripting Your First Interactive Media Player

Building on Lesson 1: From Manual to Automated

In Lesson 1, we set media manually. That's fine for a static display, but a real radio or TV needs to be interactive. It needs to be turned on and off, have its channels changed, and respond to users. This is where LSL comes in. We will use the touch_start event to trigger changes to the prim's media parameters.

The key function is llSetPrimMediaParams. It's a versatile function that takes a list of parameters to control the media on a prim. The most basic parameter is PRIM_MEDIA_CURRENT_URL, which sets the webpage or stream to display.

Advanced Technique: Scripting an On/Off Radio

Let's create a simple radio that you can touch to turn on and touch again to turn off. This introduces the concept of a "state machine" in your script, which keeps track of whether the radio is currently on or off.

Create a new script inside a prim and paste the following code:

// Simple On/Off Radio Script
// By Sorin Todys, Alife Virtual School

// --- CONFIGURATION ---
// Paste your radio stream URL here
string RADIO_STREAM_URL = "http://dradio.org:8000/7"; 
// 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:

  • PRIM_MEDIA_WIDTH and PRIM_MEDIA_HEIGHT: Integers that set the resolution of the media. This helps prevent distortion.
  • PRIM_MEDIA_AUTO_PLAY: Set to TRUE to make the video play automatically.
  • PRIM_MEDIA_AUTO_LOOP: Set to TRUE to make the video loop when it finishes.
  • PRIM_MEDIA_FIRST_CLICK_INTERACT: Set to TRUE to allow users to click through the video to the webpage it's on.

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)

  • Provide Feedback: Use llSetText to show the current status (On/Off, station name). Use llSay or llInstantMessage to confirm actions. A silent, unresponsive object is confusing.
  • Keep Scripts Efficient: Avoid putting complex calculations or loops inside touch events. Do as much setup as possible in state_entry.
  • Respect Copyright: Only use streams that you have permission to broadcast. Many internet radio stations are happy to be shared, but commercial music and movies are protected. Look for "royalty-free" or "public domain" content.
  • Inform Visitors: If you use parcel audio, consider a small sign near the entrance letting people know, so they can turn it on if they wish.
  • Clear Controls: Label your buttons clearly, either with hover text or textures. Don't make users guess what to click.

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