14.1 C
New York
Tuesday, September 3, 2024

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


I’m growing an iOS app utilizing Swift that streams video content material to a Roku gadget. 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 information served through a Python server, however the challenge persists.

Setup:

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



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

personal var webServer: GCDWebServer?
personal let rokuIP = "192.168.0.XXX"  // Hidden IP
personal let rokuPort = "XXXX"  // Hidden Port
personal 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("Did not decode or create a legitimate 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) { knowledge, response, error in
            guard let knowledge = knowledge, error == nil else {
                self.logger.error("Did not fetch video stream: (error?.localizedDescription ?? "Unknown error")")
                return
            }

            let proxyResponse = GCDWebServerDataResponse(knowledge: knowledge, 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 start out server: (error.localizedDescription)"
        logger.error("Failed to start out server: (error.localizedDescription)")
    }
}

func castToRoku(with videoURL: String) {
    guard let serverIP = getWiFiAddress(), let serverPort = webServer?.port else {
        statusMessage = "Did not get server IP or port"
        logger.error("Did not 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.internet/splash-roku5_free.jpg"))&h=(customURLEncode("(serverAddress)/roku"))&videoName=YouTube_Video&videoFormat=mp4&d=1"
    
    guard let rokuURL = URL(string: rokuURLString) else {
        statusMessage = "Did not assemble Roku URL"
        logger.error("Did not assemble Roku URL")
        return
    }
    
    logger.data("Last 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] knowledge, response, error in
        DispatchQueue.foremost.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("Acquired response from Roku. Standing code: (httpResponse.statusCode)")
                if httpResponse.statusCode == 200 {
                    self?.statusMessage = "Efficiently forged to Roku"
                    self?.logger.data("Efficiently forged to Roku")
                } else {
                    self?.statusMessage = "Did not forged to Roku. Standing code: (httpResponse.statusCode)"
                    self?.logger.error("Did not forged to Roku. Standing code: (httpResponse.statusCode)")
                    if let knowledge = knowledge, let responseBody = String(knowledge: knowledge, encoding: .utf8) {
                        self?.logger.error("Response physique: (responseBody)")
                    }
                }
            } else {
                self?.statusMessage = "Did not forged to Roku: Sudden response"
                self?.logger.error("Did not forged to Roku: Sudden response")
            }
        }
    }.resume()
}

personal func getWiFiAddress() -> String? {
    ...
}

personal 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 deal with: 192.168.0.120
Last 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
Acquired response from Roku. Standing code: 200
Efficiently forged to Roku
[DEBUG] Did open connection on socket 13
[DEBUG] Connection obtained 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}
Did not 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

Downside:

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

Questions:

1.  Is there one thing improper with the best way I’m dealing with video streaming for longer movies?
2.  Are there particular configurations for GCDWebServer or URLSession that might stop this challenge?
3.  What may very well be inflicting the unsupported URL errors for sure non-video URLs?

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles