Playing movie pulled from web app

Hey web gurus!

I have a web app. I want to use the HandleURL event to return a .mov file and I want that file to simply show and play in the client browser.

Now I know there are tons of compatibility caveats with formats, codecs, etc… but just hang on with me. All things being the same (browser, platform, movie file), what I want is to have the same behaviour when a movie file is requested by the browser, no matter if the file is sent from a standard Apache web server or from my web app.

So to start, I placed a movie file test.mov on my Apache server and typed its url http://myserver.com/test.mov in Safari 9.0.3 on my Mac. The browser plays the movie directly. This is the direct file, no html/flash player or anything. Since Safari knows how to handle that file, it plays it.

However, I cannot get the same result with a web app using the same browser to access the same file.

Here’s what I have tried in the HandleURL event:

dim thefile as folderItem = GetFolderItem("test.mov") Request.File = thefile Request.Header("Content-Type") = "video/quicktime" Request.Status = 200 return true

This results in Safari downloading the file and opening it with Quicktime Player. Analyzing the headers returned automatically by the web app, I saw that Xojo adds [quote]Content-Disposition: attachment; filename=“test.mov”[/quote] to the page returned. So it makes sense that the file is saved: the browser responds correctly to the server instructions.

To override that, I cleared the request headers with

Request.Header("Content-Disposition") = ""

With this, the browser displays it’s player… but it’s just the play bar saying “loading” and the movie never starts. I can click play/stop all I want, nothing happens.

I thought maybe Apache adds some special headers that the web app does not. So I checked the headers returned by Apache (using curl -I) to see if there would be something I’m missing. I saw that Apache returns Accept-Ranges: bytes so that hinted me that maybe the browser sends additional data to the server for playback positioning, and so after some googling, I tried adding

 Request.Header("Accept-Ranges") = "none"

… but it didn’t help. Still stuck with the frozen player.

So what am I missing?

What happens if you try

Request.Header("Content-Disposition") = "inline"

http://www.iana.org/assignments/cont-disp/cont-disp.xhtml

Yeah, forgot to mention that I tried this one too. It does not change anything. Note that Apache does not have that header at all.

[quote=247677:@Bruno Frechette]Hey web gurus!

I have a web app. I want to use the HandleURL event to return a .mov file and I want that file to simply show and play in the client browser.

Now I know there are tons of compatibility caveats with formats, codecs, etc… but just hang on with me. All things being the same (browser, platform, movie file), what I want is to have the same behaviour when a movie file is requested by the browser, no matter if the file is sent from a standard Apache web server or from my web app.

So to start, I placed a movie file test.mov on my Apache server and typed its url http://myserver.com/test.mov in Safari 9.0.3 on my Mac. The browser plays the movie directly. This is the direct file, no html/flash player or anything. Since Safari knows how to handle that file, it plays it.

However, I cannot get the same result with a web app using the same browser to access the same file.

Here’s what I have tried in the HandleURL event:

dim thefile as folderItem = GetFolderItem("test.mov") Request.File = thefile Request.Header("Content-Type") = "video/quicktime" Request.Status = 200 return true

This results in Safari downloading the file and opening it with Quicktime Player. Analyzing the headers returned automatically by the web app, I saw that Xojo adds to the page returned. So it makes sense that the file is saved: the browser responds correctly to the server instructions.

To override that, I cleared the request headers with

Request.Header("Content-Disposition") = ""

With this, the browser displays it’s player… but it’s just the play bar saying “loading” and the movie never starts. I can click play/stop all I want, nothing happens.

I thought maybe Apache adds some special headers that the web app does not. So I checked the headers returned by Apache (using curl -I) to see if there would be something I’m missing. I saw that Apache returns Accept-Ranges: bytes so that hinted me that maybe the browser sends additional data to the server for playback positioning, and so after some googling, I tried adding

 Request.Header("Accept-Ranges") = "none"

… but it didn’t help. Still stuck with the frozen player.

So what am I missing?[/quote]

Your should return an HTML page instead of trying to send the file. See http://www.w3schools.com/html/html5_video.asp

Thanks Michel. I’ll give this a try. But I still don’t understand why the browser shows the file from Apache correctly, but fails when pulled from a web app.

Apache probably sends some headers for you, when your code sends none, so the browser does not even know what kind of file it is. Using a web page will tell the browser to play the file as a movie.

Yeah. But if I could figure out what those headers are, I would send the same ones :slight_smile:

What are the response headers from Apache and your Web app?

You can fetch them using terminal with:
curl -I http://myserver.com/test.mov

@Erik Fohlin I did check the headers returned by Apache. Here they are:

HTTP/1.1 200 OK Date: Wed, 17 Feb 2016 01:30:35 GMT Server: Apache/2.2.24 (Unix) PHP/5.3.26 mod_ssl/2.2.24 OpenSSL/0.9.8y Last-Modified: Thu, 11 Feb 2016 14:10:06 GMT ETag: "139da-1ec868-52b7f1b838b80" Accept-Ranges: bytes Content-Length: 958784 Cache-Control: max-age=3600 Expires: Wed, 17 Feb 2016 02:30:35 GMT MS-Author-Via: DAV Content-Type: video/quicktime

The web app originally returned

