6

I have a client iOS application that uses GCDWebServer to serve images and videos stored in the NSSearchPathDirectory.DocumentDirectory folder of the app on my device.

Upon app startup, I start an instance of GCDWebServer and add a file response handler for my requests:

self.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerFileRequest.self) { request in

    return GCDWebServerFileResponse(file: self.filePathForResponse(request.URL))
}

I can verify that while the app is running I can successfully download files from my device:

curl -O http://192.168.0.15:8080/files/IMG_1213-1280x720.MOV

My app talks to a Chromecast device by sending URLs like the one above to a media channel, and the Chromecast receiver app loads and plays the videos at the specified URLs - so far all good.

My problem is that I want to implement seeking support for the currently playing video, and as soon as I send a seek request to the media channel, I get a "Broken pipe" error from GCDWebServer and the video playback is interrupted. Log from the server is as follows:

....
[DEBUG] Connection sent 32768 bytes on socket 24
[DEBUG] Connection sent 32768 bytes on socket 24
[ERROR] Error while writing to socket 24: Broken pipe (32)
[DEBUG] Did close connection on socket 24

My best understanding of the problem is that normal playback works because it is the same as downloading a file from beginning to end and this can be served with a regular GCDWebServerFileResponse, however seeking is equivalent to 'jumping' to a different part of the file and I'm not sure that reading a file like this would work with my configuration.

  • Is there a way that I can configure GCDWebServer to make this work? I know that the problem can be solved because there are several live apps that do this.
  • Do I need to use a server that supports other protocols such as HLS or RTSP?
  • Do I need to encode my video files in a particular manner?

For reference I have also tried another HTTP server called Swifter but I encountered the same problem.

bizz84
  • 1,964
  • 21
  • 34

1 Answers1

5

iOS' AVPlayer at first requests file size and then asks server for a chunks of data, including a desired range in the request. In this answer, the network activity of AVPlayer is shown, and the response status code is 206, i.e. Partial Content.

So, we need to respond only with desired chunk of data:

webServer?.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, asyncProcessBlock: { (request, completionBlock) in
  let response = GCDWebServerFileResponse(file: path, byteRange: request.byteRange)
  completionBlock(response)
})

Please, notice, that it is important to check, whether request.byteRange is defined by invoking request.hasByteRange().

Using the code I've provided, I was able to play a video using AVPlayer or web browser and scrubbing / seek to time worked perfectly.

Richard Topchii
  • 7,075
  • 8
  • 48
  • 115
  • 1
    I have modified my code to use the `byteRange` as suggested, however in my case the consumer is not AVPlayer but a Chromecast sending requests for the video data. It appears that byteRange.length == -1, contentType = nil and contentLength = -1. Even when using this with `hasByteRange()`, the video is very choppy and I get a lot of: [ERROR] Error while writing to socket 18: Broken pipe (32) – bizz84 Aug 19 '16 at 11:45
  • I observed, how clients (AVPlayer or Chrome) consume content: after initial requests to get video size, they request whole video from the beginning. After client has buffered enough data it stops receiving it closes the connection, hence the broken pipe error. When client needs more data (i.e. player has played almost all the buffered video or user started scrubbing to different location), client starts new request, with byterange which starts not from zero, but ends at the end of the file. – Richard Topchii Aug 21 '16 at 13:40
  • For example, if file size is 500, and there are two requests: 1. 0-499 - after player has enough data buffered, it closes connection (actually it received only 100 bytes) 2. 101-499 - when buffer becomes almost empty, player starts preload again, but does not request already buffered data. This explains broken pipe errors – Richard Topchii Aug 21 '16 at 13:40
  • Not sure how Chrome does the buffering, but judging from the choppiness of the video it seems like requests are made often and for small buffers, but data is not received quickly enough so that the video can play continuously. If I omit `byteRange` then playback is fine, but I can't seek which was my original problem. – bizz84 Aug 25 '16 at 15:52
  • I wonder if the other apps on the App Store use `GCDWebServer` or other solutions. I have also tried Swifter https://github.com/httpswift/swifter but the broken pipe problem seems even worse. – bizz84 Aug 25 '16 at 15:55
  • 1
    finally this solution worked. thanks for posting this. however this does not work when your url don't not have an extension. That url plays the video on web browser but in this case it does not play in AVPlayer using GCDWebServer – Mahesh Agrawal Dec 13 '19 at 14:00
  • i want to request partial data to server and send partial data response. in that case i don't want to save the data in a file. for that scenario, how can i send partial response to the client? – Mahesh Agrawal Jan 10 '20 at 11:36