How to control display of program date and time headers in Apple HLS chunklists for live streams (EXT-X-PROGRAM-DATE-TIME)

The following module adds the EXT-X-PROGRAM-DATE-TIME header to Apple HLS (Cupertino) live streams as well as a TXXX ID3 tag with the same info. EXT-X-PROGRAM-DATE-TIME headers use an absolute date and time tag from the first segment of the stream as the basis for seeking and displaying subsequent segments.

Note: Wowza Streaming Engine™ 4.5.0 or later is required.
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 Apple HLS chunk lists, add the cupertinoEnableProgramDateTime property to the application configuration. This property must be true and program date and time set in the chunk for the header to display.
 
  1. In the Wowza Streaming Engine Manager contents panel, click the live application name (for example, live).
     
  2. On the live application page 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.
  3. In the Custom area, click Edit.
     
  4. Click Add Custom Property, and then add the cupertinoEnableProgramDateTime property with the following values:
     
    • Path: Select /Root/Application/HTTPStreamer.
       
    • Name: Enter cupertinoEnableProgramDateTime.
       
    • Type: Select Boolean.
       
    • Value: Enter true.
  5. Click Add, then click Save, and then click Restart when prompted to apply the changes.

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