Skip to content

Commit

Permalink
add PictureInPicture
Browse files Browse the repository at this point in the history
  • Loading branch information
kingslay committed Oct 10, 2021
1 parent 8c157d5 commit 68d76c8
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 38 deletions.
20 changes: 18 additions & 2 deletions Demo/demo-iOS/demo-iOS/RootViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class RootViewController: UIViewController {
super.viewDidLoad()
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
playerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
Expand Down Expand Up @@ -106,8 +107,17 @@ extension RootViewController: UITableViewDelegate {
guard let cell = tableView.cellForRow(at: indexPath) as? TableViewCell else {
return
}
playerView.set(resource: objects[indexPath.row])
if playerView.resource != objects[indexPath.row] {
playerView.set(resource: objects[indexPath.row])
}
cell.videoView.addSubview(playerView)
playerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
playerView.topAnchor.constraint(equalTo: cell.videoView.topAnchor),
playerView.leadingAnchor.constraint(equalTo: cell.videoView.leadingAnchor),
playerView.trailingAnchor.constraint(equalTo: cell.videoView.trailingAnchor),
playerView.bottomAnchor.constraint(equalTo: cell.videoView.bottomAnchor),
])
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
Expand Down Expand Up @@ -147,7 +157,13 @@ extension RootViewController: UITableViewDelegate {
}
if playerView.resource != objects[index.row] {
playerView.set(resource: objects[index.row])
cell.videoView.addSubview(playerView)
}
cell.videoView.addSubview(playerView)
NSLayoutConstraint.activate([
playerView.topAnchor.constraint(equalTo: cell.videoView.topAnchor),
playerView.leadingAnchor.constraint(equalTo: cell.videoView.leadingAnchor),
playerView.trailingAnchor.constraint(equalTo: cell.videoView.trailingAnchor),
playerView.bottomAnchor.constraint(equalTo: cell.videoView.bottomAnchor),
])
}
}
7 changes: 6 additions & 1 deletion Sources/KSPlayer/AVPlayer/KSAVPlayer.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import AVFoundation

import AVKit
#if canImport(UIKit)
import UIKit
#else
Expand Down Expand Up @@ -90,6 +90,11 @@ public class KSAVPlayer {
}
}

@available(tvOS 14.0, *)
public private(set) lazy var pipController: AVPictureInPictureController? = {
AVPictureInPictureController(playerLayer: playerView.playerLayer)
}()

