• How to select audio and subtitle tracks from a video on demand file

    This article has example code for MP4 video-on-demand playback that converts subtitle tracks to onTextData data events.

    Notes:
    See Closed Captioning Overview for more information about closed captioning.

    The event has the following structure:

    Handler name: - onTextData

    Attributes:

    • text: - Subtitle text
    • language: - 3-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>


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