Add PictureInPicture feature

This commit is contained in:
shogo4405 2020-08-30 15:02:52 +09:00
parent 918c38d718
commit 1b28af9131
6 changed files with 371 additions and 66 deletions

View File

@ -247,57 +247,6 @@
</objects>
<point key="canvasLocation" x="749" y="402"/>
</scene>
<!--Playback-->
<scene sceneID="sgb-vK-JUQ">
<objects>
<viewController id="M6P-U9-OQB" userLabel="Playback" customClass="PlaybackController" customModule="Example_iOS" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="1Nb-68-FWe"/>
<viewControllerLayoutGuide type="bottom" id="xon-CD-cIo"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Z5m-1T-AfH">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="eXp-4i-FnG" customClass="GLHKView" customModule="HaishinKit">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="lKf-53-lJb">
<rect key="frame" x="348" y="767" width="30" height="30"/>
<color key="backgroundColor" systemColor="systemBlueColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="Abw-7u-gO4"/>
<constraint firstAttribute="height" constant="30" id="dxX-97-jzH"/>
</constraints>
<state key="normal" title="▶︎">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<action selector="didPlaybackButtonTap:" destination="M6P-U9-OQB" eventType="touchUpInside" id="PKd-Aq-bAF"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="eXp-4i-FnG" secondAttribute="trailing" id="2Eu-cX-ofs"/>
<constraint firstItem="eXp-4i-FnG" firstAttribute="top" secondItem="Z5m-1T-AfH" secondAttribute="top" id="4kR-wF-kbE"/>
<constraint firstItem="xon-CD-cIo" firstAttribute="top" secondItem="lKf-53-lJb" secondAttribute="bottom" constant="16" id="CaM-6n-hrp"/>
<constraint firstAttribute="bottom" secondItem="eXp-4i-FnG" secondAttribute="bottom" id="Hf1-6l-t8q"/>
<constraint firstItem="eXp-4i-FnG" firstAttribute="leading" secondItem="Z5m-1T-AfH" secondAttribute="leading" id="bbC-24-GqG"/>
<constraint firstAttribute="trailingMargin" secondItem="lKf-53-lJb" secondAttribute="trailing" constant="16" id="o1U-y6-vLF"/>
</constraints>
</view>
<tabBarItem key="tabBarItem" title="Playback" id="cnH-RJ-T8G"/>
<navigationItem key="navigationItem" id="DHF-rz-qM4"/>
<connections>
<outlet property="lfView" destination="eXp-4i-FnG" id="0p8-El-ZPC"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="uoF-Er-2sg" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1602.8985507246377" y="0.0"/>
</scene>
<!--Tab Bar Controller-->
<scene sceneID="yl2-sM-qoP">
<objects>
@ -310,14 +259,87 @@
</tabBar>
<connections>
<segue destination="9pv-A4-QxB" kind="relationship" relationship="viewControllers" id="u7Y-xg-7CH"/>
<segue destination="M6P-U9-OQB" kind="relationship" relationship="viewControllers" id="pqx-AR-kNX"/>
<segue destination="8rJ-Kc-sve" kind="relationship" relationship="viewControllers" id="lzU-1b-eKA"/>
<segue destination="uu9-Wt-Dxt" kind="relationship" relationship="viewControllers" id="KIw-Gf-bHs"/>
<segue destination="8rJ-Kc-sve" kind="relationship" relationship="viewControllers" id="V0f-g5-FP2"/>
</connections>
</tabBarController>
<placeholder placeholderIdentifier="IBFirstResponder" id="HuB-VB-40B" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="0.0" y="0.0"/>
</scene>
<!--Playback-->
<scene sceneID="2gE-y3-VQT">
<objects>
<viewController id="uu9-Wt-Dxt" customClass="PlaybackContainerViewController" customModule="Example_iOS" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="O2h-r1-E7F"/>
<viewControllerLayoutGuide type="bottom" id="dXy-vE-8ph"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="7NS-AC-cs3">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Awa-Nz-XRH">
<rect key="frame" x="0.0" y="0.0" width="414" height="813"/>
<connections>
<segue destination="S4s-US-fOh" kind="embed" id="ahj-EG-JS5"/>
</connections>
</containerView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="Awa-Nz-XRH" firstAttribute="leading" secondItem="7NS-AC-cs3" secondAttribute="leading" id="3qS-OU-Vri"/>
<constraint firstItem="dXy-vE-8ph" firstAttribute="top" secondItem="Awa-Nz-XRH" secondAttribute="bottom" id="6Zm-KN-92x"/>
<constraint firstAttribute="trailing" secondItem="Awa-Nz-XRH" secondAttribute="trailing" id="lWs-68-EwZ"/>
<constraint firstItem="Awa-Nz-XRH" firstAttribute="top" secondItem="7NS-AC-cs3" secondAttribute="top" id="yAY-Zt-w6U"/>
</constraints>
</view>
<tabBarItem key="tabBarItem" title="Playback" id="Pv0-9w-P0v"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="O4Q-V8-kNz" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1463.768115942029" y="-102.45535714285714"/>
</scene>
<!--Playback View Controller-->
<scene sceneID="t76-yU-vlu">
<objects>
<viewController storyboardIdentifier="PlaybackViewController" id="S4s-US-fOh" customClass="PlaybackViewController" customModule="Example_iOS" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="vko-dD-pI7"/>
<viewControllerLayoutGuide type="bottom" id="4an-yi-rp6"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="mU9-Qc-s55" customClass="GLHKView">
<rect key="frame" x="0.0" y="0.0" width="414" height="813"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="p4J-1x-O1c">
<rect key="frame" x="368" y="32" width="30" height="30"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="9tM-9Y-bwe"/>
<constraint firstAttribute="width" constant="30" id="Xcd-dW-hq8"/>
</constraints>
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
<state key="normal" title="●"/>
<connections>
<action selector="didPlaybackButtonTap:" destination="S4s-US-fOh" eventType="touchDown" id="bAJ-ml-suS"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" systemColor="systemGray2Color" red="0.68235294120000001" green="0.68235294120000001" blue="0.69803921570000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="p4J-1x-O1c" secondAttribute="trailing" constant="16" id="f5f-rE-eQ2"/>
<constraint firstItem="p4J-1x-O1c" firstAttribute="top" secondItem="mU9-Qc-s55" secondAttribute="top" constant="32" id="tXc-VA-Erm"/>
</constraints>
</view>
<connections>
<outlet property="playbackButton" destination="p4J-1x-O1c" id="ZN3-UM-SXt"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="A6d-pe-nwN" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2110" y="-102"/>
</scene>
</scenes>
<resources>
<image name="first" width="30" height="30"/>