public private(set) var bufferingProgress = 0 {
didSet {
delegate?.changeBuffering(player: self, progress: bufferingProgress)
Expand Down
3 changes: 3 additions & 0 deletions Sources/KSPlayer/AVPlayer/PlayerDefines.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import AVFoundation
import AVKit
import CoreMedia
#if canImport(UIKit)
import UIKit
Expand Down Expand Up @@ -40,6 +41,8 @@ public protocol MediaPlayerProtocol: MediaPlayback {
var playbackVolume: Float { get set }
var contentMode: UIViewContentMode { get set }
var subtitleDataSouce: SubtitleDataSouce? { get }
@available(tvOS 14.0, *)
var pipController: AVPictureInPictureController? { get }
init(url: URL, options: KSOptions)
func replace(url: URL, options: KSOptions)
func play()
Expand Down
11 changes: 11 additions & 0 deletions Sources/KSPlayer/Basic/Utility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,17 @@ extension UIView {
superview?.constraints.first { $0.firstItem === self && $0.firstAttribute == .centerY }
}

var frameConstraints: [NSLayoutConstraint] {
var frameConstraint = superview?.constraints.filter { constraint in
constraint.firstItem === self
} ?? [NSLayoutConstraint]()
for constraint in constraints where
constraint.isMember(of: NSLayoutConstraint.self) && constraint.firstItem === self && (constraint.firstAttribute == .width || constraint.firstAttribute == .height) {
frameConstraint.append(constraint)
}
return frameConstraint
}

var safeTopAnchor: NSLayoutYAxisAnchor {
if #available(macOS 11.0, *) {
return self.safeAreaLayoutGuide.topAnchor
Expand Down
6 changes: 5 additions & 1 deletion Sources/KSPlayer/Core/KSPlayerLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ extension KSPlayerLayer {
}

private func updateNowPlayingInfo() {
guard let player = self.player else { return }
guard let player = player else { return }
if MPNowPlayingInfoCenter.default().nowPlayingInfo == nil {
MPNowPlayingInfoCenter.default().nowPlayingInfo = [MPMediaItemPropertyPlaybackDuration: player.duration]
} else {
Expand Down Expand Up @@ -424,6 +424,10 @@ extension KSPlayerLayer {
return
}

if player.pipController?.isPictureInPictureActive ?? false {
return
}

if KSPlayerManager.canBackgroundPlay {
player.enterBackground()
return
Expand Down
5 changes: 5 additions & 0 deletions Sources/KSPlayer/Core/PlayerToolBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class PlayerToolBar: UIStackView {
public var timeSlider = KSSlider()
public var playbackRateButton = UIButton()
public var definitionButton = UIButton()
public var pipButton = UIButton()
public var currentTime: TimeInterval = 0 {
didSet {
guard !currentTime.isNaN else {
Expand Down Expand Up @@ -98,6 +99,9 @@ public class PlayerToolBar: UIStackView {
srtButton.setTitle(NSLocalizedString("subtitle", comment: ""), for: .normal)
srtButton.titleFont = .systemFont(ofSize: 14, weight: .medium)
srtButton.tag = PlayerButtonType.srt.rawValue
pipButton.titleFont = .systemFont(ofSize: 14, weight: .medium)
pipButton.setTitle(NSLocalizedString("pip", comment: ""), for: .normal)
pipButton.tag = PlayerButtonType.pictureInPicture.rawValue
playButton.translatesAutoresizingMaskIntoConstraints = false
srtButton.translatesAutoresizingMaskIntoConstraints = false
translatesAutoresizingMaskIntoConstraints = false
Expand All @@ -118,6 +122,7 @@ public class PlayerToolBar: UIStackView {
playbackRateButton.addTarget(target, action: action, for: .primaryActionTriggered)
definitionButton.addTarget(target, action: action, for: .primaryActionTriggered)
srtButton.addTarget(target, action: action, for: .primaryActionTriggered)
pipButton.addTarget(target, action: action, for: .primaryActionTriggered)
}

public func reset() {
Expand Down
3 changes: 2 additions & 1 deletion Sources/KSPlayer/Core/PlayerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public enum PlayerButtonType: Int {
case lock = 107
case rate = 108
case definition = 109
case pictureInPicture = 110
}

public protocol PlayerControllerDelegate: AnyObject {
Expand Down Expand Up @@ -180,7 +181,7 @@ extension PlayerView {

extension UIView {
var viewController: UIViewController? {
var next = self.next
var next = next
while next != nil {
if let viewController = next as? UIViewController {
return viewController
Expand Down
36 changes: 35 additions & 1 deletion Sources/KSPlayer/MEPlayer/KSMEPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
//

import AVFoundation
import AVKit
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif

public class KSMEPlayer {
public class KSMEPlayer: NSObject {
private var loopCount = 1
private let audioOutput: AudioPlayer & FrameOutput = AudioGraphPlayer()
private var playerItem: MEPlayerItem
Expand All @@ -24,6 +25,16 @@ public class KSMEPlayer {
}
}

@available(tvOS 14.0, *)
public private(set) lazy var pipController: AVPictureInPictureController? = {
if #available(iOS 15.0, tvOS 15.0, macOS 12.0, *) {
let contentSource = AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: videoOutput.displayLayer, playbackDelegate: self)
return AVPictureInPictureController(contentSource: contentSource)
} else {
return nil
}
}()

public private(set) var playableTime = TimeInterval(0)
public weak var delegate: MediaPlayerDelegate?
public private(set) var isPreparedToPlay = false
Expand Down Expand Up @@ -60,6 +71,7 @@ public class KSMEPlayer {
playerItem = MEPlayerItem(url: url, options: options)
videoOutput = MetalPlayView(options: options)
self.options = options
super.init()
playerItem.delegate = self
audioOutput.renderSource = playerItem
videoOutput.renderSource = playerItem
Expand Down Expand Up @@ -348,6 +360,28 @@ extension KSMEPlayer: MediaPlayerProtocol {
}
}

extension KSMEPlayer: AVPictureInPictureSampleBufferPlaybackDelegate {
public func pictureInPictureController(_: AVPictureInPictureController, setPlaying playing: Bool) {
playing ? play() : pause()
}

public func pictureInPictureControllerTimeRangeForPlayback(_: AVPictureInPictureController) -> CMTimeRange {
CMTimeRange(start: currentPlaybackTime, end: currentPlaybackTime + playableTime)
}

public func pictureInPictureControllerIsPlaybackPaused(_: AVPictureInPictureController) -> Bool {
isPlaying
}

public func pictureInPictureController(_: AVPictureInPictureController, didTransitionToRenderSize _: CMVideoDimensions) {}

public func pictureInPictureController(_: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) {
seek(time: currentPlaybackTime + skipInterval.seconds) { _ in
completionHandler()
}
}
}

public extension KSMEPlayer {
var subtitleDataSouce: SubtitleDataSouce? { playerItem }

Expand Down
2 changes: 1 addition & 1 deletion Sources/KSPlayer/MEPlayer/SubtitleDecode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class SubtitleDecode: DecodeProtocol {
func shutdown() {
scale.shutdown()
avsubtitle_free(&subtitle)
if let codecContext = self.codecContext {
if let codecContext = codecContext {
avcodec_close(codecContext)
avcodec_free_context(&self.codecContext)
}
Expand Down
17 changes: 17 additions & 0 deletions Sources/KSPlayer/Video/IOSVideoPlayerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import UIKit

open class IOSVideoPlayerView: VideoPlayerView {
private weak var originalSuperView: UIView?
private var originalframeConstraints: [NSLayoutConstraint]?
private var originalFrame = CGRect.zero
private var originalOrientations: UIInterfaceOrientationMask?
private weak var fullScreenDelegate: PlayerViewFullScreenDelegate?
private var isPlayingForCall = false
Expand Down Expand Up @@ -141,10 +143,19 @@ open class IOSVideoPlayerView: VideoPlayerView {
return
}
originalSuperView = superview
originalframeConstraints = frameConstraints
originalFrame = frame
originalOrientations = viewController.supportedInterfaceOrientations
let fullVC = PlayerFullScreenViewController(isHorizonal: isHorizonal)
fullScreenDelegate = fullVC
fullVC.view.addSubview(self)
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: fullVC.view.readableTopAnchor),
leadingAnchor.constraint(equalTo: fullVC.view.leadingAnchor),
trailingAnchor.constraint(equalTo: fullVC.view.trailingAnchor),
bottomAnchor.constraint(equalTo: fullVC.view.bottomAnchor),
])
fullVC.modalPresentationStyle = .fullScreen
fullVC.modalPresentationCapturesStatusBarAppearance = true
fullVC.transitioningDelegate = self
Expand All @@ -159,6 +170,12 @@ open class IOSVideoPlayerView: VideoPlayerView {
KSPlayerManager.supportedInterfaceOrientations = .portrait
presentingVC.dismiss(animated: true) {
self.originalSuperView?.addSubview(self)
if let constraints = self.originalframeConstraints, constraints.count > 0 {
NSLayoutConstraint.activate(constraints)
} else {
self.translatesAutoresizingMaskIntoConstraints = true
self.frame = self.originalFrame
}
if let originalOrientations = self.originalOrientations {
KSPlayerManager.supportedInterfaceOrientations = originalOrientations
}
Expand Down
44 changes: 13 additions & 31 deletions Sources/KSPlayer/Video/VideoPlayerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by kintan on 16/4/29.
//
//

import AVKit
#if canImport(UIKit)
import UIKit
#else
Expand Down Expand Up @@ -115,36 +115,6 @@ open class VideoPlayerView: PlayerView {
setupUIComponents()
}

#if os(macOS)
override open func viewDidMoveToSuperview() {
super.viewDidMoveToSuperview()
guard let superview = superview else {
return
}
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: superview.readableTopAnchor),
leadingAnchor.constraint(equalTo: superview.leadingAnchor),
trailingAnchor.constraint(equalTo: superview.trailingAnchor),
bottomAnchor.constraint(equalTo: superview.bottomAnchor),
])
}
#else
override open func didMoveToSuperview() {
super.didMoveToSuperview()
guard let superview = superview else {
return
}
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: superview.readableTopAnchor),
leadingAnchor.constraint(equalTo: superview.leadingAnchor),
trailingAnchor.constraint(equalTo: superview.trailingAnchor),
bottomAnchor.constraint(equalTo: superview.bottomAnchor),
])
}
#endif

// MARK: - Action Response

override open func onButtonPressed(type: PlayerButtonType, button: UIButton) {
Expand All @@ -168,6 +138,15 @@ open class VideoPlayerView: PlayerView {
}
alertController.addAction(UIAlertAction(title: NSLocalizedString("cancel", comment: ""), style: .cancel, handler: nil))
viewController?.present(alertController, animated: true, completion: nil)
} else if type == .pictureInPicture {
guard let pipController = playerLayer.player?.pipController else {
return
}
if pipController.isPictureInPictureActive {
pipController.stopPictureInPicture()
} else {
pipController.startPictureInPicture()
}
}
}

Expand Down Expand Up @@ -535,9 +514,12 @@ extension VideoPlayerView {
toolBar.addArrangedSubview(toolBar.playbackRateButton)
toolBar.addArrangedSubview(toolBar.definitionButton)
toolBar.addArrangedSubview(toolBar.srtButton)
toolBar.addArrangedSubview(toolBar.pipButton)
toolBar.pipButton.isHidden = !AVPictureInPictureController.isPictureInPictureSupported()
toolBar.setCustomSpacing(20, after: toolBar.timeLabel)
toolBar.setCustomSpacing(20, after: toolBar.playbackRateButton)
toolBar.setCustomSpacing(20, after: toolBar.definitionButton)
toolBar.setCustomSpacing(20, after: toolBar.srtButton)
toolBar.timeSlider.translatesAutoresizingMaskIntoConstraints = false
topMaskView.translatesAutoresizingMaskIntoConstraints = false
bottomMaskView.translatesAutoresizingMaskIntoConstraints = false
Expand Down

0 comments on commit 68d76c8

Please sign in to comment.