Skip to content

Commit

Permalink
Synchronous VideoView internal state (#348)
Browse files Browse the repository at this point in the history
Update VideoView's internal state synchronously so properties such as
avSampleBufferDisplayLayer can be accessed immediately.
  • Loading branch information
hiroshihorie authored Mar 2, 2024
1 parent 399901c commit bca5593
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 6 deletions.
26 changes: 20 additions & 6 deletions Sources/LiveKit/Views/VideoView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ public class VideoView: NativeView, Loggable {

guard let self else { return }

self.log("Mutating in main thread: \(Thread.current.isMainThread)", .trace)

let shouldRenderDidUpdate = newState.shouldRender != oldState.shouldRender
let renderModeDidUpdate = newState.renderMode != oldState.renderMode

Expand All @@ -220,8 +222,8 @@ public class VideoView: NativeView, Loggable {

// Enter .main only if the following conditions are met...
if trackDidUpdate || shouldRenderDidUpdate || renderModeDidUpdate {
Task.detached { @MainActor in

// Execute on main thread
self.mainSyncOrAsync {
var didReCreateNativeRenderer = false

if trackDidUpdate || shouldRenderDidUpdate {
Expand All @@ -244,7 +246,7 @@ public class VideoView: NativeView, Loggable {
// set new track
if let track = newState.track as? VideoTrack, newState.shouldRender {
// re-create renderer on main thread
let nr = self.reCreateNativeRenderer()
let nr = self.reCreateNativeRenderer(for: newState.renderMode)
didReCreateNativeRenderer = true

track.add(videoRenderer: self)
Expand All @@ -263,7 +265,7 @@ public class VideoView: NativeView, Loggable {
}

if renderModeDidUpdate, !didReCreateNativeRenderer {
self.reCreateNativeRenderer()
self.reCreateNativeRenderer(for: newState.renderMode)
}
}
}
Expand Down Expand Up @@ -458,13 +460,13 @@ private extension VideoView {
}

@discardableResult
func reCreateNativeRenderer() -> NativeRendererView {
func reCreateNativeRenderer(for renderMode: VideoView.RenderMode) -> NativeRendererView {
if !Thread.current.isMainThread {
log("Must be called on main thread", .error)
}

// create a new rendererView
let newView = VideoView.createNativeRendererView(for: _state.renderMode)
let newView = VideoView.createNativeRendererView(for: renderMode)
addSubview(newView)

// keep the old rendererView
Expand Down Expand Up @@ -692,3 +694,15 @@ extension LKRTCMTLVideoView: Mirrorable {
}
}
}

private extension VideoView {
func mainSyncOrAsync(operation: @escaping () -> Void) {
if Thread.current.isMainThread {
operation()
} else {
Task.detached { @MainActor in
operation()
}
}
}
}
35 changes: 35 additions & 0 deletions Tests/LiveKitTests/VideoView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2024 LiveKit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@testable import LiveKit
import XCTest

class VideoViewTests: XCTestCase {
override class func setUp() {
LiveKitSDK.setLoggerStandardOutput()
}

/// Test if avSampleBufferDisplayLayer is available immediately after creating VideoView.
@MainActor
func testAVSampleBufferDisplayLayer() {
let track = LocalVideoTrack.createCameraTrack()
let view = VideoView()
view.renderMode = .sampleBuffer
view.track = track
// avSampleBufferDisplayLayer should not be nil at this point
XCTAssert(view.avSampleBufferDisplayLayer != nil)
}
}

0 comments on commit bca5593

Please sign in to comment.