View File

@ -0,0 +1,5 @@
import Foundation
import UIKit
final class PlaybackContainerViewController: UIViewController {
}

View File

@ -2,10 +2,11 @@ import Foundation
import HaishinKit
import UIKit
final class PlaybackController: UIViewController {
final class PlaybackViewController: UIViewController, HKPictureInPicureController {
private static let maxRetryCount: Int = 5
@IBOutlet private weak var lfView: GLHKView?
@IBOutlet private weak var playbackButton: UIButton!
private var rtmpConnection = RTMPConnection()
private var rtmpStream: RTMPStream!
@ -14,21 +15,20 @@ final class PlaybackController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
rtmpStream = RTMPStream(connection: rtmpConnection)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapped(_:)))
tapGesture.numberOfTapsRequired = 2
view.addGestureRecognizer(tapGesture)
}
override func viewWillAppear(_ animated: Bool) {
logger.info("viewWillAppear")
super.viewWillAppear(animated)
rtmpStream.addObserver(self, forKeyPath: "currentFPS", options: .new, context: nil)
lfView?.attachStream(rtmpStream)
(view as? GLHKView)?.attachStream(rtmpStream)
}
override func viewWillDisappear(_ animated: Bool) {
logger.info("viewWillDisappear")
super.viewWillDisappear(animated)
rtmpStream.removeObserver(self, forKeyPath: "currentFPS")
rtmpStream.close()
rtmpStream.dispose()
}
@IBAction func didPlaybackButtonTap(_ button: UIButton) {
@ -60,7 +60,7 @@ final class PlaybackController: UIViewController {
retryCount = 0
rtmpStream.play(Preference.defaultInstance.streamName!)
case RTMPConnection.Code.connectFailed.rawValue, RTMPConnection.Code.connectClosed.rawValue:
guard retryCount <= PlaybackController.maxRetryCount else {
guard retryCount <= PlaybackViewController.maxRetryCount else {
return
}
Thread.sleep(forTimeInterval: pow(2.0, Double(retryCount)))
@ -71,6 +71,17 @@ final class PlaybackController: UIViewController {
}
}
@objc
private func didTapped(_ sender: UITapGestureRecognizer) {
if isPictureInPictureActive {
stopPictureInPicture()
playbackButton.isHidden = false
} else {
startPictureInPicture()
playbackButton.isHidden = true
}
}
@objc
private func rtmpErrorHandler(_ notification: Notification) {
logger.error(notification)
@ -86,10 +97,4 @@ final class PlaybackController: UIViewController {
private func didBecomeActive(_ notification: Notification) {
rtmpStream.receiveVideo = true
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
if Thread.isMainThread {
// currentFPSLabel?.text = "\(rtmpStream.currentFPS)"
}
}
}

View File

@ -373,6 +373,9 @@
BC9CFA9423BDE8B700917EEF /* NetStreamRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC9CFA9223BDE8B700917EEF /* NetStreamRenderer.swift */; };
BC9CFA9523BDE8B700917EEF /* NetStreamRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC9CFA9223BDE8B700917EEF /* NetStreamRenderer.swift */; };
BCFB355524FA27EA00DC5108 /* PlaybackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCFB355324FA275600DC5108 /* PlaybackViewController.swift */; };
BCFB355A24FA40DD00DC5108 /* PlaybackContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCFB355924FA40DD00DC5108 /* PlaybackContainerViewController.swift */; };
BCFB355C24FAB29B00DC5108 /* HKPictureInPicureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCFB355624FA37F700DC5108 /* HKPictureInPicureController.swift */; };
BCFB355E24FAB2D200DC5108 /* HKPictureInPicureControllerImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCFB355D24FAB2D200DC5108 /* HKPictureInPicureControllerImpl.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -667,6 +670,9 @@
BC83A4722403D83B006BDE06 /* VTCompressionSession+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VTCompressionSession+Extension.swift"; sourceTree = "<group>"; };
BC9CFA9223BDE8B700917EEF /* NetStreamRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetStreamRenderer.swift; sourceTree = "<group>"; };
BCFB355324FA275600DC5108 /* PlaybackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackViewController.swift; sourceTree = "<group>"; };
BCFB355624FA37F700DC5108 /* HKPictureInPicureController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKPictureInPicureController.swift; sourceTree = "<group>"; };
BCFB355924FA40DD00DC5108 /* PlaybackContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackContainerViewController.swift; sourceTree = "<group>"; };
BCFB355D24FAB2D200DC5108 /* HKPictureInPicureControllerImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKPictureInPicureControllerImpl.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -1007,6 +1013,7 @@
29205CBD1E461F4E009D3FFF /* Main.storyboard */,
296897411CDB01D20074D5F0 /* AppDelegate.swift */,
296897441CDB01D20074D5F0 /* LiveViewController.swift */,
BCFB355924FA40DD00DC5108 /* PlaybackContainerViewController.swift */,
BCFB355324FA275600DC5108 /* PlaybackViewController.swift */,
291468161E581C7D00E619BA /* Preference.swift */,
2950742E1E4620B7007F15A4 /* PreferenceViewController.swift */,
@ -1064,6 +1071,8 @@
29D3D4D41ED04E7100DD4AA6 /* DeviceUtil+Extenstion.swift */,
29C263171D00804A0098D4EF /* GLHKView.swift */,
299F7E3B1CD71A97001E7272 /* HaishinKit.h */,
BCFB355624FA37F700DC5108 /* HKPictureInPicureController.swift */,
BCFB355D24FAB2D200DC5108 /* HKPictureInPicureControllerImpl.swift */,
299B13261D3B751400A1E8F5 /* HKView.swift */,
299F7E3A1CD71A97001E7272 /* Info.plist */,
2999C3742071138F00892E55 /* MTHKView.swift */,
@ -1636,6 +1645,7 @@
2943ED53232FCA7C00ED6301 /* Setting.swift in Sources */,
BC3FA38C2413AEDA009C83D3 /* AVFoundation+Extension.swift in Sources */,
2915EC4D1D85BB8C00621092 /* RTMPTSocket.swift in Sources */,
BCFB355C24FAB29B00DC5108 /* HKPictureInPicureController.swift in Sources */,
2958910A1EEB8D1800CE51E1 /* FLVReader.swift in Sources */,
29C2631C1D0083B50098D4EF /* VideoIOComponent.swift in Sources */,
29B876B41CD70B2800FC07DA /* RTMPSharedObject.swift in Sources */,
@ -1705,6 +1715,7 @@
29B876781CD70ACE00FC07DA /* HTTPService.swift in Sources */,
2976A4861D4903C300B53EF2 /* DeviceUtil.swift in Sources */,
29C263181D00804A0098D4EF /* GLHKView.swift in Sources */,
BCFB355E24FAB2D200DC5108 /* HKPictureInPicureControllerImpl.swift in Sources */,
29B876881CD70AE800FC07DA /* TransportStream.swift in Sources */,
29B876BE1CD70B3900FC07DA /* EventDispatcher.swift in Sources */,
29B8769D1CD70B1100FC07DA /* NetService.swift in Sources */,
@ -1874,6 +1885,7 @@
buildActionMask = 2147483647;
files = (
291468191E581C8F00E619BA /* Preference.swift in Sources */,
BCFB355A24FA40DD00DC5108 /* PlaybackContainerViewController.swift in Sources */,
296897671CDB02940074D5F0 /* AppDelegate.swift in Sources */,
296897681CDB02940074D5F0 /* LiveViewController.swift in Sources */,
BCFB355524FA27EA00DC5108 /* PlaybackViewController.swift in Sources */,

View File

@ -0,0 +1,127 @@
import Foundation
import UIKit
private var HKPictureInPicureControllerImplKey: UInt8 = 0
public enum HKPictureInPicureControllerPosition {
case topLeft
case topRight
case bottomRight
case bottomLeft
func CGPoint(_ controller: HKPictureInPicureController, insets: UIEdgeInsets = .zero) -> CGPoint {
let margin = controller.pictureInPictureMargin
switch self {
case .topLeft:
return .init(
x: margin + insets.left,
y: margin + insets.top)
case .topRight:
return .init(
x: UIScreen.main.bounds.width - controller.pictureInPictureSize.width - margin - insets.right,
y: margin + insets.top
)
case .bottomLeft:
return .init(
x: margin + insets.left,
y: UIScreen.main.bounds.height - controller.pictureInPictureSize.height - margin - insets.bottom
)
case .bottomRight:
return .init(
x: UIScreen.main.bounds.width - controller.pictureInPictureSize.width - margin - insets.right,
y: UIScreen.main.bounds.height - controller.pictureInPictureSize.height - margin - insets.bottom
)
}
}
}
public protocol HKPictureInPicureController: class {
var isPictureInPictureActive: Bool { get }
var pictureInPictureSize: CGSize { get set }
var pictureInPicturePosition: HKPictureInPicureControllerPosition { get set }
var pictureInPictureMargin: CGFloat { get set }
var pictureInPictureCornerRadius: CGFloat { get set }
var pictureInPictureAnimationDuration: TimeInterval { get set }
func startPictureInPicture()
func stopPictureInPicture()
}
public extension HKPictureInPicureController where Self: UIViewController {
var isPictureInPictureActive: Bool {
impl.isPictureInPictureActive
}
var pictureInPictureSize: CGSize {
get {
impl.pictureInPictureSize
}
set {
impl.pictureInPictureSize = newValue
}
}
var pictureInPicturePosition: HKPictureInPicureControllerPosition {
get {
impl.pictureInPicturePosition
}
set {
impl.pictureInPicturePosition = newValue
}
}
var pictureInPictureMargin: CGFloat {
get {
impl.pictureInPictureMargin
}
set {
impl.pictureInPictureMargin = newValue
}
}
var pictureInPictureCornerRadius: CGFloat {
get {
impl.pictureInPictureCornerRadius
}
set {
impl.pictureInPictureCornerRadius = newValue
}
}
var pictureInPictureAnimationDuration: TimeInterval {
get {
impl.pictureInPictureAnimationDuration
}
set {
impl.pictureInPictureAnimationDuration = newValue
}
}
private var impl: HKPictureInPicureControllerImpl {
get {
guard let object = objc_getAssociatedObject(self, &HKPictureInPicureControllerImplKey) as? HKPictureInPicureControllerImpl else {
let impl = HKPictureInPicureControllerImpl(self)
objc_setAssociatedObject(self, &HKPictureInPicureControllerImplKey, impl, .OBJC_ASSOCIATION_RETAIN)
return impl
}
return object
}
set {
objc_setAssociatedObject(self, &HKPictureInPicureControllerImplKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
func startPictureInPicture() {
guard !impl.isPictureInPictureActive else {
return
}
impl.startPictureInPicture()
}
func stopPictureInPicture() {
guard impl.isPictureInPictureActive else {
return
}
impl.stopPictureInPicture()
}
}

View File

@ -0,0 +1,134 @@
import Foundation
class HKPictureInPicureControllerImpl: HKPictureInPicureController {
static let margin: CGFloat = 16
static let position: HKPictureInPicureControllerPosition = .bottomRight
static let cornerRadius: CGFloat = 8
static let animationDuration: TimeInterval = 0.3
// MARK: HKPictureInPicureController
var isPictureInPictureActive: Bool = false
var pictureInPictureSize: CGSize = .init(width: 160, height: 90)
var pictureInPictureMargin: CGFloat = HKPictureInPicureControllerImpl.margin
var pictureInPicturePosition: HKPictureInPicureControllerPosition = HKPictureInPicureControllerImpl.position
var pictureInPictureCornerRadius: CGFloat = HKPictureInPicureControllerImpl.cornerRadius
var pictureInPictureAnimationDuration: TimeInterval = HKPictureInPicureControllerImpl.animationDuration
private var window: UIWindow?
private var origin: CGPoint = .zero
private var parent: UIViewController?
private let viewController: UIViewController
private lazy var panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPanGestureRecognizer(_:)))
init(_ viewController: UIViewController) {
self.viewController = viewController
self.parent = viewController.parent
}
func startPictureInPicture() {
guard !isPictureInPictureActive else {
return
}
toggleWindow()
viewController.view.addGestureRecognizer(panGestureRecognizer)
NotificationCenter.default.addObserver(self, selector: #selector(orientationDidChange), name: UIDevice.orientationDidChangeNotification, object: nil)
isPictureInPictureActive = true
}
func stopPictureInPicture() {
guard isPictureInPictureActive else {
return
}
toggleWindow()
viewController.view.removeGestureRecognizer(panGestureRecognizer)
NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
isPictureInPictureActive = false
}
private func toggleWindow() {
if viewController.view.window?.rootViewController == viewController {
if window == nil {
window = viewController.view.window
if #available(iOSApplicationExtension 11.0, *) {
transform(parent?.view.window?.safeAreaInsets ?? .zero)
} else {
transform()
}
viewController.view.layer.cornerRadius = pictureInPictureCornerRadius
} else {
viewController.view.layer.cornerRadius = 0
UIView.animate(withDuration: pictureInPictureAnimationDuration) { [weak self] in
guard let self = self else {
return
}
self.window?.frame = UIScreen.main.bounds
}
window = nil
}
return
}
if window == nil {
viewController.removeFromParent()
viewController.view.removeFromSuperview()
window = UIWindow(frame: .zero)
window?.rootViewController = viewController
window?.makeKeyAndVisible()
if #available(iOSApplicationExtension 11.0, *) {
transform(parent?.view.window?.safeAreaInsets ?? .zero)
} else {
transform()
}
viewController.view.layer.cornerRadius = pictureInPictureCornerRadius
} else {
window?.rootViewController = nil
window = nil
parent?.addChild(viewController)
parent?.view.addSubview(viewController.view)
viewController.view.layer.cornerRadius = 0
UIView.animate(withDuration: pictureInPictureAnimationDuration) { [weak self] in
guard let self = self else {
return
}
self.viewController.view.frame = self.parent?.view.bounds ?? .zero
}
}
}
private func transform(_ insets: UIEdgeInsets = .zero) {
UIView.animate(withDuration: pictureInPictureAnimationDuration) { [weak self] in
guard let self = self, let window = self.window else {
return
}
if self.origin == .zero || !UIScreen.main.bounds.contains(self.origin) {
window.frame = .init(origin: self.pictureInPicturePosition.CGPoint(self, insets: insets), size: self.pictureInPictureSize)
} else {
window.frame = .init(origin: self.origin, size: self.pictureInPictureSize)
}
}
}
@objc
private func orientationDidChange() {
switch UIDevice.current.orientation {
case .landscapeLeft, .landscapeRight, .portrait, .portraitUpsideDown:
if #available(iOSApplicationExtension 11.0, *) {
transform(parent?.view.window?.safeAreaInsets ?? .zero)
} else {
transform()
}
default:
break
}
}
@objc
private func didPanGestureRecognizer(_ sender: UIPanGestureRecognizer) {
guard let window = window else {
return
}
let point: CGPoint = sender.translation(in: viewController.view)
window.center = CGPoint(x: window.center.x + point.x, y: window.center.y + point.y)
origin = window.frame.origin
sender.setTranslation(.zero, in: viewController.view)
}
}