• How to insert a pre-roll or mid-roll for video on demand playback in Flash RTMP client

    The following example shows how to create a video on demand, server-side playlist. In this example, the requested video is played in two parts with a midroll in between. You could play any number of streams whole or in part as one stream. You can use the stream name requested by the client along with other videos (as in this example) or substitute anything you want.

    This requires Flash client. There is not an equivalent for non-Flash clients at present. There is a client-side work-around for iOS (see How to do pre-roll and playlist for iOS (a client-side work-around)), but it won't have seamless playback. Similar is possible in a Silverlight client. There is no RTSP workaround.

    package com.wowza.wms.example;
    
    import com.wowza.wms.module.*;
    import com.wowza.wms.amf.*;
    import com.wowza.wms.client.*;
    import com.wowza.wms.request.*;
    import com.wowza.wms.stream.IMediaStream;
    import com.wowza.wms.util.StreamUtils;
    import com.wowza.wms.logging.WMSLogger;
    import com.wowza.wms.logging.WMSLoggerFactory;
    
    public class ModuleInsertMidRoll extends ModuleBase {
    	
    	public void play(IClient client, RequestFunction function,
            AMFDataList params) {
    		
    		String streamName = params.getString(PARAM1);
    		IMediaStream stream =getStream(client, function);
    		
    		String midRoll = client.getAppInstance().getProperties().getPropertyStr("MidRoll");
    		Integer featureBreak  = client.getAppInstance().getProperties().getPropertyInt("FeatureBreak", 10000);
    		
    		getLogger().info("Insert Midroll for: " + streamName);
    		
    		//Feature start
    		AMFDataList customDataList = new AMFDataList();
    		customDataList.add("play");
    		customDataList.add(0.0);
    		customDataList.add("null");
    		customDataList.add(streamName);
    		customDataList.add(new AMFDataItem(0)); // start value in milliseconds
    		customDataList.add(new AMFDataItem(featureBreak)); //duration (-1 means play to the end).  Value in milliseconds.
    		customDataList.add(new AMFDataItem(true)); //reset
    		invokePrevious(this, client, function, customDataList);
    		
    		//Midroll
    		customDataList.set(PARAM1, new AMFDataItem(midRoll));
    		customDataList.set(PARAM2, new AMFDataItem(0)); // start
    		customDataList.set(PARAM3, new AMFDataItem(-1)); // duration
    		customDataList.set(PARAM4, new AMFDataItem(false));
    		invokePrevious(this, client, function, customDataList);
    		
    		// Feature continued
    		customDataList.set(PARAM1, new AMFDataItem(streamName));
    		customDataList.set(PARAM2, new AMFDataItem(featureBreak)); // start
    		customDataList.set(PARAM3, new AMFDataItem(-1)); // duration
    		customDataList.set(PARAM4, new AMFDataItem(false));
    		invokePrevious(this, client, function, customDataList);
    
    		// To send the Flash client the correct duration you must calculate total duration and inject into the metadata.
    		// In this example we can calculate total duration by adding the duration of feature and midroll. 
    		// If you use start property > then 0 and/or duration property other than -1  in any item,
    		// you would have to adjust to get actual totalDuration for the playlist
    
    		double overrideDuration = StreamUtils.getStreamLength(client.getAppInstance(), streamName);
    		overrideDuration += StreamUtils.getStreamLength(client.getAppInstance(), midRoll);
    		
    		//Store the calculated  duration in a property of the stream to be extracted by the MediaReaderMP4InjectMetadata shown below.
    		stream.getProperties().put("overrideDuration", overrideDuration); // Value in seconds.
    
    		}
    }
    Add this Module to the /conf/[app-name]/Application.xml Module list. Add it to the bottom, make it the last Module.

    <Module>
    	<Name>ModuleInsertMidRoll</Name>
    	<Description>ModuleInsertMidRoll</Description>
    	<Class>com.wowza.wms.example.ModuleInsertMidRoll</Class>
    </Module>
    Add these properties to Application.xml /Properties.
    <Property>
    	<Name>MidRoll</Name>
    	<Value>mp4:sample.mp4</Value>
    </Property>
    
    <Property>
    	<Name>FeatureBreak</Name>
    	<Value>10000</Value>
    	<Type>Integer</Type>
    </Property>
    To send the calculated duration from playlist above to the Flash client, create this MediaReaderMP4InjectMetadata Class, then modify /conf/MediaReaders.xml as shown.
    package com.wowza.wms.example;
    
    import java.nio.ByteBuffer;
    import java.util.List;
    import com.wowza.wms.mediareader.h264.MediaReaderH264;
    import com.wowza.wms.amf.AMFDataItem;
    import com.wowza.wms.amf.AMFDataList;
    import com.wowza.wms.amf.AMFDataMixedArray;
    import com.wowza.wms.logging.WMSLogger;
    import com.wowza.wms.logging.WMSLoggerFactory;
    
    public class MediaReaderMP4InjectMetadata extends MediaReaderH264
    {
    	public List<ByteBuffer> getMetadata()
    	{
    		List<ByteBuffer> ret = super.getMetadata();
    		WMSLogger getLogger = WMSLoggerFactory.getLogger(null); 
    
    		// Here the calculated duration stored by above is extracted.
    		// Note that the default duration is used if there is no duration property in the stream, so simple video streaming will work as usual
    		double overrideDuration = this.stream.getProperties().getPropertyDouble("overrideDuration", (double) this.duration / 1000); // Value in seconds.
    		getLogger.info("overrideDuration: " + duration);
    		
    		while(true)
    		{
    			if (ret == null)
    				break;
    			if (ret.size() <= 0)
    				break;
    			
    			ByteBuffer packet = ret.get(0);
    			
    			AMFDataList myMetadata = new AMFDataList(packet);
    
    			AMFDataMixedArray dataObj = (AMFDataMixedArray)myMetadata.get(1);
    			dataObj.put("duration", new AMFDataItem(overrideDuration));  // duration in seconds.
    			
    			byte[] data = myMetadata.serialize();
    			ByteBuffer newPacket = ByteBuffer.wrap(data);			
    			ret.set(0, newPacket);
    			break;
    		}
    		return ret;
    	}
    }
    To hook up this class edit [install-dir]/conf/MediaReaders.xml and change the BassClass for the mp4 file type like this:

    From:
    <ClassBase>com.wowza.wms.mediareader.h264.MediaReaderH264</ClassBase>
    To:
    <ClassBase>com.wowza.wms.example.MediaReaderMP4InjectMetadata</ClassBase>
    Note: The above change is only possible with Wowza Media Server™ 3 or earlier. For Wowza Streaming Engine™ software, use the following VHost Listener.
    package com.wowza.wms.example;
    
    import com.wowza.wms.amf.AMFDataList;
    import com.wowza.wms.client.IClient;
    import com.wowza.wms.request.RequestFunction;
    import com.wowza.wms.stream.MediaReaderItem;
    import com.wowza.wms.vhost.IVHost;
    import com.wowza.wms.vhost.IVHostNotify;
    
    public class VHostListenerMediaReaderOverride implements IVHostNotify
    {
    
    	@Override
    	public void onVHostCreate(IVHost vhost)
    	{
    	}
    
    	@Override
    	public void onVHostInit(IVHost vhost)
    	{
    		MediaReaderItem item = vhost.getMediaReaders().getMediaReaderDef("mp4");
    		if (item != null)
    			item.setBaseClass("com.wowza.wms.example.MediaReaderMP4InjectMetadata");
    	}
    
    	@Override
    	public void onVHostShutdownStart(IVHost vhost)
    	{
    	}
    
    	@Override
    	public void onVHostShutdownComplete(IVHost vhost)
    	{
    	}
    
    	@Override
    	public void onVHostClientConnect(IVHost vhost, IClient inClient, RequestFunction function, AMFDataList params)
    	{
    	}
    }
    To enable the VHost Listener, edit [install-dir]/conf/Server.xml and add the following to the VHostListeners section.
    <VHostListener>
    	<BaseClass>com.wowza.wms.example.VHostListenerMediaReaderOverride</BaseClass>
    </VHostListener>
    This only works for h.264 video, but the same methodology can be used to inject calculated duration (and other metadata) into FLV streams.


    Originally Published: 10-02-2010.
    Updated: 10-31-2014.

    If you're having problems or want to discuss this article, post in our forum.