2 Replies Latest reply on Apr 5, 2010 9:16 AM by Michael Ressler

    Supporting byte-ranges inside a Seam component

    Benjamin Goll Newbie

      Hello,


      I currently try to implement a simple streaming-server for ogg vorbis-files. This server will be included into a bigger project. The ogg-files will be played back via the HTML5 audio-tag in Firefox. Serving the files to the browser works fine but not seeking inside of the file. For this to work properly, I would have to support byte-ranges inside my Seam component (according to http://pearce.org.nz/blog/?p=9). Since Tomcat supports byte-ranges by default, I would like to pass my outputstream directly to Tomcat. Is there any possibility for this?
      Maybe a bit of code can make more clear what I mean:



      @Name("streamingServer")
      @AutoCreate
      @Scope(ScopeType.APPLICATION)
      @Startup
      public class StreamingServer
      {
              @Logger
              private Log log;
              
              @In
              EntityManager entityManager;
              
              @In
              String libraryPath;
              
              @In(value="#{facesContext.externalContext}")
              private ExternalContext extCtx;
              
              @In(value="#{facesContext}")
              FacesContext facesContext;
              
              @RequestParameter
              private Long trackId;
              
              private static final int DEFAULT_BUFFER_SIZE = 1024;
              
              public void download()
              {
                      Track track = entityManager.find(Track.class, trackId);
                      
                      File fileHandle = new File(libraryPath + track.getFilePath());
      
                      HttpServletResponse response = (HttpServletResponse) extCtx.getResponse();
                              
                      response.reset();
                      response.setBufferSize(1024);
                      response.setContentType(track.getMimeType());
                      response.setContentLength((int) fileHandle.length());
                      response.setHeader("X-Content-Duration", Float.toString(track.getLength()));
                      
                      BufferedInputStream in = null;
                      BufferedOutputStream out = null;
      
                      try
                      {                       
                              in = new BufferedInputStream(new FileInputStream(fileHandle), DEFAULT_BUFFER_SIZE);
                              out = new BufferedOutputStream(response.getOutputStream(), DEFAULT_BUFFER_SIZE);
      
                              byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
                              int length;
                  
                              while ((length = in.read(buffer)) > 0)
                              {
                                   out.write(buffer, 0, length);
                              }
                  
                              in.close();
                              out.close();
                              
                              facesContext.responseComplete();
                      }
                      catch(Exception e)
                      {
                              log.error("Failure : " + e.toString());
                      }
              }       
      }



      What I would like to do is read in the ogg-file from the disk and pass it to Tomcat's default servlet which could then serve it supporting byte-ranges.
      If this is not possible, do you see any other possibility to achieve my goal?


      environment:
      JBoss 4.2.3GA
      Seam 2.2.0GA
      ICEFaces 1.8.2


      Thanks in advance!


      Regards


      Benjamin

        • 1. Re: Supporting byte-ranges inside a Seam component
          Michael Ressler Newbie

          Benjamin,


          I was struggling with a similar problem with OGG video within Tomcat.  My solution at the moment is to create a new Tomcat Context for serving all of my video related content.  This lets Tomcat do its Byte-Ranges business, but unfortunately it doesn't help me out with the response header X-Content-Duration like I'd like it to (I can, however, modify the mime-type mapping through Tomcat's web.xml under the conf directory).  I would like to point out that the browsers I've tested this in do a fine job of dealing without the X-Content-Duration header set and even dealing without the mime-type set in some cases.  The MDC has a good write-up of how to be a good HTML5 content-serving citizen here.  The only downside to not serving up the X-Content-Duration header seem to be a few more HTTP requests (with byte-ranges) to seek to the end of the file to determine the duration and then back to the beginning of the requested content.


          I came across your question while looking for a solution to serving up OGG content in Tomcat with X-Content-Duration headers set.  My current thought is to play with Tomcat Filters.  It seems like a Filter would be able to intercept the request at the right point and muck with the headers before returning the response (and still allowing for the byte-ranges goodness).


          I'll report my findings here since this post ranks well in Tomcat 'x-content-duration' searches :)


          Mike Ressler

          • 2. Re: Supporting byte-ranges inside a Seam component
            Michael Ressler Newbie

            Adding a Filter that adds the X-Content-Duration header works like a charm.  The catch now is to cache the duration information somewhere sensible to be retrieved between server starts and stops.


            I used the Filter example from here and at the moment I'm hard-coding the duration.  This brought the number of requests for the one particular video down from 5 requests to 1!  Not very noticeable when it's hosted on my machine, but I'm sure that will help users streaming video online.


            For fun, here's my ContentDurationFilter:




            import java.io.IOException;
            
            import javax.servlet.Filter;
            import javax.servlet.FilterChain;
            import javax.servlet.FilterConfig;
            import javax.servlet.ServletException;
            import javax.servlet.ServletRequest;
            import javax.servlet.ServletResponse;
            import javax.servlet.http.HttpServletRequest;
            import javax.servlet.http.HttpServletResponse;
            
            public class ContentDurationFilter implements Filter {
                 
                 @SuppressWarnings("unused")
                 private FilterConfig filterConfig = null;
            
                 @Override
                 public void init(FilterConfig filterConfig) throws ServletException {
                      // apparently I need this?
                      this.filterConfig = filterConfig;
                 }
            
                 @Override
                 public void destroy() {
                      this.filterConfig = null;
                 }
            
                 @Override
                 public void doFilter(
                           ServletRequest request, 
                           ServletResponse response,
                           FilterChain chain) 
                      throws IOException, ServletException 
                 {
                      System.out.println("Filter ran on " + ((HttpServletRequest)request).getRequestURI());
                      
                      // TODO: Not the laziest thing you could think of.  Actually do some intelligent caching of this data.
                      ((HttpServletResponse)response).addHeader("X-Content-Duration", "1178.818726");
                      
                      chain.doFilter(request, response);
                 }
            
            }



            The rest (byte-ranges, mime type, content length) is all handled by Tomcat behind the scenes.  For my particular video application, I use the ffmpeg2theora library wrapped in a slightly modified JAVE layer for conversion and introspection of the videos.


            Hope this helps other folks!