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

Remove nested layers to avoid animation issues #1010

Merged
merged 2 commits into from
Sep 16, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Quick/Nimble.git",
"state" : {
"revision" : "54b4e52183f16fe806014cbfd63718a84f8ba072",
"version" : "13.4.0"
"revision" : "cecacf00ddf36c1efff8d14beb65087f2a676be9",
"version" : "13.5.0"
}
},
{
Expand Down
28 changes: 16 additions & 12 deletions Sources/Player/PictureInPicture/CustomPictureInPicture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ final class CustomPictureInPicture: NSObject {
weak var delegate: PictureInPictureDelegate?
private var referenceCount = 0

var playerLayer: AVPlayerLayer? {
controller?.playerLayer
}
var view: VideoLayerView?

override init() {
super.init()
Expand All @@ -43,12 +41,13 @@ final class CustomPictureInPicture: NSObject {
}
}

func acquire(for playerLayer: AVPlayerLayer) {
if self.playerLayer === playerLayer {
func acquire(for view: VideoLayerView) {
if self.view === view {
referenceCount += 1
}
else {
controller = AVPictureInPictureController(playerLayer: playerLayer)
self.view = view
controller = AVPictureInPictureController(playerLayer: view.playerLayer)
if let controller {
controller.delegate = self
referenceCount = 1
Expand All @@ -59,11 +58,12 @@ final class CustomPictureInPicture: NSObject {
}
}

func relinquish(for playerLayer: AVPlayerLayer) {
guard self.playerLayer === playerLayer else { return }
func relinquish(for view: VideoLayerView) {
guard self.view === view else { return }
referenceCount -= 1
if referenceCount == 0 {
controller = nil
self.view = nil
}
}

Expand All @@ -81,8 +81,8 @@ final class CustomPictureInPicture: NSObject {
///
/// See https://github.com/SRGSSR/pillarbox-apple/issues/612 for more information.
func detach(with player: AVPlayer) {
guard playerLayer?.player === player else { return }
playerLayer?.player = nil
guard view?.player === player else { return }
view?.player = nil
}

private func configureIsPossiblePublisher() {
Expand All @@ -100,7 +100,9 @@ final class CustomPictureInPicture: NSObject {
extension CustomPictureInPicture: AVPictureInPictureControllerDelegate {
func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
isActive = true
acquire(for: pictureInPictureController.playerLayer)
if let view, view.playerLayer == pictureInPictureController.playerLayer {
acquire(for: view)
}
delegate?.pictureInPictureWillStart()
}

Expand Down Expand Up @@ -133,7 +135,9 @@ extension CustomPictureInPicture: AVPictureInPictureControllerDelegate {
}

func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
relinquish(for: pictureInPictureController.playerLayer)
if let view, view.playerLayer == pictureInPictureController.playerLayer {
relinquish(for: view)
}
delegate?.pictureInPictureDidStop()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ struct PictureInPictureSupportingVideoView: UIViewRepresentable {
let gravity: AVLayerVideoGravity

static func dismantleUIView(_ uiView: VideoLayerView, coordinator: Void) {
PictureInPicture.shared.custom.relinquish(for: uiView.playerLayer)
PictureInPicture.shared.custom.relinquish(for: uiView)
}

func makeUIView(context: Context) -> VideoLayerView {
let view = VideoLayerView(from: PictureInPicture.shared.custom.playerLayer)
PictureInPicture.shared.custom.acquire(for: view.playerLayer)
let view = PictureInPicture.shared.custom.view ?? VideoLayerView()
PictureInPicture.shared.custom.acquire(for: view)
return view
}

Expand Down
42 changes: 7 additions & 35 deletions Sources/Player/UserInterface/VideoLayerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,16 @@ import AVFoundation
import UIKit

final class VideoLayerView: UIView {
let playerLayer: AVPlayerLayer

var player: AVPlayer? {
get { playerLayer.player }
set { playerLayer.player = newValue }
}

init(from playerLayer: AVPlayerLayer? = nil) {
self.playerLayer = playerLayer ?? .init()
super.init(frame: .zero)
layer.addSublayer(self.playerLayer)
override static var layerClass: AnyClass {
AVPlayerLayer.self
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
var playerLayer: AVPlayerLayer {
layer as! AVPlayerLayer
}

override func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
layer.synchronizeLayoutChanges {
playerLayer.frame = layer.bounds
}
}
}

private extension CALayer {
func synchronizeLayoutChanges(_ changes: () -> Void) {
if let positionAnimation = animation(forKey: "position") {
CATransaction.begin()
CATransaction.setAnimationDuration(positionAnimation.duration)
CATransaction.setAnimationTimingFunction(positionAnimation.timingFunction)
changes()
CATransaction.commit()
}
else {
changes()
sublayers?.forEach { $0.removeAllAnimations() }
}
var player: AVPlayer? {
get { playerLayer.player }
set { playerLayer.player = newValue }
}
}