HTTP/1.1 200 OK Content-Type: video/quicktime Content-Description: File Transfer Content-Disposition: attachment; filename="test.mov" Content-Length: 958784

I tried to removed the Content-Disposition and Content-Description: that results in the video playback bar to appear, but it’s stuck on “loading” and nothing appears. I tried with Content-Disposition: inline, but same result. I also tried to replicate every header Apache sends, but again same result (stuck playback bar). I figured maybe there’s a seeking mechanism, hinted by Accept-Ranges: bytes, so I changed that to Accept-Ranges: none: no change.

Setting…

Request.Header("Content-Description") ="" Request.Header("Content-Disposition") =""
…should make the movie load in the browser window.

Setting Accept-Ranges: none is probably wise as well.

After that it probably has to do with how the actual video data is delivered, all data may need to be delivered before the movie can play at all. Does the movie load eventually?
Im not sure how the file is read and sent out to the client.
Maybe xojo first reads the whole file into memory, if it is a big file that could probably cause problems or delays.

Hi Erik, thanks a lot for your time with this.

No, the movie never loads.

I’m in the middle of more testing right now and it seems that the browser makes a second request to the server… I’ll try to sniff the content to look at what exactly the browser requests in its headers. As far as I know, I cannot see the headers of the request in the debugger.

I’ll post my results :slight_smile:

Are you not sending content-length from the web app?

I suggest that you put the videos on a cdn and then send a 304 redirect back to the app through handleurl.

OK, I got it working finally. After sniffing the traffic, I understood what Apache is doing that the web app was not.

The browser actually makes several requests to the server using the Range header to request only part of the file.

Here’s part of the conversation with only the relevant headers included:

[code]Browser:
GET /test.mov HTTP/1.1

Server:
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 958784
(sends the file - the browser may close the connection at any time)

Browser:
GET /test.mov HTTP/1.1
Range: bytes=0-1

Server:
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Length: 2
Content-Range: bytes 0-1/958784
(sends 2 bytes)

Browser:
GET /test.mov HTTP/1.1
Range: bytes=950272-958783

Server:
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Length: 8512
Content-Range: bytes 950272-958783/958784
(sends 8512 bytes)[/code]

I implemented this in my web app, getting and decoding the Content-Range requested. The file is then opened as a binary stream, positioned where the browser requests and sending the number of bytes requested. The browser now displays the video, just like when served from Apache.

Anyone interested in the code, let me know and I’ll post it. It’s fairly simple.

Thanks!!!

Please post.
I suspected something like that, playback of internet video relies heavily on 206 Partial Content these days.

I have actually built my own web server using server socket to do exactly this but it would be great if we could also have the web app do it using HandleURL event.

I have one question regarding this that maybe Greg O’Lone can answer.

What if the video file is 1, 4 or 12 giga bytes in file size?
How would that be handled in the HandleURL event?
How much RAM would be needed?

What exactly does WebRequest.Print do?
Docs only say it “Appends the provided Data to the response.”

Is what we Print actually delivered out to the client directly, or is it only appended to some buffer somewhere before actual delivery?
Could we somehow deliver a very large video file in smaller chunks using WebRequest.Print?
If so, it would be great.
If not, we will need to use a separate custom server socket for it or, as Greg suggests, host the video on a separate server, at least for larger video files.

here’s the code I used for testing.

In HandleURL:

dim thefile as folderItem = GetFolderItem("/Users/bruno/Desktop/test.mov",FolderItem.PathTypeShell) return SendFileData(Request,thefile)

And the method wrapping:

[code]Function SendFileData(request as WebRequest , theFile as FolderItem) As Boolean

if theFile = nil then Return False

dim bs as BinaryStream
try
bs = BinaryStream.Open(theFile)
Catch
return False
end try

'determine the portion of the file to send
dim firstByte as integer = 0
dim lastByte as integer = bs.Length

dim byteRangeHeader as string = request.GetRequestHeader(“Range”)
if byteRangeHeader <> “” then
dim Parts() as string = byteRangeHeader.Split("=")
if parts.Ubound = 1 and parts(0) = “bytes” then
dim byteRange() as string = parts(1).Split("-")
if byteRange.Ubound = 1 then
firstByte = max(byteRange(0).Val,0)
lastByte = min(byteRange(1).Val,lastByte)
end if
end if
end if

'build header
'note that content-length is calculated automatically by xojo
Request.Header(“Accept-Ranges”) = “bytes”
Request.Header(“Content-Range”) = “bytes " + Format(firstByte,“0”)+”-"+Format(lastByte,“0”)+"/"+format(bs.Length,“0”)
Request.Header(“Connection”) = “Close”
Request.Header(“Content-Type”) = “video/quicktime”

'set status depending if the range is there or not
if byteRangeHeader = “” then
Request.Status = 200
else
Request.Status = 206
end if

'write the bytes
bs.Position = firstByte
request.Print bs.Read(lastByte-firstByte+1)

return true
End Function
[/code]

Now nothing is optimized. The file is reopened with each access. I know the browser also adds a header with a unique ID to identify the file playback instance, so it could be used for some caching mechanism. In my case, the video files are small and so it’s not necessary for the moment. But you have a base to work on.