Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,24 @@
- デフォルト値の `unspecified` の場合はシグナリングパラメータに `simulcast_request_rid` を含めない
- role が sendrecv または recvonly の場合、かつ simulcast が true の場合にのみ有効
- @zztkm
- [ADD] サイマルキャストの rid を表す汎用型 `Rid` 列挙型を追加する
- @zztkm
- [ADD] RPC 機能を追加する
- RPC メソッドを表す列挙型 `RPCMethod` を追加する
- `SignalingOffer` に以下の項目を追加する
- `rpcMethods: [String]?`
- `simulcastRpcRids: [Rid]?` を追加する
- `MediaChannel``rpc` メソッドを追加する
- `MediaChannel` に以下の項目を追加する
- `rpcMethods: [RPCMethod]`
- `rpcSimulcastRids: [Rid]`
- RPC メソッドを定義するための `RPCMethodProtocol` プロトコルを追加する
- `RPCMethodProtocol` に準拠した型を追加する
- `RequestSimulcastRid`
- `RequestSpotlightRid`
- `ResetSpotlightRid`
- `PutSignalingNotifyMetadata`
- `PutSignalingNotifyMetadataItem`
Comment on lines +52 to +66
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここらの追加分は #291 で書けてなかった内容を追加したものです

- RPC の ID を表す `RPCID` 列挙型を追加する
- `int(Int)``string(String)` の 2 つのケースをサポート
- RPC エラー応答の詳細を表す `RPCErrorDetail` 構造体を追加する
Expand All @@ -71,6 +86,9 @@
- [UPDATE] jazzy の設定ファイルを更新する
- `module_version` を 2025.3.0 に変更
- @zztkm
- [ADD] `Package.swift``testTarget` を追加する
- xcodebuild で test を実行するために target を追加
- @zztkm

## 2025.2.0

Expand Down
15 changes: 14 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: fmt fmt-lint lint
.PHONY: build fmt fmt-lint lint

# すべてを実行
all: fmt fmt-lint lint
Expand All @@ -7,6 +7,19 @@ all: fmt fmt-lint lint
fmt:
swift format --in-place --recursive Sora SoraTests

# build
build:
xcodebuild \
-scheme 'Sora' \
-sdk iphoneos26.1 \
-configuration Release \
-derivedDataPath build \
-destination 'generic/platform=iOS' \
clean build \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGN_IDENTITY= \
PROVISIONING_PROFILE=
Comment on lines +10 to +21
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あったら使うかなと思い追加しました


