This article provides example code for MP4 video-on-demand playback that converts subtitle tracks to onTextData data events by using the Wowza Streaming Engine™ media server software Java API.
Notes:
- For a version of this code sample that supports SMIL files, see Select multiple tracks from a VOD file with a Wowza Streaming Engine Java module.
- See About closed captioning in Wowza Streaming Engine for more information about closed captioning.
The event has the following structure:
Handler name: onTextData
Attributes:
- text: Subtitle text
- language: Three-letter language code (see ISO-639 Language Codes)
- trackid: Track ID of the subtitle track
If a file has multiple subtitle tracks, by default the first subtitle track is used. You can select individual subtitle tracks by language. Following is sample code for selecting individual subtitle tracks by language (it also includes the ability to select audio and video tracks):
package com.wowza.wms.plugin.test.module; import java.util.*; import com.wowza.util.*; import com.wowza.wms.module.*; import com.wowza.wms.amf.*; import com.wowza.wms.application.*; import com.wowza.wms.request.*; import com.wowza.wms.stream.*; import com.wowza.wms.mediareader.h264.*; import com.wowza.wms.client.*; import com.wowza.wms.mediareader.h264.atom.*; public class ModuleMP4AudioChannelSelector extends ModuleBase { public static final String PROPERTY_AUDIOINDEX = "audioindex"; public static final String PROPERTY_VIDEOINDEX = "videoindex"; public static final String PROPERTY_DATAINDEX = "dataindex"; public static final String[] PROPERTY_INDEXES = {PROPERTY_AUDIOINDEX, PROPERTY_VIDEOINDEX, PROPERTY_DATAINDEX}; class MediaReaderListener implements IMediaReaderActionNotify { public void onMediaReaderCreate(IMediaReader mediaReader) { getLogger().info("ModuleMediaReaderNotify#MediaReaderListener.onMediaReaderCreate"); } public void onMediaReaderInit(IMediaReader mediaReader, IMediaStream stream) { getLogger().info("ModuleMediaReaderNotify#MediaReaderListener.onMediaReaderInit: "+stream.getName()); } public void onMediaReaderOpen(IMediaReader mediaReader, IMediaStream stream) { getLogger().info("ModuleMediaReaderNotify#MediaReaderListener.onMediaReaderOpen: "+stream.getName()); while(true) { IClient client = stream.getClient(); if (client == null) break; int audioIndex = -1; int videoIndex = -1; int dataIndex = -1; Integer audioIndexObj = (Integer)client.getProperties().getProperty(PROPERTY_AUDIOINDEX); Integer videoIndexObj = (Integer)client.getProperties().getProperty(PROPERTY_VIDEOINDEX); Integer dataIndexObj = (Integer)client.getProperties().getProperty(PROPERTY_DATAINDEX); if (audioIndexObj != null) audioIndex = audioIndexObj.intValue(); if (videoIndexObj != null) videoIndex = videoIndexObj.intValue(); if (dataIndexObj != null) dataIndex = dataIndexObj.intValue(); if (mediaReader instanceof MediaReaderH264) { MediaReaderH264 mediaReaderH264 = (MediaReaderH264)mediaReader; int audioTrackCount = mediaReaderH264.getTrackCountAudio(); for(int i=0;i<audioTrackCount;i++) { String langStr = mediaReaderH264.getTrackLanguageAudio(i); long trackId = mediaReaderH264.getTrackAudioTrackId(i); QTAtomtrak trackAtom = mediaReaderH264.getTrackAudioAtom(i); getLogger().info(" audio["+i+"]: trackId:"+trackId+" lang:"+langStr+" more:"+trackAtom.getTkhdAtom().toString()); } int videoTrackCount = mediaReaderH264.getTrackCountVideo(); for(int i=0;i<videoTrackCount;i++) { long trackId = mediaReaderH264.getTrackVideoTrackId(i); long trackWidth = mediaReaderH264.getTrackVideoWidth(i); long trackHeight = mediaReaderH264.getTrackVideoHeight(i); QTAtomtrak trackAtom = mediaReaderH264.getTrackVideoAtom(i); getLogger().info(" video["+i+"]: trackId:"+trackId+" width:"+trackWidth+" height:"+trackHeight+" more:"+trackAtom.getTkhdAtom().toString()); } int dataTrackCount = mediaReaderH264.getTrackCountData(); for(int i=0;i<dataTrackCount;i++) { String langStr = mediaReaderH264.getTrackLanguageData(i); long trackId = mediaReaderH264.getTrackDataTrackId(i); QTAtomtrak trackAtom = mediaReaderH264.getTrackDataAtom(i); getLogger().info(" data["+i+"]: trackId:"+trackId+" lang:"+langStr+" more:"+trackAtom.getTkhdAtom().toString()); } if (audioIndex >= 0) { getLogger().info(" setTrackIndexAudio: "+audioIndex); mediaReaderH264.setTrackIndexAudio(audioIndex); } if (videoIndex >= 0) { getLogger().info(" setTrackIndexVideo: "+videoIndex); mediaReaderH264.setTrackIndexVideo(videoIndex); } if (dataIndex >= 0) { getLogger().info(" setTrackIndexData: "+dataIndex); mediaReaderH264.setTrackIndexData(dataIndex); } } break; } } public void onMediaReaderExtractMetaData(IMediaReader mediaReader, IMediaStream stream) { getLogger().info("ModuleMediaReaderNotify#MediaReaderListener.onMediaReaderExtractMetaData: "+stream.getName()); } public void onMediaReaderClose(IMediaReader mediaReader, IMediaStream stream) { getLogger().info("ModuleMediaReaderNotify#MediaReaderListener.onMediaReaderClose: "+stream.getName()); } } public void onAppStart(IApplicationInstance appInstance) { appInstance.addMediaReaderListener(new MediaReaderListener()); } public void play(IClient client, RequestFunction function, AMFDataList params) { String streamName = params.getString(PARAM1); getLogger().info("ModuleMediaReaderNotify.play: "+streamName); if (streamName != null) { int qindex = streamName.indexOf("?"); if (qindex >= 0) { String queryStr = streamName.substring(qindex+1); Map<String, String> queryParams = HTTPUtils.splitQueryStr(queryStr); for(int i=0;i<PROPERTY_INDEXES.length;i++) { String indexStr = PROPERTY_INDEXES[i]; if (queryParams.containsKey(indexStr)) { int index = -1; try { index = Integer.parseInt(queryParams.get(indexStr)); } catch(Exception e) { } if (index >= 0) { client.getProperties().setProperty(indexStr, new Integer(index)); getLogger().info(" "+indexStr+": "+index); } } } } } invokePrevious(client, function, params); } }
Add this module last in the Modules list of [install-dir]/conf/[app-name]/Application.xml:
<Module> <Name>ModuleMP4AudioChannelSelector</Name> <Description>ModuleMP4AudioChannelSelector</Description> <Class>com.wowza.wms.plugin.test.module.ModuleMP4AudioChannelSelector</Class> </Module>