MMPlayerView
Custom AVPlayerLayer on view and transition player with good effect like youtube and facebook
Install / Use
/learn @MillmanY/MMPlayerViewREADME
MMPlayerView
Demo-Swift
List / Shrink / Transition / Landscape

MMPlayerLayer
ex. use when change player view frequently like tableView / collectionView
import MMPlayerView
mmPlayerLayer.playView = cell.imgView
mmPlayerLayer.getStatusBlock { [weak self] (status) in
}
self.mmPlayerLayer.set(url: DemoSource.shared.demoData[indexPath.row].play_Url)
self.mmPlayerLayer.resume()
Transition
##PresentedViewController
1. Set transition config
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.mmPlayerTransition.present.pass { (config) in
// setting
.....
}
}
2. Set MMPLayerToProtocol on PresentedViewController
3. Set MMPlayerPrsentFromProtocol on PresentingViewController
Landscape
1.Set MMPlayerLayer
// roation screen to landscape can change player to fullscreen
mmplayerLayer.fullScreenWhenLandscape = true
2. Set from code
mmplayerLayer.setOrientation(.landsacpeLeft)
3. Observer
mmPlayerLayer.getOrientationChange { (status) in
print("Player OrientationChange \(status)")
}
Cover View

## add cover item view on player
play.replace(cover: CoverA.instantiateFromNib())
Progress
// Custom your progress view and it will add on player center
// view need to implement ProgressProtocol, and add progress in this view, when start/stop control what need to do
.custom(view: <#T##MMProgressProtocol#>)
public protocol MMProgressProtocol {
func start()
func stop()
}
Cache
playerLayer.cacheType = .memory(count: 10)
public enum MMPlayerCacheType {
case none
case memory(count: Int) // set this to cache seek time in memory and if cache out of count will remove first you
stored
}
Layer Protocol
// detect if touch in videoRect
// if touch out of videoRect will not trigger show/hide cover view event
public protocol MMPlayerLayerProtocol: class {
func touchInVideoRect(contain:
) //
}
Downloader
var downloadObservation: MMPlayerObservation?
downloadObservation = MMPlayerDownloader.shared.observe(downloadURL: downloadURL) { [weak self] (status) in
switch status {
case .cancelled:
print("Canceld")
case .completed:
DispatchQueue.main.async {
self?.downloadBtn.setTitle("Delete", for: .normal)
self?.downloadBtn.isHidden = false
self?.progress.isHidden = true
}
case .downloading(let value):
self?.downloadBtn.isHidden = true
self?.progress.isHidden = false
self?.progress.progress = value
print("Exporting \(value) \(downloadURL)")
case .failed(let err):
DispatchQueue.main.async {
self?.downloadBtn.setTitle("Download", for: .normal)
}
self?.downloadBtn.isHidden = false
self?.progress.isHidden = true
print("Download Failed \(err)")
case .none:
DispatchQueue.main.async {
self?.downloadBtn.setTitle("Download", for: .normal)
}
self?.downloadBtn.isHidden = false
self?.progress.isHidden = true
case .exist:
DispatchQueue.main.async {
self?.downloadBtn.setTitle("Delete", for: .normal)
}
self?.downloadBtn.isHidden = false
self?.progress.isHidden = true
}
Delete
if let info = MMPlayerDownloader.shared.localFileFrom(url: downloadURL) {
MMPlayerDownloader.shared.deleteVideo(info)
}
Subtitle

let subTitleStr = Bundle.main.path(forResource: "test", ofType: "srt")!
if let str = try? String.init(contentsOfFile: subTitleStr) {
self.mmPlayerLayer.subtitleSetting.subtitleType = .srt(info: str)
self.mmPlayerLayer.subtitleSetting.defaultTextColor = .red
self.mmPlayerLayer.subtitleSetting.defaultFont = UIFont.boldSystemFont(ofSize: 20)
}
}
Shrink
// Return a view which you want back
self.mmPlayerLayer.shrinkView(onVC: self, isHiddenVC: false) { [weak self] () -> UIView? in
guard let self = self, let path = self.findCurrentPath() else {return nil}
let cell = self.findCurrentCell(path: path) as! PlayerCell
self.mmPlayerLayer.set(url: cell.data!.play_Url)
self.mmPlayerLayer.resume()
return cell.imgView
}
// Check player is shrink or not
self.mmPlayerL.isShrink
Demo-SwiftUI
Control
let control = MMPlayerControl()
//Play
control.set(url: self.videoList[idx].play_Url)
control.resume()
//InValidate
control.invalidate()
//Observation parameter
@Published
public var orientation: PlayerOrientation = .protrait
@Published
public var timeInfo = TimeInfo()
@Published
public var isMuted = false
@Published
public var isBackgroundPause = true
@Published
@Published
public var repeatWhenEnd: Bool = false
@Published
public var isLoading: Bool = false
public var cacheType: PlayerCacheType = .none
@Published
public private(set) var isCoverShow = false
@Published
public var autoHideCoverType = CoverAutoHideType.disable
public var coverAnimationInterval = 0.3
@Published
public var error: MMPlayerViewUIError?
Init View
let player = MMPlayerViewUI(control: self.control, cover: CoverAUI())
Download
view.modifier(MMPlayerDownloaderModifier(url: obj.play_Url!, status: $downloadStatus))
Transition Player
//func playerTransition<Content: View>(view: Content, from: CGRect?) -> AnyTransition
if showDetailIdx != nil {
DetailView(obj: self.playListViewModel.videoList[showDetailIdx!], showDetailIdx: $showDetailIdx)
.edgesIgnoringSafeArea(.all)
.transition(.playerTransition(view: MMPlayerViewUI(control: control) ,from: fromFrame))
.zIndex(1)
}
// Trigger transition
withAnimation {
self.fromFrame = obj.frame
self.showDetailIdx = offset
}
Requirements
iOS 12.0+
Swift 5.0+
Installation
MMPlayerView is available through CocoaPods. To install it, simply add the following line to your Podfile:
Swift 5
pod 'MMPlayerView'
Author
millmanyang@gmail.com
License
MMPlayerView is available under the MIT license. See the LICENSE file for more info.
Related Skills
node-connect
345.9kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
106.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
345.9kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
345.9kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。

