3

I'm trying to serve a video file as response when certain request hits the server (server running on a mobile app). eg: someone goes to http://mylocalip:8080/video, and I want to serve him with a video.

This video file can be stored locally, or can be external. I started trying to serve a file located on a SMB server, so I tried using this code to get the file from the server and returning it (I know it's waiting for the whole file to read instead of reading and sending chunks, but should work right?):

webServer.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: {request in
            print("########\n########Request: \(request)")
            let webrequested:GCDWebServerRequest = request;
            let urlcita:String = String(webrequested.URL)
            print("URL of requests\(urlcita)")
            if urlcita.rangeOfString("videosmb") != nil{
                print("It's a video SMB request")

                //Test fake SMB file predefined. Read File using KxSMB

                let route:String = "smb://192.168.1.205/ServidorAH/video.avi";
                let extensFile:NSString = (route as NSString).pathExtension
                let contentype = GCDWebServerGetMimeTypeForExtension(extensFile as String);
                print("Content type MIME \(contentype)")


                //Open SMB file using KxSMB
                let authcredentials = KxSMBAuth.smbAuthWorkgroup("", username: "Guest", password: "");
                let provider = KxSMBProvider.sharedSmbProvider()!
                let archivoes = provider.fetchAtPath(route, auth: authcredentials)

                //Switch to manually end the reading when switched to true. In the future will send chunks of data until the end, instead of reading the whole file first.
                var interruptor:Bool = false

                //Response Stream block
                let responseStream: GCDWebServerStreamedResponse = GCDWebServerStreamedResponse(contentType: contentype, asyncStreamBlock: { completionBlock in

                    if (interruptor == true)
                    {
                        print("Test: End of reading")
                        completionBlock(NSData(), nil);
                    }

                    if archivoes!.isKindOfClass(NSError){
                        //It can not find the file, SMB error, so empty NSDATA to completion block
                        let errorcito:NSError = archivoes as! NSError
                        print("Error obteniendo archivo SMB: \(errorcito.localizedDescription)");
                        completionBlock(NSData(), nil);
                    }else{
                        print("Archivo SMB adecuado \(archivoes)")
                        let datos:NSData = archivoes!.readDataToEndOfFile()
                        //Print lenght of data (to check the size of the file)
                        print("Data lenght \(datos.length)")
                        //Set switch to true, so the next call will send an empty daya completion block
                        interruptor = true
                        //Send data chunk (in this case everything)
                        completionBlock(datos, nil);
                    }

                })
                return responseStream


            }else{
                //Default response
                return GCDWebServerDataResponse(HTML:"<html><body><p>Hello World<br></p></body></html>")
 }

However, I can't make it work. I always get a broken pipe error, and the web player accessing the web server (browsing from the mac and iOS too) doesn't play anything. I've also tried using an embeded iOS player to log the response (KxMovie). I get something like this:

[DEBUG] Did open connection on socket 19
[DEBUG] Connection received 177 bytes on socket 19
[DEBUG] Connection on socket 19 preflighting request "GET /videosmb" with 177 bytes body
[DEBUG] Connection on socket 19 processing request "GET /videosmb" with 177 bytes body
[DEBUG] Did connect
[DEBUG] Did start background task
[DEBUG] Connection sent 175 bytes on socket 19
...

Using a local player (KxMovie) from inside the App, here appears to be reading the file headers and it gets the right file size and dimensions of the video. However it doesn't play and it ends saying it reached the end of the video (without playing it). Just after that, WebServer shows an error:

...       

[ERROR] Error while writing to socket 19: Broken pipe (32)
[DEBUG] Did close connection on socket 19
[VERBOSE] [fe80::cd0:28cd:3a37:b871%en1:8080] fe80::cd0:28cd:3a37:b871%en1:50109 200 "GET /videosmb" (177 | 175)
[DEBUG] Did disconnect
[DEBUG] Did end background task

Given the fact this is the first time I'm dealing with SMB servers, I though that maybe I was doing something wrong with the SMB part, so I decided to go for a simplified method just for testing. This time I tried to serve a simple mp4 file stored on a remote webserver (not SMB). It didn't work either. Finally I tried to serve a local file included in the main Bundle of the App, and the same happened: nothing. Here is the code:

webServer.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: {request in
       print("########\n########Request: \(request)")
       let webrequested:GCDWebServerRequest = request;
       let url:String = String(webrequested.URL)
       print("URL of request: \(url)")
       if url.rangeOfString("video") != nil{
            print("It's a video request")

            let rutalocalita = (NSBundle.mainBundle()).pathForResource("video", ofType: "avi")
            let datos = NSData(contentsOfFile: rutalocalita!)!
            print("video size: \(datos.length)")
            return GCDWebServerDataResponse(data: datos, contentType: "video/avi")

        }else{
           //Default Response: Simple web
           return GCDWebServerDataResponse(HTML:"<html><body><p>Hello World<br></p></body></html>")
        }
})

This is what the log looks like:

[DEBUG] Did open connection on socket 19
[DEBUG] Connection received 177 bytes on socket 19
[DEBUG] Connection on socket 19 preflighting request "GET /video" with 177 bytes body
[DEBUG] Connection on socket 19 processing request "GET /video" with 177 bytes body
[DEBUG] Did connect
[DEBUG] Did start background task
[myCUSTOMDebug] Read 13584902 b. I'm going to send the response back to the request.
[DEBUG] Connection sent 173 bytes on socket 19
...

Here the local Playing I'm using inside the app to track the response, is able to read things like:

header='HTTP/1.1 200 OK'
2015-10-17 17:51:41.571 videotvtest[262:14252] http_code=200
2015-10-17 17:51:41.571 videotvtest[262:14252] header='Cache-Control: no-cache'
2015-10-17 17:51:41.571 videotvtest[262:14252] header='Content-Length: 13584902'
2015-10-17 17:51:41.572 videotvtest[262:14252] header='Content-Type: video/avi'
2015-10-17 17:51:41.572 videotvtest[262:14252] header='Connection: Close'
2015-10-17 17:51:41.573 videotvtest[262:14252] header='Server: GCDWebServer'

...
[ERROR] Error while writing to socket 19: Broken pipe (32)
[DEBUG] Did close connection on socket 19
[VERBOSE] [fe80::cd0:28cd:3a37:b871%en1:8080] fe80::cd0:28cd:3a37:b871%en1:50155 200 "GET /video" (177 | 173)
netbios_ns_send_name_query, name query sent for '*'.
[DEBUG] Did disconnect
[DEBUG] Did end background task

I'm playing around with tvOS and Xcode 7 for this, but I guess it should be ok if I'm able to show a regular HTML response... so I'm sure I'm missing something, or maybe I missed some framework when installing WebServer (I'm not using pods)? Thanks in advance

eiprol
  • 389
  • 1
  • 3
  • 14
  • You have too many moving parts here between SMB, video file, watch OS, web server... You should start by serving a regular local file off a Mac app or iPhone app, then a video file, then over SMB and finally move to watch OS. – Pol Oct 17 '15 at 17:47
  • Hi Pol, thanks for your response. However, there is nothing related to watch OS on my code!The main problem here, after more testing, seems to be related to content range, since I'm able to play every file tested (local, smb, remote, etc) from Desktop Google Chrome flawlessly! However, when I try iOS player, or OS X Safari, it fails... telling me something about 'failed to load resource: plug-in module managed' in developer console. I know it's possible to serve some kind of range files with if they are stored locally... but what about 3rd party? should I download them first locally? – eiprol Oct 17 '15 at 17:51
  • You should update the title of this question, as it's too generic and not really a question either. – Pol Oct 17 '15 at 21:46
  • Hi, pardon me as its not related to your question but what changes do you make to KxSMB to make it able to build on swift? @eiprol – perwyl Jul 27 '16 at 07:11

1 Answers1

1

If you want to serve a video file that can play in the browser using the video tag, at least on Chrome and Safari, you need to implement HTTP range requests.

GCDWebServer automatically implement range support if you use GCDWebServerFileResponse. If you use another type of response you would need to implement it yourself based on the byteRange property of the incoming GCDWebServerRequest. You should copy-paste the logic from GCDWebServerFileResponse.m.

Pol
  • 3,848
  • 1
  • 38
  • 55
  • I guess that something similar to this would be the approach; however, since I'm trying que read external/remote files (not stored on the same device than GCDWebServer) and serve them as the response to some request, I can't use that type without changing it a little bit (providing a path, as GCDWebServerFileResponse does, to serve a file stored on a SMB server would be useless, and I should provide instead a method to read that file asynchronously (maybe) and return NSData. Maybe I should do a hybrid between GCDWebServerFileResponse (for byte range), GCDWebServerStreamedResponse (async read) – eiprol Oct 18 '15 at 00:20