Serving files with html5 audio

447 views
Skip to first unread message

Hanna

unread,
Mar 15, 2012, 10:12:28 AM3/15/12
to lif...@googlegroups.com
Hi,

I'm having problems serving audio files in Lift, to be played using the html5 audio element.

Here's an example of the audio element:

 <audio>
  <source type="audio/mpeg" src="/virtual-path/audio.mp3" />
 </audio>


The audio files are uploaded by users, and stored on the web server. When a request is received, the exact location of the file is computed by Boot before the file is loaded, and a response is sent.

After some first failed attempts, a response looking something like this seemed to work (with Chrome and IE9). (The code is in LiftRules.dispatch.append { ... } )

// ba: a byte array with the file contents
InMemoryResponse(ba
,List("Content-Type" -> "audio/mpeg"
      ,"Accept-Ranges" -> "bytes"
      ,"Content-Length" -> ba.length.toString
)
,Nil
,200)



But this doesn't work well with larger files, so I tried using a StreamingResponse instead. (The test file is 6.5M.) A response similar to that below works for playing the audio file once, if it's not paused during playback.

// stream: an input stream with the file contents
// file: java.io.File
StreamingResponse(stream
                ,() => {} // I've tried stream.close here, too
,file.length
,List("Content-Disposition" -> ("attachment; " + file.getName))
,Nil
,200) // I've tried with 206 here, too

But with the StreamingResponse, the file cannot be played more than once, and it's very sensitive to pausing or moving the cursor. I've tested various header contents as well, but Content-Disposition is what I've found in examples.

If I put the same audio file in static, and let Jetty handle the response, it works just fine.

Does anyone have any suggestions on how to write the response? Or is there any way to just compute the exact location of the file, and pass a java input stream (or similar) over to Jetty instead, where it seems to work already?

Or maybe I'm asking the wrong question. Any feedback would be appreciated.

(If needed, I can put together a sample Lift app with a few examples.)

Cheers,
/Hanna


David Pollak

unread,
Mar 15, 2012, 4:56:47 PM3/15/12
to lif...@googlegroups.com
Hanna,

At this point, Lift does not support chunked HTTP response (I don't know the right words for the feature, but it allows an HTTP request to ask for only a range of a file.)  That would likely make your audio file play back more evenly.

Please open a ticket at http://ticket.liftweb.net and link to this thread.

If anyone else has ideas/suggestions, please post them.

Thanks,

David



--
Lift, the simply functional web framework: http://liftweb.net
Code: http://github.com/lift
Discussion: http://groups.google.com/group/liftweb
Stuck? Help us help you: https://www.assembla.com/wiki/show/liftweb/Posting_example_code



--
Visi.Pro, Cloud Computing for the Rest of Us http://visi.pro
Lift, the simply functional web framework http://liftweb.net


Naftoli Gugenheim

unread,
Mar 15, 2012, 8:08:43 PM3/15/12
to lif...@googlegroups.com
On Thursday, March 15, 2012, David Pollak wrote:
Hanna,

At this point, Lift does not support chunked HTTP response (I don't know the right words for the feature, but it allows an HTTP request to ask for only a range of a file.)  That would likely make your audio file play back more evenly.

Please open a ticket at http://ticket.liftweb.net and link to this thread.

If anyone else has ideas/suggestions, please post them.

Not sure whether it's relevant to the topic or not, but you may want to have a look at how Play 2 uses Enumerator/Iteratee for custom HTTP.

Jeppe Nejsum Madsen

unread,
Mar 16, 2012, 4:08:55 AM3/16/12
to lif...@googlegroups.com
Hanna <han....@gmail.com> writes:


[...]

> If I put the same audio file in static, and let Jetty handle the response,
> it works just fine.

Would be interesting to see the HTTP flow in this case to determine if
it uses Chunked transfer encoding....

> Does anyone have any suggestions on how to write the response? Or is there
> any way to just compute the exact location of the file, and pass a java
> input stream (or similar) over to Jetty instead, where it seems to work
> already?

You could just map a path to be handled directly by jetty, bypassing
lift, and then store the content there.

/Jeppe

Damian Helme

unread,
Mar 16, 2012, 8:43:45 AM3/16/12
to lif...@googlegroups.com
I think David's  'chunked HTTP response' is referring to HTTP 'Range' requests.

I found this example Java code a while back for handling Range requests:


I used this as a starting point for coding up my own Lift version.

I've posted my code here: https://gist.github.com/2049865 if you want to take a look as a starting point. It's a while since I've visited it and it's in need of a bit of refactoring. I can't remember how far I got with testing (the project has been parked for the moment & hasn't gone live), so proceed with caution ... 






David Pollak

unread,
Mar 16, 2012, 11:45:48 AM3/16/12
to lif...@googlegroups.com
Thank you very much!!  If you're in London, come on over to the Slaughtered Lamb tonight and I'll buy you a beer!

--
Lift, the simply functional web framework: http://liftweb.net
Code: http://github.com/lift
Discussion: http://groups.google.com/group/liftweb
Stuck? Help us help you: https://www.assembla.com/wiki/show/liftweb/Posting_example_code

Damian Helme

unread,
Mar 16, 2012, 12:04:10 PM3/16/12
to lif...@googlegroups.com
Thanks - would have loved to but am back in Cambridge now. I might keep it as a credit for next time, though ;-)

