Skip to content

Commit

Permalink
SwiftUI view の実装を追加
Browse files Browse the repository at this point in the history
  • Loading branch information
zztkm committed Sep 25, 2024
1 parent 905635c commit 2638c02
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 0 deletions.
152 changes: 152 additions & 0 deletions Sora/Video.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import Foundation
import SwiftUI
import UIKit

/**
ストリームの映像を描画する SwiftUI ビューです。
*/
public struct Video<Background>: View where Background: View {
private var stream: MediaStream?
private var background: Background

@ObservedObject private var controller: VideoController

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

- parameter stream: 描画される映像ストリーム。 nil の場合は何も描画されません
*/
public init(_ stream: MediaStream?) where Background == EmptyView {
self.init(stream, background: EmptyView())
}

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

- parameter stream: 描画される映像ストリーム nil の場合は何も描画されません
- paramater background: 映像のクリア時に表示する背景ビュー
*/
public init(_ stream: MediaStream?, background: Background) {
self.stream = stream
self.background = background
controller = VideoController(stream: stream)
}

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

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

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

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

/// 映像のクリア時に表示する背景ビューを指定します。
/// TODO(zztkm): 以下の警告が出るので Swift 6 対応までに修正する
/// ジェネリックパラメータ 'Background' は、同じ名前を持つ外部スコープのジェネリックパラメータを隠します。これは Swift 6 言語モードのエラーです。
public func videoBackground<Background>(_ background: Background) -> Video<Background> where Background: View {
var new = Video<Background>(stream, background: background)
new.controller = controller
return new
}

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

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

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

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

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

@ObservedObject private var controller: VideoController

public init(_ controller: VideoController) {
self.controller = controller
}

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

public func updateUIView(_ uiView: VideoView, context: Context) {
controller.stream?.videoRenderer = 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
}
}
11 changes: 11 additions & 0 deletions Sora/VideoView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ public enum VideoViewConnectionMode {
case manual
}

// Video (SwiftUI 用) で使う
/// :nodoc:
public struct VideoViewHandlers {
public var onChange: ((CGSize) -> Void)?
public var onRender: ((VideoFrame?) -> Void)?
}

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

Expand Down Expand Up @@ -59,6 +66,8 @@ public class VideoView: UIView {
self.addSubview(view)
return view
}()

public var handlers = VideoViewHandlers()

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

Expand Down Expand Up @@ -206,11 +215,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

0 comments on commit 2638c02

Please sign in to comment.