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 slide show thumbnails #358

Open
wants to merge 2 commits into
base: master
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
61 changes: 61 additions & 0 deletions Example/BMPlayer/BMPlayerCustomControlView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,24 @@ import BMPlayer

class BMPlayerCustomControlView: BMPlayerControlView {

/// slider Thumbnails
var thumbnailsImageView = UIImageView()

var playbackRateButton = UIButton(type: .custom)
var playRate: Float = 1.0

var rotateButton = UIButton(type: .custom)
var rotateCount: CGFloat = 0

var videoSize: CGSize = CGSize(width: 160, height: 90) {
didSet {
thumbnailsImageView.snp.updateConstraints { make in
make.width.equalTo(videoSize.width)
make.height.equalTo(videoSize.height)
}
}
}

/**
Override if need to customize UI components
*/
Expand Down Expand Up @@ -54,6 +66,14 @@ class BMPlayerCustomControlView: BMPlayerControlView {
$0.right.equalTo(playbackRateButton.snp.left).offset(-5)
$0.centerY.equalTo(chooseDefinitionView)
}
mainMaskView.addSubview(thumbnailsImageView)
thumbnailsImageView.isHidden = true
thumbnailsImageView.snp.remakeConstraints { [unowned self](make) in
make.bottom.equalTo(bottomWrapperView.snp.top)
make.width.equalTo(videoSize.width)
make.height.equalTo(videoSize.height)
make.centerX.equalToSuperview()
}
}


Expand Down Expand Up @@ -105,7 +125,30 @@ class BMPlayerCustomControlView: BMPlayerControlView {
delegate?.controlView?(controlView: self, didChangeVideoPlaybackRate: playRate)
}

override func showSeekToView(to toSecound: TimeInterval, total totalDuration:TimeInterval, isAdd: Bool) {
super.showSeekToView(to: toSecound, total: totalDuration, isAdd: isAdd)
self.showThumbnail(toSecound: toSecound)
}

override func progressSliderValueChanged(_ sender: UISlider) {
super.progressSliderValueChanged(sender)
let toSecound = Double(sender.value) * totalDuration
self.showThumbnail(toSecound: toSecound)
}

override func progressSliderTouchEnded(_ sender: UISlider) {
super.progressSliderTouchEnded(sender)
self.hideThumbnailsImage()
}

override func hideSeekToView() {
super.hideSeekToView()
self.hideThumbnailsImage()
}

func hideThumbnailsImage() {
self.thumbnailsImageView.isHidden = true
}

@objc func onRotateButtonPressed() {
guard let layer = player?.playerLayer else {
Expand All @@ -116,4 +159,22 @@ class BMPlayerCustomControlView: BMPlayerControlView {
layer.transform = CGAffineTransform(rotationAngle: rotateCount * CGFloat(Double.pi/2))
layer.frame = player!.bounds
}

/// 显示
func showThumbnail(toSecound: TimeInterval) {
guard let playerLayer = self.player?.playerLayer else {
return
}
self.thumbnailsImageView.isHidden = false
if !playerLayer.isM3U8 {
playerLayer.generateThumbnails(times: [toSecound], maximumSize: CGSize(width: self.videoSize.width, height: self.videoSize.height)) { (thumbnails) in
if !thumbnails.isEmpty {
let thumbnail = thumbnails[0]
if thumbnail.result == .succeeded {
self.thumbnailsImageView.image = thumbnail.image
}
}
}
}
}
}
6 changes: 3 additions & 3 deletions Example/BMPlayer/VideoPlayViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ class VideoPlayViewController: UIViewController {
player.setVideo(resource: asset)

case (2,0):
player.panGesture.isEnabled = false
// player.panGesture.isEnabled = false
let asset = self.preparePlayerItem()
player.setVideo(resource: asset)

Expand Down Expand Up @@ -228,9 +228,9 @@ class VideoPlayViewController: UIViewController {
准备播放器资源model
*/
func preparePlayerItem() -> BMPlayerResource {
let res0 = BMPlayerResourceDefinition(url: URL(string: "http://baobab.wdjcdn.com/1457162012752491010143.mp4")!,
let res0 = BMPlayerResourceDefinition(url: URL(string: "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")!,
definition: "高清")
let res1 = BMPlayerResourceDefinition(url: URL(string: "http://baobab.wdjcdn.com/1457162012752491010143.mp4")!,
let res1 = BMPlayerResourceDefinition(url: URL(string: "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")!,
definition: "标清")

let asset = BMPlayerResource(name: "周末号外丨中国第一高楼",
Expand Down
8 changes: 4 additions & 4 deletions Source/BMPlayerControlView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ open class BMPlayerControlView: UIView {
self.maskImageView.image = nil
}
self.hideLoader()
});
})
}
}
}
Expand Down Expand Up @@ -426,19 +426,19 @@ open class BMPlayerControlView: UIView {
}