David Pollak

unread,
Mar 16, 2012, 12:24:18 PM3/16/12
to lif...@googlegroups.com
Definitely!



On Mar 16, 2012, at 4:04 PM, Damian Helme <damia...@gmail.com> wrote:

Thanks - would have loved to but am back in Cambridge now. I might keep it as a credit for next time, though ;-)

--

Hanna

unread,
Mar 19, 2012, 10:42:34 AM3/19/12
to lif...@googlegroups.com
Hi, thanks for all replies!

I think I'll give bypassing the audio to Jetty a try, then. If you could point me in the right direction, such as an example to use as a starting point, that would be very much appreciated.
(And if it involves editing the webdefault.xml, how do I do that when running jetty in dev mode from sbt?)

Damian: thanks for creating the ticket.

Cheers,
Hanna

Damian Helme

unread,
Mar 20, 2012, 1:14:37 PM3/20/12
to lif...@googlegroups.com
Hi,
Adding something like:

LiftRules.liftRequest.append { 
  case r @ Req("mediadir" :: filename :: Nil,_,_) => { 
      debug("passing request onto container: " +  r)
      false
  }
}

in your Boot.scala will pass matching requests onto Jetty, but there are quite a few more issues to iron out (like making sure you don't try to upload files into a directory in your WAR file). There are plenty of threads in this group that discuss various approaches.

Richard Dallaway

unread,
Mar 21, 2012, 7:29:20 AM3/21/12
to lif...@googlegroups.com
This would make a great cookbook.liftweb.net recipe if anyone wants to take it on.  I'd suggest the Pipeline chapter.

--

Hanna

unread,
Mar 23, 2012, 6:51:37 AM3/23/12
to lif...@googlegroups.com
Thanks Damian, that was enough to get me started!

I managed to make a standalone Jetty environment handle files (Jetty deployed from this example ), by creating an xml file with the following content in a folder "contexts":

<Configure class="org.mortbay.jetty.servlet.Context">
  <Set name="contextPath">/jettybypass</Set>
  <Set name="resourceBase">jettybypasstest</Set>
  <Call name="addServlet">
    <Arg>org.mortbay.jetty.servlet.DefaultServlet</Arg>
    <Arg>/</Arg>
  </Call>
</Configure>

But I couldn't find out how to do the same thing when running Jetty from within sbt.

Another thing is that I'd want to check some access rights as well before passing the request onto Jetty, but after what I've read in other threads, it seems difficult to do that in a stateful context (within a user session), am I right?

Hanna
Reply all
Reply to author
Forward
0 new messages