ios – Reconnect via RTMP using Haishinkit to the server creates loop


I’m trying to create a transcriber using this api https://docs.rev.ai/api/streaming/requests/#rtmp-streams , for audio streaming I use Haishinkit. When you connect for the first time, the stream works correctly and the API returns text. I have a button where the user can select a language; when selecting a language, the websocket is disconnected and the stream ends, then I try to reconnect: send a request to the api to get links for the stream and the name of the stream, but when I restart the stream I get these messages in the console:

2024-16-01 13:36:32.951 [Info] [com.haishinkit.HaishinKit] [RTMPNWSocket.swift:168] stateDidChange(to:) > Connection is ready.
2024-16-01 13:36:32.953 [Info] [com.haishinkit.HaishinKit] [RTMPNWSocket.swift:159] viabilityDidChange(to:) > Connection viability changed to true
NetConnection.Connect.Success
2024-16-01 13:36:34.158 [Info] [com.haishinkit.HaishinKit] [IOAudioResampler.swift:227] setUp(_:) > inputFormat:<AVAudioFormat 0x2804f00a0: 1 ch, 48000 Hz, Int16>,outputFormat:<AVAudioFormat 0x2804f25d0: 1 ch, 48000 Hz, Int16>
2024-16-01 13:36:34.158 [Info] [com.haishinkit.HaishinKit] [AudioCodec.swift:149] makeAudioConverter() > inputFormat:<AVAudioFormat 0x2804f25d0: 1 ch, 48000 Hz, Int16>,outputFormat:<AVAudioFormat 0x2804f06e0: 1 ch, 48000 Hz, aac (0x00000002) 0 bits/channel, 0 bytes/packet, 1024 frames/packet, 0 bytes/frame>
2024-16-01 13:36:34.440 [Error] [com.haishinkit.HaishinKit] [RTMPMessage.swift:309] payload > AMF0Serializer{data: 14 bytes,position: 14,reference: HaishinKit.AMFReference}
NetStream.Publish.Start
NetConnection.Call.Failed
2024-01-16 13:36:37.219701+0300 AmazingTranscriber[1972:138995] [tcp] tcp_input [C5.1.1:2] flags=[R] seq=2497584374, ack=4294967196, win=0 state=CLOSE_WAIT rcv_nxt=2497584375, snd_una=1639471199
2024-01-16 13:36:37.251997+0300 AmazingTranscriber[1972:138995] [] nw_flow_prepare_output_frames Failing the write requests [57: Socket is not connected]
2024-01-16 13:36:37.252031+0300 AmazingTranscriber[1972:138995] [connection] nw_write_request_report [C5] Send failed with error “Socket is not connected”
2024-16-01 13:36:37.252 [Warn] [com.haishinkit.HaishinKit] [RTMPConnection.swift:403] on(status:) >
2024-01-16 13:36:37.252199+0300 AmazingTranscriber[1972:138995] [tcp] tcp_output [C5.1.1:2] flags=[R.] seq=1639475405, ack=2497584375, win=2048 state=CLOSED rcv_nxt=2497584375, snd_una=1639471199
NetConnection.Connect.Closed
2024-16-01 13:36:39.151 [Info] [com.haishinkit.HaishinKit] [RTMPNWSocket.swift:168] stateDidChange(to:) > Connection is ready.
2024-16-01 13:36:39.152 [Info] [com.haishinkit.HaishinKit] [RTMPNWSocket.swift:159] viabilityDidChange(to:) > Connection viability changed to true
NetConnection.Connect.Success
2024-16-01 13:36:40.891 [Error] [com.haishinkit.HaishinKit] [RTMPMessage.swift:309] payload > AMF0Serializer{data: 14 bytes,position: 14,reference: HaishinKit.AMFReference}
NetStream.Publish.Start
NetConnection.Call.Failed
2024-01-16 13:36:43.799807+0300 AmazingTranscriber[1972:138993] [tcp] tcp_input [C6.1.1:2] flags=[R] seq=3439027247, ack=4294967196, win=0 state=CLOSE_WAIT rcv_nxt=3439027248, snd_una=3732763433
2024-01-16 13:36:43.864706+0300 AmazingTranscriber[1972:138993] [] nw_flow_prepare_output_frames Failing the write requests [57: Socket is not connected]
2024-01-16 13:36:43.864738+0300 AmazingTranscriber[1972:138993] [connection] nw_write_request_report [C6] Send failed with error “Socket is not connected”
2024-16-01 13:36:43.865 [Warn] [com.haishinkit.HaishinKit] [RTMPConnection.swift:403] on(status:) >
2024-01-16 13:36:43.864895+0300 AmazingTranscriber[1972:138993] [tcp] tcp_output [C6.1.1:2] flags=[R.] seq=3732767695, ack=3439027248, win=2048 state=CLOSED rcv_nxt=3439027248, snd_una=3732763433
NetConnection.Connect.Closed

