Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] SwiftUI view の実装を追加 #212

Open
wants to merge 36 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2638c02
SwiftUI view の実装を追加
zztkm Sep 25, 2024
8382db4
format
zztkm Sep 26, 2024
643a010
Video を SwiftUIVideoView に名称変更
zztkm Sep 30, 2024
ab4aa96
プロジェクトに SwiftUIVideoView.swift を追加
zztkm Sep 30, 2024
f1776d9
SwiftUIVideoView の rename 漏れの対応
zztkm Oct 1, 2024
0308873
コードミス修正
zztkm Oct 1, 2024
b478020
VideoController を public に変更し、SDK ユーザー側で利用可能にする
zztkm Oct 1, 2024
d1afe08
debug print 追加
zztkm Oct 2, 2024
7c92762
APIを変更
zztkm Oct 2, 2024
a93561e
修正
zztkm Oct 2, 2024
bfc1119
Binding 変数をオプションに変更する
zztkm Oct 2, 2024
32bdebc
Binding 変数の定義方法を改めた
zztkm Oct 2, 2024
e6f10fc
onChange の initial 引数をなくす
zztkm Oct 2, 2024
4a813aa
onChange(of:perform:) は iOS 14.0 以降で利用可能
zztkm Oct 2, 2024
2603cc3
format 修正
zztkm Oct 2, 2024
b879a5a
debug printを追加
zztkm Oct 2, 2024
8f6361d
debug print
zztkm Oct 2, 2024
0446cfe
debug 中
zztkm Oct 3, 2024
1dbd8a0
debug print 追加する
zztkm Oct 3, 2024
15947d0
videoStop のメソッドチェーン対応
zztkm Oct 3, 2024
d317bb5
SwiftUIVideoView が表示される前に videoStop method を実行する
zztkm Oct 4, 2024
3b8484f
VideoView を SwiftUIVideoView から制御する実装を追加
zztkm Oct 7, 2024
de6ac6b
stopVideo を isStop に変更
zztkm Oct 7, 2024
3481ad5
isClear を追加し、clear() method が呼ばれるようにする
zztkm Oct 7, 2024
bac23fc
コンストラクタ修正
zztkm Oct 7, 2024
1467699
.contains -> .constant に修正する
zztkm Oct 7, 2024
1e27b35
uiView を var に修正する
zztkm Oct 7, 2024
aa02b52
View の状態が変わったときにSDKユーザー側で検知するためのハンドラを追加
zztkm Oct 8, 2024
8465148
} 忘れ
zztkm Oct 8, 2024
351d863
ハンドラ指定間違いを修正する
zztkm Oct 8, 2024
8f57757
handler の実行タイミングを調整
zztkm Oct 8, 2024
a53ec1f
不要なハンドラを削除
zztkm Oct 8, 2024
1b2811c
削除漏れ
zztkm Oct 8, 2024
7ff85ca
[WIP] VideoController で SwiftUIVideoView (VideoView) を制御する
zztkm Oct 9, 2024
f2dc731
動いていた commit に戻す
zztkm Jan 18, 2025
93db42b
削除漏れがありました
zztkm Jan 18, 2025
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
4 changes: 4 additions & 0 deletions Sora.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
91FA6F211D93CA9800D38DB4 /* VideoFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FA6F201D93CA9800D38DB4 /* VideoFrame.swift */; };
91FD95751DCA06F700047BA9 /* RTC+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FD95741DCA06F700047BA9 /* RTC+Description.swift */; };
AD7227122716B5F100001A23 /* DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD7227112716B5F100001A23 /* DataChannel.swift */; };
B3E5B61A2CAA4FF7005C39D2 /* SwiftUIVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3E5B6192CAA4FF7005C39D2 /* SwiftUIVideoView.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -117,6 +118,7 @@
91FA6F201D93CA9800D38DB4 /* VideoFrame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoFrame.swift; sourceTree = "<group>"; };
91FD95741DCA06F700047BA9 /* RTC+Description.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RTC+Description.swift"; sourceTree = "<group>"; };
AD7227112716B5F100001A23 /* DataChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataChannel.swift; sourceTree = "<group>"; };
B3E5B6192CAA4FF7005C39D2 /* SwiftUIVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIVideoView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -206,6 +208,7 @@
9161AEC92410D75A00BA3E0E /* URLSessionWebSocketChannel.swift */,
3591D8EF27032233000AD514 /* SoraDispatcher.swift */,
9145A58F1F0CB093002D6EC6 /* Utilities.swift */,
B3E5B6192CAA4FF7005C39D2 /* SwiftUIVideoView.swift */,
9177FF9B1F2E2D1600B4FA1A /* VideoCapturer.swift */,
9174A8921F73F8CF00D586C4 /* VideoCodec.swift */,
91FA6F201D93CA9800D38DB4 /* VideoFrame.swift */,
Expand Down Expand Up @@ -385,6 +388,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B3E5B61A2CAA4FF7005C39D2 /* SwiftUIVideoView.swift in Sources */,
91554F431F179CFD00403C39 /* WebSocketChannel.swift in Sources */,
91756E221F90ADE900B70C53 /* PeerChannel.swift in Sources */,
910F2DB124D7F6EE007336A6 /* Statistics.swift in Sources */,
Expand Down
197 changes: 197 additions & 0 deletions Sora/SwiftUIVideoView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import Foundation
import SwiftUI
import UIKit

