Wie man eine mobile Streaming-App für iOS erstellt

Livestreaming von Mobilgeräten aus ermöglicht es Ihnen, mit Ihrem Publikum zu connecten, unabhängig davon, wo Sie sich gerade befinden. Entsprechende Dienste sind sehr beliebt und werden in den verschiedensten Anwendungsbereichen genutzt.

In unserem vorherigen Artikel „Wie man eine mobile Streaming-App für Android erstellt“ haben wir erläutert, wie man das Streamen auf Android-Geräten einrichtet. In diesem Artikel werden wir näher darauf eingehen, wie Sie Ihre eigene Anwendung für mobiles Streaming und das Betrachten von Livestreams auf iOS erstellen und wie Sie diese in Gcore Streaming-Plattform integrieren können.

Wie man eine mobile Streaming-App für iOS erstellt
Mobiles Streamen mit Gcore Streaming-Plattform

Streaming-Protokolle

Streaming-Plattformen unterstützt. RTMP ist zuverlässig und eignet sich aufgrund der niedrigen Latenzzeit und der wiederholten Übertragung von Datenpaketen auf der Grundlage des TCP Protokolls perfekt für Livestreaming.

Um Content auf die Geräte von Nutzern zu bringen und ihn dort abzuspielen, bieten Streaming-Plattformen die beliebten und skalierbaren Broadcast-Formate HLS und DASH an. Darüber hinaus sind iOS-Geräte von Haus aus mit einem AVPlayer ausgestattet, der die HLS Wiedergabe unterstützt. Aus diesem Grund werden wir uns schwerpunktmäßig mit diesem Protokoll befassen.

Auswählen einer Bibliothek zum Erstellen eines RTMP Streams

Es gibt nur wenige Open-Source-Lösungen für RTMP Streaming über iOS-Geräte, und noch weniger wirklich funktionelle Optionen. Werfen wir einen Blick auf die vorrangigen Lösungen:

  • LFLiveKit ist veraltet — das letzte Update war am 21. Dezember 2016.
  • HaishinKit wird derzeit unterstützt und ist kostenfrei.
  • Larix SDK funktioniert, ist aber kostenpflichtig (300 $ für die Bereitstellung der Quelldateien).
  • KSYLive ist veraltet — das letzte Update war am 22. März 2020, und die Beschreibung ist auf Chinesisch.

Vor diesem Hintergrund ist HaishinKit die am besten geeignete Bibliothek. Sie ist auf dem neuesten Stand, funktionell und zugleich eine kostenfreie Lösung mit guter Dokumentation.

Zudem hat HaishinKit zahlreiche Vorteile:

  • Wird regelmäßig aktualisiert
  • Unterstützt RTMP Wiedergabe
  • Einfache Installation in der Anwendung
  • Blendet die interne Verarbeitung mit AVCaptureSession aus — was besonders praktisch ist, wenn die Anwendung keine zusätzliche Verarbeitung mit der Sitzung benötigt
  • Unterstützt das Wechseln und Ausschalten der Kamera sowie das Deaktivieren des Mikrofons während des Streamens
  • Kann die Auflösung und die Bitrate ändern sowie Video und Audio während des Streamens aktivieren/deaktivieren
  • Bietet die Möglichkeit, die Broadcast-Parameter flexibel zu konfigurieren
  • Verfügt über eine Option, den Stream anzuhalten

Es gibt jedoch auch eine Reihe von Nachteilen:

  • Keine Option für eine adaptive Bitrate
  • Blendet die interne Verarbeitung von AVCaptureSession aus, was zu Problemen führen kann, wenn Aktionen mit dem eingehenden Audio- und Videomaterial erforderlich sind

Streaming-Implementierung über das RTMP Protokoll von einem iOS-Gerät

Die Bibliothek bietet für das Streamen zwei Arten von Objekten: RTMPStream und RTMPConnection.

Schauen wir uns Schritt für Schritt an, wie man mobiles Streaming einrichtet.

