• How to debug timed data events (ID3 tags) from Apple HLS streams in iOS devices

    This article describes a method for debugging ID3 tags in Apple HLS streams when streaming to iOS devices. The process of debugging ID3 tags is complicated, mainly due to security restrictions on the iOS device. First, to get ID3 tag events to the iOS browser, the HTML page that hosts the video and the playlist URL must come from the same domain or IP address and the same port. This is to avoid cross domain scripting attacks. Second, the JavaScript DOM events we leverage are only available when streaming to iOS devices running iOS 5 and later and won't work when streaming to OS X.

    This articles assumes you already have an Apple HLS stream (live or video on demand) that has ID3 tags. For more information on how to inject ID3 tags into Apple HLS streams, see How to convert onTextData events in a live, VOD or nDVR stream to timed events (ID3 tags) in an Apple HLS stream.

    The following code demonstrates how the Wowza media server HTTPProvider extension mechanism is used to add a mini web server that serves up the HTML with the proper JavaScript to capture DOM events thrown by embedded ID3 tags. The HTTPProvider intercepts request for any URLs that end with iosdebugid3tags.html. The HTTPProvider then loads the HTML template located at [install-dir]/conf/iosdebugid3tags.html. It then takes the URL that was used to invoke the HTTPProvider, replaces the last path element with playlist.m3u8, and substitutes the template variable ${com.wowza.wms.HTTPIOSDebugID3Tags.playlistURL} with the resultant URL.

    For example, if you want to test ID3 tags embedded in stream with the URL:
    http://[wowza-ip-address]:1935/vod/mp4:sample.mp4/playlist.m3u8
    Enter the following URL into the browser:
    http://[wowza-ip-address]:1935/vod/mp4:sample.mp4/iosdebugid3tags.html
    Here is the HTTPProvider code. This must be compiled into a .jar file using the Wowza IDE.
    package com.wowza.wms.plugin.test.http;
    
    import java.io.*;
    
    import com.wowza.io.*;
    import com.wowza.wms.vhost.*;
    import com.wowza.wms.logging.*;
    import com.wowza.wms.http.*;
    
    public class HTTPIOSDebugID3Tags extends HTTProvider2Base
    {
    	public static final String PLACEHOLDER_URL = "${com.wowza.wms.HTTPIOSDebugID3Tags.playlistURL}";
    	
    	public void init()
    	{
    		super.init();
    		
    		WMSLoggerFactory.getLogger(HTTProvider2Base.class).info("HTTPIOSDebugID3Tags.init");
    	}
    
    	public void onHTTPRequest(IVHost vhost, IHTTPRequest req, IHTTPResponse resp)
    	{
    		if (!doHTTPAuthentication(vhost, req, resp))
    			return;
    		
            String domainName = req.getHeader("host");
    		String context = req.getHeader("context");
    				
    		int ploc = context.lastIndexOf("/");
    
                    // NOTE: for DVR testing of ID3 tags, this line should change to:
     		//if (ploc >= 0)
    		//	context = context.substring(0, ploc)+"/playlist.m3u8?dvr";
        
    		if (ploc >= 0)
    			context = context.substring(0, ploc)+"/playlist.m3u8";
    		
    		String playlistURL = "http://"+domainName+"/"+context;
    		
    		String htmlResponse = "";
    		
    		String htmlPath = vhost.getHomePath()+"/conf/iosdebugid3tags.html";
    		File htmlFile = new File(htmlPath);
    		
    		WowzaRandomAccessFile inf = null;
    		while(true)
    		{
    			if (!htmlFile.exists())
    			{
    				WMSLoggerFactory.getLogger(HTTProvider2Base.class).warn("HTTPIOSDebugID3Tags.onHTTPRequest: HTML template not found: "+htmlPath);
    				break;
    			}
    			
    			int maxSize = 1024*1024;
    			if (htmlFile.length() > maxSize)
    			{
    				WMSLoggerFactory.getLogger(HTTProvider2Base.class).warn("HTTPIOSDebugID3Tags.onHTTPRequest: HTML template file too large: size:"+htmlFile.length()+" limnit:"+maxSize);
    				break;
    			}
    			
    			byte[] buffer = new byte[(int)htmlFile.length()];
    			if (buffer == null)
    				break;
    
    			try
    			{
    				inf = new WowzaRandomAccessFile(htmlFile, "r");
    				inf.read(buffer);
    			}
    			catch(Exception e)
    			{
    				WMSLoggerFactory.getLogger(HTTProvider2Base.class).error("HTTPIOSDebugID3Tags.onHTTPRequest["+htmlFile+"]: Error reading file: "+e.toString());
    			}
    			
    			try
    			{
    				htmlResponse = new String(buffer, "UTF-8");
    			}
    			catch(Exception e)
    			{
    				WMSLoggerFactory.getLogger(HTTProvider2Base.class).error("HTTPIOSDebugID3Tags.onHTTPRequest["+htmlFile+"]: Error converting file to string: "+e.toString());
    			}
    			
    			if (htmlResponse == null)
    				htmlResponse = "";
    			
    			htmlResponse = htmlResponse.replace(PLACEHOLDER_URL, playlistURL);
    			WMSLoggerFactory.getLogger(HTTProvider2Base.class).info("HTTPIOSDebugID3Tags.onHTTPRequest["+htmlFile+"]: "+PLACEHOLDER_URL+": "+playlistURL);
    			break;
    		}
    		
    		if (inf != null)
    		{
    			try
    			{
    				inf.close();
    				inf = null;
    			}
    			catch(Exception e)
    			{
    			}
    		}
    
    		try
    		{
    			if (htmlResponse.length() > 0)
    			{
    				resp.setHeader("Content-Type", "text/html");
    
    				OutputStream out = resp.getOutputStream();
    				out.write(htmlResponse.getBytes());
    			}
    			else
    			{
    				resp.setResponseCode(404);
    			}
    		}
    		catch(Exception e)
    		{
    			WMSLoggerFactory.getLogger(HTTProvider2Base.class).error("HTTPIOSDebugID3Tags.onHTTPRequest: "+e.toString());
    		}
    	}
    
    }
    In [install-dir]/conf/VHost.xml, add the following HTTPProvider entry to any of the <HostPort> definitions that you're using for HTTP streaming. Add it as the second-to-last entry in the <HTTPProviders> list (leaving the HTTPServerVersion entry as the last entry):
    <HTTPProvider>
    	<BaseClass>com.wowza.wms.plugin.test.http.HTTPIOSDebugID3Tags</BaseClass>
    	<RequestFilters>*iosdebugid3tags.html</RequestFilters>
    	<AuthenticationMethod>none</AuthenticationMethod>
    </HTTPProvider>
    Use a text editor to create a file named [install-dir]/conf/iosdebugid3tags.html with this content. This serves as the HTML template for Apple HLS playback. You can edit this HTML template as needed to help debug your ID3 tags. By default, the included JavaScript sends alert messages when the page is loaded and for each ID3 event:
    <!DOCTYPE html>
    <html>
    <head>
    <title>Wowza Media Server: iOS ID3 Tag Tester</title>
    <script type="text/javascript">
    function loadPage()
    {
            var myMovie = document.getElementById("mymovie");
            if (myMovie)
            {
                    myMovie.addEventListener( "qt_timedmetadataupdated", updateMetadata, false );
                    alert("loadPage: "+myMovie);
            }
    }
    
    function updateMetadata()
    {
            var metadata = this.GetTimedMetadataUpdates();
            if (metadata)
            {
                    var eventStr = "";
                    for(var objIndex in metadata)
                    {
                            eventStr += "timedEvent:
    ";
                            for(var key in metadata[objIndex])
                            {
                                    eventStr += "  "+key+": "+metadata[objIndex][key]+"
    ";
                            }
                    }
                    alert(eventStr);
            }
    }
    </script>
    </head>
    <body onload="loadPage();">
    <div>
    <embed width="320" height="180" src="${com.wowza.wms.HTTPIOSDebugID3Tags.playlistURL}" type="application/x-mpegURL" postdomevents="true" id="mymovie">
    </embed>
    </div>
    </body>
    </html>
    Note: This debugging method won't work on OS X.

    Originally Published: 11-17-2011.

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