• How to add custom playlist headers to Apple HLS manifests

    This article describes how to add custom Apple HLS M3U headers into manifest responses created by Wowza Streaming Engine. You can do this by configuring a property in Wowza Streaming Engine Manager or programmatically by using the Wowza Streaming Engine Java API.

    Note: Wowza Streaming Engine™ software version 4.4.0 or later is required.

    About Apple HLS headers


    Clients request Apple HLS (Cupertino) streams from Wowza Streaming Engine media servers in two stages. The first stage is to request the playlist. A common request URL would be:

    http://[wowza-ip-address]:1935/vod/mp4:sample.mp4/playlist.m3u8

    Where [wowza-ip-address] is the media server IP address. The media server responds to this request with output similar to the following:
    #EXTM3U
    #EXT-X-VERSION:3
    #EXT-X-STREAM-INF:BANDWIDTH=868638,CODECS="avc1.66.21,mp4a.40.2",RESOLUTION=512x288
    chunklist_w345834439.m3u8
    In the second stage, clients request the chunklist provided in the initial media server response. The chunklist request is relative to the playlist, for example:

    http://[wowza-ip-address]:1935/vod/mp4:sample.mp4/chunklist_w345834439.m3u8

    The media server responds to this request with output similar to the following.
    EXTM3U
    #EXT-X-VERSION:3
    #EXT-X-TARGETDURATION:10
    #EXT-X-MEDIA-SEQUENCE:0
    #EXTINF:10.0,
    media_w345834439_0.ts
    #EXTINF:10.0,
    media_w345834439_1.ts
    #EXTINF:10.0,
    media_w345834439_2.ts
    Both media server-to-client responses #EXT* headers. You can include additional headers in the responses by configuring a property in Wowza Streaming Engine Manager or by using the Wowza Streaming Engine Java API. This article describes both methods.


    Add Apple HLS headers using Wowza Streaming Engine Manager


    To add Apple HLS manifest headers to media server chunklist responses, you must add a property to your application configuration. The property you add depends on the application type (on-demand or live) and on the chunklist type (audio/video, audio-only, or video-only).

    1. In Wowza Streaming Engine Manager, click the Applications tab and then click the name of your application.

    2. On the application page Properties tab, click Custom in the Quick Links bar.

      Note: Access to the Properties tab is limited to administrators with advanced permissions. For more information, see Manage credentials.
    3. In the Custom section, click Edit, and then click Add Custom Property.

    4. Use the following tables to select the appropriate property for your application type and chunklist type, and then enter the property Path, Name, Type and Value in the Add Custom Property dialog box.

      On-demand application properties


      Path
      Name
      Type
      Value
      Notes
      /Root/Application/HTTPStreamer cupertinoManifestHeaders String EXT-CUSTOM-TEST:nameone="valueone"|EXT-CUSTOM-TEST:nametwo=2|EXT-CUSTOM-TEST:namethree|EXT-CUSTOM-TEST2|AUTHENTICATE:authname="authvalue" For chunklists with combined audio and video chunks. The default value is NONE.
      /Root/Application/HTTPStreamer cupertinoManifestHeadersAudio String EXT-CUSTOM-TEST:nameone="valueone"|EXT-CUSTOM-TEST:nametwo=2|EXT-CUSTOM-TEST:namethree|EXT-CUSTOM-TEST2|AUTHENTICATE:authname="authvalue" For audio-only chunklists. The default value is NONE.
      /Root/Application/HTTPStreamer cupertinoManifestHeadersVideo String EXT-CUSTOM-TEST:nameone="valueone"|EXT-CUSTOM-TEST:nametwo=2|EXT-CUSTOM-TEST:namethree|EXT-CUSTOM-TEST2|AUTHENTICATE:authname="authvalue" For video-only chunklists. The default value is NONE.

      Live application properties


      Path
      Name
      Type
      Value
      Notes
      /Root/Application/LiveStreamPacketizer cupertinoManifestHeaders String EXT-CUSTOM-TEST:nameone="valueone"|EXT-CUSTOM-TEST:nametwo=2|EXT-CUSTOM-TEST:namethree|EXT-CUSTOM-TEST2|AUTHENTICATE:authname=authvalue For chunklists with combined audio and video chunks. The default value is NONE.
      /Root/Application/LiveStreamPacketizer cupertinoManifestHeadersAudio String EXT-CUSTOM-TEST:nameone="valueone"|EXT-CUSTOM-TEST:nametwo=2|EXT-CUSTOM-TEST:namethree|EXT-CUSTOM-TEST2|AUTHENTICATE:authname=authvalue For audio-only chunklists. The default value is NONE.
      /Root/Application/LiveStreamPacketizer cupertinoManifestHeadersVideo String EXT-CUSTOM-TEST:nameone="valueone"|EXT-CUSTOM-TEST:nametwo=2|EXT-CUSTOM-TEST:namethree|EXT-CUSTOM-TEST2|AUTHENTICATE:authname=authvalue For video-only chunklists. The default value is NONE.

      Note: In the property Value, to add multiple parameters to the same header or to include multiple headers, you can delimit the header names, parameters, and values by using the pipe (|) character.
    5. Click Add, click Save, and then restart the application when prompted to apply the changes.

    With a property added, the media server chunklist response to the client will change to include the new headers, for example:
    #EXTM3U
    #EXT-X-VERSION:3
    #EXT-X-TARGETDURATION:10
    #EXT-X-MEDIA-SEQUENCE:0
    #AUTHENTICATE:authname="authvalue"
    #EXT-CUSTOM-TEST:namethree,nametwo=2,nameone="valueone"
    #EXT-CUSTOM-TEST2
    #EXTINF:10.0,
    media_w345834439_0.ts
    #EXTINF:10.0,
    media_w345834439_1.ts
    #EXTINF:10.0,
    media_w345834439_2.ts
    In the above example response, two EXT-CUSTOM-TEST parameters are merged into one header and the AUTHENTICATE header is included as a separate header.

    Add Apple HLS headers using Wowza Streaming Engine Java API


    To add Apple HLS manifest headers to media server chunklist responses programmatically, you must use the Wowza Streaming Engine addUserManifestHeader API. This section has examples that show how to use the API for on-demand streaming, live streaming, and live streaming with digital rights management (DRM).

    On-demand streaming example

    For on-demand streams, the API is available via the HTTP session for the specified stream. The following example code shows that the onHTTPCupertinoStreamingSessionCreate event is called when a client connects.
    public void onHTTPCupertinoStreamingSessionCreate(HTTPStreamerSessionCupertino httpSession)
    {
    	if (httpSession instanceof HTTPStreamerSessionCupertino)
    	{
    		HTTPStreamerSessionCupertino httpSessionCupertino = (HTTPStreamerSessionCupertino)httpSession;
    
    		CupertinoUserManifestHeaders userManifestHeaders = httpSessionCupertino.getUserManifestHeaders();
    		if (userManifestHeaders != null)
    		{
    			userManifestHeaders.addHeader("EXT-CUSTOM-TEST", "nameone", "valueone");
    			userManifestHeaders.addHeader("EXT-CUSTOM-TEST", "nametwo", 2);
    			userManifestHeaders.addHeader("EXT-CUSTOM-TEST", "namethree");
    			userManifestHeaders.addHeader("EXT-CUSTOM-TEST2");
    		}
    	}
    }
    Although any headers can be added, only certain headers are included in the manifest response, depending on what other media server functionality is enabled. For example, the EXT-X-KEY header is only included in the manifest if digital rights management (DRM) is enabled, so even if you add a custom parameter for the EXT-X-KEY header, it won't be included in the manifest response unless DRM is enabled for the session.

    Live streaming example

    For live streams, the API is available via the live stream packetizer for the specified stream. The following example code shows that the IHTTPStreamerCupertinoLivePacketizerDataHandler2 interface is called when a client connects.
    class LiveStreamPacketizerDataHandler implements IHTTPStreamerCupertinoLivePacketizerDataHandler2
    {
    	private LiveStreamPacketizerCupertino liveStreamPacketizer = null;
    	private boolean isFirstChunk = false;
    
    	public LiveStreamPacketizerDataHandler(LiveStreamPacketizerCupertino liveStreamPacketizer)
    	{
    		this.liveStreamPacketizer = liveStreamPacketizer;
    	}
    
    	public void onFillChunkStart(LiveStreamPacketizerCupertinoChunk chunk)
    	{
    		if (isFirstChunk)
    		{
    			CupertinoUserManifestHeaders userManifestHeaders = liveStreamPacketizer.getUserManifestHeaders(chunk.getRendition());
    			if (userManifestHeaders != null)
    			{
    				// Add custom headers to chunklist header
    				userManifestHeaders.addHeader("EXT-CUSTOM-TEST", "nameone", "valueone");
    				userManifestHeaders.addHeader("EXT-CUSTOM-TEST", "nametwo", 2);
    				userManifestHeaders.addHeader("EXT-CUSTOM-TEST", "namethree");
    			}			
    		}
    
    		CupertinoUserManifestHeaders userManifestHeaders = chunk.getUserManifestHeaders();
    		if (userManifestHeaders != null)
    		{
    			// Add custom headers to chunklist body for a given chunk
    			userManifestHeaders.addHeader("EXT-CUSTOM-TEST", "nameone", "valueone");
    			userManifestHeaders.addHeader("EXT-CUSTOM-TEST", "nametwo", 2);
    			userManifestHeaders.addHeader("EXT-CUSTOM-TEST", "namethree");
    		}			
    
    		isFirstChunk = false;
    	}
    
    	public void onFillChunkEnd(LiveStreamPacketizerCupertinoChunk chunk, long timecode)
    	{
    	}
    
    	public void onFillChunkMediaPacket(LiveStreamPacketizerCupertinoChunk chunk, CupertinoPacketHolder holder, AMFPacket packet)
    	{
    	}
    
    	public void onFillChunkDataPacket(LiveStreamPacketizerCupertinoChunk chunk, CupertinoPacketHolder holder, AMFPacket packet, ID3Frames id3Frames)
    	{
    	}
    }
    
    class LiveStreamPacketizerListener extends LiveStreamPacketizerActionNotifyBase
    {
    	public void onLiveStreamPacketizerCreate(ILiveStreamPacketizer liveStreamPacketizer, String streamName)
    	{
    		if (liveStreamPacketizer instanceof LiveStreamPacketizerCupertino)
    		{
    			((LiveStreamPacketizerCupertino)liveStreamPacketizer).setDataHandler(new LiveStreamPacketizerDataHandler((LiveStreamPacketizerCupertino)liveStreamPacketizer));
    		}
    	}
    }
    
    public void onAppStart(IApplicationInstance appInstance)
    {		
    	appInstance.addLiveStreamPacketizerListener(new LiveStreamPacketizerListener());
    }

    Live streaming (with DRM) example

    For live streams with digital rights management (DRM), the API is available via the live stream packetizer for the specified stream. The following example code shows that additional methods are called each time an Apple HLS chunk is created to give you the opportunity to control how that chunk is encrypted.
    public void onHTTPCupertinoEncryptionKeyLiveChunk(ILiveStreamPacketizer liveStreamPacketizer, String streamName, CupertinoEncInfo encInfo, long chunkId, int mode)
    {
    
    	LiveStreamPacketizerCupertino myPacketizer = null;
    
    	if (liveStreamPacketizer instanceof LiveStreamPacketizerCupertino)
    		myPacketizer = (LiveStreamPacketizerCupertino)liveStreamPacketizer;
    
    	if (myPacketizer != null)
    	{
    		CupertinoUserManifestHeaders userManifestHeaders = myPacketizer.getUserManifestHeaders();
    		if (userManifestHeaders != null)
    		{
    			userManifestHeaders.addHeader("EXT-X-KEY", "CID", "my-content-id");
    			userManifestHeaders.addHeader("EXT-X-KEY", "Temp", "setting");
    			userManifestHeaders.addHeader("EXT-X-KEY", "URI", "urn:marlin-drm?{encKeySessionid}&{query}");
    		}
    	}
    
    	encInfo.setEncUrl("http://mydomain.com");
    	encInfo.setEncMethod(CupertinoEncInfo.METHOD_AES_128);
    	encInfo.setEncKeyBytes(BufferUtils.decodeHexString("123456789ABCDEF123456789ABCDEF12"));
    	encInfo.setEncIVBytes(BufferUtils.decodeHexString("FEDCBA9876543210FEDCBA9876543210"));
    	encInfo.setEncKeyFormatVersion("1");
    
    }
    The above example code shows that additional EXT-X-KEY headers are mapped. Note that the URI element will replace the URI that's normally sent back. This provides an opportunity to replace the value with an attribute that works with custom clients and software.

    The {encKeySessionId} and {query} variables in the URI are translated to a session ID and query parameters (if appropriate) before they're included in the manifest response to the client. This can be useful in some implementations to allow per-session key requests on any DRM server.

    For more information about this encryption method, see How to secure Apple HLS streaming using DRM encryption.
    Originally Published: For Wowza Streaming Engine 4.4.0 on 02-03-2016.

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