Rename Session -> StreamSesison. (#1879)

This commit is contained in:
shogo4405
2026-02-11 17:51:48 +09:00
committed by GitHub
parent 00c99d04b1
commit 30f31b466d
27 changed files with 89 additions and 175 deletions
+3 -3
View File
@@ -28,9 +28,9 @@ struct HaishinApp: App {
}
private func initialize() async {
await SessionBuilderFactory.shared.register(RTMPSessionFactory())
await SessionBuilderFactory.shared.register(SRTSessionFactory())
await SessionBuilderFactory.shared.register(HTTPSessionFactory())
await StreamSessionBuilderFactory.shared.register(RTMPSessionFactory())
await StreamSessionBuilderFactory.shared.register(SRTSessionFactory())
await StreamSessionBuilderFactory.shared.register(HTTPSessionFactory())
await RTCLogger.shared.setLevel(.debug)
await SRTLogger.shared.setLevel(.debug)
+3 -3
View File
@@ -6,7 +6,7 @@ import SwiftUI
@MainActor
final class PlaybackViewModel: ObservableObject {
@Published private(set) var readyState: SessionReadyState = .closed
@Published private(set) var readyState: StreamSessionReadyState = .closed
@Published private(set) var error: Error?
@Published var hasError = false
@@ -36,7 +36,7 @@ final class PlaybackViewModel: ObservableObject {
}
private var view: PiPHKView?
private var session: (any Session)?
private var session: (any StreamSession)?
private let audioPlayer = AudioPlayer(audioEngine: AVAudioEngine())
private var pictureInPictureController: AVPictureInPictureController?
@@ -66,7 +66,7 @@ final class PlaybackViewModel: ObservableObject {
func makeSession() async {
do {
session = try await SessionBuilderFactory.shared.make(Preference.default.makeURL())
session = try await StreamSessionBuilderFactory.shared.make(Preference.default.makeURL())
.setMode(.playback)
.build()
await session?.setMaxRetryCount(0)
+1 -1
View File
@@ -64,7 +64,7 @@ enum VideoEffectItem: String, CaseIterable, Identifiable, Sendable {
}
struct StreamButton: View {
let readyState: SessionReadyState
let readyState: StreamSessionReadyState
let onStart: () -> Void
let onStop: () -> Void
+3 -3
View File
@@ -30,7 +30,7 @@ final class PublishViewModel: ObservableObject {
@Published var showPreLiveDialog = false
@Published private(set) var isAudioMuted = false
@Published private(set) var isTorchEnabled = false
@Published private(set) var readyState: SessionReadyState = .closed
@Published private(set) var readyState: StreamSessionReadyState = .closed
@Published var audioSource: AudioSource = .empty {
didSet {
guard audioSource != oldValue else {
@@ -70,7 +70,7 @@ final class PublishViewModel: ObservableObject {
}
private(set) var mixer = MediaMixer()
private var tasks: [Task<Void, Swift.Error>] = []
private var session: (any Session)?
private var session: (any StreamSession)?
private var recorder: StreamRecorder?
private var currentPosition: AVCaptureDevice.Position = .back
private var audioSourceService = AudioSourceService()
@@ -235,7 +235,7 @@ final class PublishViewModel: ObservableObject {
func makeSession(_ preference: PreferenceViewModel) async {
do {
session = try await SessionBuilderFactory.shared.make(preference.makeURL())
session = try await StreamSessionBuilderFactory.shared.make(preference.makeURL())
.setMode(.publish)
.build()
guard let session else {
+5 -5
View File
@@ -11,15 +11,15 @@ nonisolated let logger = LBLogger.with("com.haishinkit.Screencast")
final class SampleHandler: RPBroadcastSampleHandler, @unchecked Sendable {
private var slider: UISlider?
private var session: Session?
private var session: StreamSession?
private var mixer = MediaMixer(captureSessionMode: .manual, multiTrackAudioMixingEnabled: true)
private var needVideoConfiguration = true
override init() {
Task {
await SessionBuilderFactory.shared.register(RTMPSessionFactory())
await SessionBuilderFactory.shared.register(SRTSessionFactory())
await SessionBuilderFactory.shared.register(HTTPSessionFactory())
await StreamSessionBuilderFactory.shared.register(RTMPSessionFactory())
await StreamSessionBuilderFactory.shared.register(SRTSessionFactory())
await StreamSessionBuilderFactory.shared.register(HTTPSessionFactory())
await SRTLogger.shared.setLevel(.debug)
await RTCLogger.shared.setLevel(.info)
@@ -34,7 +34,7 @@ final class SampleHandler: RPBroadcastSampleHandler, @unchecked Sendable {
// mixer.audioMixerSettings.tracks[1] = .default
Task {
do {
session = try await SessionBuilderFactory.shared.make(Preference.default.makeURL()).build()
session = try await StreamSessionBuilderFactory.shared.make(Preference.default.makeURL()).build()
// ReplayKit is sensitive to memory, so we limit the queue to a maximum of five items.
var videoSetting = await mixer.videoMixerSettings
videoSetting.mode = .passthrough
+3 -3
View File
@@ -15,7 +15,7 @@ final class UVCViewModel: ObservableObject {
}
}
@Published var isShowError = false
@Published private(set) var readyState: SessionReadyState = .closed
@Published private(set) var readyState: StreamSessionReadyState = .closed
@Published private(set) var isRecording = false
@Published var isHDREnabled = false {
didSet {
@@ -36,7 +36,7 @@ final class UVCViewModel: ObservableObject {
// let mixer = MediaMixer(captureSesionMode: .multi)
private(set) var mixer = MediaMixer(captureSessionMode: .single)
private var tasks: [Task<Void, Swift.Error>] = []
private var session: (any Session)?
private var session: (any StreamSession)?
private var recorder: StreamRecorder?
init() {
@@ -136,7 +136,7 @@ final class UVCViewModel: ObservableObject {
func makeSession(_ preference: PreferenceViewModel) async {
// Make session.
do {
session = try await SessionBuilderFactory.shared.make(preference.makeURL())
session = try await StreamSessionBuilderFactory.shared.make(preference.makeURL())
.setMode(.publish)
.build()
guard let session else {
+3 -3
View File
@@ -19,9 +19,9 @@ struct HaishinApp: App {
init() {
Task {
await SessionBuilderFactory.shared.register(RTMPSessionFactory())
await SessionBuilderFactory.shared.register(SRTSessionFactory())
await SessionBuilderFactory.shared.register(HTTPSessionFactory())
await StreamSessionBuilderFactory.shared.register(RTMPSessionFactory())
await StreamSessionBuilderFactory.shared.register(SRTSessionFactory())
await StreamSessionBuilderFactory.shared.register(HTTPSessionFactory())
await RTCLogger.shared.setLevel(.debug)
await SRTLogger.shared.setLevel(.debug)
+3 -3
View File
@@ -4,12 +4,12 @@ import SwiftUI
@MainActor
final class PlaybackViewModel: ObservableObject {
@Published private(set) var readyState: SessionReadyState = .closed
@Published private(set) var readyState: StreamSessionReadyState = .closed
@Published private(set) var error: Error?
@Published var isShowError = false
private var view: PiPHKView?
private var session: (any Session)?
private var session: (any StreamSession)?
private let audioPlayer = AudioPlayer(audioEngine: AVAudioEngine())
private var pictureInPictureController: AVPictureInPictureController?
@@ -43,7 +43,7 @@ final class PlaybackViewModel: ObservableObject {
private func makeSession(_ preference: PreferenceViewModel) async {
do {
session = try await SessionBuilderFactory.shared.make(preference.makeURL())
session = try await StreamSessionBuilderFactory.shared.make(preference.makeURL())
.setMode(.playback)
.build()
guard let session else {
+3 -3
View File
@@ -10,10 +10,10 @@ final class PublishViewModel: ObservableObject {
@Published private(set) var error: Error?
@Published var isShowError = false
@Published private(set) var isTorchEnabled = false
@Published private(set) var readyState: SessionReadyState = .closed
@Published private(set) var readyState: StreamSessionReadyState = .closed
private(set) var mixer = MediaMixer(captureSessionMode: .multi)
private var tasks: [Task<Void, Swift.Error>] = []
private var session: (any Session)?
private var session: (any StreamSession)?
private var currentPosition: AVCaptureDevice.Position = .back
@ScreenActor private var currentVideoEffect: VideoEffect?
@@ -49,7 +49,7 @@ final class PublishViewModel: ObservableObject {
func makeSession(_ preference: PreferenceViewModel) async {
// Make session.
do {
session = try await SessionBuilderFactory.shared.make(preference.makeURL())
session = try await StreamSessionBuilderFactory.shared.make(preference.makeURL())
.setMode(.publish)
.build()
guard let session else {
+3 -3
View File
@@ -19,9 +19,9 @@ struct HaishinApp: App {
init() {
Task {
await SessionBuilderFactory.shared.register(RTMPSessionFactory())
await SessionBuilderFactory.shared.register(SRTSessionFactory())
await SessionBuilderFactory.shared.register(HTTPSessionFactory())
await StreamSessionBuilderFactory.shared.register(RTMPSessionFactory())
await StreamSessionBuilderFactory.shared.register(SRTSessionFactory())
await StreamSessionBuilderFactory.shared.register(HTTPSessionFactory())
await RTCLogger.shared.setLevel(.debug)
await SRTLogger.shared.setLevel(.debug)
+2 -2
View File
@@ -16,8 +16,8 @@ struct HaishinApp: App {
init() {
Task {
await SessionBuilderFactory.shared.register(RTMPSessionFactory())
await SessionBuilderFactory.shared.register(SRTSessionFactory())
await StreamSessionBuilderFactory.shared.register(RTMPSessionFactory())
await StreamSessionBuilderFactory.shared.register(SRTSessionFactory())
}
}
}
@@ -1,86 +0,0 @@
# ``HaishinKit``
メインモジュールです。
## 🔍 概要
ライブストリーミングに必要なカメラやマイクのミキシング機能の提供を行います。各モジュールに対して共通の処理を提供します。
### モジュール構成
|モジュール|説明|
|:-|:-|
|HaishinKit|本モジュールです。|
|RTMPHaishinKit|RTMPプロトコルスタックを提供します。|
|SRTHaishinKit|SRTプロトコルスタックを提供します。|
|RTCHaishinKit|WebRTCのWHEP/WHIPプロトコルスタックを提供します。現在α版です。|
|MoQTHaishinKit|MoQTプロトコルスタックを提供します。現在α版です。
## 🎨 機能
以下の機能を提供しています。
- ライブミキシング
- [映像のミキシング](doc://HaishinKit/videomixing)
- カメラ映像や静止画を一つの配信映像ソースとして扱います。
- 音声のミキシング
- 異なるマイク音声を合成して一つの配信音声ソースとして扱います。
- Session
- RTMP/SRT/WHEP/WHIPといったプロトコルを統一的なAPIで扱えます。
## 📖 利用方法
### ライブミキシング
```swift
let mixer = MediaMixer()
Task {
do {
// Attaches the microphone device.
try await mixer.attachAudio(AVCaptureDevice.default(for: .audio))
} catch {
print(error)
}
do {
// Attaches the camera device.
try await mixer.attachVideo(AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back))
} catch {
print(error)
}
// Associates the stream object with the MediaMixer.
await mixer.addOutput(stream)
await mixer.startRunning()
}
```
### Session api.
RTMPやSRTとのクライアントとしての実装を統一的なAPIで扱えます。リトライ処理などもAPI内部で行います。
#### 前準備
```swift
import HaishinKit
import RTMPHaishinKit
import SRTHaishinKit
Task {
await SessionBuilderFactory.shared.register(RTMPSessionFactory())
await SessionBuilderFactory.shared.register(SRTSessionFactory())
}
```
#### Sessionの作成
```swift
let session = try await SessionBuilderFactory.shared.make(URL(string: "rtmp://hostname/live/live"))
.setMode(.ingest)
.build()
```
```swift
let session = try await SessionBuilderFactory.shared.make(URL(string: "srt://hostname:448?stream=xxxxx"))
.setMode(.playback)
.build()
```
#### 接続
配信や視聴を行います。
```swift
try session.connect {
print("on disconnected")
}
```
+6 -6
View File
@@ -50,7 +50,7 @@ Task {
}
```
### Session API
### StreamSession API
Provides a unified API for implementing clients with RTMP and SRT. Retry handling is also performed internally by the API.
#### Preparation
@@ -60,22 +60,22 @@ import RTMPHaishinKit
import SRTHaishinKit
Task {
await SessionBuilderFactory.shared.register(RTMPSessionFactory())
await SessionBuilderFactory.shared.register(SRTSessionFactory())
await StreamSessionBuilderFactory.shared.register(RTMPSessionFactory())
await StreamSessionBuilderFactory.shared.register(SRTSessionFactory())
}
```
#### Make Session
#### Make StreamSession
**RTMP**
Please provide the RTMP connection URL combined with the streamName.
```swift
let session = try await SessionBuilderFactory.shared.make(URL(string: "rtmp://hostname/appName/stramName"))
let session = try await StreamSessionBuilderFactory.shared.make(URL(string: "rtmp://hostname/appName/stramName"))
.setMode(.publish)
.build()
```
**SRT**
```swift
let session = try await SessionBuilderFactory.shared.make(URL(string: "srt://hostname:448?stream=xxxxx"))
let session = try await StreamSessionBuilderFactory.shared.make(URL(string: "srt://hostname:448?stream=xxxxx"))
.setMode(.playback)
.build()
```
@@ -1,4 +0,0 @@
import Foundation
public protocol SessionConfiguration: Encodable, Sendable {
}
@@ -1,9 +1,9 @@
import Foundation
package let kSession_maxRetryCount: Int = 3
package let kStreamSession_maxRetryCount: Int = 3
/// Represents the type of session to establish.
public enum SessionMode: Sendable {
public enum StreamSessionMode: Sendable {
/// A publishing session, used to stream media from the local device to a server or peers.
case publish
/// A playback session, used to receive and play media streamed from a server or peers.
@@ -11,7 +11,7 @@ public enum SessionMode: Sendable {
}
/// Represents the current connection state of a session.
public enum SessionReadyState: Int, Sendable {
public enum StreamSessionReadyState: Int, Sendable {
/// The session is currently attempting to establish a connection.
case connecting
/// The session has been successfully established and is ready for communication.
@@ -27,15 +27,15 @@ public enum SessionReadyState: Int, Sendable {
/// It is designed so that various streaming services can be used through a common API.
/// While coding with the conventional Connection offered flexibility,
/// it also required a certain level of maturity in properly handling network communication.
public protocol Session: NetworkConnection {
public protocol StreamSession: NetworkConnection {
/// The current ready state.
var readyState: AsyncStream<SessionReadyState> { get }
var readyState: AsyncStream<StreamSessionReadyState> { get }
/// The stream instance.
var stream: any StreamConvertible { get async }
/// Creates a new session with uri.
init(uri: URL, mode: SessionMode, configuration: (any SessionConfiguration)?)
init(uri: URL, mode: StreamSessionMode, configuration: (any StreamSessionConfiguration)?)
/// Sets a max retry count.
func setMaxRetryCount(_ maxRetryCount: Int)
@@ -1,31 +1,31 @@
import Foundation
/// An actor that provides builder for Session object.
public actor SessionBuilder {
private let factory: SessionBuilderFactory
public actor StreamSessionBuilder {
private let factory: StreamSessionBuilderFactory
private let uri: URL
private var mode: SessionMode = .publish
private var configuration: (any SessionConfiguration)?
private var mode: StreamSessionMode = .publish
private var configuration: (any StreamSessionConfiguration)?
init(factory: SessionBuilderFactory, uri: URL) {
init(factory: StreamSessionBuilderFactory, uri: URL) {
self.factory = factory
self.uri = uri
}
/// Sets a method.
public func setMode(_ mode: SessionMode) -> Self {
public func setMode(_ mode: StreamSessionMode) -> Self {
self.mode = mode
return self
}
/// Sets a config.
public func setConfiguration(_ configuration: (any SessionConfiguration)?) -> Self {
public func setConfiguration(_ configuration: (any StreamSessionConfiguration)?) -> Self {
self.configuration = configuration
return self
}
/// Creates a Session instance with the specified fields.
public func build() async throws -> (any Session)? {
public func build() async throws -> (any StreamSession)? {
return try await factory.build(uri, method: mode, configuration: configuration)
}
}
@@ -8,12 +8,12 @@ import Foundation
/// import RTMPHaishinKit
/// import SRTHaishinKit
///
/// await SessionBuilderFactory.shared.register(RTMPSessionFactory())
/// await SessionBuilderFactory.shared.register(SRTSessionFactory())
/// await StreamSessionBuilderFactory.shared.register(RTMPSessionFactory())
/// await StreamSessionBuilderFactory.shared.register(SRTSessionFactory())
/// ```
public actor SessionBuilderFactory {
public actor StreamSessionBuilderFactory {
/// The shared instance.
public static let shared = SessionBuilderFactory()
public static let shared = StreamSessionBuilderFactory()
/// The error domain codes.
public enum Error: Swift.Error {
@@ -23,28 +23,28 @@ public actor SessionBuilderFactory {
case notFound
}
private var factories: [any SessionFactory] = []
private var factories: [any StreamSessionFactory] = []
private init() {
}
/// Makes a new session builder.
public func make(_ uri: URL?) throws -> SessionBuilder {
public func make(_ uri: URL?) throws -> StreamSessionBuilder {
guard let uri else {
throw Error.illegalArgument
}
return SessionBuilder(factory: self, uri: uri)
return StreamSessionBuilder(factory: self, uri: uri)
}
/// Registers a factory.
public func register(_ factory: some SessionFactory) {
public func register(_ factory: some StreamSessionFactory) {
guard !factories.contains(where: { $0.supportedProtocols == factory.supportedProtocols }) else {
return
}
factories.append(factory)
}
func build(_ uri: URL?, method: SessionMode, configuration: (any SessionConfiguration)?) throws -> (any Session) {
func build(_ uri: URL?, method: StreamSessionMode, configuration: (any StreamSessionConfiguration)?) throws -> (any StreamSession) {
guard let uri else {
throw Error.illegalArgument
}
@@ -0,0 +1,4 @@
import Foundation
public protocol StreamSessionConfiguration: Encodable, Sendable {
}
@@ -1,10 +1,10 @@
import Foundation
/// A type that represents a streaming session factory.
public protocol SessionFactory {
public protocol StreamSessionFactory {
/// The supported protocols.
var supportedProtocols: Set<String> { get }
/// Makes a new session by uri.
func make(_ uri: URL, mode: SessionMode, configuration: (any SessionConfiguration)?) -> any Session
func make(_ uri: URL, mode: StreamSessionMode, configuration: (any StreamSessionConfiguration)?) -> any StreamSession
}
+4 -4
View File
@@ -1,7 +1,7 @@
import Foundation
import HaishinKit
actor HTTPSession: Session {
actor HTTPSession: StreamSession {
var connected: Bool {
get async {
peerConnection?.connectionState == .connected
@@ -9,7 +9,7 @@ actor HTTPSession: Session {
}
@AsyncStreamed(.closed)
private(set) var readyState: AsyncStream<SessionReadyState>
private(set) var readyState: AsyncStream<StreamSessionReadyState>
var stream: any StreamConvertible {
_stream
@@ -19,11 +19,11 @@ actor HTTPSession: Session {
private var location: URL?
private var maxRetryCount: Int = 0
private var _stream = RTCStream()
private var mode: SessionMode
private var mode: StreamSessionMode
private var configuration: HTTPSessionConfiguration?
private var peerConnection: RTCPeerConnection?
init(uri: URL, mode: SessionMode, configuration: (any SessionConfiguration)?) {
init(uri: URL, mode: StreamSessionMode, configuration: (any StreamSessionConfiguration)?) {
logger.level = .debug
self.uri = uri
self.mode = mode
@@ -6,7 +6,7 @@ import HaishinKit
/// an `RTCConfiguration` and applied when creating the underlying
/// `RTCPeerConnection`.
///
public struct HTTPSessionConfiguration: SessionConfiguration, RTCConfigurationConvertible {
public struct HTTPSessionConfiguration: StreamSessionConfiguration, RTCConfigurationConvertible {
public var iceServers: [String] = []
public var bindAddress: String?
public var certificateType: RTCCertificateType?
@@ -1,13 +1,13 @@
import Foundation
import HaishinKit
public struct HTTPSessionFactory: SessionFactory {
public struct HTTPSessionFactory: StreamSessionFactory {
public let supportedProtocols: Set<String> = ["http", "https"]
public init() {
}
public func make(_ uri: URL, mode: SessionMode, configuration: (any SessionConfiguration)?) -> any Session {
public func make(_ uri: URL, mode: StreamSessionMode, configuration: (any StreamSessionConfiguration)?) -> any StreamSession {
return HTTPSession(uri: uri, mode: mode, configuration: configuration)
}
}
@@ -1,7 +1,7 @@
import Foundation
import HaishinKit
actor RTMPSession: Session {
actor RTMPSession: StreamSession {
var connected: Bool {
get async {
await connection.connected
@@ -9,16 +9,16 @@ actor RTMPSession: Session {
}
@AsyncStreamed(.closed)
private(set) var readyState: AsyncStream<SessionReadyState>
private(set) var readyState: AsyncStream<StreamSessionReadyState>
var stream: any StreamConvertible {
_stream
}
private let uri: RTMPURL
private let mode: SessionMode
private let mode: StreamSessionMode
private var retryCount: Int = 0
private var maxRetryCount = kSession_maxRetryCount
private var maxRetryCount = kStreamSession_maxRetryCount
private lazy var connection: RTMPConnection = {
switch mode {
case .publish:
@@ -41,7 +41,7 @@ actor RTMPSession: Session {
}
}
init(uri: URL, mode: SessionMode, configuration: (any SessionConfiguration)?) {
init(uri: URL, mode: StreamSessionMode, configuration: (any StreamSessionConfiguration)?) {
self.uri = RTMPURL(url: uri)
self.mode = mode
}
@@ -1,13 +1,13 @@
import Foundation
import HaishinKit
public struct RTMPSessionFactory: SessionFactory {
public struct RTMPSessionFactory: StreamSessionFactory {
public let supportedProtocols: Set<String> = ["rtmp", "rtmps"]
public init() {
}
public func make(_ uri: URL, mode: SessionMode, configuration: (any SessionConfiguration)?) -> any Session {
public func make(_ uri: URL, mode: StreamSessionMode, configuration: (any StreamSessionConfiguration)?) -> any StreamSession {
return RTMPSession(uri: uri, mode: mode, configuration: configuration)
}
}
+1 -1
View File
@@ -99,7 +99,7 @@ try await connection.connect("srt://host:port?key=value")
```swift
import SRTHaishinKit
await SessionBuilderFactory.shared.register(SRTSessionFactory())
await StreamSessionBuilderFactory.shared.register(SRTSessionFactory())
```
## 🔧 Test
+5 -5
View File
@@ -2,7 +2,7 @@
import Foundation
import HaishinKit
actor SRTSession: Session {
actor SRTSession: StreamSession {
var connected: Bool {
get async {
await connection.connected
@@ -10,16 +10,16 @@ actor SRTSession: Session {
}
@AsyncStreamed(.closed)
private(set) var readyState: AsyncStream<SessionReadyState>
private(set) var readyState: AsyncStream<StreamSessionReadyState>
var stream: any StreamConvertible {
_stream
}
private let uri: URL
private let mode: SessionMode
private let mode: StreamSessionMode
private var retryCount: Int = 0
private var maxRetryCount = kSession_maxRetryCount
private var maxRetryCount = kStreamSession_maxRetryCount
private lazy var connection = SRTConnection()
private lazy var _stream: SRTStream = {
SRTStream(connection: connection)
@@ -31,7 +31,7 @@ actor SRTSession: Session {
}
}
init(uri: URL, mode: SessionMode, configuration: (any SessionConfiguration)?) {
init(uri: URL, mode: StreamSessionMode, configuration: (any StreamSessionConfiguration)?) {
self.uri = uri
self.mode = mode
}
@@ -1,13 +1,13 @@
import Foundation
import HaishinKit
public struct SRTSessionFactory: SessionFactory {
public struct SRTSessionFactory: StreamSessionFactory {
public let supportedProtocols: Set<String> = ["srt"]
public init() {
}
public func make(_ uri: URL, mode: SessionMode, configuration: (any SessionConfiguration)?) -> any Session {
public func make(_ uri: URL, mode: StreamSessionMode, configuration: (any StreamSessionConfiguration)?) -> any StreamSession {
return SRTSession(uri: uri, mode: mode, configuration: configuration)
}
}