1. Init

Um die Bibliothek HaishinKit in Ihrem Projekt zu verwenden, müssen Sie sie über SPM hinzufügen, indem Sie Folgendes eingeben:

https://github.com/shogo4405/HaishinKit.swift

Aktuelle Version der Bibliothek

2. Berechtigungen

Geben Sie die erforderlichen Berechtigungen in der Info.plist des Projekts an:

  • NSMicrophoneUsageDescription (Privatsphäre — Beschreibung der Mikrofonnutzung)
  • NSCameraUsageDescription (Privatsphäre — Beschreibung der Kameranutzung)

3. Anzeigen des Kamera-Streams

Wenn Sie mit einer Smartphone-Kamera streamen, müssen Sie sehen, was der Stream zeigt. Zu diesem Zweck wird in einer entsprechenden Ansicht der Kamerastream auf dem Bildschirm angezeigt. In iOS wird für diese Zwecke ein Objekt der Klasse MTHKView verwendet, an das das Objekt RTMPStream angehängt ist.

let hkView = MTHKView(frame: .zero)
hkView.videoGravity = AVLayerVideoGravity.resizeAspectFill
hkView.attachStream(rtmpStream)
NSLayoutConstraint.activate([
 hkView.topAnchor.constraint(equalTo: topAnchor),
 hkView.leftAnchor.constraint(equalTo: leftAnchor),
 hkView.rightAnchor.constraint(equalTo: rightAnchor),
 hkView.bottomAnchor.constraint(equalTo: bottomAnchor)
])

4. Vorbereitungen für das Streamen

Zuallererst müssen Sie AVAudioSession konfigurieren und aktivieren. Sie können dies in der Anwendungsklasse AppDelegate tun:

private func setupSession() {
 do {
 try AVAudioSession.sharedInstance().setCategory(.playAndRecord,
 mode: .default,
 options: [.defaultToSpeaker, .allowBluetooth])
 try AVAudioSession.sharedInstance().setActive(true)
 } catch {
 print(error)
 }
 }
}

Erstellen Sie die Objekte RTMPConnection und RTMPStream:

let rtmpConnection = RTMPConnection()
let rtmpStream = RTMPStream(connection: rtmpConnection)
rtmpStream.attachAudio(AVCaptureDevice.default(for: AVMediaType.audio)) { error in
 // print(error)
}
rtmpStream.attachCamera(DeviceUtil.device(withPosition: .back)) { error in
 // print(error)
}

Legen Sie die Parameter von rtmpStream fest:

rtmpStream.captureSettings = [
 .fps: 30, // FPS
 .sessionPreset: AVCaptureSession.Preset.medium, // input video width/height
 // .isVideoMirrored: false,
 // .continuousAutofocus: false, // use camera autofocus mode
 // .continuousExposure: false, // use camera exposure mode
 // .preferredVideoStabilizationMode: AVCaptureVideoStabilizationMode.auto
]
rtmpStream.audioSettings = [
 .muted: false, // mute audio
 .bitrate: 32 * 1000,
]
rtmpStream.videoSettings = [
 .width: 640, // video output width
 .height: 360, // video output height
 .bitrate: 160 * 1000, // video output bitrate
 .profileLevel: kVTProfileLevel_H264_Baseline_3_1, // H264 Profile require "import VideoToolbox"
 .maxKeyFrameIntervalDuration: 2, // key frame / sec
]
// "0" means the same of input
rtmpStream.recorderSettings = [
 AVMediaType.audio: [
 AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
 AVSampleRateKey: 0,
 AVNumberOfChannelsKey: 0,
 // AVEncoderBitRateKey: 128000,
 ],
 AVMediaType.video: [
 AVVideoCodecKey: AVVideoCodecH264,
 AVVideoHeightKey: 0,
 AVVideoWidthKey: 0,
 /*
 AVVideoCompressionPropertiesKey: [
 AVVideoMaxKeyFrameIntervalDurationKey: 2,
 AVVideoProfileLevelKey: AVVideoProfileLevelH264Baseline30,
 AVVideoAverageBitRateKey: 512000
 ]
 */
 ],
]

