diff --git a/Sources/LiveKit/Core/SignalClient.swift b/Sources/LiveKit/Core/SignalClient.swift index fe05ff28a..3716ef0b7 100644 --- a/Sources/LiveKit/Core/SignalClient.swift +++ b/Sources/LiveKit/Core/SignalClient.swift @@ -696,7 +696,7 @@ private extension SignalClient { self.pingTimeoutTimer = { let timer = DispatchQueueTimer(timeInterval: TimeInterval(jr.pingTimeout), queue: self.queue) - timer.handler = { [weak self] in + timer.setOnTimer { [weak self] in guard let self = self else { return } self.log("ping/pong timed out", .error) self.cleanUp(reason: .networkError(SignalClientError.serverPingTimedOut())) @@ -729,7 +729,7 @@ private extension SignalClient { pingIntervalTimer = { let timer = DispatchQueueTimer(timeInterval: TimeInterval(jr.pingInterval), queue: queue) - timer.handler = { [weak self] in self?.onPingIntervalTimer() } + timer.setOnTimer { [weak self] in self?.onPingIntervalTimer() } timer.resume() return timer }() diff --git a/Sources/LiveKit/Core/Transport.swift b/Sources/LiveKit/Core/Transport.swift index 0b903e33e..4b0191769 100644 --- a/Sources/LiveKit/Core/Transport.swift +++ b/Sources/LiveKit/Core/Transport.swift @@ -101,7 +101,7 @@ internal class Transport: MulticastDelegate { DispatchQueue.liveKitWebRTC.sync { pc.delegate = self } add(delegate: delegate) - statsTimer.handler = { [weak self] in + statsTimer.setOnTimer { [weak self] in self?.onStatsTimer() } diff --git a/Sources/LiveKit/Support/DispatchQueueTimer.swift b/Sources/LiveKit/Support/DispatchQueueTimer.swift index 4f06bc581..a0fd86165 100644 --- a/Sources/LiveKit/Support/DispatchQueueTimer.swift +++ b/Sources/LiveKit/Support/DispatchQueueTimer.swift @@ -18,71 +18,97 @@ import Foundation internal class DispatchQueueTimer: Loggable { + typealias OnTimer = (() -> Void) + public enum State { case suspended case resumed } - private let queue: DispatchQueue? - private let timeInterval: TimeInterval - private var timer: DispatchSourceTimer! - public var handler: (() -> Void)? - public private(set) var state: State = .suspended + private let _queue: DispatchQueue? + private let _timeInterval: TimeInterval + private var _timer: DispatchSourceTimer! + private var _state: State = .suspended + private var _handler: OnTimer? + + private let _lock = UnfairLock() public init(timeInterval: TimeInterval, queue: DispatchQueue? = nil) { - self.timeInterval = timeInterval - self.queue = queue - self.timer = createTimer() + _timeInterval = timeInterval + _queue = queue + _timer = _createTimer() } deinit { - cleanUpTimer() - handler = nil + _cleanUpTimer() + _handler = nil } - // reset the state - public func reset() { - cleanUpTimer() - timer = createTimer() - state = .suspended - } + // MARK: - Public - public func restart() { - reset() - resume() + public func setOnTimer(_ block: @escaping OnTimer) { + _lock.sync { + _handler = block + } } - // continue from where it was suspended public func resume() { - if state == .resumed { - return + _lock.sync { + _resume() } - state = .resumed - timer.resume() } public func suspend() { - if state == .suspended { - return + _lock.sync { + _suspend() + } + } + + public func restart() { + _lock.sync { + _restart() } - state = .suspended - timer.suspend() } - private func createTimer() -> DispatchSourceTimer { - let timer = DispatchSource.makeTimerSource(queue: queue) - timer.schedule(deadline: .now() + self.timeInterval, repeating: self.timeInterval) - timer.setEventHandler { [weak self] in self?.handler?() } + // MARK: - Private + + private func _reset() { + _cleanUpTimer() + _timer = _createTimer() + _state = .suspended + } + + private func _restart() { + _reset() + _resume() + } + + private func _resume() { + if _state == .resumed { return } + _state = .resumed + _timer.resume() + } + + private func _suspend() { + if _state == .suspended { return } + _state = .suspended + _timer.suspend() + } + + private func _createTimer() -> DispatchSourceTimer { + let timer = DispatchSource.makeTimerSource(queue: _queue) + timer.schedule(deadline: .now() + self._timeInterval, repeating: self._timeInterval) + timer.setEventHandler { [weak self] in self?._handler?() } return timer } - private func cleanUpTimer() { - timer.setEventHandler {} - timer.cancel() + private func _cleanUpTimer() { + _timer.setEventHandler {} + _timer.cancel() /* If the timer is suspended, calling cancel without resuming triggers a crash. This is documented here https://forums.developer.apple.com/thread/15902 */ - resume() + _resume() } } diff --git a/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift b/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift index b8c8225f4..e0e001ae0 100644 --- a/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift +++ b/Sources/LiveKit/Track/Capturers/MacOSScreenCapturer.swift @@ -112,7 +112,7 @@ public class MacOSScreenCapturer: VideoCapturer { let timeInterval: TimeInterval = 1 / Double(options.fps) dispatchSourceTimer = DispatchQueueTimer(timeInterval: timeInterval, queue: captureQueue) - dispatchSourceTimer?.handler = { [weak self] in self?.onDispatchSourceTimer() } + dispatchSourceTimer?.setOnTimer { [weak self] in self?.onDispatchSourceTimer() } dispatchSourceTimer?.resume() } @@ -447,7 +447,7 @@ extension MacOSScreenCapturer { frameResendTimer = { let timer = DispatchQueueTimer(timeInterval: timeInterval, queue: self.captureQueue) - timer.handler = { [weak self] in self?.onFrameResendTimer() } + timer.setOnTimer { [weak self] in self?.onFrameResendTimer() } timer.resume() return timer }() diff --git a/Sources/LiveKit/Track/Track.swift b/Sources/LiveKit/Track/Track.swift index b028441ff..e03fea6ae 100644 --- a/Sources/LiveKit/Track/Track.swift +++ b/Sources/LiveKit/Track/Track.swift @@ -184,7 +184,7 @@ public class Track: NSObject, Loggable { } } - statsTimer.handler = { [weak self] in + statsTimer.setOnTimer { [weak self] in self?.onStatsTimer() } } diff --git a/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift b/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift index 80630b6bc..cf599058b 100644 --- a/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift +++ b/Sources/LiveKit/TrackPublications/RemoteTrackPublication.swift @@ -47,7 +47,7 @@ public class RemoteTrackPublication: TrackPublication { track: track, participant: participant) - asTimer.handler = { [weak self] in self?.onAdaptiveStreamTimer() } + asTimer.setOnTimer { [weak self] in self?.onAdaptiveStreamTimer() } } deinit { diff --git a/Sources/LiveKit/Views/VideoView.swift b/Sources/LiveKit/Views/VideoView.swift index a2b5f61bd..966aa2203 100644 --- a/Sources/LiveKit/Views/VideoView.swift +++ b/Sources/LiveKit/Views/VideoView.swift @@ -339,7 +339,7 @@ public class VideoView: NativeView, Loggable { } } - _renderTimer.handler = { [weak self] in + _renderTimer.setOnTimer { [weak self] in guard let self = self else { return } @@ -351,7 +351,7 @@ public class VideoView: NativeView, Loggable { } } - _fpsTimer.handler = { [weak self] in + _fpsTimer.setOnTimer { [weak self] in guard let self = self else { return }