多协议、性能稳定、丰富API的流媒体服务器软件
如何为HLS的m3u8索引添加自定义的Header?
这篇文章介绍了如何在Wowza Streaming Engine中为HLS的 M3U8索引添加自定义的Header。 你有两种方式实现这个功能:一是在Wowza Streaming Engine Manager 的管理界面中添加自定义参数;二是在用Wowza Streaming Engine 提供的Java API来实现。

注意: 需要Wowza Streaming Engine™ 4.4.0 及以上版本的支持。

关于Apple HLS headers


当一个Apple HLS (Cupertino) 流媒体请求到达Wowza Streaming Engine 服务器后,会经过两个步骤: 在第一个步骤中,HLS客户端要请求一个playlist。通常,URL格式如下:

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

其中[wowza-ip-address]是Wowza服务器的IP地址。 Wowza Streaming Engine会做如下响应:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=868638,CODECS="avc1.66.21,mp4a.40.2",RESOLUTION=512x288
chunklist_w345834439.m3u8
		
在第二个步骤中,客户端会继续请求在上一个步骤中得到的chunklist。 在这个请求中,采用相对路径,格式如下:

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

在这个步骤中,Wowza Streaming Engine会做如下响应:
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
		
在响应中,以#EXT*开头的,在本文的概念中都属于一种Header。 你可以在Wowza Streaming Engine Manager的管理界面上通过自定义参数设置这些Header或者用Wowza Streaming Engine 提供的Java API来设置它。
以下是对这两种方式的介绍:


用Wowza Streaming Engine Manager 管理界面来添加Apple HLS header


你可以在Wowza的管理界面上,在对应的应用上添加自定义参数是实现这个功能:

  1. 在Wowza Streaming Engine Manager管理界面点击Applications菜单,再点击你应用的名字

  2. 在这个应用主界面的Propertiestab页,在Quick Links导航条上点击Custom链接。

  3. Custom的参数设置部分,点击Edit,然后再点击Add Custom Property.

  4. 根据下面表格中的信息,在Add Custom Property对话框中添加你需要的Header:

    Vod应用的参数配置


    Path
    Name
    Type
    Value
    说明
    /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" 针对同时包含音频和视频的切片列表。默认值是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" 针对只有音频的切片列表。默认值是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" 针对只有视频的切片列表,默认值是NONE.

    Live application properties


    Path
    Name
    Type
    Value
    说明
    /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 针对同时包含音频和视频的切片列表。默认值是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 针对只有音频的切片列表。默认值是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 针对只有视频的切片列表,默认值是NONE.

    注意:Value值中,如果要添加多个参数值给一个Header,或者是添加多个Header,你可以用管道符(|)隔开即可。
  5. 点击Add,点击Save,然后根据提示重启Wowza应用

在添加以上的自定义参数后,Wowza Streaming Engine的响应会变成下面的样子:
#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
		

用Wowza Streaming Engine 的Java API来添加Apple HLS header


如果你需要用自己的程序来控制给HLS添加自定义的Header,那么你必须用Wowza Streaming Engine 的addUserManifestHeader API。 下面的例子,展示了如何用这个API,包括针对VOD点播流的场景和live 直播流的场景,以及直播流并配合DRM的场景。

针对VOD点播流的场景

针对VOD点播流,这个API可以在HTTP Session被创建时调用。
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");
		}
	}
}
		
尽管所有自定义的Header都可以被添加进去,但是也不是说,你添加了,它就一定会出现在响应中,具体还要看其它功能配置,比如EXT-X-KEYheader 只会在你启用了DRM功能后才会被真正添加进去。

针对Live直播流的场景

针对Live直播流,你可以在HTTP打包流程中调用这个API.
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());
}

针对直播流并使用DRM功能的场景

针对直播流并使用了DRM功能的情况下,下面的代码展示了在每一次Apple HLS切片生成时调用API来添加Header。
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.