Einstellungen von HaishinKit

5. Adaptive Videobitrate und -auflösung

Zur Implementierung der adaptiven Videobitrate und -auflösung werden zwei RTMPStreamDelegate-Methoden verwendet:

  1. func rtmpStream (_stream: RTMPStream, didPublishSufficientBW connection: RTMPConnection)—>— die Methode wird einmal pro Sekunde aufgerufen, wenn ausreichend Bandbreite zur Verfügung steht (wird zur Erhöhung der Bitrate und Auflösung verwendet)
  2. func rtmpStream (_stream: RTMPStream, didPublishInsufficientBW connection: RTMPConnection)—die Methode wird aufgerufen, wenn nicht ausreichend Bandbreite zur Verfügung steht (wird zur Verringerung der Bitrate und Auflösung verwendet)

Beispiele für die Implementierung:

func rtmpStream(_ stream: RTMPStream, didPublishSufficientBW connection: RTMPConnection) {
 guard isLive, appState != .background
 else { return }
 sufficientBWCount += 1
 let currentBitrate = stream.videoSettings[.bitrate] as! UInt32
 let setting = VideoSettings.getVideoResolution(type: videoType)
 if currentBitrate < setting.bitrate && isLive && sufficientBWCount >= 3 {
 sufficientBWCount = 0
 let updatedBitrate = currentBitrate + 100_000
 stream.videoSettings[.bitrate] = updatedBitrate
 increasingResolution(bitrate: Int(updatedBitrate))
 print("Increasing the bitrate to \(stream.videoSettings[.bitrate] ?? 0)")
 }
 if sufficientBWCount >= 60 {
 sufficientBWCount = 0
 }
 }
 func rtmpStream(_ stream: RTMPStream, didPublishInsufficientBW connection: RTMPConnection) {
 guard isLive, appState != .background
 else { return }
 sufficientBWCount = 0
 insufficientBWCount += 1
 let currentBitrate = stream.videoSettings[.bitrate] as! UInt32
 if insufficientBWCount >= 3 {
 insufficientBWCount = 0
 let updatedBitrate = Double(currentBitrate) * 0.7
 stream.videoSettings[.bitrate] = updatedBitrate
 reducingResolution(bitrate: Int(updatedBitrate))
 print("Bitrate reduction to \(stream.videoSettings[.bitrate] ?? 0)")
 }
 }
 private func reducingResolution(bitrate: Int) {
 guard let lowerType = VideoSettings.VideoType(rawValue: currentVideoType.rawValue - 1)
 else { return }
 let lowerSettings = VideoSettings.getVideoResolution(type: lowerType)
 let currentSettings = VideoSettings.getVideoResolution(type: currentVideoType)
 if bitrate < currentSettings.bitrate - ((currentSettings.bitrate - lowerSettings.bitrate) / 2) {
 setupVideoSettings(type: lowerType)
 currentVideoType = lowerType
 print("Reducing the resolutin to \(lowerSettings.width) x \(lowerSettings.height)")
 }
 }
 private func increasingResolution(bitrate: Int) {
 guard let upperType = VideoSettings.VideoType(rawValue: currentVideoType.rawValue + 1),
 upperType.rawValue <= videoType.rawValue
 else { return }
 let upperSettings = VideoSettings.getVideoResolution(type: upperType)
 let currentSettings = VideoSettings.getVideoResolution(type: currentVideoType)
 if bitrate > currentSettings.bitrate - ((currentSettings.bitrate - upperSettings.bitrate) / 2) {
 setupVideoSettings(type: upperType)
 currentVideoType = upperType
 print("Increasing the resolutin to \(upperSettings.width) x \(upperSettings.height)")
 }
 }