/**
ストリームの映像を描画する SwiftUI ビューです。
*/
@available(iOS 14, *)
public struct SwiftUIVideoView<Background>: View where Background: View {
private var stream: MediaStream?
private var background: Background

// TODO(zztkm): わかりやすいコメントを書く
// 親 View で定義された stopVideo 変数と接続するための変数
@Binding private var isStop: Bool
@Binding private var isClear: Bool

@ObservedObject private var controller: VideoController

/**
ビューを初期化します。

- parameter stream: 描画される映像ストリーム。 nil の場合は何も描画されません
- parameter isStop: 映像の描画を制御するフラグ (default: false)
- parameter isClear: 映像をクリアして背景 View を表示するためのフラグ (default: false)
*/
public init(_ stream: MediaStream?, isStop: Binding<Bool>? = nil, isClear: Binding<Bool>? = nil) where Background == EmptyView {
self.init(stream, background: EmptyView(), isStop: isStop, isClear: isClear)
}

/**
ビューを初期化します。

- parameter stream: 描画される映像ストリーム nil の場合は何も描画されません
- parameter background: 映像のクリア時に表示する背景ビュー
- parameter isStop: 映像の描画を制御するフラグ (default: false)
- parameter isClear: 映像をクリアして背景 View を表示するためのフラグ (default: false)
*/
public init(_ stream: MediaStream?, background: Background, isStop: Binding<Bool>? = nil, isClear: Binding<Bool>? = nil) {
self.stream = stream
self.background = background
// 指定がない場合は固定値 false を与える
_isStop = isStop ?? .constant(false)
_isClear = isClear ?? .constant(false)
controller = VideoController(stream: stream)
}

/// :nodoc:
public var body: some View {
ZStack {
background
.opacity(controller.isCleared ? 1 : 0)
RepresentedVideoView(controller, isStop: $isStop, isClear: $isClear)
.opacity(controller.isCleared ? 0 : 1)
}
}

/**
デバッグモードを有効にします。
有効にすると、映像の上部に解像度とフレームレートを表示します。
*/
public func debugMode(_ flag: Bool) -> SwiftUIVideoView<Background> {
controller.videoView.debugMode = flag
return self
}

/// 映像ソース停止時の処理を指定します。
public func connectionMode(_ mode: VideoViewConnectionMode) -> SwiftUIVideoView<Background> {
controller.videoView.connectionMode = mode
return self
}

/// 映像のアスペクト比を指定します。
public func videoAspect(_ contentMode: ContentMode) -> SwiftUIVideoView<Background> {
var uiContentMode: UIView.ContentMode
switch contentMode {
case .fill:
uiContentMode = .scaleAspectFill
case .fit:
uiContentMode = .scaleAspectFit
}
controller.videoView.contentMode = uiContentMode
return self
}

/// 映像のクリア時に表示する背景ビューを指定します。
public func videoBackground<Background>(_ background: Background) -> SwiftUIVideoView<Background> where Background: View {
var new = SwiftUIVideoView<Background>(stream, background: background)
new.controller = controller
return new
}

/**
映像の描画を停止します。
*/
private func videoStop(_ flag: Bool) -> SwiftUIVideoView<Background> {
if flag {
controller.videoView.stop()
} else if !controller.videoView.isRendering {
controller.videoView.start()
}
return self
}

/**
画面を背景ビューに切り替えます。
このメソッドは描画停止時のみ有効です。
*/
public func videoClear(_ flag: Bool) -> SwiftUIVideoView<Background> {
if flag {
controller.videoView.clear()
controller.isCleared = true
}
return self
}

/// 映像のサイズの変更時に実行されるブロックを指定します。
public func videoOnChange(perform: @escaping (CGSize) -> Void) -> SwiftUIVideoView<Background> {
controller.videoView.handlers.onChange = perform
return self
}

/// 映像フレームの描画時に実行されるブロックを指定します。
public func videoOnRender(perform: @escaping (VideoFrame?) -> Void) -> SwiftUIVideoView<Background> {
controller.videoView.handlers.onRender = perform
return self
}
}