Please tell me where I’m wrong, what I’m doing wrong. Or could this be a server side error?

Code for rtmp class:

import AVFoundation
import HaishinKit

class RTMP: ObservableObject {
    
    private let audioSession: AVAudioSession
    
    private var connection: RTMPConnection?
    private var rtmpStream: RTMPStream!
    private var streamName = ""
    private var connectTo = ""
    private var retryCount: Int = 0
    let maxRetryCount: Int = 5
    
    init() {
        
        self.audioSession = AVAudioSession.sharedInstance()
        if audioSession.recordPermission != .granted {
            audioSession.requestRecordPermission { (isGranted) in
                if !isGranted {
                    fatalError("Access denied")
                }
            }
        }
        
        do {
            try audioSession.setCategory(.playAndRecord, mode: .default, options: [])
            try audioSession.setActive(true)
        } catch {
            fatalError(error.localizedDescription)
        }
    }
    
    func startStream(streamName: String?, connectTo: String?) {
        guard let stream = streamName,
              let connect = connectTo else {
            print("Invalid streamName, invalid connection")
            return
        }
        self.streamName = stream
        self.connectTo = connect
        
        connection = RTMPConnection()
        rtmpStream = RTMPStream(connection: connection!)
        connection?.addEventListener(.rtmpStatus, selector: #selector(rtmpStatusHandler), observer: self)
        connection?.addEventListener(.ioError, selector: #selector(rtmpErrorHandler), observer: self)
        
        rtmpStream.attachAudio(AVCaptureDevice.default(for: .audio)) { error in
            print("attachAudio \(error)")
        }
//        connection?.requireNetworkFramework = true
        connection?.connect(connect)
    }

    func stopPublish(completion: @escaping () -> ()) {
        rtmpStream.close()
        connection?.close()
        connection?.removeEventListener(.rtmpStatus, selector: #selector(rtmpStatusHandler), observer: self)
        connection?.removeEventListener(.ioError, selector: #selector(rtmpErrorHandler), observer: self)
        connection = nil
        completion()
    }
    
    func pausePublish() {
        rtmpStream.paused.toggle()
    }
    
    @objc
    private func rtmpStatusHandler(_ notification: Notification) {
        let e = Event.from(notification)
        guard let data: ASObject = e.data as? ASObject, let code: String = data["code"] as? String else {
            return
        }
        print(code)
        switch code {
        case RTMPConnection.Code.connectSuccess.rawValue:
            retryCount = 0
            rtmpStream.publish(streamName)
        case RTMPConnection.Code.connectFailed.rawValue, RTMPConnection.Code.connectClosed.rawValue:
            guard retryCount <= maxRetryCount else {
                return
            }
            Thread.sleep(forTimeInterval: pow(2.0, Double(retryCount)))
            connection?.connect(connectTo)
            retryCount += 1
            
        default:
            break
        }
    }
    
    @objc
    private func rtmpErrorHandler(_ notification: Notification) {
        let e = Event.from(notification)
        guard let data: ASObject = e.data as? ASObject, let code: String = data["level"] as? String, let description: String = data["description"] as? String else {
            return
        }
        print("rtmpErrorHandler: \(code)")
        print("rtmpErrorHandler description: \(description)")
        connection?.connect(connectTo)
    }
}

code for reconnection:

struct TestView: View {
    @EnvironmentObject private var configLanguage: LanguageConfig
    @EnvironmentObject var rtmp: RTMP
    @EnvironmentObject var websocket: Websocket
    @EnvironmentObject var network: Network
    
    @State private var switchLanguage: Bool = false
    
    var body: some View {
        ZStack {
            Button(action: {
                print("switch language")
                switchLanguage.toggle()
            }, label: {
                Text(configLanguage.language.rawValue)
            })
        }
        .sheet(isPresented: $switchLanguage) {
            if configLanguage.isChanged {
                websocket.disconnect()
                rtmp.stopPublish(completion: {
                    network.postRequest(language: configLanguage.language, completion: { model in
                        websocket.connect(with: model.read_url, completion: {
                            rtmp.startStream(streamName: model.stream_name, connectTo: model.ingestion_url)
                            
                        })
                    })
                })
            }
        } content: {
            SwitchLanguageView()
                .presentationDetents([.large])
                .presentationDragIndicator(.visible)
        }
    }
}

code for websocket connection:

func connect(with: String?, completion: @escaping () -> ()) {
        
        guard let string = with else {
            print("Invalid string to connect websocket")
            return
        }
        
        guard let url = URL(string: string) else { return }
        let request = URLRequest(url: url)
        webSocketTask = URLSession.shared.webSocketTask(with: request)
        webSocketTask?.resume()
        receiveMessage()
        completion()
    }

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img