ios – GCDWebServer – concern with streaming lengthy size movies ? (10 minutes + )

ios – GCDWebServer – concern with streaming lengthy size movies ? (10 minutes + )


I’m creating an iOS app utilizing Swift that streams video content material to a Roku system. I’m utilizing GCDWebServer to proxy video streams, however I’m encountering an issue the place longer movies (each on-line and offline) fail to play appropriately. The video begins taking part in however stops after a number of seconds, and the connection appears to drop. I’ve tried each on-line video URLs and native video recordsdata served through a Python server, however the concern persists.

Setup:

•   iOS app utilizing Swift and GCDWebServer
•   Streaming to Roku system utilizing a customized URL format
•   Movies underneath a sure size (round 2-3 minutes) appear to play effective, however longer movies don't



class CastViewModel: ObservableObject {
@Printed var isServerRunning = false
@Printed var statusMessage = "Server not operating"

non-public var webServer: GCDWebServer?
non-public let rokuIP = "192.168.0.XXX"  // Hidden IP
non-public let rokuPort = "XXXX"  // Hidden Port
non-public let logger = Logger(subsystem: "com.yourapp", class: "CastViewModel")

func startServer() {
    webServer = GCDWebServer()
    
    // Customized handler utilizing MatchBlock
    webServer?.addHandlerWithMatchBlock({ (requestMethod, requestURL, requestHeaders, urlPath, urlQuery) -> GCDWebServerRequest? in
        // Solely deal with GET requests for /video/ path
        if requestMethod == "GET", urlPath.hasPrefix("/video/") {
            return GCDWebServerRequest(technique: requestMethod, url: requestURL, headers: requestHeaders, path: urlPath, question: urlQuery)
        }
        return nil
    }, processBlock: { [weak self] (request) -> GCDWebServerResponse? in
        // Processing video request
        guard let self = self else { return nil }
        let urlString = request.url.absoluteString
        guard let vary = urlString.vary(of: "/video/") else {
            return GCDWebServerResponse(statusCode: 400)
        }

        let videoURL = String(urlString[range.upperBound...])
        guard let decodedURL = videoURL.removingPercentEncoding, let validURL = URL(string: decodedURL) else {
            self.logger.error("Didn't decode or create a sound URL from: (videoURL)")
            return GCDWebServerResponse(statusCode: 400)
        }

        self.logger.data("Proxying video stream: (decodedURL)")

        // Stream the video content material from the unique URL
        URLSession.shared.dataTask(with: validURL) { information, response, error in
            guard let information = information, error == nil else {
                self.logger.error("Didn't fetch video stream: (error?.localizedDescription ?? "Unknown error")")
                return
            }

            let proxyResponse = GCDWebServerDataResponse(information: information, contentType: "video/mp4")
            proxyResponse.statusCode = 200
            proxyResponse.setValue("bytes", forAdditionalHeader: "Settle for-Ranges")
            proxyResponse.cacheControlMaxAge = 360
            return proxyResponse
        }.resume()
        return nil
    })
    
    // Begin the server
    do {
        attempt webServer?.begin(choices: [
            GCDWebServerOption_Port: 8081,  // Hidden Port
            GCDWebServerOption_BindToLocalhost: false,
            GCDWebServerOption_AutomaticallySuspendInBackground: true
        ])
        isServerRunning = true
        statusMessage = "Server operating on port 8081"  // Hidden Port
        logger.data("Server began efficiently on port 8081")  // Hidden Port
    } catch {
        statusMessage = "Failed to begin server: (error.localizedDescription)"
        logger.error("Failed to begin server: (error.localizedDescription)")
    }
}

func castToRoku(with videoURL: String) {
    guard let serverIP = getWiFiAddress(), let serverPort = webServer?.port else {
        statusMessage = "Didn't get server IP or port"
        logger.error("Didn't get server IP or port")
        return
    }
    
    let serverAddress = "http://(serverIP):(serverPort)"  // Hidden IP & Port
    
    // Encode the whole video URL, together with the server half
    let encodedVideoURL = customURLEncode("(serverAddress)/video/(videoURL)")
    
    // Assemble the Roku ECP URL manually
    let rokuURLString = "http://(rokuIP):(rokuPort)/enter/15985?t=v&u=(encodedVideoURL)&okay=(customURLEncode("(serverAddress)/splash/http://d2ucfbcfq1vh3b.cloudfront.web/splash-roku5_free.jpg"))&h=(customURLEncode("(serverAddress)/roku"))&videoName=YouTube_Video&videoFormat=mp4&d=1"
    
    guard let rokuURL = URL(string: rokuURLString) else {
        statusMessage = "Didn't assemble Roku URL"
        logger.error("Didn't assemble Roku URL")
        return
    }
    
    logger.data("Ultimate Roku URL: (rokuURL.absoluteString)")
    
    var request = URLRequest(url: rokuURL)
    request.httpMethod = "POST"
    
    // Set headers
    request.setValue("*/*", forHTTPHeaderField: "Settle for")
    request.setValue("gzip, deflate", forHTTPHeaderField: "Settle for-Encoding")
    request.setValue("en-GB,en;q=0.9", forHTTPHeaderField: "Settle for-Language")
    request.setValue("no-cache", forHTTPHeaderField: "Cache-Management")
    request.setValue("keep-alive", forHTTPHeaderField: "Connection")
    request.setValue("textual content/plain;charset=utf-8", forHTTPHeaderField: "Content material-Kind")
    request.setValue("Solid Browser Roku/3.12.1 CF Community/1496.0.7 Darwin/23.5.0", forHTTPHeaderField: "Person-Agent")
    
    logger.data("Sending POST request to Roku")
    URLSession.shared.dataTask(with: request) { [weak self] information, response, error in
        DispatchQueue.important.async {
            if let error = error {
                self?.statusMessage = "Error casting to Roku: (error.localizedDescription)"
                self?.logger.error("Error casting to Roku: (error.localizedDescription)")
            } else if let httpResponse = response as? HTTPURLResponse {
                self?.logger.data("Obtained response from Roku. Standing code: (httpResponse.statusCode)")
                if httpResponse.statusCode == 200 {
                    self?.statusMessage = "Efficiently solid to Roku"
                    self?.logger.data("Efficiently solid to Roku")
                } else {
                    self?.statusMessage = "Didn't solid to Roku. Standing code: (httpResponse.statusCode)"
                    self?.logger.error("Didn't solid to Roku. Standing code: (httpResponse.statusCode)")
                    if let information = information, let responseBody = String(information: information, encoding: .utf8) {
                        self?.logger.error("Response physique: (responseBody)")
                    }
                }
            } else {
                self?.statusMessage = "Didn't solid to Roku: Sudden response"
                self?.logger.error("Didn't solid to Roku: Sudden response")
            }
        }
    }.resume()
}

non-public func getWiFiAddress() -> String? {
    ...
}

non-public func customURLEncode(_ string: String) -> String {
    ...
}

func sendKeypressToRoku() {
    ...
}

}

Logs:

[DEBUG] Did open IPv4 listening socket 3
[DEBUG] Did open IPv6 listening socket 4
[INFO] GCDWebServer began on port 8081 and reachable at http://192.168.0.120:8081/
Server began efficiently on port 8081
Discovered WiFi tackle: 192.168.0.120
Ultimate Roku URL: http://192.168.0.XXX:XXXX/enter/15985?t=v&u=httppercent3Apercent2Fpercent2F192.168.0.120percent3A8081percent2Fvideopercent2Fhttppercent3Apercent2Fpercent2F192.168.0.XXXpercent3A8080percent2F1.mp4&okay=httppercent3Apercent2Fpercent2F192.168.0.120percent3A8081percent2Fsplashpercent2Fhttppercent3Apercent2Fpercent2Fd2ucfbcfq1vh3b.cloudfront.netpercent2Fsplash-roku5_free.jpg&h=httppercent3Apercent2Fpercent2F192.168.0.120percent3A8081percent2Froku&videoName=YouTube_Video&videoFormat=mp4&d=1
Sending POST request to Roku
Obtained response from Roku. Standing code: 200
Efficiently solid to Roku
[DEBUG] Did open connection on socket 13
[DEBUG] Connection acquired 138 bytes on socket 13
[DEBUG] Connection on socket 13 processing request “GET /video/http://192.168.0.XXX:8080/1.mp4” with 138 bytes physique
Proxying video stream: http://192.168.0.XXX:8080/1.mp4
[DEBUG] Connection despatched 845651 bytes on socket 13
[DEBUG] Did shut connection on socket 13
[DEBUG] Connection despatched 845651 bytes on socket 14
[DEBUG] Did shut connection on socket 14
[DEBUG] Connection on socket 13 processing request “GET /roku/state-change/video/play” with 167 bytes physique
Proxying video stream: play
[DEBUG] Connection on socket 14 processing request “GET /roku/occasion/video/began” with 163 bytes physique
Proxying video stream: began
Activity .<4> completed with error [-1002] Error Area=NSURLErrorDomain Code=-1002 “unsupported URL” UserInfo={NSLocalizedDescription=unsupported URL, NSErrorFailingURLStringKey=play, NSErrorFailingURLKey=play}
Didn’t fetch video stream: unsupported URL
[DEBUG] Connection on socket 13 processing request “GET /roku/occasion/video/completed” with 164 bytes physique
Proxying video stream: completed

Drawback:

•   The Video of size 5-10 minute performs effective, whereas better than that get caught on loading. even when its a neighborhood video. 
•   Logs point out that the video information is being despatched, however the connection drops abruptly.
•   Moreover, I’m receiving errors akin to NSURLErrorDomain Code=-1002 "unsupported URL" for sure URLs like "/roku/state-change/video/play".

Questions:

1.  Is there one thing flawed with the way in which I’m dealing with video streaming for longer movies?
2.  Are there particular configurations for GCDWebServer or URLSession that would stop this concern?
3.  What could possibly be inflicting the unsupported URL errors for sure non-video URLs?

Leave a Reply

Your email address will not be published. Required fields are marked *