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

Add video pausing option on VideoView #469

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions Sources/LiveKit/SwiftUI/SwiftUIVideoView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public struct SwiftUIVideoView: NativeViewRepresentable {
let renderMode: VideoView.RenderMode
let rotationOverride: VideoRotation?
let pinchToZoomOptions: VideoView.PinchToZoomOptions
let isPaused: Bool
let isDebugMode: Bool

let videoViewDelegateReceiver: VideoViewDelegateReceiver
Expand All @@ -40,6 +41,7 @@ public struct SwiftUIVideoView: NativeViewRepresentable {
rotationOverride: VideoRotation? = nil,
pinchToZoomOptions: VideoView.PinchToZoomOptions = [],
isDebugMode: Bool = false,
isPaused: Bool = false,
isRendering: Binding<Bool>? = nil)
{
self.track = track
Expand All @@ -48,6 +50,7 @@ public struct SwiftUIVideoView: NativeViewRepresentable {
self.renderMode = renderMode
self.rotationOverride = rotationOverride
self.isDebugMode = isDebugMode
self.isPaused = isPaused
self.pinchToZoomOptions = pinchToZoomOptions

videoViewDelegateReceiver = VideoViewDelegateReceiver(isRendering: isRendering)
Expand All @@ -66,6 +69,7 @@ public struct SwiftUIVideoView: NativeViewRepresentable {
videoView.renderMode = renderMode
videoView.rotationOverride = rotationOverride
videoView.pinchToZoomOptions = pinchToZoomOptions
videoView.isPaused = isPaused
videoView.isDebugMode = isDebugMode

Task.detached { @MainActor in
Expand Down
46 changes: 34 additions & 12 deletions Sources/LiveKit/Views/VideoView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ public class VideoView: NativeView, Loggable {
get { _state.isEnabled }
set { _state.mutate { $0.isEnabled = newValue } }
}

@objc
public var isPaused: Bool {
get { _state.isPaused }
set { _state.mutate { $0.isPaused = newValue } }
}

private var pausedVideoFrame: VideoFrame?

@objc
override public var isHidden: Bool {
Expand Down Expand Up @@ -190,6 +198,7 @@ public class VideoView: NativeView, Loggable {
struct State {
weak var track: Track?
var isEnabled: Bool = true
var isPaused: Bool = false
var isHidden: Bool = false

// layout related
Expand Down Expand Up @@ -221,9 +230,11 @@ public class VideoView: NativeView, Loggable {
var captureOptions: VideoCaptureOptions? = nil
var captureDevice: AVCaptureDevice? = nil

var lastRenderedFrame: VideoFrame?

// whether if current state should be rendering
var shouldRender: Bool {
track != nil && isEnabled && !isHidden
track != nil && isEnabled && !isHidden && !isPaused
}
}

Expand Down Expand Up @@ -284,15 +295,18 @@ public class VideoView: NativeView, Loggable {
// clean up old track
if let track = oldState.track as? VideoTrack {
track.remove(videoRenderer: self)

if let r = self._primaryRenderer {
r.removeFromSuperview()
self._primaryRenderer = nil
}

if let r = self._secondaryRenderer {
r.removeFromSuperview()
self._secondaryRenderer = nil

// avoid destroying and re-creating the renderers when the video is paused
if !newState.isPaused {
if let r = self._primaryRenderer {
r.removeFromSuperview()
self._primaryRenderer = nil
}

if let r = self._secondaryRenderer {
r.removeFromSuperview()
self._secondaryRenderer = nil
}
}
}

Expand All @@ -305,15 +319,22 @@ public class VideoView: NativeView, Loggable {
track.add(videoRenderer: self)

if let frame = track._state.videoFrame {
self.log("rendering cached frame tack: \(String(describing: track._state.sid))")
self.log("rendering cached frame track: \(String(describing: track._state.sid))")
nr.renderFrame(frame.toRTCType())
self.setNeedsLayout()
}
}
}

if renderModeDidUpdate, !didReCreateNativeRenderer {
self.recreatePrimaryRenderer(for: newState.renderMode)
let nr = self.recreatePrimaryRenderer(for: newState.renderMode)

// re-render last rendered frame before pause
if let frame = newState.lastRenderedFrame, newState.isPaused {
self.log("rendering last rendered frame before pause")
nr.renderFrame(frame.toRTCType())
self.setNeedsLayout()
}
}
}
}
Expand Down Expand Up @@ -625,6 +646,7 @@ extension VideoView: VideoRenderer {
$0.didRenderFirstFrame = true
$0.isRendering = true
$0.renderDate = Date()
$0.lastRenderedFrame = frame

// Update renderTarget if capture position changes
if let oldCaptureDevicePosition, oldCaptureDevicePosition != captureDevice?.position {
Expand Down
Loading