07 Sep 2016

Triggering actions in Wowza over HTTP

In this post, you’ll look at a couple of ways to trigger applications and live streams using HTTP requests. You can use this to treat Video-On-Demand content as if it were live without having to set up a schedule or stream it from an encoder, or to control Wowza from an external location.

Prerequisites:

You should have

  • a local instance of Wowza Streaming Engine
  • an IDE such as Eclipse – here’s a link to get started with the Wowza IDE
  • experience programming in Java
  • experience with creating Wowza modules – here’s a link to developer documentation for the Wowza Streaming Engine Java API

 

Method 1:

You’ll be starting an application and a live-stream by sending a request to:

http://localhost:8086/customListener/myVideo/live

You’ll need to create a HTTP Provider, which can be used to send and receive information from the Wowza server.  You’ll make use of the HTTProvider2Base class in order to enable this capability. After you build your project into a .jar, you’ll install this module as a server listener through the configuration files.

 

Explanation:

public class Application extends HTTProvider2Base {
    @Override
    public void onHTTPRequest(IVHost vhost, IHTTPRequest req, IHTTPResponse resp) {
    
        String[] splitPath = req.getPath().split("/");

When an HTTP request comes in, you’ll need to catch it by implementing onHTTPRequest. When a request arrives, most of the important information will be inside of the IHTTPRequest object. Extract this important information from the path and store it in an array.

    String resource = "";
    String application = "";
    String applicationInstance = "";
 
    if(splitPath.length > 2){
        //Resource to play
        resource = splitPath[1];
        //Application to play it on
        application = splitPath[2];
    }else if (splitPath.length > 3){
        //Optional instance of application
        applicationInstance = splitPath[3];
    }

Next, assign this information to values that you’ll use later. The first part of your path, splitPath[0], is used only to navigate to your HTTP Provider, so you can safely ignore it. The second part of the path at splitPath[1] should be the resource you wish to stream. This should be a file name without an extension. For example:  myVideo

The third part of your path should be the application you want to utilize. For example: live

Optionally, you may add a fourth segment, which will be the application instance where you want to create the stream. For example, include channel1 if you wish to stream to live/channel1. If you do not specify this value, it will default to _definst_.

    //start application
    vhost.startApplicationInstance(application);
    //get application
    IApplication app = vhost.getApplication(application);
    IApplicationInstance instance = app.getAppInstance(applicationInstance);
    //Start stream
    Stream stream = Stream.createInstance(instance, "MyStream");
    stream.play(resource,0,-1,false);

Finally, you’ll  start up the application, access the application instance (which may just be the default), and use the resource named in the url to start your stream. In this example, your stream will be titled MyStream.

Your stream will be accessible at rtmp://localhost:1935/live/MyStream

Complete code:

package com.realeyes.wowza.modules.httpDemo
 
import com.wowza.wms.application.IApplication;
import com.wowza.wms.application.IApplicationInstance;
import com.wowza.wms.http.HTTProvider2Base;
import com.wowza.wms.http.IHTTPRequest;
import com.wowza.wms.http.IHTTPResponse;
import com.wowza.wms.stream.publish.Stream;
import com.wowza.wms.vhost.IVHost;
 
public class Application extends HTTProvider2Base {
    @Override
    public void onHTTPRequest(IVHost vhost, IHTTPRequest req, IHTTPResponse resp) {
 
    String[] splitPath = req.getPath().split("/");
    String resource = "";
    String application = "";
    String applicationInstance = "";
 
    if(splitPath.length > 2){
        //Resource to play
        resource = splitPath[1];
        //Application to play it on
        application = splitPath[2];
    }else if (splitPath.length > 3){
        //Optional instance of application
        applicationInstance = splitPath[3];
    }
 
    //start application
    vhost.startApplicationInstance(application);
    //get application
    IApplication app = vhost.getApplication(application);
    IApplicationInstance instance = app.getAppInstance(applicationInstance);
    //Start stream
    Stream stream = Stream.createInstance(instance, "MyStream");
    stream.play(resource,0,-1,false);
 
    }
}

Post-code Setup:

After you’ve saved your code and built it into a .jar file (which should be done for you automatically when running the default settings), you need to specify this as an HTTP Provider.

  1. Navigate to your wowza install directory and open conf/VHost.xml
  2. Under <HttpProviders>, add a <HTTPProvider> entry.
  3. Specify BaseClass as the fully qualified class path to your module.
  4. Specify RequestFilters as “customListener*”
  5. Specify AuthenticationMethod as “none”

It should look something like this:

<HTTPProvider>
	<BaseClass>com.realeyes.wowza.modules.httpDemo.Application</BaseClass>
	<RequestFilters>customListener*</RequestFilters>
	<AuthenticationMethod>none</AuthenticationMethod>
</HTTPProvider>


The value you specify for RequestFilters will end up being splitPath[0], and only requests that match this filter will run through your code.

Restart Wowza, and you should be able to hit your endpoint at http://localhost:8086/customListener/myVideo/myApplication

Method 2:

What if you don’t want to use port 8086, or what if you’d rather start and connect to a stream with just one request? You can use a similar technique at the application level as well, by implementing a built-in listener called “onHTTPSessionCreate”. Using this, you’ll intercept the request and delay the response until you’ve started your stream.

Your URL will look a little different as well. It should look like this:

http://localhost:1935/live/myStream/playlist.m3u8?fileName=sample.mp4

For this method, you’ll access the application and the stream directly, and specify the resource to use through a query parameter.

 

Explanation:

public class Application extends ModuleBase {

    IApplicationInstance thisInstance;
    public Map <String,String> query = null;

    public void onAppStart(IApplicationInstance appInstance) {
        thisInstance = appInstance;
    }

First you’ll want to store your application instance and create a map for your query parameters. You’ll use this later.

public void onHTTPSessionCreate(IHTTPStreamerSession httpSession) {

    try {
        query = splitQueryString(httpSession.getQueryStr());
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    
    //...
}
        
//Taken mostly from http://stackoverflow.com/a/13592567/773737
public Map <String, String> splitQueryString(String str) throws UnsupportedEncodingException {
    final Map <String, String> query_pairs = new LinkedHashMap <String, String>();
    final String[] pairs = str.split("&");
    for (String pair: pairs) {
        final int idx = pair.indexOf("=");
        final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
        if (!query_pairs.containsKey(key)) {
            query_pairs.put(key, null);
        }
        final String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null;
        query_pairs.put(key, value);
    }
    return query_pairs;
}

The onHTTPSessionCreate listener catches requests to HLS streams that end with playlist.m3u8. From here, extract the query string from your request into the map you created previously.

    if (query.get("fileName") != null) {
        String fileName = query.get("fileName");
        Stream stream = Stream.createInstance(thisInstance, "myStream");
        stream.play(fileName, 0, -1, false);

        //Sleep so we don't disconnect the client while the stream is starting up
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
             e.printStackTrace();
        }
    }

Finally, use the fileName parameter to start up your stream in the same way as method #1. You don’t need to specify your application or your application instance, because this code is being run inside of your application instance.  You’re actually accessing a stream that doesn’t yet exist, and since the stream creation process call takes a few seconds to complete, this would normally return an error. To prevent this, sleep for a few seconds in order to delay the response.

Your client should experience a short delay in addition to the seconds you spend sleeping, but afterwards, it should be connected to the new stream.

Complete code:

 

package com.realeyes.wowza.modules.httpDemo2;

import com.wowza.wms.application.*;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.LinkedHashMap;
import java.util.Map;

import com.wowza.wms.amf.*;
import com.wowza.wms.client.*;
import com.wowza.wms.module.*;
import com.wowza.wms.request.*;
import com.wowza.wms.stream.publish.Stream;
import com.wowza.wms.httpstreamer.model.*;
import com.wowza.wms.httpstreamer.cupertinostreaming.httpstreamer.*;

public class Application extends ModuleBase {

    IApplicationInstance thisInstance;
    public Map <String,String> query = null;

    public void onAppStart(IApplicationInstance appInstance) {
        thisInstance = appInstance;
    }
    
    public void onHTTPSessionCreate(IHTTPStreamerSession httpSession) {

        try {
            query = splitQueryString(httpSession.getQueryStr());
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        
        if (query.get("fileName") != null) {
            String fileName = query.get("fileName");
            Stream stream = Stream.createInstance(thisInstance, "myStream");
            stream.play(fileName, 0, -1, false);

            //Sleep so we don't disconnect the client while the stream is starting up
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
        }
    }

    //Taken mostly from http://stackoverflow.com/a/13592567/773737
    public Map <String, String> splitQueryString(String str) throws UnsupportedEncodingException {
        final Map <String, String> query_pairs = new LinkedHashMap <String, String>();
        final String[] pairs = str.split("&");
        for (String pair: pairs) {
            final int idx = pair.indexOf("=");
            final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
            if (!query_pairs.containsKey(key)) {
                query_pairs.put(key, null);
            }
            final String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null;
            query_pairs.put(key, value);
        }
        return query_pairs;
     }
}

 

 

Post-code Setup:

After you compile and build your module, add it to your application.

To install this module to the default “live” application:

  1. Navigate to your Wowza install directory and
  2. Open /conf/live/Application.xml,
  3. Add a new <Module> to your <Modules> entry, like so:
<Module>
 <Name>httpDemo</Name>
 <Description>demo</Description>
 <Class>com.realeyes.wowza.modules.httpDemo2.Application</Class>
</Module>

Restart your server and you should be good to go.

Conclusion:

Being able to trigger applications or streams over HTTP can be a useful tool. You can make further use of this functionality to stop streams, gather metrics, kick users or inject advertisements. You could even create a REST service using this, and integrate Wowza into your back-end server.

To see the official documentation behind the concepts used here, follow these links:

Stream Class Example

How to create an HTTP Provider

How to control access to RTSP/RTP streams

How to control access to HTTP streams

 

Share this

© 2017 RealEyes Media, LLC. All rights reserved.