• How to do scheduled streaming with Stream class streams (server listener)

    A server listener that starts streams with playlists and schedules from smil file

    Like a TV station, a Stream in this schedule is a channel, the Playlists are programs with one or more Video segments. A schedule can have as many Streams (channels) as you want, with as many Playlists (programs) as you want, where each Playlist (program) is scheduled to play on a Stream (channel) at a certain time. If a Playlist is scheduled to start on a Stream while another Playlist is running, it will start. If you set a schedule in the past the Playlist will play immediately.

    Note: This runs when Wowza starts only. See this post for a version that will load this schedule on demand

    Code:
    package com.wowza.wms.plugin.collection.serverlistener;
    
    import com.wowza.wms.application.*;
    import com.wowza.wms.server.*;
    import com.wowza.wms.vhost.*;
    import com.wowza.wms.stream.publish.*;
    import com.wowza.wms.logging.*;
    import java.io.File;
    import java.text.SimpleDateFormat; 
    import java.util.*; 
    
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import org.w3c.dom.Document;
    import org.w3c.dom.Element; 
    import org.w3c.dom.Node; 
    import org.w3c.dom.NodeList;
     
    public class ServerListenerStreamPublisher implements IServerNotify { 
        
        WMSLogger log = WMSLoggerFactory.getLogger(null); 
        
        Map<String, Stream> streamMap = new HashMap<String, Stream>();
        Map<String, Playlist> playlistMap = new HashMap<String, Playlist>();
        
        public void onServerInit(IServer server)
        {
            log.info("ServerListenerStreamPublisher Started.");
            
            IVHost vhost = null;
            IApplication app = null;
            
            
            try
                {
                vhost = VHostSingleton.getInstance(server.getProperties().getPropertyStr("PublishToVHost", "_defaultVHost_"));
                } catch (Exception evhost) {
                    log.info("ServerListenerStreamPublisher: Failed to get Vhost can not run.");
                    return;
                }
    
            try 
                {
                app = vhost.getApplication(server.getProperties().getPropertyStr("PublishToApplication", "live"));
                } catch (Exception eapp) {
                    log.info("ServerListenerStreamPublisher: Failed to get Application can not run.");
                    return;
                }
    
            
                // Belt and Braces check for VHost and App
                if ( vhost == null || app == null )
                    {
                    log.info("ServerListenerStreamPublisher: VHost or Application failed, not running.");
                    return;
                    }
    
    
                Boolean passThruMetaData = server.getProperties().getPropertyBoolean("PassthruMetaData", true);
                
                String storageDir = app.getAppInstance("_definst_").getStreamStorageDir();
                
            try
                {            
                String smilLoc = storageDir + "/streamschedule.smil";
                File playlistxml = new File(smilLoc);
                    
                if (playlistxml.exists() == false){
                    log.info("ServerListenerStreamPublisher: Could not find playlist file: " + smilLoc);
                    return; 
                }
                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                
                
                DocumentBuilder db = null;
                Document document = null;
                try {
                
                db = dbf.newDocumentBuilder();
                document = db.parse("file:///" + smilLoc);
                
                } catch (Exception e ) { log.info("ServerListenerStreamPublisher: XML Parse failed"); return; }
    
                
                document.getDocumentElement().normalize();
                
                NodeList streams = document.getElementsByTagName("stream");
                for (int i = 0; i < streams.getLength(); i++)
                {
                    Node streamItem = streams.item(i);
                    if (streamItem.getNodeType() == Node.ELEMENT_NODE)
                    { 
                        Element e = (Element) streamItem;
                        String streamName = e.getAttribute("name");
                        
                        log.info("ServerListenerStreamPublisher: Streame name is '"+streamName+"'");
                        
                        Stream stream = Stream.createInstance(vhost, app.getName(), streamName);
                        streamMap.put(streamName, stream);
                        app.getAppInstance("_definst_").getProperties().setProperty(streamName, stream);
                    }
                }
                
                NodeList playList = document.getElementsByTagName("playlist");
                if (playList.getLength() == 0){
                    log.info("ServerListenerStreamPublisher: No playlists defined in smil file");
                    return;
                } 
                for (int i = 0; i < playList.getLength(); i++)
                {
                    Node scheduledPlayList = playList.item(i);
                    
                    if (scheduledPlayList.getNodeType() == Node.ELEMENT_NODE)
                    {
                        Element e = (Element) scheduledPlayList;    
                        
                        NodeList videos = e.getElementsByTagName("video");
                        if (videos.getLength() == 0){
                             log.info("ServerListenerStreamPublisher: No videos defined in stream");
                            return;
                        }
                        
                        String streamName = e.getAttribute("playOnStream");
                        if (streamName.length()==0)
                            continue;
                        
                        Playlist playlist = new Playlist(streamName);
                        playlist.setRepeat((e.getAttribute("repeat").equals("false"))?false:true);
                        
                        playlistMap.put(e.getAttribute("name"), playlist);
                        
                        for (int j = 0; j < videos.getLength(); j++)
                        {
                            Node video = videos.item(j);                
                            if (video.getNodeType() == Node.ELEMENT_NODE)
                            {
                                Element e2 = (Element) video;
                                String src = e2.getAttribute("src");
                                Integer start = Integer.parseInt(e2.getAttribute("start"));
                                Integer length = Integer.parseInt(e2.getAttribute("length"));
                                playlist.addItem(src, start, length);
                            }
                        }
                        String scheduled = e.getAttribute("scheduled");
                        SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        Date startTime = null;
                        try {
                        
                        startTime = parser.parse(scheduled);
                        } catch (Exception z ) { log.info("Parsing schedule time failed."); return ; }
                        Stream stream = streamMap.get(streamName);
                        stream.setSendOnMetadata(passThruMetaData);
                        ScheduledItem item = new ScheduledItem(startTime, playlist, stream);
                        item.start();
                        IStreamActionNotify actionNotify  = new StreamListener(app.getAppInstance("_definst_"));
                        stream.addListener(actionNotify);
                        log.info("ServerListenerStreamPublisher Scheduled: " + stream.getName() + " for: " + scheduled);
                    }    
                }
            }
            catch(Exception ex)
            {
                log.info("ServerListenerStreamPublisher: Error from playlist manager is '"+ex.getMessage()+"'");
            }
        }
        
    private class ScheduledItem {
            public Timer mTimer;
            public TimerTask mTask;
            public Date mStart;
            public Playlist mPL;
            public Stream mStream;
            public ScheduledItem(Date d, Playlist pl, Stream s){
                mStart = d;
                mPL = pl;
                mStream = s;
                mTask = new TimerTask(){
                    public void run() {
                        //synchronized(mStream.getLock())
                        //{
                            mPL.open(mStream);
                        //}
                        log.info("ServerListenerStreamPublisher Scheduled stream is now live: " + mStream.getName());
                    }
                };
                mTimer = new Timer();
            }
            
            public void start(){ 
                
                if (mTimer==null)
                    mTimer = new Timer();
                mTimer.schedule(mTask, mStart);
                log.info("scheduled playlist: "+mPL.getName()+
                            " on stream: "+mStream.getName()+
                            " for:"+mStart.toString());
            }
            
            public void stop(){
                if (mTimer != null){
                    mTimer.cancel();
                    mTimer=null;
                    log.info("cancelled playlist: "+mPL.getName()+
                            " on stream: "+mStream.getName()+
                            " for:"+mStart.toString());
                }
            }
        }
        public void onServerCreate(IServer server)
        {
        }
        public void onServerShutdownComplete(IServer server)
        {
            log.info("ServerListenerStreamPublisher: Shutdown server start");
            for (Map.Entry<String, Stream> entry : streamMap.entrySet())
            {
                try
                {
                Stream stream = entry.getValue();
                stream.close();
                stream = null;
                log.info("ServerListenerStreamPublisher Closed Stream: " + entry.getKey());
                }
                catch(Exception ex)
                {
                    log.error(ex.getMessage());
                }
            }
            for (Map.Entry<String, Playlist> entry : playlistMap.entrySet())
            {
                try
                {
                Playlist pl = entry.getValue();
                pl = null;
                }
                catch(Exception ex)
                {
                    log.error(ex.getMessage());
                }
            }
        }
    
        public void onServerShutdownStart(IServer server)
        {
            
        }
        
        class StreamListener implements IStreamActionNotify
        {
            StreamListener(IApplicationInstance appInstance)
            {
            }    
            public void onPlaylistItemStop(Stream stream, PlaylistItem item)
            {
            	if (item.getIndex() == (stream.getPlaylist().size() - 1))
            	{
            	if (! stream.getRepeat())
            	{
            	stream.close();
            	WMSLoggerFactory.getLogger(null).info("ServerListenerStreamPublisher: closing stream: " + stream.getName());
            	}
            	}
            }
            public void onPlaylistItemStart(Stream stream, PlaylistItem item) 
            {
                try
                {
                String name = stream.getCurrentItem().getName();
                stream.getPublisher().getAppInstance().broadcastMsg("PlaylistItemStart", name);
                WMSLoggerFactory.getLogger(null).info("ServerListenerStreamPublisher PlayList Item Start: " + name);
                }
                catch(Exception ex)
                {
                    WMSLoggerFactory.getLogger(null).info("ServerListenerStreamPublisher Get Item error: " + ex.getMessage());
                }
            }
        }
    }
    Add the server listener to /conf/Server.xml /ServerListeners:
    Code:
    <ServerListener>
        <BaseClass>com.wowza.wms.plugin.collection.serverlistener.ServerListenerStreamPublisher</BaseClass>
    </ServerListener>
    IMPORTANT: this server listener requires that there be an application named "live" where the Streams and Playlists are created. You can set another name for the application to be populated in /conf/Server.xml /Properties:

    Code:
    <Property>
    <Name>PublishToApplication</Name>
    <Value>MyLiveApp</Value>
    </Property>
    And if you want to publish to another VHost, add this Property to /conf/Server.xml /Properties:

    Code:
    <Property>
    <Name>PublishToVHost</Name>
    <Value>CustomVHost</Value>
    </Property>
    MetaData from media files are passed to the live stream by default, if you don't want that to happen, add this Property to /conf/Server.xml /Properties:

    Code:
    <Property>
        <Name>PassthruMetaData</Name>
        <Value>false</Value>
        <Type>Boolean</Type>
    </Property>
    The target application must have an Application.xml /StreamType "live" or "live-lowlatency"

    Create the smil file [install-dir]/content/streamschedule.smil

    Create at least one stream, then create at least one playlist that plays on that stream. Set repeat to true or false for each playlist. Use start="-2" for live sources. Use length="-1" to play until media ends.

    You can create an elaborate schedule with several streams and many playlists scheduled to play on a stream.

    Code:
    <smil>
        <head>
        </head>
        <body>
    
            <stream name="Stream1"></stream>
            <stream name="Stream2"></stream>
            
            <playlist name="pl1" playOnStream="Stream1" repeat="true" scheduled="2009-12-11 16:00:00">
                <video src="mp4:sample.mp4" start="5" length="5"/>
                <video src="mp4:sample.mp4" start="50" length="5"/>
                <video src="mp4:sample.mp4" start="150" length="5"/>
            </playlist>
            <playlist name="pl2" playOnStream="Stream1" repeat="true" scheduled="2009-12-11 16:30:00">
                <video src="mp4:sample.mp4" start="0" length="-1"/>
            </playlist>
            
            <playlist name="pl3" playOnStream="Stream2" repeat="true" scheduled="2009-12-11 16:00:00">
                <video src="mp4:sample.mp4" start="30" length="5"/>
            </playlist>
    
        </body>
    </smil>
    Create a Wowza app named "live", change the Application.xml /StreamType to "live"

    To play open:
    [install-dir]/examples/LiveVideoStreaming/FlashRTMPPlayer/player.html (Wowza Media Server 3.5 and later)
    -or-
    [install-dir]/examples/LiveVideoStreaming/client/live.html (prior version of Wowza Media Server)

    Server: rtmp://[wowza-ip-address]/live
    Stream: Stream1 (or Stream2. As set in smil file)

    A compiled version of this server listener is included in the Wowza Modules Collection. Download and unzip the collection, then copy /lib/wms-plugin-collection.jar from the package to the Wowza /lib folder. Then restart Wowza.

    This ServerListener broadcasts Playlist item changes to Flash clients. You can listen for these messages by adding a callback "PlaylistItemStart" on the NetConnection in your Flash application, something like this:
    Code:
    var clientObj:Object = new Object();
    clientObj.PlaylistItemStart(itemName:String):void
    {
    trace("Item Name: " + itemName);
    });
    
    netconnection.client = clientObj;
    netconnection.connect("rtmp://[wowza-ip-address]:1935/[app-name]");
    Also take a look at the Stream Class Controller example:
    http://www.wowzamedia.com/forums/content.php?187


    Comments 226 Comments
    1. killerwake -
      hi,

      what part of code i have to change if instead of scheduling a playlist to a particular day and time i want it to start as the stream starts, an example of what i need would be:

      <smil>
      <head>
      </head>
      <body>

      <stream name="Stream1"></stream>
      <stream name="Stream2"></stream>

      <playlist name="pl1" playOnStream="Stream1" repeat="true" scheduled="now">
      <video src="mp4:Extremists.m4v" start="5" length="5"/>
      <video src="mp4:Extremists.m4v" start="50" length="5"/>
      <video src="mp4:Extremists.m4v" start="150" length="5"/>
      </playlist>
      <playlist name="pl2" playOnStream="Stream1" repeat="true" scheduled="2009-12-11 16:30:00">
      <video src="mp4:Extremists.m4v" start="0" length="-1"/>
      </playlist>

      <playlist name="pl3" playOnStream="Stream2" repeat="true" scheduled="2009-12-11 16:00:00">
      <video src="mp4:Extremists.m4v" start="30" length="5"/>
      </playlist>

      </body>
      </smil>
    1. rrlanham -
      Set the "scheduled" date in the past and it will start as soon as the server starts.

      Richard
    1. killerwake -
      wow easy, didn't test it

      thanks rrlanham!
    1. johnstorey -
      Hey, I am using my own scheduler module based on early posts by Charlie. It's great to find out that you have an "official" one. (quotes as I don't know if it's supported or just given out of the kindness of your hearts.)

      My client now wants to do this, but instead of just streaming to Flash, also stream them to iPhones and Android. I have a dim memory that just throwing H264 videos in here and pointing the iPhone to them will not work, especially since Apple seems to require multi-bitrate streams over HTTPS. Is my memory correct, or will this actually work in that scenario?

      Thanks again for the great modules and fantastic forum support.
    1. rrlanham -
      It works the same as any live stream. It will work as long as the format is correct for the client. They all need H.264 video with AAC or MP3 audio. Older IPhone requires h.246 Baseline 3 profile video. For 3g mobile bitrate has to be minimal, for Roku bitrate it has to be 250kbs minimum.

      Richard
    1. barichon -
      Hi when compiling, I obtain:

      javac ServerListenerStreamPublisher.java
      ServerListenerStreamPublisher.java:3: package com.wowza.wms.application does not exist
      import com.wowza.wms.application.*;
      ^
      ServerListenerStreamPublisher.java:4: package com.wowza.wms.server does not exist
      import com.wowza.wms.server.*;
      ^
      ServerListenerStreamPublisher.java:5: package com.wowza.wms.vhost does not exist
      import com.wowza.wms.vhost.*;
      ^
      ServerListenerStreamPublisher.java:7: package com.wowza.wms.stream.publish does not exist
      import com.wowza.wms.stream.publish.*;
      ^
      ServerListenerStreamPublisher.java:8: package com.wowza.wms.logging does not exist
      import com.wowza.wms.logging.*;


      Could you help me please ?

      Thx by advance.
    1. rrlanham -
      This is a of this duplicate post:
      http://www.wowzamedia.com/forums/sho...laylist-module

      Please don't duplicate posts, it's confusing and time consuming to sort out.

      Richard
    1. davidsantos -
      Hi,

      I'm trying to dynamically create a smill file using PHP.

      Is it possible to point to http://mysite/schedulle.php instead of the schedulle.smil file?

      thanks
    1. ianbeyer -
      The stream class schedule smil file is loaded on server startup and will persist until the server is restarted. You'd need to have your PHP dynamically write the SMIL file to the /content directory and restart the application.
    1. BiggyTV1 -
      How do you restart a specific application or did you mean "You'd need to have your PHP dynamically write the SMIL file to the /content directory and restart the SERVER."
    1. rrlanham -
      Yes, it is the server that needs to be restarted with this method.

      Richard
    1. notreg -
      How make it to work with smooth? When switch happens smooth player stopped, not working anymore.
    1. davidsantos -
      So, if I'm thinking right...

      i now have a PHP script that creates the SMIL file. In order to refresh the stream with the newly created playlists I would have to do one of the following:

      A- restart the server - probably with a chron job every day at 4 am for example

      or

      B - with flash tell wowza to load the playlists to the stream, just like the StreamCassControl does

      My question now is, how do I create my own StreamClassControl swf ? where does the first block of code go to? to Flash ? Is this all done in Flash ?
    1. rrlanham -
      @notreg,
      The video and audio encoding parameters should be identical or very similar, otherwise switching can fail. This is true for iOS devices too.

      @davidsantos,
      The example includes Flash Actionscript you can use:
      http://www.wowzamedia.com/forums/con...eam-controller

      Richard
    1. ianbeyer -
      Have you tried it with /live/_definst_/Stream1.sdp/playlist.m3u8 ?

      Anything in the logs when you try to access the stream from iPad?
    1. dnetman99 -
      Can anyone tell why this smil does not work?

      <smil>
      <head>
      </head>
      <body>

      <stream name="streamBlank"></stream>
      <stream name="streamPre"></stream>


      <playlist name="pl1" playOnStream="streamBlank" repeat="true" scheduled="2011-02-25 09:00:00">
      <video src="blankWebcast.flv" start="1" length="8"/>
      </playlist>
      <playlist name="pl2" playOnStream="streamPre" repeat="true" scheduled="2011-02-25 09:00:00">
      <video src="preWebcast.flv" start="2" length="8"/>
      </playlist>
      </body>
      </smil>

      What I get is that pl1 stream will start but you see nothing when you view, but the file works as you can see it using on VOD. Then pl2 stream never even starts. No errors in the log at all.

      Any ideas? Do I had both starts set to 1 and that did not work either.

      Thanks,
      Robert
    1. rrlanham -
      Robert,

      Did you add the ServerListener to /conf/Server.xml, and the collection jar to Wowza /lib folder, and restart Wowza?

      If you start Wowza in stand-alone mode, in a console (/bin/startup.bat), you see these streams being started in the console. Good for testing and development.

      The scheduler is a ServerListener, you have to restart Wowza to load the schedule and start streams.

      Richard
    1. dnetman99 -
      I have this working on other servers. It seems that the module I use for SO for streams is causing a problem as it needs a client object to add to the SO. Any idea how to do the same thing without needing a client object? I am using your original code from AVCHAT I got off here. I use the shared object for the controller side of our app to switch streams on the viewer. Right now just using a seperate application for it to work, but was hoping to keep from having so many Netconnections in my app, but it works.

      Thanks
    1. rrlanham -
      This type playlist is involved with scheduler example, which is a server listener. It has to be named streamschedule.smil and be located in the content folder. You need the jar file from the Wowza module collection, unless you have built the source yourself.

      Richard
    1. dnetman99 -
      Actually what I was saying is that the problem we were encoutering was because I was using another module int he application that on streamcreate modifies a Shared Object. It is the code from AVCHAT found on the forum. It requires a client connection so it was causing a problem with the stream creation since there was actually no client connection involved since it is a server listener. I was wondering if anyone had a quick and dirty to modify and not require the client connect to create the Shared Object, so that we can still use it in the original Netconnection instead of adding another one to the application, so that our controller app can see all the streams in that one application. No big deal as we got it resolved, just asked a follow-up after I found the problem.

      Thanks,