Notes:
- This functionality is supported in Wowza Media ServerŪ 3.0.3.02 or later.
- The UTC-based playlist request functionality is supported in Wowza Media Server 3.0.4 or later.
- Microsoft Silverlight players do not currently recognize non-live streams. Recorded streams are presented as live streams. This is a known issue that we are working on.
Default Behavior
By default, nDVR determines the playlist to play when a DVR manifest or playlist is requested via URL.
- Strobe/OSMF player:
Code:http://[wowza-address]:1935/dvr/myStream/manifest.f4m?DVR
- Apple iOS device:
Code:http://[wowza-address]:1935/dvr/myStream/playlist.m3u8?DVR
- Microsoft Silverlight:
Code:http://[wowza-address]:1935/dvr/myStream/Manifest?DVR
The default logic is as follows:
- If the stream is being recorded it is considered live, otherwise it is considered recorded.
- Both live and recorded use as a starting time:
- The earliest point in the recording, or
- The latest time minus the DVR window if one is been specified.
- A live stream has no end time.
- A recorded stream is played back 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 do not 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.
Playlist Request Delegate
nDVR provides a delegate property "dvrPlaylistRequestDelegate" which is a mechanism to provide a different playlist request via Java.
The property should be set in Application.xml under Application/DVR/Properties and should reference a fully-qualified classname that extends the class "com.wowza.wms.dvr.DvrBasePlaylistRequestDeleg ate".
When the playlist is requested, this delegate's method getDvrPlaylistRequest() will be called to provide the playlist request.
Playlist Request
The Playlist Request Delegate is responsible for generating a playlist request object. It is 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 beginning time and an optional end-time.
If the start time specified is not valid (for example, 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 is currently recording), the playlist presented to the player will be a live playlist.
- For a recorded store (one that is not recording), the playlist presented to the player will be 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 will be of the recorded variety because live playlists do not have an end-time.
An example
Wowza nDVR includes one playlist request delegate: "com.wowza.wms.dvr.impl.DvrStartDurationPlaylistRe questDelegate". 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:
Code:http://[wowza-address]:1935/dvr/myStream/manifest.f4m?DVR&wowzadvrplayliststart=60000&wowzadvrplaylistduration=300000
- Apple iOS device:
Code:http://[wowza-address]:1935/dvr/myStream/playlist.m3u8?DVR&wowzadvrplayliststart=60000&wowzadvrplaylistduration=300000
- Microsoft Silverlight:
Code:http://[wowza-address]:1935/dvr/myStream/Manifest?DVR&wowzadvrplayliststart=60000&wowzadvrplaylistduration=300000
To use this delegate, add the following property to your Application.xml under Application/DVR/Properties:
Code:
<Properties> <Property> <Name>dvrPlaylistRequestDelegate</Name> <Value>com.wowza.wms.dvr.impl.DvrStartDurationPlaylistRequestDelegate</Value> </Property> </Properties>
Code:
<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>
Code:
<Properties>
<Property>
<Name>dvrDebugPlaylistRequest</Name>
<Value>true</Value>
<Type>Boolean</Type>
</Property>
</Properties>
To provide your own playlist delegate, it would look very much like "DvrStartDurationPlaylistRequestDelegate". You must provide 2 public methods as well as logic to determine your playlist request.
This method is called for a single bitrate playlist request:
Code:
public DvrPlaylistRequest getDvrPlaylistRequest(IHTTPStreamerApplicationContext appContext, IDvrStreamStore store, Map<String, String> queryMap)
Code:
public DvrPlaylistRequest getDvrPlaylistRequest(IHTTPStreamerApplicationContext appContext, List<IDvrStreamStore> stores, Map<String, String> queryMap)
Consult the User's Guide on how to add your own code to Wowza.
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;
}
}
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 contains mapping between dvr-time, packet time, and utc time every time the time has been reset.
Code:
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\n", 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\n", formatTime(lastLiveEntry));
}
DvrManifestEntry lastRecordedEntry = manifest.getLastRecordedEntry(type);
System.out.printf("lastRec : %s\n", 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\n", 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
This snippet demonstrates how to create a playlist request based on UTC time. This snippet can be used with code above.
Code:
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\n", 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;
}
- Click here, if you are having problems or would like to discuss this article.
- Leave a comment below, if there is some aspect of this article you would like to see changed or improved.


Article List
Categories
Wowza Media