/*
VideoView を SwiftUIVideoView に統合するためのラッパーです。
*/
private struct RepresentedVideoView: UIViewRepresentable {
typealias UIViewType = VideoView

@ObservedObject private var controller: VideoController
@Binding private var isStop: Bool
@Binding private var isClear: Bool

public init(_ controller: VideoController, isStop: Binding<Bool>, isClear: Binding<Bool>) {
self.controller = controller
_isStop = isStop
_isClear = isClear
}

public func makeUIView(context: Context) -> VideoView {
controller.videoView
}

/// VideoView を更新する処理です
///
/// 引数の uiView を更新し、更新したあとに controller.stream?.videoRenderer に
/// uiView をセットすることで、VideoView の挙動を制御することができます。
public func updateUIView(_ uiView: VideoView, context: Context) {
var uiView = uiView
// VideoView.clear() はVideoView.stop() が実行されたあとにのみ実行可能になる
uiView = clear(stop(uiView))
controller.stream?.videoRenderer = uiView
}

/**
映像の描画を停止します。TODO(zztkm): method 名の検討 (stop start の toggle なので、stop はおかしいかも?
*/
private func stop(_ uiView: VideoView) -> VideoView {
if isStop {
uiView.stop()
} else {
uiView.start()
}
return uiView
}

/**
画面を背景ビューに切り替えます。
このメソッドは描画停止時のみ有効です。
*/
private func clear(_ uiView: VideoView) -> VideoView {
if isClear {
uiView.clear()
}
return uiView
}
}

class VideoController: ObservableObject {
var stream: MediaStream?

// init() で VideoView を生成すると次のエラーが出るので、生成のタイミングを遅らせておく
// Failed to bind EAGLDrawable: <CAEAGLLayer: 0x********> to GL_RENDERBUFFER 1
lazy var videoView = VideoView()

@Published var isCleared: Bool = false

init(stream: MediaStream?) {
self.stream = stream
}
}
13 changes: 13 additions & 0 deletions Sora/VideoView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ public enum VideoViewConnectionMode {
case manual
}

// SwiftUIVideoView (SwiftUI 用) で使う
/// :nodoc:
public struct VideoViewHandlers {
/// 映像のサイズ変更時に実行される
public var onChange: ((CGSize) -> Void)?
/// 映像フレーム描画時に実行される
public var onRender: ((VideoFrame?) -> Void)?
}

/**
VideoRenderer プロトコルのデフォルト実装となる UIView です。

Expand Down Expand Up @@ -60,6 +69,8 @@ public class VideoView: UIView {
return view
}()

public var handlers = VideoViewHandlers()

// MARK: - インスタンスの生成

/**
Expand Down Expand Up @@ -206,11 +217,13 @@ extension VideoView: VideoRenderer {
/// :nodoc:
public func onChange(size: CGSize) {
contentView.onVideoFrameSizeUpdated(size)
handlers.onChange?(size)
}

/// :nodoc:
public func render(videoFrame: VideoFrame?) {
if isRendering {
handlers.onRender?(videoFrame)
contentView.render(videoFrame: videoFrame)
}
}
Expand Down
Loading