Skip to content

Commit

Permalink
Merge pull request #1525 from shogo4405/release/2.0.0
Browse files Browse the repository at this point in the history
Support Strict Concurrency and Swift 6.0 beta 1
  • Loading branch information
shogo4405 authored Aug 9, 2024
2 parents a1a2d9b + dc878a9 commit 992ee08
Show file tree
Hide file tree
Showing 106 changed files with 3,911 additions and 6,001 deletions.
12 changes: 6 additions & 6 deletions Examples/iOS/AudioCapture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ protocol AudioCaptureDelegate: AnyObject {
}

final class AudioCapture {
var isRunning: Atomic<Bool> = .init(false)
var isRunning = false
weak var delegate: (any AudioCaptureDelegate)?
private let audioEngine = AVAudioEngine()
}

extension AudioCapture: Running {
extension AudioCapture: Runner {
func startRunning() {
guard !isRunning.value else {
guard !isRunning else {
return
}
let input = audioEngine.inputNode
Expand All @@ -25,17 +25,17 @@ extension AudioCapture: Running {
}
do {
try audioEngine.start()
isRunning.mutate { $0 = true }
isRunning = true
} catch {
logger.error(error)
}
}

func stopRunning() {
guard isRunning.value else {
guard isRunning else {
return
}
audioEngine.stop()
isRunning.mutate { $0 = false }
isRunning = false
}
}
268 changes: 134 additions & 134 deletions Examples/iOS/IngestViewController.swift

Large diffs are not rendered by default.

189 changes: 56 additions & 133 deletions Examples/iOS/NetStreamSwitcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,179 +3,102 @@ import Foundation
import HaishinKit
import SRTHaishinKit

final class NetStreamSwitcher {
private static let maxRetryCount: Int = 5
actor NetStreamSwitcher {
static let maxRetryCount: Int = 5

enum Mode {
case rtmp
case srt

func makeStream(_ swithcer: NetStreamSwitcher) -> IOStream {
switch self {
case .rtmp:
let connection = RTMPConnection()
swithcer.connection = connection
return RTMPStream(connection: connection)
case .srt:
let connection = SRTConnection()
swithcer.connection = connection
return SRTStream(connection: connection)
}
}
}

enum Method {
case ingest
case playback
}

var uri = "" {
didSet {
if uri.contains("srt://") {
mode = .srt
return
}
mode = .rtmp
}
}
private(set) var mode: Mode = .rtmp {
didSet {
stream = mode.makeStream(self)
}
}
private var retryCount = 0
private var preference: Preference?
private(set) var mode: Mode = .rtmp
private var connection: Any?
private var method: Method = .ingest
private(set) var stream: IOStream = .init() {
didSet {
stream.delegate = self
private(set) var stream: (any IOStream)?

func setPreference(_ preference: Preference) async {
self.preference = preference
if preference.uri?.contains("srt://") == true {
let connection = SRTConnection()
self.connection = connection
stream = await SRTStream(connection: connection)
mode = .srt
} else {
let connection = RTMPConnection()
self.connection = connection
stream = RTMPStream(connection: connection)
mode = .rtmp
}
}

func open(_ method: Method) {
func open(_ method: Method) async {
guard let preference else {
return
}
self.method = method
switch mode {
case .rtmp:
guard let connection = connection as? RTMPConnection else {
guard
let connection = connection as? RTMPConnection,
let stream = stream as? RTMPStream else {
return
}
switch method {
case .ingest:
// Performing operations for FMLE compatibility purposes.
(stream as? RTMPStream)?.fcPublishName = Preference.default.streamName
case .playback:
break
do {
let response = try await connection.connect(preference.uri ?? "")
logger.info(response)
switch method {
case .ingest:
let response = try await stream.publish(Preference.default.streamName)
logger.info(response)
case .playback:
let response = try await stream.play(Preference.default.streamName)
logger.info(response)
}
} catch RTMPConnection.Error.requestFailed(let response) {
logger.warn(response)
} catch RTMPStream.Error.requestFailed(let response) {
logger.warn(response)
} catch {
logger.warn(error)
}
connection.addEventListener(.rtmpStatus, selector: #selector(rtmpStatusHandler), observer: self)
connection.addEventListener(.ioError, selector: #selector(rtmpErrorHandler), observer: self)
connection.connect(uri)
case .srt:
guard let connection = connection as? SRTConnection, let stream = stream as? SRTStream else {
return
}
Task {
do {
try await connection.open(URL(string: uri))
switch method {
case .playback:
stream.play()
case .ingest:
stream.publish()
}
} catch {
logger.warn(error)
do {
try await connection.open(URL(string: preference.uri ?? ""))
switch method {
case .playback:
await stream.play()
case .ingest:
await stream.publish()
}
} catch {
logger.warn(error)
}
}
}

func close() {
func close() async {
switch mode {
case .rtmp:
guard let connection = connection as? RTMPConnection else {
return
}
connection.close()
connection.removeEventListener(.rtmpStatus, selector: #selector(rtmpStatusHandler), observer: self)
connection.removeEventListener(.ioError, selector: #selector(rtmpErrorHandler), observer: self)
try? await connection.close()
logger.info("conneciton.close")
case .srt:
guard let connection = connection as? SRTConnection else {
return
}
Task {
await connection.close()
}
await connection.close()
logger.info("conneciton.close")
}
}

@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
}
logger.info(code)
switch code {
case RTMPConnection.Code.connectSuccess.rawValue:
retryCount = 0
switch method {
case .playback:
(stream as? RTMPStream)?.play(Preference.default.streamName!)
case .ingest:
(stream as? RTMPStream)?.publish(Preference.default.streamName!)
}
case RTMPConnection.Code.connectFailed.rawValue, RTMPConnection.Code.connectClosed.rawValue:
guard retryCount <= NetStreamSwitcher.maxRetryCount else {
return
}
Thread.sleep(forTimeInterval: pow(2.0, Double(retryCount)))
(connection as? RTMPConnection)?.connect(uri)
retryCount += 1
default:
break
}
}

@objc
private func rtmpErrorHandler(_ notification: Notification) {
logger.error(notification)
(connection as? RTMPConnection)?.connect(Preference.default.uri!)
}
}

extension NetStreamSwitcher: IOStreamDelegate {
// MARK: NetStreamDelegate
func stream(_ stream: IOStream, track: UInt8, didInput buffer: AVAudioBuffer, when: AVAudioTime) {
}

func stream(_ stream: IOStream, track: UInt8, didInput buffer: CMSampleBuffer) {
}

/// Tells the receiver to video codec error occured.
func stream(_ stream: IOStream, videoErrorOccurred error: IOVideoUnitError) {
}

/// Tells the receiver to audio codec error occured.
func stream(_ stream: IOStream, audioErrorOccurred error: IOAudioUnitError) {
}

/// Tells the receiver that the ready state will change.
func stream(_ stream: IOStream, willChangeReadyState state: IOStream.ReadyState) {
}

/// Tells the receiver that the ready state did change.
func stream(_ stream: IOStream, didChangeReadyState state: IOStream.ReadyState) {
}

#if os(iOS) || os(tvOS)
/// Tells the receiver to session was interrupted.
@available(tvOS 17.0, *)
func stream(_ stream: IOStream, sessionWasInterrupted session: AVCaptureSession, reason: AVCaptureSession.InterruptionReason?) {
}

/// Tells the receiver to session interrupted ended.
@available(tvOS 17.0, *)
func stream(_ stream: IOStream, sessionInterruptionEnded session: AVCaptureSession) {
}
#endif
}
55 changes: 34 additions & 21 deletions Examples/iOS/PlaybackViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@ import UIKit
final class PlaybackViewController: UIViewController {
@IBOutlet private weak var playbackButton: UIButton!
private let netStreamSwitcher: NetStreamSwitcher = .init()
private var stream: IOStream {
return netStreamSwitcher.stream
}
private var pictureInPictureController: AVPictureInPictureController?

override func viewWillAppear(_ animated: Bool) {
logger.info("viewWillAppear")
super.viewWillAppear(animated)
netStreamSwitcher.uri = Preference.default.uri ?? ""
(view as? (any IOStreamView))?.attachStream(stream)
if #available(iOS 15.0, *), let layer = view.layer as? AVSampleBufferDisplayLayer, pictureInPictureController == nil {
pictureInPictureController = AVPictureInPictureController(contentSource: .init(sampleBufferDisplayLayer: layer, playbackDelegate: self))
}
Task {
await netStreamSwitcher.setPreference(Preference.default)
if let stream = await netStreamSwitcher.stream {
if let view = view as? (any IOStreamObserver) {
await stream.addObserver(view)
}
}
}
}

override func viewWillDisappear(_ animated: Bool) {
Expand All @@ -32,52 +35,62 @@ final class PlaybackViewController: UIViewController {
}

@IBAction func didPlaybackButtonTap(_ button: UIButton) {
if button.isSelected {
UIApplication.shared.isIdleTimerDisabled = false
netStreamSwitcher.close()
button.setTitle("", for: [])
} else {
UIApplication.shared.isIdleTimerDisabled = true
netStreamSwitcher.open(.playback)
button.setTitle("", for: [])
Task {
if button.isSelected {
UIApplication.shared.isIdleTimerDisabled = false
await netStreamSwitcher.close()
button.setTitle("", for: [])
} else {
UIApplication.shared.isIdleTimerDisabled = true
await netStreamSwitcher.open(.playback)
button.setTitle("", for: [])
}
button.isSelected.toggle()
}
button.isSelected.toggle()
}

@objc
private func didBecomeActive(_ notification: Notification) {
logger.info(notification)
if pictureInPictureController?.isPictureInPictureActive == false {
(stream as? RTMPStream)?.receiveVideo = true
Task {
if let stream = await netStreamSwitcher.stream as? RTMPStream {
_ = try? await stream.receiveVideo(true)
}
}
}
}

@objc
private func didEnterBackground(_ notification: Notification) {
logger.info(notification)
if pictureInPictureController?.isPictureInPictureActive == false {
(stream as? RTMPStream)?.receiveVideo = false
Task {
if let stream = await netStreamSwitcher.stream as? RTMPStream {
_ = try? await stream.receiveVideo(false)
}
}
}
}
}

extension PlaybackViewController: AVPictureInPictureSampleBufferPlaybackDelegate {
// MARK: AVPictureInPictureControllerDelegate
func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, setPlaying playing: Bool) {
nonisolated func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, setPlaying playing: Bool) {
}

func pictureInPictureControllerTimeRangeForPlayback(_ pictureInPictureController: AVPictureInPictureController) -> CMTimeRange {
nonisolated func pictureInPictureControllerTimeRangeForPlayback(_ pictureInPictureController: AVPictureInPictureController) -> CMTimeRange {
return CMTimeRange(start: .zero, duration: .positiveInfinity)
}

func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
nonisolated func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
return false
}

func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, didTransitionToRenderSize newRenderSize: CMVideoDimensions) {
nonisolated func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, didTransitionToRenderSize newRenderSize: CMVideoDimensions) {
}

func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) {
nonisolated func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) {
completionHandler()
}
}
Loading

0 comments on commit 992ee08

Please sign in to comment.