# swift-format lint
fmt-lint:
swift format lint --strict --parallel --recursive Sora SoraTests
Expand Down
5 changes: 5 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,10 @@ let package = Package(
exclude: ["Info.plist"],
resources: [.process("VideoView.xib")]
),
.testTarget(
name: "SoraTests",
dependencies: ["Sora"],
path: "SoraTests"
),
]
)
2 changes: 1 addition & 1 deletion Sora/MediaChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ public final class MediaChannel {
/// Sora サーバーから通知された、RPC で操作可能なサイマルキャスト rid が取得できます。
///
/// - Returns: 利用可能なサイマルキャスト rid の一覧。RPC が初期化されていない場合は空配列を返します
public var rpcSimulcastRids: [SimulcastRequestRid] {
public var rpcSimulcastRids: [Rid] {
peerChannel.rpcChannel?.simulcastRpcRids ?? []
}

Expand Down
4 changes: 2 additions & 2 deletions Sora/PeerChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class PeerChannel: NSObject, RTCPeerConnectionDelegate {
var switchedToDataChannel: Bool = false
var signalingOfferMessageDataChannels: [[String: Any]] = []
var rpcAllowedMethods: [String] = []
var rpcSimulcastRids: [SimulcastRequestRid] = []
var rpcSimulcastRids: [Rid] = []
var rpcChannel: RPCChannel?

weak var mediaChannel: MediaChannel?
Expand Down Expand Up @@ -956,7 +956,7 @@ class PeerChannel: NSObject, RTCPeerConnectionDelegate {
}

if let simulcastRpcRids = offer.simulcastRpcRids {
rpcSimulcastRids = simulcastRpcRids.toSimulcastRequestRids()
rpcSimulcastRids = simulcastRpcRids
} else {
rpcSimulcastRids = []
}
Expand Down
4 changes: 2 additions & 2 deletions Sora/RPC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ public final class RPCChannel {
private let allowedMethodNames: Set<String>

/// Sora から払い出されたサイマルキャスト rid の一覧
let simulcastRpcRids: [SimulcastRequestRid]
let simulcastRpcRids: [Rid]

init?(
dataChannel: DataChannel, rpcMethods: [String], simulcastRpcRids: [SimulcastRequestRid]
dataChannel: DataChannel, rpcMethods: [String], simulcastRpcRids: [Rid]
) {
guard !rpcMethods.isEmpty else {
return nil
Expand Down
11 changes: 2 additions & 9 deletions Sora/Signaling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ public struct SignalingOffer {
public var rpcMethods: [String]?

/// RPC 経由で切り替えられるサイマルキャストの rid
public var simulcastRpcRids: [String]?
public var simulcastRpcRids: [Rid]?

/// audio
public let audio: Bool?
Expand Down Expand Up @@ -1167,7 +1167,7 @@ extension SignalingOffer: Codable {
videoCodecType = try container.decodeIfPresent(String.self, forKey: .video_codec_type)
videoBitRate = try container.decodeIfPresent(Int.self, forKey: .video_bit_rate)
rpcMethods = try container.decodeIfPresent([String].self, forKey: .rpc_methods)
simulcastRpcRids = try container.decodeIfPresent([String].self, forKey: .simulcast_rpc_rids)
simulcastRpcRids = try container.decodeIfPresent([Rid].self, forKey: .simulcast_rpc_rids)
}

public func encode(to encoder: Encoder) throws {
Expand Down Expand Up @@ -1446,10 +1446,3 @@ extension SignalingClose: Decodable {
reason = try container.decode(String.self, forKey: .reason)
}
}

/// :nodoc:
extension Array where Element == String {
func toSimulcastRequestRids() -> [SimulcastRequestRid] {
compactMap { simulcastRequestRidTable.right(other: $0) }
}
}
41 changes: 41 additions & 0 deletions Sora/Types.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/// 映像の rid を表します。
/// type: offer の simulcastRpcRids や RPC で利用される汎用 rid 型です。
public enum Rid: Equatable {
/// 映像を受信しない
case none

/// r0
case r0

/// r1
case r1

/// r2
case r2
}

private var ridTable: PairTable<String, Rid> =
PairTable(
name: "rid",
pairs: [
("none", .none),
("r0", .r0),
("r1", .r1),
("r2", .r2),
])

/// :nodoc:
extension Rid: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
guard let rid = ridTable.right(other: string) else {
throw SoraError.invalidSignalingMessage
}
self = rid
}

public func encode(to encoder: Encoder) throws {
try ridTable.encode(self, to: encoder)
}
}
33 changes: 33 additions & 0 deletions SoraTests/RidTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import XCTest

@testable import Sora

class RidTests: XCTestCase {
func testRidEncodingAndDecoding() throws {
let cases: [(Rid, String)] = [
(.none, "\"none\""),
(.r0, "\"r0\""),
(.r1, "\"r1\""),
(.r2, "\"r2\""),
]

for (rid, expectedJson) in cases {
// Encoding
let encoder = JSONEncoder()
let encodedData = try encoder.encode(rid)
let encodedJson = String(data: encodedData, encoding: .utf8)
XCTAssertEqual(encodedJson, expectedJson, "Failed encoding \(rid)")

// Decoding
let decoder = JSONDecoder()
let decodedRid = try decoder.decode(Rid.self, from: expectedJson.data(using: .utf8)!)
XCTAssertEqual(decodedRid, rid, "Failed decoding \(expectedJson)")
}
}

func testDecodeInvalidRidThrowsError() throws {
let decoder = JSONDecoder()
let data = "\"invalid\"".data(using: .utf8)!
XCTAssertThrowsError(try decoder.decode(Rid.self, from: data))
}
}
66 changes: 66 additions & 0 deletions SoraTests/SignalingOfferTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import XCTest

@testable import Sora

class SignalingOfferTests: XCTestCase {
func testDecodeSimulcastRpcRids() throws {
let testCases: [(simulcastRpcRids: [String], expectedRids: [Rid]?, shouldThrow: Bool)] = [
(["r0", "r1", "r2"], [.r0, .r1, .r2], false),
(["none"], [.none], false),
([], [], false),
(["invalid"], nil, true),
]

for (simulcastRpcRids, expectedRids, shouldThrow) in testCases {
let json: String
if simulcastRpcRids.isEmpty {
json = """
{
"type": "offer",
"client_id": "client123",
"connection_id": "conn123",
"sdp": "v=0\\r\\no=- 1 1 IN IP4 127.0.0.1\\r\\n",
"simulcast_rpc_rids": []
}
"""
} else {
let ridsJson = simulcastRpcRids.map { "\"\($0)\"" }.joined(separator: ", ")
json = """
{
"type": "offer",
"client_id": "client123",
"connection_id": "conn123",
"sdp": "v=0\\r\\no=- 1 1 IN IP4 127.0.0.1\\r\\n",
"simulcast_rpc_rids": [\(ridsJson)]
}
"""
}

let data = json.data(using: .utf8)!
let decoder = JSONDecoder()

if shouldThrow {
XCTAssertThrowsError(try decoder.decode(SignalingOffer.self, from: data))
} else {
let offer = try decoder.decode(SignalingOffer.self, from: data)
XCTAssertEqual(offer.simulcastRpcRids, expectedRids)
}
}
}

func testDecodeSimulcastRpcRidsNotPresent() throws {
let json = """
{
"type": "offer",
"client_id": "client123",
"connection_id": "conn123",
"sdp": "v=0\\r\\no=- 1 1 IN IP4 127.0.0.1\\r\\n"
}
"""
let data = json.data(using: .utf8)!
let decoder = JSONDecoder()
let offer = try decoder.decode(SignalingOffer.self, from: data)

XCTAssertNil(offer.simulcastRpcRids)
}
}