• How to use Wowza nDVR Playlist Request API

    This article demonstrates how to use a Wowza nDVR API to control playlist requests.

    Notes:
    By default, Wowza nDVR determines which playlist to play when a DVR manifest or playlist is requested via a URL.

    Strobe/OSMF player

    http://[wowza-ip-address]:1935/dvr/myStream/manifest.f4m?DVR

    Apple iOS device

    http://[wowza-ip-address]:1935/dvr/myStream/playlist.m3u8?DVR

    Microsoft Silverlight

    http://[wowza-ip-address]:1935/dvr/myStream/Manifest?DVR
    The default logic is as follows:

    • If the stream is being recorded, it's considered live; otherwise, it's considered recorded.
    • Both live and recorded use one of the following as a start-time:
      • The earliest point in the recording
      • The latest time, minus the DVR window if one is specified
    • A live stream has no end-time.
    • A recorded stream is played with the end-time of the recording.

    Due to the nature of live HTTP streaming, live streams must cache extra chunks after the currently playing live point. Recorded streams don't have this restriction and include these cached chunks in their playlist.

    The playlist/manifest presented to the player looks different based on whether the DVR store is live or recorded. Different player technologies handle these playlists differently as well. For example, live playlists typically start playing at the "live point" while recorded playlists start playing at the earliest time.

    About the playlist request delegate


    Wowza nDVR provides the dvrPlaylistRequestDelegate delegate property, which is a mechanism to provide a different playlist request via Java. The property should be set in the Application.xml file under <Application>/<DVR>/<Properties> and should reference a fully-qualified classname that extends the com.wowza.wms.dvr.DvrBasePlaylistRequestDelegate class.

    When the playlist is requested, this delegate's getDvrPlaylistRequest() method is called to provide the playlist request.

    Playlist request

    The Playlist Request delegate is responsible for generating a playlist request object. It's passed an application context, the DVR store object (which contains methods for querying the underlying known chunks), and a queryMap of URL parameters that were passed in.

    A playlist request has a start-time and an optional end-time.

    If the start-time specified isn't valid (for example, either before the beginning of the recording or after the end of the recording or the specified end-time), the beginning of the recording is defaulted to as the start-time.

    If no end-time is specified:

    • For a live store (one that's currently recording), the playlist presented to the player is a live playlist.
    • For a recorded store (one that isn't recording), the playlist presented to the player is a recorded playlist, using the end-time of the recording.

    If a specific end-time is specified for a live DVR store, the resultant playlist is of the recorded variety because live playlists don't have an end-time.

    An example


    Wowza nDVR includes one playlist request delegate (com.wowza.wms.dvr.impl.DvrStartDurationPlaylistRequestDelegate). This delegate generates the playlist request based on URL query parameters when specifying the playlist or manifest. For example, to play from minute 1 through minute 6, we specify a start time of 60 seconds (60000 ms) and a duration of 300 seconds (300000 ms).

    Strobe/OSMF player

    http://[wowza-ip-address]:1935/dvr/myStream/manifest.f4m?DVR&wowzadvrplayliststart=60000&wowzadvrplaylistduration=300000

    Apple iOS device

    http://[wowza-ip-address]:1935/dvr/myStream/playlist.m3u8?DVR&wowzadvrplayliststart=60000&wowzadvrplaylistduration=300000

    Microsoft Silverlight

    http://[wowza-ip-address]:1935/dvr/myStream/Manifest?DVR&wowzadvrplayliststart=60000&wowzadvrplaylistduration=300000
    To use this delegate, add the following property to your Application.xml file under <Application>/<DVR>/<Properties>:
    <Properties>
    	<Property>
    		<Name>dvrPlaylistRequestDelegate</Name>
    		<Value>com.wowza.wms.dvr.impl.DvrStartDurationPlaylistRequestDelegate</Value>
    	</Property>	
    </Properties>
    To use different query parameters, also include the following properties in the same location and change the values:
    <Properties>
    	<Property>
    		<Name>dvrPlaylistRequestDelegate</Name>
    		<Value>com.wowza.wms.dvr.impl.DvrStartDurationPlaylistRequestDelegate</Value>
    	</Property>
    	<Property>
    		<Name>dvrPlaylistDurationQueryParameter</Name>
    		<Value>wowzadvrplaylistduration</Value>
    	</Property>	
    	<Property>
    		<Name>dvrPlaylistStartQueryParameter</Name>
    		<Value>wowzadvrplayliststart</Value>
    	</Property>	
    </Properties>
    To add debug logging for this delegate, add the following property to your Application.xml file under <Application>/<DVR>/<Properties>:
    <Properties>
    	<Property>
    		<Name>dvrPlaylistDebugRequests</Name>
    		<Value>true</Value>
                     <Type>Boolean</Type>
    	</Property>	
    </Properties>

    UTC-based request delegate in Wowza Streaming Engine

    Wowza Streaming Engine software includes a UTC-based playlist request delegate. To use this delegate, add the following property to your Application.xml file under <Application>/<DVR>/<Properties>:
    <Properties>
    	<Property>
    		<Name>dvrPlaylistRequestDelegate</Name>
    		<Value>com.wowza.wms.dvr.impl.DvrUtcStartDurationPlaylistRequestDelegate</Value>
    	</Property>	
    </Properties>
    Note: By default, the UTC delegate uses GMT time and the UTC format yyyyMMddHHmmss (Wowza Streaming Engine 4.0.4 and later). In prior versions of Wowza Streaming Engine software, the default format is yyy-MM-dd-HH:mm:ss. However, we don't recommend using formats that have dashes, colons, or other special characters. Use the properties described below to set an appropriate format.

    Consult this list of valid timezone values.
    This delegate uses the following properties (as documented above):

    • dvrPlaylistDurationQueryParameter
    • dvrPlaylistStartQueryParameter
    • dvrPlaylistDebugRequests

    In addition, it provides the following properties to control the format and conversion of the UTC string provided in the URL:
    <Properties>
    	<Property>
    		<Name>dvrPlaylistUTCFormat</Name>
    		<Value>yyyyMMddHHmmss</Value>
    	</Property>
    	<Property>
    		<Name>dvrPlaylistUTCTimeZone</Name>
    		<Value>Europe/London</Value>
    	</Property>	
    </Properties>
    To use this delegate, the URLs would look like the following:

    Strobe/OSMF player

    http://[wowza-ip-address]:1935/dvr/myStream/manifest.f4m?DVR&wowzadvrplayliststart=20140211083000&wowzadvrplaylistduration=300000

    Apple iOS device

    http://[wowza-ip-address]:1935/dvr/myStream/playlist.m3u8?DVR&wowzadvrplayliststart=20140211083000&wowzadvrplaylistduration=300000

    Microsoft Silverlight

    http://[wowza-ip-address]:1935/dvr/myStream/Manifest?DVR&wowzadvrplayliststart=20140211083000&wowzadvrplaylistduration=300000

    Changes to playlist delegate behavior in Wowza Streaming Engine

    In Wowza Media Server software, the default playlist delegate returns the entire playlist of a DVR store if the start and duration provided in the URL request doesn't make sense (for example, if the start is after the end of the recording). Because this shouldn't have been the default behavior, in Wowza Streaming Engine software, the base class that used to write custom playlist delegates (com.wowza.wms.dvr.DvrBasePlaylistRequestDelegate) is changed to return an empty playlist in these cases (this also affects DvrStartDurationPlaylistRequestDelegate).

    If the old behavior is desired, a legacy playlist request delegate that gives the old behavior is provided. This delegate has the same properties as DvrStartDurationPlaylistRequestDelegate, just different behavior when an invalid request is made.

    To use this delegate, add the following property to your Application.xml file under <Application>/<DVR>/[/B]<Properties>[/B]:
    <Properties>
    	<Property>
    		<Name>dvrPlaylistRequestDelegate</Name>
    		<Value>com.wowza.wms.dvr.impl.DvrStartDurationPlaylistRequestDelegateLegacy</Value>
    	</Property>	
    </Properties>

    Example delegate

    If you want to provide your own playlist delegate, it would look very much like DvrStartDurationPlaylistRequestDelegate. You must provide two public methods and logic to determine your playlist request.

    This method is called for a single bitrate playlist request:
    public DvrPlaylistRequest getDvrPlaylistRequest(IHTTPStreamerApplicationContext appContext, IDvrStreamStore store, Map<String, String> queryMap)
    This method is called to request an adaptive-bitrate playlist:
    public DvrPlaylistRequest getDvrPlaylistRequest(IHTTPStreamerApplicationContext appContext, List<IDvrStreamStore> stores, Map<String, String> queryMap)
    Consult the Wowza Streaming Engine User's Guide on how to add your own code.
    package com.example.dvr.impl;
    
    import java.util.*;
    
    import com.wowza.wms.application.WMSProperties;
    import com.wowza.wms.dvr.*;
    import com.wowza.wms.dvr.IDvrConstants.DvrTimeScale;
    import com.wowza.wms.httpstreamer.model.IHTTPStreamerApplicationContext;
    import com.wowza.wms.logging.WMSLoggerFactory;
    
    public class DvrStartDurationPlaylistRequestDelegate extends DvrBasePlaylistRequestDelegate {
    	private static final String CLASSNAME = "DvrStartDurationPlaylistRequestDelegate";
    	private static final Class<DvrStartDurationPlaylistRequestDelegate> CLASS = DvrStartDurationPlaylistRequestDelegate.class;
        
        public static final String DVR_QUERYSTR_PLAYLIST_DURATION = "wowzadvrplaylistduration";
        public static final String DVR_QUERYSTR_PLAYLIST_START = "wowzadvrplayliststart";
        public static final String PROPKEY_DVR_PLAYLIST_DURATION_QUERY_PARAMETER = "dvrPlaylistDurationQueryParameter";
        public static final String PROPKEY_DVR_PLAYLIST_START_QUERY_PARAMETER = "dvrPlaylistStartQueryParameter";
        public static final String PROPKEY_DVR_PLAYLIST_LOG_REQUESTS = "dvrPlaylistDebugRequests";
        
        private boolean doDebug = false;
    
        
        public DvrPlaylistRequest getDvrPlaylistRequest(IHTTPStreamerApplicationContext appContext, IDvrStreamStore store, Map<String, String> queryMap) {       
            DvrPlaylistRequest availablePlaylist = getDefaultPlaylistRequest(DvrTimeScale.DVR_TIME, store);
            
            DvrPlaylistRequest newRequest = createRequestFromQueryParams(appContext, queryMap, availablePlaylist);
            
            return newRequest;
        }
    
    
        public DvrPlaylistRequest getDvrPlaylistRequest(IHTTPStreamerApplicationContext appContext, 
                                                                            List<IDvrStreamStore> stores, Map<String, String> queryMap) {
            DvrPlaylistRequest availablePlaylist = getDefaultPlaylistRequest(DvrTimeScale.DVR_TIME, stores);
    
            DvrPlaylistRequest newRequest = createRequestFromQueryParams(appContext, queryMap, availablePlaylist);
    
            return newRequest;
        }
    
        private DvrPlaylistRequest createRequestFromQueryParams(IHTTPStreamerApplicationContext appContext, 
                                                                                              Map<String, String> queryMap, DvrPlaylistRequest availablePlaylist) {
            DvrPlaylistRequest newRequest = new DvrPlaylistRequest();
            if (availablePlaylist != null) {
                newRequest.setPlaylistEnd(availablePlaylist.getPlaylistEnd());
                newRequest.setPlaylistStart(availablePlaylist.getPlaylistStart());
            }
            
            WMSProperties dvrProperties = getDvrProperties(appContext);
            String playStartQueryParameter = dvrProperties.getPropertyStr(PROPKEY_DVR_PLAYLIST_START_QUERY_PARAMETER, DVR_QUERYSTR_PLAYLIST_START);
            String playDurationQueryParameter = dvrProperties.getPropertyStr(PROPKEY_DVR_PLAYLIST_DURATION_QUERY_PARAMETER, DVR_QUERYSTR_PLAYLIST_DURATION);
    
    	this.doDebug = dvrProperties.getPropertyBoolean(PROPKEY_DVR_PLAYLIST_LOG_REQUESTS, doDebug);
    
    
            String playStartStr = queryMap.get(playStartQueryParameter);
            String playDurationStr = queryMap.get(playDurationQueryParameter);
    
            if (doDebug) {
                WMSLoggerFactory.getLogger(CLASS).info(String.format("%s : Request: %s:%s %s:%s ", CLASSNAME, playStartQueryParameter, playStartStr, playDurationQueryParameter, playDurationStr)); 
                WMSLoggerFactory.getLogger(CLASS).info(String.format("%s : Available Playlist: %s ", CLASSNAME, availablePlaylist));   	
            }
    
            if (availablePlaylist == null) {
                if (doDebug) {
                    WMSLoggerFactory.getLogger(CLASS).warn(String.format("%s : availablePlaylist is null.", CLASSNAME));    
                }
                return newRequest;
            }
    
            if (playStartStr != null)
            {
                try
                {
                    long playStart = Long.parseLong(playStartStr);
                    if (playStart < availablePlaylist.getPlaylistStart()) {
                    	if (doDebug) {
                    		WMSLoggerFactory.getLogger(CLASS).warn(String.format("%s : requestedStart:%d < availableStart:%d.  Using availableStart.", CLASSNAME, playStart, availablePlaylist.getPlaylistStart()));   	
                    	}
                    } 
                    else if (availablePlaylist.hasSpecifiedEnd() && playStart > availablePlaylist.getPlaylistEnd()) {
                    	if (doDebug) {
                    		WMSLoggerFactory.getLogger(CLASS).warn(String.format("%s : requestedStart:%d > availableEnd:%d.", CLASSNAME, playStart, availablePlaylist.getPlaylistEnd()));   	
                    	}
                    } else {
                        newRequest.setPlaylistStart(playStart);
                    } 
                    
                }
                catch(Exception e)
                {
                }
            }
            
    
    
            if (playDurationStr != null)
            {
                try
                {
                    long playDuration = Long.parseLong(playDurationStr);
                    long playEnd = newRequest.getPlaylistStart() + playDuration;
                    if (playEnd < availablePlaylist.getPlaylistStart()) {
                    	if (doDebug) {
                    		WMSLoggerFactory.getLogger(CLASS).warn(String.format("%s : requestedEnd:%d < availableStart:%d.  Using availableStart.", CLASSNAME, playEnd, availablePlaylist.getPlaylistStart()));   	
                    	}
                    } else if (availablePlaylist.hasSpecifiedEnd() && playEnd > availablePlaylist.getPlaylistEnd()) {
                    	if (doDebug) {
                    		WMSLoggerFactory.getLogger(CLASS).warn(String.format("%s : requestedEnd:%d > availableEnd:%d.  Using availableEnd.", CLASSNAME, playEnd, availablePlaylist.getPlaylistEnd()));   	
                    	}
                    } else {
                        newRequest.setPlaylistEnd(playEnd);
                    } 
                }
                catch(Exception e)
                {
                }
            }
            if (doDebug) {
                WMSLoggerFactory.getLogger(CLASS).info(String.format("%s : Resolved Playlist: %s ", CLASSNAME, newRequest));   	
            }
    
            return newRequest;
        }
    }

    Example code: Querying DVR store times

    This snippet demonstrates how to query the DVR store in your playlist delegate to determine the available times. Similar code may be useful when creating your own playlist request delegate.

    The Time Map has mapping between dvr-time, packet time, and UTC time when the time is reset:
    private static final DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
    
    public DvrPlaylistRequest getDvrPlaylistRequest(IHTTPStreamerApplicationContext appContext, IDvrStreamStore store, Map<String, String> queryMap) {
        // . . .
            
        // Look at store and determine type (audio or video)
        int type = this.chooseManifestType(store);
            
        IDvrManifest manifest = store.getManifest();
    
        DvrManifestEntry firstEntry = manifest.getFirstEntry(type);
        System.out.printf("first   : %s
    ", formatTime(firstEntry));
    
        // The last live DVR chunk is earlier than the last recorded for "live" stores
        if (store.isLive()) {
            DvrManifestEntry lastLiveEntry = manifest.getLastLiveEntry(type);
            System.out.printf("lastLive: %s
    ", formatTime(lastLiveEntry));
        }
            
        DvrManifestEntry lastRecordedEntry = manifest.getLastRecordedEntry(type);
        System.out.printf("lastRec : %s
    ", formatTime(lastRecordedEntry));
            
        IDvrTimeMap timeMap = manifest.getTimeMap();
        Collection<DvrManifestEntry> times = timeMap.getIndexMap().values();
        for (DvrManifestEntry e : times) {
            DvrManifestTimeMapEntry te = (DvrManifestTimeMapEntry)e;
            System.out.printf("timeSpan: %s
    ", formatTime(te));
        }
            
        // . . .
    }
    
    private String formatTime(DvrManifestEntry entry) {
        if (formatter == null || entry == null) {
            return "format error";
        }
        return String.format("dvrTime:%12d pt:%12d utcTime:%s", 
                                      entry.getStartTimecode(), entry.getPacketStartTime(), formatter.format(new Date(entry.getUtcStartTime())));
    }

    Example code: Creating a UTC-time-based playlist request

    Note: Wowza Streaming Engine software has a UTC-based playlist delegate (See above).
    The following code snippet demonstrates how to create a playlist request based on UTC time (this snippet can be used with code above):
    String UTC_FORMAT = "yyyy-MM-dd-HH:mm:ss";
    DateFormat formatter = new SimpleDateFormat(UTC_FORMAT);
    
    public DvrPlaylistRequest getDvrPlaylistRequest(IHTTPStreamerApplicationContext appContext, IDvrStreamStore store, Map<String, String> queryMap) {
        // . . . This could come from URL param or some other manner
        String startStr= "2012-02-14-11:30:00";
    
         // This is entire playlist request in UTC
         DvrPlaylistRequest fullPlaylistRequest = getDefaultLivePlaylistRequest(DvrTimeScale.UTC_TIME, store);
    
        // Convert start String to UTC
        Date date = null;
        if (!StringUtils.isEmpty(startStr)) {
             try {
                date = (Date)formatter.parse(startStr);  
             } catch (ParseException e) {
                date = null;
                //e.printStackTrace();
            }
        }
        // System.out.printf("'%s' --> date:%s
    ", startStr, date);
    
         // If the date specified is less than the initial date we have to play, its not valid
         if (date != null && date.before(new Date(fullPlaylistRequest.getPlaylistStart())))
         {
              System.out.println("Requested start time before actual recording.");
              date = new Date(fullPlaylistRequest.getPlaylistStart());
         }
    
        DvrPlaylistRequest req;
        if (date != null) {
            req = new DvrPlaylistRequest(DvrTimeScale.UTC_TIME);
            req.setPlaylistStart(date.getTime());
        } else {
            // Use default
            req = super.getDvrPlaylistRequest(appContext, store, queryMap);
        }
        return req;
    }
    Consult the Wowza Streaming Engine User's Guide on how to add your own code.

    Originally Published: 10-31-2011,
    Updated: For Wowza Streaming Engine 4.0.4 on 06-17-2014.

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