6. Hintergrund-Streaming

Apple lässt keine Videoaufzeichnung im Hintergrund zu, was bedeutet, dass die Bibliothek keine Videofragmente an den Server senden kann. Es führt auf Serverseite zu Abstürzen und Unterbrechungen des Streams.

Daher haben wir beschlossen, die Bibliothek um zusätzliche Funktionen zum Senden eines statischen Bildes im Hintergrund zu erweitern. Unsere Version der Bibliothek können Sie unserem Projekt auf GitHub entnehmen.

7. Starten und Beenden von Livestreams

Stellen Sie die Verbindung zum Server her und starten Sie den Stream:

rtmpConnection.connect(connectString)
rtmpStream.publish(publishString)

Um einen Stream anzuhalten, verwenden Sie die boolesche Eigenschaft „paused“ mit dem Wert „True“:

rtmpStream.paused = true

Integration mit Gcore Streaming-Plattform

Erstellen eines Kontos bei Gcore

Um die Streaming-Plattform in das Projekt zu integrieren, müssen Sie mit Ihrer E-Mail und Ihrem Kennwort ein kostenfreies Konto bei Gcore erstellen.

Aktivieren Sie den Dienst, indem Sie Free Live oder einen anderen geeigneten Tarif wählen.

Für die Interaktion mit Gcore Streaming-Plattform werden wir die Gcore-API verwenden. Anfragen werden von nativem Code unter Verwendung der Methoden der Struktur NetworkManager ausgeführt, die Daten an die Klasse HTTPCommunication überträgt. Das Parsen von Daten erfolgt über das Protokoll CodingKey unter Verwendung der Struktur DataParser. Sie können bei Bedarf auch eine andere HTTP Bibliothek verwenden.

Ein Beispiel für eine Methode zur Erstellung einer Anfrage:

private enum HTTPMethod: String {
 case GET, PATCH, POST, DELETE
 }
 private func createRequest(url: URL, token: String? = nil, json: [String:Any]? = nil, httpMethod: HTTPMethod) ->
URLRequest {
 var request = URLRequest(url: url)
 if let token = token {
 request.allHTTPHeaderFields = [ "Authorization" : "Bearer \(token)" ]
 }
 if let json = json {
 let jsonData = try? JSONSerialization.data(withJSONObject: json)
 request.setValue("application/json", forHTTPHeaderField: "Content-Type")
 request.httpBody = jsonData
 }
 request.httpMethod = httpMethod.rawValue
 return request
 }

Autorisierung

Loggen Sie sich ein, um mit der API zu arbeiten. Verwenden Sie die E-Mail und das Kennwort, die Sie bei der Registrierung eingegeben haben, um den Access Token zu erhalten, den Sie für weitere Anfragen benötigen.

 func authorizationRequest(login: String, password: String, completionHandler: @escaping ((Data?, HTTPError?) ->
Void)) {
 guard let url = URL(string: "https://api.gcdn.co/auth/jwt/login")
 else { return }
 self.completionHandler = completionHandler
 //json for http body request
 let json: [String: Any] = ["username": login, "password": password]
 //setup request
 let request = createRequest(url: url, json: json, httpMethod: .POST)
 let task = session.downloadTask(with: request)
 task.resume()
 }

Die PUSH URL abrufen

Es gibt zwei Möglichkeiten, die URL zum Senden des RTMP Streams abzurufen:

Methode 1. Senden Sie die Anfrage Get all live streams, um alle Livestreams abzurufen. Als Antwort erhalten Sie Daten über alle in Ihrem Konto erstellten Streams.

Ein Beispiel für das Senden einer Anfrage:

func allStreamsRequest(token: String, completionHandler: @escaping ((Data?, HTTPError?) -> Void)) {
 var components = URLComponents(string: "https://api.gcdn.co/vp/api/streams")
 //"with_broadcasts = 0" - because we get broadcasts in another method
 components?.queryItems = [ URLQueryItem(name: "with_broadcasts", value: "0") ]
 guard let url = components?.url
 else { return }
 self.completionHandler = completionHandler
 let request = createRequest(url: url, token: token, httpMethod: .GET)
 let task = session.downloadTask(with: request)
 task.resume()
 }
}

