Use this custom module to add a EXT-X-PROGRAM-DATE-TIME header and TXXX ID3 to HLS streams. The EXT-X-PROGRAM-DATE-TIME header uses the absolute date time from the stream's first segment as the basis to seek and display subsequent segments.
Note: Wowza Streaming Engine™ 4.5.0 or later is required.
To use a custom Java module: compile it, package it into a .jar file, then put it in the [install-dir]/lib folder. Then add it to your application's Module tab in WSE Manager. (Click Add Module, and specify the module's Name, Description, and FQDN.)
package com.wowza.wms.plugin.test2.hlsprogramdatetime;
import java.util.*;
import org.apache.commons.lang.time.*;
import com.wowza.util.*;
import com.wowza.wms.amf.*;
import com.wowza.wms.application.*;
import com.wowza.wms.httpstreamer.cupertinostreaming.livestreampacketizer.*;
import com.wowza.wms.media.mp3.model.idtags.*;
import com.wowza.wms.module.*;
import com.wowza.wms.stream.*;
import com.wowza.wms.stream.livepacketizer.*;
public class ModuleCupertinoProgramDateTime extends ModuleBase
{
private static final Class<ModuleCupertinoProgramDateTime> CLASS = ModuleCupertinoProgramDateTime.class;
private static final String CLASSNAME = "ModuleCupertinoProgramDateTime";
public static final String PROPNAME_TRACKER = "ModuleCupertinoProgramDateTime.ProgramDateTimeTracker";
public static final String DATEFORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'+00:00'"; // The date/time representation is ISO/IEC 8601:2004 - 2010-02-19T14:54:23.031+08:00
private FastDateFormat fastDateFormat = FastDateFormat.getInstance(DATEFORMAT, SystemUtils.gmtTimeZone, Locale.US);
private IApplicationInstance appInstance = null;
class ProgramDateTimeTracker
{
long timeOffset = 0;
IMediaStream stream = null;
public ProgramDateTimeTracker(IMediaStream stream)
{
this.stream = stream;
}
}
class LiveStreamPacketizerDataHandler implements IHTTPStreamerCupertinoLivePacketizerDataHandler2
{
private LiveStreamPacketizerCupertino liveStreamPacketizer = null;
private int textId = 1;
private String streamName = null;
public LiveStreamPacketizerDataHandler(LiveStreamPacketizerCupertino liveStreamPacketizer, String streamName)
{
this.liveStreamPacketizer = liveStreamPacketizer;
this.streamName = streamName;
}
public ProgramDateTimeTracker getTracker()
{
ProgramDateTimeTracker tracker = null;
while(true)
{
IMediaStream stream = appInstance.getStreams().getStream(this.streamName);
if (stream == null)
break;
WMSProperties props = stream.getProperties();
synchronized(props)
{
tracker = (ProgramDateTimeTracker)props.getProperty(PROPNAME_TRACKER);
if (tracker == null)
{
tracker = new ProgramDateTimeTracker(stream);
props.put(PROPNAME_TRACKER, tracker);
}
}
break;
}
return tracker;
}
@Override
public void onFillChunkStart(LiveStreamPacketizerCupertinoChunk chunk)
{
ProgramDateTimeTracker tracker = getTracker();
if (tracker != null)
{
ElapsedTimer elapsedTime = tracker.stream.getElapsedTime();
long createTime = elapsedTime.getDate().getTime()+tracker.timeOffset;
String programDateTimeStr = fastDateFormat.format(new Date(createTime));
chunk.setProgramDateTime(programDateTimeStr);
ID3Frames id3Header = liveStreamPacketizer.getID3FramesHeader(chunk.getRendition());
if (id3Header != null)
{
ID3V2FrameTextInformationUserDefined comment = new ID3V2FrameTextInformationUserDefined();
comment.setDescription("programDateTime");
comment.setValue(programDateTimeStr);
id3Header.clear();
id3Header.putFrame(comment);
}
}
}
@Override
public void onFillChunkEnd(LiveStreamPacketizerCupertinoChunk chunk, long timecode)
{
ProgramDateTimeTracker tracker = getTracker();
if (tracker != null)
tracker.timeOffset += chunk.getDuration();
}
@Override
public void onFillChunkDataPacket(LiveStreamPacketizerCupertinoChunk chunk, CupertinoPacketHolder holder, AMFPacket packet, ID3Frames id3Frames)
{
}
@Override
public void onFillChunkMediaPacket(LiveStreamPacketizerCupertinoChunk chunk, CupertinoPacketHolder holder, AMFPacket packet)
{
}
}
class LiveStreamPacketizerListener extends LiveStreamPacketizerActionNotifyBase
{
public void onLiveStreamPacketizerCreate(ILiveStreamPacketizer liveStreamPacketizer, String streamName)
{
if (liveStreamPacketizer instanceof LiveStreamPacketizerCupertino)
{
getLogger().info(CLASSNAME+"#MyLiveListener.onLiveStreamPacketizerCreate["+((LiveStreamPacketizerCupertino)liveStreamPacketizer).getContextStr()+"]");
((LiveStreamPacketizerCupertino)liveStreamPacketizer).setDataHandler(new LiveStreamPacketizerDataHandler((LiveStreamPacketizerCupertino)liveStreamPacketizer, streamName));
}
}
}
public void onAppStart(IApplicationInstance appInstance)
{
this.appInstance = appInstance;
appInstance.addLiveStreamPacketizerListener(new LiveStreamPacketizerListener());
getLogger().info(CLASSNAME+".onAppStart["+appInstance.getContextStr()+"]");
}
}To enable EXT-X-PROGRAM-DATE-TIME in HLS chunklists, add the cupertinoEnableProgramDateTime property to your application's configuration using WSE Manager. This property must be true and program date and time set in the chunk for the header to display.
- Navigate to the WSE Manager contents panel.
- Select your live app.
- Go to the application's Properties tab
- Click Custom in the Quick Links bar (or scroll to the bottom of the page).
Note: Access to the Properties tab is limited to administrators with advanced permissions. For more information, see Manage credentials. - Click Edit.
- Click Add Custom Property
- Add the cupertinoEnableProgramDateTime property with the following values:
- Path: Select
/Root/Application/HTTPStreamer. - Name: Enter
cupertinoEnableProgramDateTime. - Type: Select
Boolean. - Value: Enter
true.
- Path: Select
- Click Add.
- Click Save.
- Click Restart to apply the changes.