// MARK: - handle UI slider actions
@objc func progressSliderTouchBegan(_ sender: UISlider) {
@objc open func progressSliderTouchBegan(_ sender: UISlider) {
delegate?.controlView(controlView: self, slider: sender, onSliderEvent: .touchDown)
}

@objc func progressSliderValueChanged(_ sender: UISlider) {
@objc open func progressSliderValueChanged(_ sender: UISlider) {
hidePlayToTheEndView()
cancelAutoFadeOutAnimation()
let currentTime = Double(sender.value) * totalDuration
currentTimeLabel.text = BMPlayer.formatSecondsToString(currentTime)
delegate?.controlView(controlView: self, slider: sender, onSliderEvent: .valueChanged)
}

@objc func progressSliderTouchEnded(_ sender: UISlider) {
@objc open func progressSliderTouchEnded(_ sender: UISlider) {
autoFadeOutControlViewWithAnimation()
delegate?.controlView(controlView: self, slider: sender, onSliderEvent: .touchUpInside)
}
Expand Down
139 changes: 119 additions & 20 deletions Source/BMPlayerLayerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ public enum BMPlayerAspectRatio : Int {
case four2THREE
}

/// 返回的截图对象
public struct BMPlayerThumbnail {
public var requestedTime: CMTime
public var image: UIImage?
public var actualTime: CMTime
public var result: AVAssetImageGenerator.Result
public var error: Error?
}

public protocol BMPlayerLayerViewDelegate : class {
func bmPlayer(player: BMPlayerLayerView, playerStateDidChange state: BMPlayerState)
func bmPlayer(player: BMPlayerLayerView, loadedTimeDidChange loadedDuration: TimeInterval, totalDuration: TimeInterval)
Expand All @@ -56,6 +65,13 @@ open class BMPlayerLayerView: UIView {
/// 视频跳转秒数置0
open var seekTime = 0

/// 视频截图
open private(set) var imageGenerator: AVAssetImageGenerator?
/// 视频截图m3u8
open private(set) var videoOutput: AVPlayerItemVideoOutput?

open private(set) var isM3U8 = false

/// 播放属性
open var playerItem: AVPlayerItem? {
didSet {
Expand Down Expand Up @@ -96,7 +112,18 @@ open class BMPlayerLayerView: UIView {
/// 计时器
var timer: Timer?

fileprivate var urlAsset: AVURLAsset?
fileprivate var urlAsset: AVURLAsset?{
didSet{
if oldValue != urlAsset{
if urlAsset != nil {
self.imageGenerator = AVAssetImageGenerator(asset: urlAsset!)
}else{
self.imageGenerator = nil
}
self.isM3U8 = (urlAsset?.url.pathExtension == "m3u8")
}
}
}

fileprivate var lastPlayerItem: AVPlayerItem?
/// playerLayer
Expand Down Expand Up @@ -184,24 +211,27 @@ open class BMPlayerLayerView: UIView {
}

open func resetPlayer() {
// 初始化状态变量

self.playDidEnd = false
self.playerItem = nil
self.lastPlayerItem = nil
self.seekTime = 0

self.timer?.invalidate()

self.pause()
// 移除原来的layer
self.playerLayer?.removeFromSuperlayer()
// 替换PlayerItem为nil
self.player?.replaceCurrentItem(with: nil)
player?.removeObserver(self, forKeyPath: "rate")

// 把player置为nil
self.player = nil
if self.videoOutput != nil {
self.playerItem?.remove(self.videoOutput!)
}
self.videoOutput = nil

self.playDidEnd = false
self.playerItem = nil
self.lastPlayerItem = nil
self.seekTime = 0

self.timer?.invalidate()

self.pause()
// 移除原来的layer
self.playerLayer?.removeFromSuperlayer()
// 替换PlayerItem为nil
self.player?.replaceCurrentItem(with: nil)
player?.removeObserver(self, forKeyPath: "rate")

// 把player置为nil
self.player = nil
}

open func prepareToDeinit() {
Expand All @@ -218,7 +248,7 @@ open class BMPlayerLayerView: UIView {
if secounds.isNaN {
return
}
setupTimer()
// setupTimer()
if self.player?.currentItem?.status == AVPlayerItem.Status.readyToPlay {
let draggedTime = CMTime(value: Int64(secounds), timescale: 1)
self.player!.seek(to: draggedTime, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero, completionHandler: { (finished) in
Expand Down Expand Up @@ -267,6 +297,10 @@ open class BMPlayerLayerView: UIView {
}

fileprivate func configPlayer(){
if self.videoOutput != nil {
self.playerItem?.remove(self.videoOutput!)
}
self.videoOutput = nil
player?.removeObserver(self, forKeyPath: "rate")
playerItem = AVPlayerItem(asset: urlAsset!)
player = AVPlayer(playerItem: playerItem!)
Expand Down Expand Up @@ -459,3 +493,68 @@ open class BMPlayerLayerView: UIView {
}
}

// MARK: - generateThumbnails
extension BMPlayerLayerView {
//不支持m3u8
public func generateThumbnails(times: [TimeInterval],maximumSize: CGSize, completionHandler: @escaping (([BMPlayerThumbnail]) -> Swift.Void )){
guard let imageGenerator = self.imageGenerator else {
return
}

var values = [NSValue]()
for time in times {
values.append(NSValue(time: CMTimeMakeWithSeconds(time,preferredTimescale: CMTimeScale(NSEC_PER_SEC))))
}

var thumbnailCount = values.count
var thumbnails = [BMPlayerThumbnail]()
imageGenerator.cancelAllCGImageGeneration()
imageGenerator.appliesPreferredTrackTransform = true// 截图的时候调整到正确的方向
imageGenerator.maximumSize = maximumSize//设置后可以获取缩略图
imageGenerator.generateCGImagesAsynchronously(forTimes:values) { (requestedTime: CMTime,image: CGImage?,actualTime: CMTime,result: AVAssetImageGenerator.Result,error: Error?) in

let thumbnail = BMPlayerThumbnail(requestedTime: requestedTime, image: image == nil ? nil : UIImage(cgImage: image!) , actualTime: actualTime, result: result, error: error)
thumbnails.append(thumbnail)
thumbnailCount -= 1
if thumbnailCount == 0 {
DispatchQueue.main.async {
completionHandler(thumbnails)
}

}
}
}


//支持m3u8
public func snapshotImage() -> UIImage? {
guard let playerItem = self.playerItem else { //playerItem is AVPlayerItem
return nil
}

if self.videoOutput == nil {
self.videoOutput = AVPlayerItemVideoOutput(pixelBufferAttributes: nil)
playerItem.remove(self.videoOutput!)
playerItem.add(self.videoOutput!)
}

guard let videoOutput = self.videoOutput else {
return nil
}

let time = videoOutput.itemTime(forHostTime: CACurrentMediaTime())
if videoOutput.hasNewPixelBuffer(forItemTime: time) {
let lastSnapshotPixelBuffer = videoOutput.copyPixelBuffer(forItemTime: time, itemTimeForDisplay: nil)
if lastSnapshotPixelBuffer != nil {
let ciImage = CIImage(cvPixelBuffer: lastSnapshotPixelBuffer!)
let context = CIContext(options: nil)
let rect = CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(CVPixelBufferGetWidth(lastSnapshotPixelBuffer!)), height: CGFloat(CVPixelBufferGetHeight(lastSnapshotPixelBuffer!)))
let cgImage = context.createCGImage(ciImage, from: rect)
if cgImage != nil {
return UIImage(cgImage: cgImage!)
}
}
}
return nil
}
}