Methode 2. Senden Sie die Anfrage Get live stream, um einen bestimmten Livestream abzurufen. Als Antwort erhalten Sie nur Daten über den angegebenen Stream, sofern dieser existiert.

Ein Beispiel für das Senden einer Anfrage:

 func createStreamRequest(name: String, token: String, completionHandler: @escaping ((Data?, HTTPError?) -> Void)) {
 guard let url = URL(string: "https://api.gcdn.co/vp/api/streams")
 else { return }
 self.completionHandler = completionHandler
 //json for http body request
 let json: [String: Any] = [ "name" : name ]
 //setup request
 let request = createRequest(url: url, token: token, json: json, httpMethod: .POST)
 let task = session.downloadTask(with: request)
 task.resume()
 }

>Die Antworten auf diese Anfragen enthalten eine push_url, die als URL für das Senden des RTMP Streams verwendet wird. Sobald der Stream beginnt, wird die Übertragung automatisch gestartet. Wählen Sie den gewünschten Stream in Ihrem persönlichen Konto aus. Sie können die Vorschau verwenden, bevor Sie den Stream auf Ihrer Website oder in Ihrem Player bereitstellen.

Wiedergabe eines aktiven Streams

Mit Gcore Streaming-Plattform können Sie Streams auf Ressourcen von Drittanbietern in verschiedenen Formaten, einschließlich HLS, übertragen.

In unserem Beispiel berücksichtigen wir kein simultanes Streamen und Wiedergeben auf einem Gerät. Stattdessen sollte das Streamen von einem anderen Gerät aus gestartet werden.

Um den aktiven Stream wiederzugeben, verwenden Sie den standardmäßigen AVPlayer.

Wiedergabe starten

Vor der Wiedergabe sollte die hls_playlist_url des aktiven Streams in den Player eingebettet werden, während dieser initialisiert wird. Die hls_playlist_url wird in der Antwort auf die oben erwähnte Anfrage „Get livestream“ zurückgegeben.

 func downloadHLS(for streamsID: [Int]) {
 guard let token = token
 else { return }
 for id in streamsID {
 let http = HTTPCommunication()
 http.getStreamRequest(token: token, streamID: id) { data, error in
 if let data = data, let url = URL(string: dataParser.parseStreamHLS(dаta: data)) {
 delegate?.streamHLSDidDownload(url, streamID: id)
 }
 }
 }
 }

Initialisierung des Players:

 let broadcast = model.broadcasts[indexPath.row]
 guard let streamID = broadcast.streamIDs.first,
 let hls = model.getStreamHLS(streamID: streamID)
 else { return }
 let playerItem = AVPlayerItem(asset: AVURLAsset(url: hls))
 playerItem.preferredPeakBitRate = 800
 playerItem.preferredForwardBufferDuration = 1
 let player = AVPlayer(playerItem: playerItem)
 let playerVC = GCPlayerViewController(player: player)
 playerVC.modalPresentationStyle = .fullScreen
 present(playerVC, animated: true, completion: nil)

Zusammenfassung

Anhand unserer Beispiele ist das Einrichten eines Livestreams in einer iOS-Anwendung denkbar einfach und nimmt nicht viel Zeit in Anspruch. Alles, was Sie dazu benötigen, ist die Open-Source-Bibliothek HaishinKit und Gcore Streaming-Plattform.

Gcore Streaming-API

Jeglichen im Artikel erwähnten Code finden Sie auf GitHub.

Zurück zum Inhalt

Melden Sie sich an, um die neuesten Updates, Nachrichten und Funktionen zu erhalten.

Wir respektieren Ihren Posteingang und bemühen uns, Spam zu vermeiden