ios 如何使用 Swift 播放本地视频?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25348877/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to play a local video with Swift?
提问by 36 By Design
I have a short mp4
video file that I've added to my current Xcode6 Beta project.
我有一个简短的mp4
视频文件,已添加到我当前的 Xcode6 Beta 项目中。
I want to play the video in my app.
我想在我的应用中播放视频。
After hours searching, I can't find anything remotely helpful. Is there a way to accomplish this with Swift or do you have to use Objective-C? Can I get pointed in the right direction? I can't be the only one wondering this.
经过几个小时的搜索,我找不到任何远程有用的东西。有没有办法用 Swift 来完成这个,或者你必须使用 Objective-C?我可以指出正确的方向吗?我不能是唯一一个想知道这一点的人。
回答by Luca Angeletti
Sure you can use Swift!
当然你可以使用 Swift!
1. Adding the video file
1. 添加视频文件
Add the video (lets call it video.m4v
) to your Xcode project
将视频(我们称之为video.m4v
)添加到您的 Xcode 项目中
2. Checking your video is into the Bundle
2. 检查您的视频是否在 Bundle 中
Open the Project Navigator
cmd+ 1
打开Project Navigator
cmd+1
Then select your project root
> your Target
> Build Phases
> Copy Bundle Resources
.
然后选择your project root
> your Target
> Build Phases
> Copy Bundle Resources
。
Your video MUSTbe here. If it's not, then you should add it using the plus button
您的视频必须在这里。如果不是,那么您应该使用加号按钮添加它
3. Code
3. 代码
Open your View Controller and write this code.
打开您的视图控制器并编写此代码。
import UIKit
import AVKit
import AVFoundation
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
playVideo()
}
private func playVideo() {
guard let path = Bundle.main.path(forResource: "video", ofType:"m4v") else {
debugPrint("video.m4v not found")
return
}
let player = AVPlayer(url: URL(fileURLWithPath: path))
let playerController = AVPlayerViewController()
playerController.player = player
present(playerController, animated: true) {
player.play()
}
}
}
回答by nyxee
another Swift 3 Example. The provided solution did not work for me.
另一个 Swift 3 示例。提供的解决方案对我不起作用。
private func playVideo(from file:String) {
let file = file.components(separatedBy: ".")
guard let path = Bundle.main.path(forResource: file[0], ofType:file[1]) else {
debugPrint( "\(file.joined(separator: ".")) not found")
return
}
let player = AVPlayer(url: URL(fileURLWithPath: path))
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.bounds
self.view.layer.addSublayer(playerLayer)
player.play()
}
useage:
用途:
playVideo(from: "video.extension")
Note: Check Copy Bundle Resourcesunder Build Phasesto ensure that the video is available to the Project.
注意:选中Build Phases下的Copy Bundle Resources以确保视频可用于项目。
回答by gbk
U can setup AVPlayer
in another way, that open for u full customization of your video Player screen
你可以AVPlayer
用另一种方式设置,打开你的视频播放器屏幕的完全自定义
Swift 2.3
斯威夫特 2.3
Create
UIView
subclass for playing video (basically u can use anyUIView
object and only needed isAVPlayerLayer
. I setup in this way because it much clearer for me)import AVFoundation import UIKit class PlayerView: UIView { override class func layerClass() -> AnyClass { return AVPlayerLayer.self } var player:AVPlayer? { set { if let layer = layer as? AVPlayerLayer { layer.player = player } } get { if let layer = layer as? AVPlayerLayer { return layer.player } else { return nil } } } }
Setup your player
import AVFoundation import Foundation protocol VideoPlayerDelegate { func downloadedProgress(progress:Double) func readyToPlay() func didUpdateProgress(progress:Double) func didFinishPlayItem() func didFailPlayToEnd() } let videoContext:UnsafeMutablePointer<Void> = nil class VideoPlayer : NSObject { private var assetPlayer:AVPlayer? private var playerItem:AVPlayerItem? private var urlAsset:AVURLAsset? private var videoOutput:AVPlayerItemVideoOutput? private var assetDuration:Double = 0 private var playerView:PlayerView? private var autoRepeatPlay:Bool = true private var autoPlay:Bool = true var delegate:VideoPlayerDelegate? var playerRate:Float = 1 { didSet { if let player = assetPlayer { player.rate = playerRate > 0 ? playerRate : 0.0 } } } var volume:Float = 1.0 { didSet { if let player = assetPlayer { player.volume = volume > 0 ? volume : 0.0 } } } // MARK: - Init convenience init(urlAsset:NSURL, view:PlayerView, startAutoPlay:Bool = true, repeatAfterEnd:Bool = true) { self.init() playerView = view autoPlay = startAutoPlay autoRepeatPlay = repeatAfterEnd if let playView = playerView, let playerLayer = playView.layer as? AVPlayerLayer { playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill } initialSetupWithURL(urlAsset) prepareToPlay() } override init() { super.init() } // MARK: - Public func isPlaying() -> Bool { if let player = assetPlayer { return player.rate > 0 } else { return false } } func seekToPosition(seconds:Float64) { if let player = assetPlayer { pause() if let timeScale = player.currentItem?.asset.duration.timescale { player.seekToTime(CMTimeMakeWithSeconds(seconds, timeScale), completionHandler: { (complete) in self.play() }) } } } func pause() { if let player = assetPlayer { player.pause() } } func play() { if let player = assetPlayer { if (player.currentItem?.status == .ReadyToPlay) { player.play() player.rate = playerRate } } } func cleanUp() { if let item = playerItem { item.removeObserver(self, forKeyPath: "status") item.removeObserver(self, forKeyPath: "loadedTimeRanges") } NSNotificationCenter.defaultCenter().removeObserver(self) assetPlayer = nil playerItem = nil urlAsset = nil } // MARK: - Private private func prepareToPlay() { let keys = ["tracks"] if let asset = urlAsset { asset.loadValuesAsynchronouslyForKeys(keys, completionHandler: { dispatch_async(dispatch_get_main_queue(), { self.startLoading() }) }) } } private func startLoading(){ var error:NSError? guard let asset = urlAsset else {return} let status:AVKeyValueStatus = asset.statusOfValueForKey("tracks", error: &error) if status == AVKeyValueStatus.Loaded { assetDuration = CMTimeGetSeconds(asset.duration) let videoOutputOptions = [kCVPixelBufferPixelFormatTypeKey as String : Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)] videoOutput = AVPlayerItemVideoOutput(pixelBufferAttributes: videoOutputOptions) playerItem = AVPlayerItem(asset: asset) if let item = playerItem { item.addObserver(self, forKeyPath: "status", options: .Initial, context: videoContext) item.addObserver(self, forKeyPath: "loadedTimeRanges", options: [.New, .Old], context: videoContext) NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(playerItemDidReachEnd), name: AVPlayerItemDidPlayToEndTimeNotification, object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(didFailedToPlayToEnd), name: AVPlayerItemFailedToPlayToEndTimeNotification, object: nil) if let output = videoOutput { item.addOutput(output) item.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmVarispeed assetPlayer = AVPlayer(playerItem: item) if let player = assetPlayer { player.rate = playerRate } addPeriodicalObserver() if let playView = playerView, let layer = playView.layer as? AVPlayerLayer { layer.player = assetPlayer print("player created") } } } } } private func addPeriodicalObserver() { let timeInterval = CMTimeMake(1, 1) if let player = assetPlayer { player.addPeriodicTimeObserverForInterval(timeInterval, queue: dispatch_get_main_queue(), usingBlock: { (time) in self.playerDidChangeTime(time) }) } } private func playerDidChangeTime(time:CMTime) { if let player = assetPlayer { let timeNow = CMTimeGetSeconds(player.currentTime()) let progress = timeNow / assetDuration delegate?.didUpdateProgress(progress) } } @objc private func playerItemDidReachEnd() { delegate?.didFinishPlayItem() if let player = assetPlayer { player.seekToTime(kCMTimeZero) if autoRepeatPlay == true { play() } } } @objc private func didFailedToPlayToEnd() { delegate?.didFailPlayToEnd() } private func playerDidChangeStatus(status:AVPlayerStatus) { if status == .Failed { print("Failed to load video") } else if status == .ReadyToPlay, let player = assetPlayer { volume = player.volume delegate?.readyToPlay() if autoPlay == true && player.rate == 0.0 { play() } } } private func moviewPlayerLoadedTimeRangeDidUpdated(ranges:Array<NSValue>) { var maximum:NSTimeInterval = 0 for value in ranges { let range:CMTimeRange = value.CMTimeRangeValue let currentLoadedTimeRange = CMTimeGetSeconds(range.start) + CMTimeGetSeconds(range.duration) if currentLoadedTimeRange > maximum { maximum = currentLoadedTimeRange } } let progress:Double = assetDuration == 0 ? 0.0 : Double(maximum) / assetDuration delegate?.downloadedProgress(progress) } deinit { cleanUp() } private func initialSetupWithURL(url:NSURL) { let options = [AVURLAssetPreferPreciseDurationAndTimingKey : true] urlAsset = AVURLAsset(URL: url, options: options) } // MARK: - Observations override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) { if context == videoContext { if let key = keyPath { if key == "status", let player = assetPlayer { playerDidChangeStatus(player.status) } else if key == "loadedTimeRanges", let item = playerItem { moviewPlayerLoadedTimeRangeDidUpdated(item.loadedTimeRanges) } } } } }
Usage:
创建
UIView
用于播放视频的子类(基本上你可以使用任何UIView
对象,只需AVPlayerLayer
要是。我以这种方式设置,因为它对我来说更清晰)import AVFoundation import UIKit class PlayerView: UIView { override class func layerClass() -> AnyClass { return AVPlayerLayer.self } var player:AVPlayer? { set { if let layer = layer as? AVPlayerLayer { layer.player = player } } get { if let layer = layer as? AVPlayerLayer { return layer.player } else { return nil } } } }
设置您的播放器
import AVFoundation import Foundation protocol VideoPlayerDelegate { func downloadedProgress(progress:Double) func readyToPlay() func didUpdateProgress(progress:Double) func didFinishPlayItem() func didFailPlayToEnd() } let videoContext:UnsafeMutablePointer<Void> = nil class VideoPlayer : NSObject { private var assetPlayer:AVPlayer? private var playerItem:AVPlayerItem? private var urlAsset:AVURLAsset? private var videoOutput:AVPlayerItemVideoOutput? private var assetDuration:Double = 0 private var playerView:PlayerView? private var autoRepeatPlay:Bool = true private var autoPlay:Bool = true var delegate:VideoPlayerDelegate? var playerRate:Float = 1 { didSet { if let player = assetPlayer { player.rate = playerRate > 0 ? playerRate : 0.0 } } } var volume:Float = 1.0 { didSet { if let player = assetPlayer { player.volume = volume > 0 ? volume : 0.0 } } } // MARK: - Init convenience init(urlAsset:NSURL, view:PlayerView, startAutoPlay:Bool = true, repeatAfterEnd:Bool = true) { self.init() playerView = view autoPlay = startAutoPlay autoRepeatPlay = repeatAfterEnd if let playView = playerView, let playerLayer = playView.layer as? AVPlayerLayer { playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill } initialSetupWithURL(urlAsset) prepareToPlay() } override init() { super.init() } // MARK: - Public func isPlaying() -> Bool { if let player = assetPlayer { return player.rate > 0 } else { return false } } func seekToPosition(seconds:Float64) { if let player = assetPlayer { pause() if let timeScale = player.currentItem?.asset.duration.timescale { player.seekToTime(CMTimeMakeWithSeconds(seconds, timeScale), completionHandler: { (complete) in self.play() }) } } } func pause() { if let player = assetPlayer { player.pause() } } func play() { if let player = assetPlayer { if (player.currentItem?.status == .ReadyToPlay) { player.play() player.rate = playerRate } } } func cleanUp() { if let item = playerItem { item.removeObserver(self, forKeyPath: "status") item.removeObserver(self, forKeyPath: "loadedTimeRanges") } NSNotificationCenter.defaultCenter().removeObserver(self) assetPlayer = nil playerItem = nil urlAsset = nil } // MARK: - Private private func prepareToPlay() { let keys = ["tracks"] if let asset = urlAsset { asset.loadValuesAsynchronouslyForKeys(keys, completionHandler: { dispatch_async(dispatch_get_main_queue(), { self.startLoading() }) }) } } private func startLoading(){ var error:NSError? guard let asset = urlAsset else {return} let status:AVKeyValueStatus = asset.statusOfValueForKey("tracks", error: &error) if status == AVKeyValueStatus.Loaded { assetDuration = CMTimeGetSeconds(asset.duration) let videoOutputOptions = [kCVPixelBufferPixelFormatTypeKey as String : Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)] videoOutput = AVPlayerItemVideoOutput(pixelBufferAttributes: videoOutputOptions) playerItem = AVPlayerItem(asset: asset) if let item = playerItem { item.addObserver(self, forKeyPath: "status", options: .Initial, context: videoContext) item.addObserver(self, forKeyPath: "loadedTimeRanges", options: [.New, .Old], context: videoContext) NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(playerItemDidReachEnd), name: AVPlayerItemDidPlayToEndTimeNotification, object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(didFailedToPlayToEnd), name: AVPlayerItemFailedToPlayToEndTimeNotification, object: nil) if let output = videoOutput { item.addOutput(output) item.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmVarispeed assetPlayer = AVPlayer(playerItem: item) if let player = assetPlayer { player.rate = playerRate } addPeriodicalObserver() if let playView = playerView, let layer = playView.layer as? AVPlayerLayer { layer.player = assetPlayer print("player created") } } } } } private func addPeriodicalObserver() { let timeInterval = CMTimeMake(1, 1) if let player = assetPlayer { player.addPeriodicTimeObserverForInterval(timeInterval, queue: dispatch_get_main_queue(), usingBlock: { (time) in self.playerDidChangeTime(time) }) } } private func playerDidChangeTime(time:CMTime) { if let player = assetPlayer { let timeNow = CMTimeGetSeconds(player.currentTime()) let progress = timeNow / assetDuration delegate?.didUpdateProgress(progress) } } @objc private func playerItemDidReachEnd() { delegate?.didFinishPlayItem() if let player = assetPlayer { player.seekToTime(kCMTimeZero) if autoRepeatPlay == true { play() } } } @objc private func didFailedToPlayToEnd() { delegate?.didFailPlayToEnd() } private func playerDidChangeStatus(status:AVPlayerStatus) { if status == .Failed { print("Failed to load video") } else if status == .ReadyToPlay, let player = assetPlayer { volume = player.volume delegate?.readyToPlay() if autoPlay == true && player.rate == 0.0 { play() } } } private func moviewPlayerLoadedTimeRangeDidUpdated(ranges:Array<NSValue>) { var maximum:NSTimeInterval = 0 for value in ranges { let range:CMTimeRange = value.CMTimeRangeValue let currentLoadedTimeRange = CMTimeGetSeconds(range.start) + CMTimeGetSeconds(range.duration) if currentLoadedTimeRange > maximum { maximum = currentLoadedTimeRange } } let progress:Double = assetDuration == 0 ? 0.0 : Double(maximum) / assetDuration delegate?.downloadedProgress(progress) } deinit { cleanUp() } private func initialSetupWithURL(url:NSURL) { let options = [AVURLAssetPreferPreciseDurationAndTimingKey : true] urlAsset = AVURLAsset(URL: url, options: options) } // MARK: - Observations override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) { if context == videoContext { if let key = keyPath { if key == "status", let player = assetPlayer { playerDidChangeStatus(player.status) } else if key == "loadedTimeRanges", let item = playerItem { moviewPlayerLoadedTimeRangeDidUpdated(item.loadedTimeRanges) } } } } }
用法:
assume u have view
假设你有观点
@IBOutlet private weak var playerView: PlayerView!
private var videoPlayer:VideoPlayer?
and in viewDidLoad()
并在 viewDidLoad()
private func preparePlayer() {
if let filePath = NSBundle.mainBundle().pathForResource("intro", ofType: "m4v") {
let fileURL = NSURL(fileURLWithPath: filePath)
videoPlayer = VideoPlayer(urlAsset: fileURL, view: playerView)
if let player = videoPlayer {
player.playerRate = 0.67
}
}
}
Objective-C
目标-C
PlayerView.h
播放器视图.h
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
/*!
@class PlayerView
@discussion Represent View for playinv video. Layer - PlayerLayer
@availability iOS 7 and Up
*/
@interface PlayerView : UIView
/*!
@var player
@discussion Player object
*/
@property (strong, nonatomic) AVPlayer *player;
@end
PlayerView.m
PlayerView.m
#import "PlayerView.h"
@implementation PlayerView
#pragma mark - LifeCycle
+ (Class)layerClass
{
return [AVPlayerLayer class];
}
#pragma mark - Setter/Getter
- (AVPlayer*)player
{
return [(AVPlayerLayer *)[self layer] player];
}
- (void)setPlayer:(AVPlayer *)player
{
[(AVPlayerLayer *)[self layer] setPlayer:player];
}
@end
VideoPlayer.h
视频播放器.h
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
#import "PlayerView.h"
/*!
@protocol VideoPlayerDelegate
@discussion Events from VideoPlayer
*/
@protocol VideoPlayerDelegate <NSObject>
@optional
/*!
@brief Called whenever time when progress of played item changed
@param progress
Playing progress
*/
- (void)progressDidUpdate:(CGFloat)progress;
/*!
@brief Called whenever downloaded item progress changed
@param progress
Playing progress
*/
- (void)downloadingProgress:(CGFloat)progress;
/*!
@brief Called when playing time changed
@param time
Playing progress
*/
- (void)progressTimeChanged:(CMTime)time;
/*!
@brief Called when player finish play item
*/
- (void)playerDidPlayItem;
/*!
@brief Called when player ready to play item
*/
- (void)isReadyToPlay;
@end
/*!
@class VideoPlayer
@discussion Video Player
@code
self.videoPlayer = [[VideoPlayer alloc] initVideoPlayerWithURL:someURL playerView:self.playerView];
[self.videoPlayer prepareToPlay];
self.videoPlayer.delegate = self; //optional
//after when required play item
[self.videoPlayer play];
@endcode
*/
@interface VideoPlayer : NSObject
/*!
@var delegate
@abstract Delegate for VideoPlayer
@discussion Set object to this property for getting response and notifications from this class
*/
@property (weak, nonatomic) id <VideoPlayerDelegate> delegate;
/*!
@var volume
@discussion volume of played asset
*/
@property (assign, nonatomic) CGFloat volume;
/*!
@var autoRepeat
@discussion indicate whenever player should repeat content on finish playing
*/
@property (assign, nonatomic) BOOL autoRepeat;
/*!
@brief Create player with asset URL
@param urlAsset
Source URL
@result
instance of VideoPlayer
*/
- (instancetype)initVideoPlayerWithURL:(NSURL *)urlAsset;
/*!
@brief Create player with asset URL and configure selected view for showing result
@param urlAsset
Source URL
@param view
View on wchich result will be showed
@result
instance of VideoPlayer
*/
- (instancetype)initVideoPlayerWithURL:(NSURL *)urlAsset playerView:(PlayerView *)view;
/*!
@brief Call this method after creating player to prepare player to play
*/
- (void)prepareToPlay;
/*!
@brief Play item
*/
- (void)play;
/*!
@brief Pause item
*/
- (void)pause;
/*!
@brief Stop item
*/
- (void)stop;
/*!
@brief Seek required position in item and pla if rquired
@param progressValue
% of position to seek
@param isPlaying
YES if player should start to play item implicity
*/
- (void)seekPositionAtProgress:(CGFloat)progressValue withPlayingStatus:(BOOL)isPlaying;
/*!
@brief Player state
@result
YES - if playing, NO if not playing
*/
- (BOOL)isPlaying;
/*!
@brief Indicate whenever player can provide CVPixelBufferRef frame from item
@result
YES / NO
*/
- (BOOL)canProvideFrame;
/*!
@brief CVPixelBufferRef frame from item
@result
CVPixelBufferRef frame
*/
- (CVPixelBufferRef)getCurrentFramePicture;
@end
VideoPlayer.m
视频播放器.m
#import "VideoPlayer.h"
typedef NS_ENUM(NSUInteger, InternalStatus) {
InternalStatusPreparation,
InternalStatusReadyToPlay,
};
static const NSString *ItemStatusContext;
@interface VideoPlayer()
@property (strong, nonatomic) AVPlayer *assetPlayer;
@property (strong, nonatomic) AVPlayerItem *playerItem;
@property (strong, nonatomic) AVURLAsset *urlAsset;
@property (strong, atomic) AVPlayerItemVideoOutput *videoOutput;
@property (assign, nonatomic) CGFloat assetDuration;
@property (strong, nonatomic) PlayerView *playerView;
@property (assign, nonatomic) InternalStatus status;
@end
@implementation VideoPlayer
#pragma mark - LifeCycle
- (instancetype)initVideoPlayerWithURL:(NSURL *)urlAsset
{
if (self = [super init]) {
[self initialSetupWithURL:urlAsset];
}
return self;
}
- (instancetype)initVideoPlayerWithURL:(NSURL *)urlAsset playerView:(PlayerView *)view
{
if (self = [super init]) {
((AVPlayerLayer *)view.layer).videoGravity = AVLayerVideoGravityResizeAspectFill;
[self initialSetupWithURL:urlAsset playerView:view];
}
return self;
}
#pragma mark - Public
- (void)play
{
if ((self.assetPlayer.currentItem) && (self.assetPlayer.currentItem.status == AVPlayerItemStatusReadyToPlay)) {
[self.assetPlayer play];
}
}
- (void)pause
{
[self.assetPlayer pause];
}
- (void)seekPositionAtProgress:(CGFloat)progressValue withPlayingStatus:(BOOL)isPlaying
{
[self.assetPlayer pause];
int32_t timeScale = self.assetPlayer.currentItem.asset.duration.timescale;
__weak typeof(self) weakSelf = self;
[self.assetPlayer seekToTime:CMTimeMakeWithSeconds(progressValue, timeScale) completionHandler:^(BOOL finished) {
DLog(@"SEEK To time %f - success", progressValue);
if (isPlaying && finished) {
[weakSelf.assetPlayer play];
}
}];
}
- (void)setPlayerVolume:(CGFloat)volume
{
self.assetPlayer.volume = volume > .0 ? MAX(volume, 0.7) : 0.0f;
[self.assetPlayer play];
}
- (void)setPlayerRate:(CGFloat)rate
{
self.assetPlayer.rate = rate > .0 ? rate : 0.0f;
}
- (void)stop
{
[self.assetPlayer seekToTime:kCMTimeZero];
self.assetPlayer.rate =.0f;
}
- (BOOL)isPlaying
{
return self.assetPlayer.rate > 0 ? YES : NO;
}
#pragma mark - Private
- (void)initialSetupWithURL:(NSURL *)url
{
self.status = InternalStatusPreparation;
[self setupPlayerWithURL:url];
}
- (void)initialSetupWithURL:(NSURL *)url playerView:(PlayerView *)view
{
[self setupPlayerWithURL:url];
self.playerView = view;
}
- (void)setupPlayerWithURL:(NSURL *)url
{
NSDictionary *assetOptions = @{ AVURLAssetPreferPreciseDurationAndTimingKey : @YES };
self.urlAsset = [AVURLAsset URLAssetWithURL:url options:assetOptions];
}
- (void)prepareToPlay
{
NSArray *keys = @[@"tracks"];
__weak VideoPlayer *weakSelf = self;
[weakSelf.urlAsset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf startLoading];
});
}];
}
- (void)startLoading
{
NSError *error;
AVKeyValueStatus status = [self.urlAsset statusOfValueForKey:@"tracks" error:&error];
if (status == AVKeyValueStatusLoaded) {
self.assetDuration = CMTimeGetSeconds(self.urlAsset.duration);
NSDictionary* videoOutputOptions = @{ (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)};
self.videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:videoOutputOptions];
self.playerItem = [AVPlayerItem playerItemWithAsset: self.urlAsset];
[self.playerItem addObserver:self
forKeyPath:@"status"
options:NSKeyValueObservingOptionInitial
context:&ItemStatusContext];
[self.playerItem addObserver:self
forKeyPath:@"loadedTimeRanges"
options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld
context:&ItemStatusContext];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.playerItem];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didFailedToPlayToEnd)
name:AVPlayerItemFailedToPlayToEndTimeNotification
object:nil];
[self.playerItem addOutput:self.videoOutput];
self.assetPlayer = [AVPlayer playerWithPlayerItem:self.playerItem];
[self addPeriodicalObserver];
[((AVPlayerLayer *)self.playerView.layer) setPlayer:self.assetPlayer];
DLog(@"Player created");
} else {
DLog(@"The asset's tracks were not loaded:\n%@", error.localizedDescription);
}
}
#pragma mark - Observation
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
BOOL isOldKey = [change[NSKeyValueChangeNewKey] isEqual:change[NSKeyValueChangeOldKey]];
if (!isOldKey) {
if (context == &ItemStatusContext) {
if ([keyPath isEqualToString:@"status"] && !self.status) {
if (self.assetPlayer.status == AVPlayerItemStatusReadyToPlay) {
self.status = InternalStatusReadyToPlay;
}
[self moviePlayerDidChangeStatus:self.assetPlayer.status];
} else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
[self moviewPlayerLoadedTimeRangeDidUpdated:self.playerItem.loadedTimeRanges];
}
}
}
}
- (void)moviePlayerDidChangeStatus:(AVPlayerStatus)status
{
if (status == AVPlayerStatusFailed) {
DLog(@"Failed to load video");
} else if (status == AVPlayerItemStatusReadyToPlay) {
DLog(@"Player ready to play");
self.volume = self.assetPlayer.volume;
if (self.delegate && [self.delegate respondsToSelector:@selector(isReadyToPlay)]) {
[self.delegate isReadyToPlay];
}
}
}
- (void)moviewPlayerLoadedTimeRangeDidUpdated:(NSArray *)ranges
{
NSTimeInterval maximum = 0;
for (NSValue *value in ranges) {
CMTimeRange range;
[value getValue:&range];
NSTimeInterval currenLoadedRangeTime = CMTimeGetSeconds(range.start) + CMTimeGetSeconds(range.duration);
if (currenLoadedRangeTime > maximum) {
maximum = currenLoadedRangeTime;
}
}
CGFloat progress = (self.assetDuration == 0) ? 0 : maximum / self.assetDuration;
if (self.delegate && [self.delegate respondsToSelector:@selector(downloadingProgress:)]) {
[self.delegate downloadingProgress:progress];
}
}
- (void)playerItemDidReachEnd:(NSNotification *)notification
{
if (self.delegate && [self.delegate respondsToSelector:@selector(playerDidPlayItem)]){
[self.delegate playerDidPlayItem];
}
[self.assetPlayer seekToTime:kCMTimeZero];
if (self.autoRepeat) {
[self.assetPlayer play];
}
}
- (void)didFailedToPlayToEnd
{
DLog(@"Failed play video to the end");
}
- (void)addPeriodicalObserver
{
CMTime interval = CMTimeMake(1, 1);
__weak typeof(self) weakSelf = self;
[self.assetPlayer addPeriodicTimeObserverForInterval:interval queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
[weakSelf playerTimeDidChange:time];
}];
}
- (void)playerTimeDidChange:(CMTime)time
{
double timeNow = CMTimeGetSeconds(self.assetPlayer.currentTime);
if (self.delegate && [self.delegate respondsToSelector:@selector(progressDidUpdate:)]) {
[self.delegate progressDidUpdate:(CGFloat) (timeNow / self.assetDuration)];
}
}
#pragma mark - Notification
- (void)setupAppNotification
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
}
- (void)didEnterBackground
{
[self.assetPlayer pause];
}
- (void)willEnterForeground
{
[self.assetPlayer pause];
}
#pragma mark - GetImagesFromVideoPlayer
- (BOOL)canProvideFrame
{
return self.assetPlayer.status == AVPlayerItemStatusReadyToPlay;
}
- (CVPixelBufferRef)getCurrentFramePicture
{
CMTime currentTime = [self.videoOutput itemTimeForHostTime:CACurrentMediaTime()];
if (self.delegate && [self.delegate respondsToSelector:@selector(progressTimeChanged:)]) {
[self.delegate progressTimeChanged:currentTime];
}
if (![self.videoOutput hasNewPixelBufferForItemTime:currentTime]) {
return 0;
}
CVPixelBufferRef buffer = [self.videoOutput copyPixelBufferForItemTime:currentTime itemTimeForDisplay:NULL];
return buffer;
}
#pragma mark - CleanUp
- (void)removeObserversFromPlayer
{
@try {
[self.playerItem removeObserver:self forKeyPath:@"status"];
[self.playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] removeObserver:self.assetPlayer];
}
@catch (NSException *ex) {
DLog(@"Cant remove observer in Player - %@", ex.description);
}
}
- (void)cleanUp
{
[self removeObserversFromPlayer];
self.assetPlayer.rate = 0;
self.assetPlayer = nil;
self.playerItem = nil;
self.urlAsset = nil;
}
- (void)dealloc
{
[self cleanUp];
}
@end
Off cause resource (video file) should have target membership setted to your project
关闭原因资源(视频文件)应该为您的项目设置目标成员资格
Additional - link to perfect Apple Developer guide
回答by Aayushi
Swift 3
斯威夫特 3
if let filePath = Bundle.main.path(forResource: "small", ofType: ".mp4") {
let filePathURL = NSURL.fileURL(withPath: filePath)
let player = AVPlayer(url: filePathURL)
let playerController = AVPlayerViewController()
playerController.player = player
self.present(playerController, animated: true) {
player.play()
}
}
回答by iman kazemayni
these code is converted code from gbk answer in swift 4
这些代码是从 swift 4 中的 gbk answer 转换而来的代码
1.in your main controllview :
1.在你的主控制视图中:
if let filePath = Bundle.main.path(forResource: "clip", ofType: "mp4") {
let fileURL = NSURL(fileURLWithPath: filePath)
videoPlayer = VideoPlayer(urlAsset: fileURL, view: playerView)
if let player = videoPlayer {
player.playerRate = 1.00
}
}
you need VideoPlayer class
import AVFoundation import Foundation protocol VideoPlayerDelegate { func downloadedProgress(progress:Double) func readyToPlay() func didUpdateProgress(progress:Double) func didFinishPlayItem() func didFailPlayToEnd() } let videoContext:UnsafeMutablePointer<Void>? = nil class VideoPlayer : NSObject { private var assetPlayer:AVPlayer? private var playerItem:AVPlayerItem? private var urlAsset:AVURLAsset? private var videoOutput:AVPlayerItemVideoOutput? private var assetDuration:Double = 0 private var playerView:PlayerView? private var autoRepeatPlay:Bool = true private var autoPlay:Bool = true var delegate:VideoPlayerDelegate? var playerRate:Float = 1 { didSet { if let player = assetPlayer { player.rate = playerRate > 0 ? playerRate : 0.0 } } } var volume:Float = 0 { didSet { if let player = assetPlayer { player.volume = 50 } } } // MARK: - Init convenience init(urlAsset:NSURL, view:PlayerView, startAutoPlay:Bool = true, repeatAfterEnd:Bool = true) { self.init() playerView = view autoPlay = startAutoPlay autoRepeatPlay = repeatAfterEnd if let playView = playerView{ if let playerLayer = playView.layer as? AVPlayerLayer { playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill } } initialSetupWithURL(url: urlAsset) prepareToPlay() } override init() { super.init() } // MARK: - Public func isPlaying() -> Bool { if let player = assetPlayer { return player.rate > 0 } else { return false } } func seekToPosition(seconds:Float64) { if let player = assetPlayer { pause() if let timeScale = player.currentItem?.asset.duration.timescale { player.seek(to: CMTimeMakeWithSeconds(seconds, timeScale), completionHandler: { (complete) in self.play() }) } } } func pause() { if let player = assetPlayer { player.pause() } } func play() { if let player = assetPlayer { if (player.currentItem?.status == .readyToPlay) { player.play() player.rate = playerRate } } } func cleanUp() { if let item = playerItem { item.removeObserver(self, forKeyPath: "status") item.removeObserver(self, forKeyPath: "loadedTimeRanges") } NotificationCenter.default.removeObserver(self) assetPlayer = nil playerItem = nil urlAsset = nil } // MARK: - Private private func prepareToPlay() { let keys = ["tracks"] if let asset = urlAsset { asset.loadValuesAsynchronously(forKeys: keys, completionHandler: { DispatchQueue.global(qos: .userInitiated).async { // Bounce back to the main thread to update the UI DispatchQueue.main.async { self.startLoading() } } }) } } private func startLoading(){ var error:NSError? guard let asset = urlAsset else {return} // let status:AVKeyValueStatus = asset.statusOfValueForKey("tracks", error: &error) let status:AVKeyValueStatus = asset.statusOfValue(forKey: "tracks", error: nil) if status == AVKeyValueStatus.loaded { assetDuration = CMTimeGetSeconds(asset.duration) let videoOutputOptions = [kCVPixelBufferPixelFormatTypeKey as String : Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)] videoOutput = AVPlayerItemVideoOutput(pixelBufferAttributes: videoOutputOptions) playerItem = AVPlayerItem(asset: asset) if let item = playerItem { item.addObserver(self, forKeyPath: "status", options: .initial, context: videoContext) item.addObserver(self, forKeyPath: "loadedTimeRanges", options: [.new, .old], context: videoContext) NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(didFailedToPlayToEnd), name:NSNotification.Name.AVPlayerItemFailedToPlayToEndTime, object: nil) if let output = videoOutput { item.add(output) item.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithm.varispeed assetPlayer = AVPlayer(playerItem: item) if let player = assetPlayer { player.rate = playerRate } addPeriodicalObserver() if let playView = playerView, let layer = playView.layer as? AVPlayerLayer { layer.player = assetPlayer } } } } } private func addPeriodicalObserver() { let timeInterval = CMTimeMake(1, 1) if let player = assetPlayer { player.addPeriodicTimeObserver(forInterval: timeInterval, queue: DispatchQueue.main , using: { (time) in self.playerDidChangeTime(time: time) }) } } private func playerDidChangeTime(time:CMTime) { if let player = assetPlayer { let timeNow = CMTimeGetSeconds(player.currentTime()) let progress = timeNow / assetDuration delegate?.didUpdateProgress(progress: progress) } } @objc private func playerItemDidReachEnd() { delegate?.didFinishPlayItem() if let player = assetPlayer { player.seek(to: kCMTimeZero) if autoRepeatPlay == true { play() } } } @objc private func didFailedToPlayToEnd() { delegate?.didFailPlayToEnd() } private func playerDidChangeStatus(status:AVPlayerStatus) { if status == .failed { print("Failed to load video") } else if status == .readyToPlay, let player = assetPlayer { volume = player.volume delegate?.readyToPlay() if autoPlay == true && player.rate == 0.0 { play() } } } private func moviewPlayerLoadedTimeRangeDidUpdated(ranges:Array<NSValue>) { var maximum:TimeInterval = 0 for value in ranges { let range:CMTimeRange = value.timeRangeValue let currentLoadedTimeRange = CMTimeGetSeconds(range.start) + CMTimeGetSeconds(range.duration) if currentLoadedTimeRange > maximum { maximum = currentLoadedTimeRange } } let progress:Double = assetDuration == 0 ? 0.0 : Double(maximum) / assetDuration delegate?.downloadedProgress(progress: progress) } deinit { cleanUp() } private func initialSetupWithURL(url:NSURL) { let options = [AVURLAssetPreferPreciseDurationAndTimingKey : true] // urlAsset = AVURLAsset(URL: url, options: options) urlAsset = AVURLAsset(url: url as URL, options: options) } // MARK: - Observations override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?){ if (context as? UnsafeMutablePointer<Void> ) == videoContext { if let key = keyPath { if key == "status", let player = assetPlayer { playerDidChangeStatus(status: player.status) } else if key == "loadedTimeRanges", let item = playerItem { moviewPlayerLoadedTimeRangeDidUpdated(ranges: item.loadedTimeRanges) } } } } }
and a PlayerView class to create a videoview:
import AVFoundation import UIKit class PlayerView: UIView { override class var layerClass: AnyClass { get { return AVPlayerLayer.self } } var player:AVPlayer? { set { if let layer = layer as? AVPlayerLayer { layer.player = player } } get { if let layer = layer as? AVPlayerLayer { return layer.player } else { return nil } } } }
你需要 VideoPlayer 类
import AVFoundation import Foundation protocol VideoPlayerDelegate { func downloadedProgress(progress:Double) func readyToPlay() func didUpdateProgress(progress:Double) func didFinishPlayItem() func didFailPlayToEnd() } let videoContext:UnsafeMutablePointer<Void>? = nil class VideoPlayer : NSObject { private var assetPlayer:AVPlayer? private var playerItem:AVPlayerItem? private var urlAsset:AVURLAsset? private var videoOutput:AVPlayerItemVideoOutput? private var assetDuration:Double = 0 private var playerView:PlayerView? private var autoRepeatPlay:Bool = true private var autoPlay:Bool = true var delegate:VideoPlayerDelegate? var playerRate:Float = 1 { didSet { if let player = assetPlayer { player.rate = playerRate > 0 ? playerRate : 0.0 } } } var volume:Float = 0 { didSet { if let player = assetPlayer { player.volume = 50 } } } // MARK: - Init convenience init(urlAsset:NSURL, view:PlayerView, startAutoPlay:Bool = true, repeatAfterEnd:Bool = true) { self.init() playerView = view autoPlay = startAutoPlay autoRepeatPlay = repeatAfterEnd if let playView = playerView{ if let playerLayer = playView.layer as? AVPlayerLayer { playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill } } initialSetupWithURL(url: urlAsset) prepareToPlay() } override init() { super.init() } // MARK: - Public func isPlaying() -> Bool { if let player = assetPlayer { return player.rate > 0 } else { return false } } func seekToPosition(seconds:Float64) { if let player = assetPlayer { pause() if let timeScale = player.currentItem?.asset.duration.timescale { player.seek(to: CMTimeMakeWithSeconds(seconds, timeScale), completionHandler: { (complete) in self.play() }) } } } func pause() { if let player = assetPlayer { player.pause() } } func play() { if let player = assetPlayer { if (player.currentItem?.status == .readyToPlay) { player.play() player.rate = playerRate } } } func cleanUp() { if let item = playerItem { item.removeObserver(self, forKeyPath: "status") item.removeObserver(self, forKeyPath: "loadedTimeRanges") } NotificationCenter.default.removeObserver(self) assetPlayer = nil playerItem = nil urlAsset = nil } // MARK: - Private private func prepareToPlay() { let keys = ["tracks"] if let asset = urlAsset { asset.loadValuesAsynchronously(forKeys: keys, completionHandler: { DispatchQueue.global(qos: .userInitiated).async { // Bounce back to the main thread to update the UI DispatchQueue.main.async { self.startLoading() } } }) } } private func startLoading(){ var error:NSError? guard let asset = urlAsset else {return} // let status:AVKeyValueStatus = asset.statusOfValueForKey("tracks", error: &error) let status:AVKeyValueStatus = asset.statusOfValue(forKey: "tracks", error: nil) if status == AVKeyValueStatus.loaded { assetDuration = CMTimeGetSeconds(asset.duration) let videoOutputOptions = [kCVPixelBufferPixelFormatTypeKey as String : Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)] videoOutput = AVPlayerItemVideoOutput(pixelBufferAttributes: videoOutputOptions) playerItem = AVPlayerItem(asset: asset) if let item = playerItem { item.addObserver(self, forKeyPath: "status", options: .initial, context: videoContext) item.addObserver(self, forKeyPath: "loadedTimeRanges", options: [.new, .old], context: videoContext) NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(didFailedToPlayToEnd), name:NSNotification.Name.AVPlayerItemFailedToPlayToEndTime, object: nil) if let output = videoOutput { item.add(output) item.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithm.varispeed assetPlayer = AVPlayer(playerItem: item) if let player = assetPlayer { player.rate = playerRate } addPeriodicalObserver() if let playView = playerView, let layer = playView.layer as? AVPlayerLayer { layer.player = assetPlayer } } } } } private func addPeriodicalObserver() { let timeInterval = CMTimeMake(1, 1) if let player = assetPlayer { player.addPeriodicTimeObserver(forInterval: timeInterval, queue: DispatchQueue.main , using: { (time) in self.playerDidChangeTime(time: time) }) } } private func playerDidChangeTime(time:CMTime) { if let player = assetPlayer { let timeNow = CMTimeGetSeconds(player.currentTime()) let progress = timeNow / assetDuration delegate?.didUpdateProgress(progress: progress) } } @objc private func playerItemDidReachEnd() { delegate?.didFinishPlayItem() if let player = assetPlayer { player.seek(to: kCMTimeZero) if autoRepeatPlay == true { play() } } } @objc private func didFailedToPlayToEnd() { delegate?.didFailPlayToEnd() } private func playerDidChangeStatus(status:AVPlayerStatus) { if status == .failed { print("Failed to load video") } else if status == .readyToPlay, let player = assetPlayer { volume = player.volume delegate?.readyToPlay() if autoPlay == true && player.rate == 0.0 { play() } } } private func moviewPlayerLoadedTimeRangeDidUpdated(ranges:Array<NSValue>) { var maximum:TimeInterval = 0 for value in ranges { let range:CMTimeRange = value.timeRangeValue let currentLoadedTimeRange = CMTimeGetSeconds(range.start) + CMTimeGetSeconds(range.duration) if currentLoadedTimeRange > maximum { maximum = currentLoadedTimeRange } } let progress:Double = assetDuration == 0 ? 0.0 : Double(maximum) / assetDuration delegate?.downloadedProgress(progress: progress) } deinit { cleanUp() } private func initialSetupWithURL(url:NSURL) { let options = [AVURLAssetPreferPreciseDurationAndTimingKey : true] // urlAsset = AVURLAsset(URL: url, options: options) urlAsset = AVURLAsset(url: url as URL, options: options) } // MARK: - Observations override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?){ if (context as? UnsafeMutablePointer<Void> ) == videoContext { if let key = keyPath { if key == "status", let player = assetPlayer { playerDidChangeStatus(status: player.status) } else if key == "loadedTimeRanges", let item = playerItem { moviewPlayerLoadedTimeRangeDidUpdated(ranges: item.loadedTimeRanges) } } } } }
和一个 PlayerView 类来创建一个视频视图:
import AVFoundation import UIKit class PlayerView: UIView { override class var layerClass: AnyClass { get { return AVPlayerLayer.self } } var player:AVPlayer? { set { if let layer = layer as? AVPlayerLayer { layer.player = player } } get { if let layer = layer as? AVPlayerLayer { return layer.player } else { return nil } } } }
回答by Sangwon Park
PlayerView for swift 4.2
用于swift 4.2 的PlayerView
import AVFoundation
import UIKit
class PlayerView: UIView {
var player: AVPlayer? {
get {
return playerLayer.player
}
set {
playerLayer.player = newValue
}
}
var playerLayer: AVPlayerLayer {
return layer as! AVPlayerLayer
}
// Override UIView property
override static var layerClass: AnyClass {
return AVPlayerLayer.self
}
}
回答by Woody Jean-louis
gbk's solution in swift 3
gbk 在 swift 3 中的解决方案
PlayerView
播放器视图
import AVFoundation
import UIKit
class PlayerView: UIView {
override class var layerClass: AnyClass {
return AVPlayerLayer.self
}
var player:AVPlayer? {
set {
if let layer = layer as? AVPlayerLayer {
layer.player = player
}
}
get {
if let layer = layer as? AVPlayerLayer {
return layer.player
} else {
return nil
}
}
}
}
VideoPlayer
视频播放器
import AVFoundation
import Foundation
protocol VideoPlayerDelegate {
func downloadedProgress(progress:Double)
func readyToPlay()
func didUpdateProgress(progress:Double)
func didFinishPlayItem()
func didFailPlayToEnd()
}
let videoContext:UnsafeMutableRawPointer? = nil
class VideoPlayer : NSObject {
private var assetPlayer:AVPlayer?
private var playerItem:AVPlayerItem?
private var urlAsset:AVURLAsset?
private var videoOutput:AVPlayerItemVideoOutput?
private var assetDuration:Double = 0
private var playerView:PlayerView?
private var autoRepeatPlay:Bool = true
private var autoPlay:Bool = true
var delegate:VideoPlayerDelegate?
var playerRate:Float = 1 {
didSet {
if let player = assetPlayer {
player.rate = playerRate > 0 ? playerRate : 0.0
}
}
}
var volume:Float = 1.0 {
didSet {
if let player = assetPlayer {
player.volume = volume > 0 ? volume : 0.0
}
}
}
// MARK: - Init
convenience init(urlAsset: URL, view:PlayerView, startAutoPlay:Bool = true, repeatAfterEnd:Bool = true) {
self.init()
playerView = view
autoPlay = startAutoPlay
autoRepeatPlay = repeatAfterEnd
if let playView = playerView, let playerLayer = playView.layer as? AVPlayerLayer {
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
}
initialSetupWithURL(url: urlAsset)
prepareToPlay()
}
override init() {
super.init()
}
// MARK: - Public
func isPlaying() -> Bool {
if let player = assetPlayer {
return player.rate > 0
} else {
return false
}
}
func seekToPosition(seconds:Float64) {
if let player = assetPlayer {
pause()
if let timeScale = player.currentItem?.asset.duration.timescale {
player.seek(to: CMTimeMakeWithSeconds(seconds, timeScale), completionHandler: { (complete) in
self.play()
})
}
}
}
func pause() {
if let player = assetPlayer {
player.pause()
}
}
func play() {
if let player = assetPlayer {
if (player.currentItem?.status == .readyToPlay) {
player.play()
player.rate = playerRate
}
}
}
func cleanUp() {
if let item = playerItem {
item.removeObserver(self, forKeyPath: "status")
item.removeObserver(self, forKeyPath: "loadedTimeRanges")
}
NotificationCenter.default.removeObserver(self)
assetPlayer = nil
playerItem = nil
urlAsset = nil
}
// MARK: - Private
private func prepareToPlay() {
let keys = ["tracks"]
if let asset = urlAsset {
asset.loadValuesAsynchronously(forKeys: keys, completionHandler: {
DispatchQueue.main.async {
self.startLoading()
}
})
}
}
private func startLoading(){
var error:NSError?
guard let asset = urlAsset else {return}
let status:AVKeyValueStatus = asset.statusOfValue(forKey: "tracks", error: &error)
if status == AVKeyValueStatus.loaded {
assetDuration = CMTimeGetSeconds(asset.duration)
let videoOutputOptions = [kCVPixelBufferPixelFormatTypeKey as String : Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)]
videoOutput = AVPlayerItemVideoOutput(pixelBufferAttributes: videoOutputOptions)
playerItem = AVPlayerItem(asset: asset)
if let item = playerItem {
item.addObserver(self, forKeyPath: "status", options: .initial, context: videoContext)
item.addObserver(self, forKeyPath: "loadedTimeRanges", options: [.new, .old], context: videoContext)
NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didFailedToPlayToEnd), name: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime, object: nil)
if let output = videoOutput {
item.add(output)
item.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmVarispeed
assetPlayer = AVPlayer(playerItem: item)
if let player = assetPlayer {
player.rate = playerRate
}
addPeriodicalObserver()
if let playView = playerView, let layer = playView.layer as? AVPlayerLayer {
layer.player = assetPlayer
print("player created")
}
}
}
}
}
private func addPeriodicalObserver() {
let timeInterval = CMTimeMake(1, 1)
if let player = assetPlayer {
player.addPeriodicTimeObserver(forInterval: timeInterval, queue: DispatchQueue.main, using: { (time) in
self.playerDidChangeTime(time: time)
})
}
}
private func playerDidChangeTime(time:CMTime) {
if let player = assetPlayer {
let timeNow = CMTimeGetSeconds(player.currentTime())
let progress = timeNow / assetDuration
delegate?.didUpdateProgress(progress: progress)
}
}
@objc private func playerItemDidReachEnd() {
delegate?.didFinishPlayItem()
if let player = assetPlayer {
player.seek(to: kCMTimeZero)
if autoRepeatPlay == true {
play()
}
}
}
@objc private func didFailedToPlayToEnd() {
delegate?.didFailPlayToEnd()
}
private func playerDidChangeStatus(status:AVPlayerStatus) {
if status == .failed {
print("Failed to load video")
} else if status == .readyToPlay, let player = assetPlayer {
volume = player.volume
delegate?.readyToPlay()
if autoPlay == true && player.rate == 0.0 {
play()
}
}
}
private func moviewPlayerLoadedTimeRangeDidUpdated(ranges:Array<NSValue>) {
var maximum:TimeInterval = 0
for value in ranges {
let range:CMTimeRange = value.timeRangeValue
let currentLoadedTimeRange = CMTimeGetSeconds(range.start) + CMTimeGetSeconds(range.duration)
if currentLoadedTimeRange > maximum {
maximum = currentLoadedTimeRange
}
}
let progress:Double = assetDuration == 0 ? 0.0 : Double(maximum) / assetDuration
delegate?.downloadedProgress(progress: progress)
}
deinit {
cleanUp()
}
private func initialSetupWithURL(url: URL) {
let options = [AVURLAssetPreferPreciseDurationAndTimingKey : true]
urlAsset = AVURLAsset(url: url, options: options)
}
// MARK: - Observations
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == videoContext {
if let key = keyPath {
if key == "status", let player = assetPlayer {
playerDidChangeStatus(status: player.status)
} else if key == "loadedTimeRanges", let item = playerItem {
moviewPlayerLoadedTimeRangeDidUpdated(ranges: item.loadedTimeRanges)
}
}
}
}
}
回答by swiftBoy
None of the above worked for me Swift 5for Local Video Player
以上都不适合我Swift 5for Local Video Player
after reading apple documentation I was able to create simple example for playing video from Local resources
阅读苹果文档后,我能够创建简单的示例来播放来自本地资源的视频
Here is code snip
这是代码片段
import UIKit
import AVKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//TODO : Make Sure Add and copy "SampleVideo.mp4" file in project before play
}
@IBAction func playLocalVideo(_ sender: Any) {
guard let path = Bundle.main.path(forResource: "SampleVideo", ofType: "mp4") else {
return
}
let videoURL = NSURL(fileURLWithPath: path)
// Create an AVPlayer, passing it the local video url path
let player = AVPlayer(url: videoURL as URL)
let controller = AVPlayerViewController()
controller.player = player
present(controller, animated: true) {
player.play()
}
}
}
PS: Make sure you don't forget to add and copy video named "SampleVideo.mp4" in project
PS:确保您不要忘记在项目中添加和复制名为“SampleVideo.mp4”的视频
回答by iKK
Here a solution for Swift 5.2
这是 Swift 5.2 的解决方案
PlayerView.swift:
PlayerView.swift:
import AVFoundation
import UIKit
class PlayerView: UIView {
var player: AVPlayer? {
get {
return playerLayer.player
}
set {
playerLayer.player = newValue
}
}
var playerLayer: AVPlayerLayer {
return layer as! AVPlayerLayer
}
// Override UIView property
override static var layerClass: AnyClass {
return AVPlayerLayer.self
}
}
VideoPlayer.swift
VideoPlayer.swift
import AVFoundation
import Foundation
protocol VideoPlayerDelegate {
func downloadedProgress(progress:Double)
func readyToPlay()
func didUpdateProgress(progress:Double)
func didFinishPlayItem()
func didFailPlayToEnd()
}
let videoContext: UnsafeMutableRawPointer? = nil
class VideoPlayer : NSObject {
private var assetPlayer:AVPlayer?
private var playerItem:AVPlayerItem?
private var urlAsset:AVURLAsset?
private var videoOutput:AVPlayerItemVideoOutput?
private var assetDuration:Double = 0
private var playerView:PlayerView?
private var autoRepeatPlay:Bool = true
private var autoPlay:Bool = true
var delegate:VideoPlayerDelegate?
var playerRate:Float = 1 {
didSet {
if let player = assetPlayer {
player.rate = playerRate > 0 ? playerRate : 0.0
}
}
}
var volume:Float = 1.0 {
didSet {
if let player = assetPlayer {
player.volume = volume > 0 ? volume : 0.0
}
}
}
// MARK: - Init
convenience init(urlAsset:NSURL, view:PlayerView, startAutoPlay:Bool = true, repeatAfterEnd:Bool = true) {
self.init()
playerView = view
autoPlay = startAutoPlay
autoRepeatPlay = repeatAfterEnd
if let playView = playerView, let playerLayer = playView.layer as? AVPlayerLayer {
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
}
initialSetupWithURL(url: urlAsset)
prepareToPlay()
}
override init() {
super.init()
}
// MARK: - Public
func isPlaying() -> Bool {
if let player = assetPlayer {
return player.rate > 0
} else {
return false
}
}
func seekToPosition(seconds:Float64) {
if let player = assetPlayer {
pause()
if let timeScale = player.currentItem?.asset.duration.timescale {
player.seek(to: CMTimeMakeWithSeconds(seconds, preferredTimescale: timeScale), completionHandler: { (complete) in
self.play()
})
}
}
}
func pause() {
if let player = assetPlayer {
player.pause()
}
}
func play() {
if let player = assetPlayer {
if (player.currentItem?.status == .readyToPlay) {
player.play()
player.rate = playerRate
}
}
}
func cleanUp() {
if let item = playerItem {
item.removeObserver(self, forKeyPath: "status")
item.removeObserver(self, forKeyPath: "loadedTimeRanges")
}
NotificationCenter.default.removeObserver(self)
assetPlayer = nil
playerItem = nil
urlAsset = nil
}
// MARK: - Private
private func prepareToPlay() {
let keys = ["tracks"]
if let asset = urlAsset {
asset.loadValuesAsynchronously(forKeys: keys, completionHandler: {
DispatchQueue.main.async {
self.startLoading()
}
})
}
}
private func startLoading(){
var error:NSError?
guard let asset = urlAsset else {return}
let status:AVKeyValueStatus = asset.statusOfValue(forKey: "tracks", error: &error)
if status == AVKeyValueStatus.loaded {
assetDuration = CMTimeGetSeconds(asset.duration)
let videoOutputOptions = [kCVPixelBufferPixelFormatTypeKey as String : Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)]
videoOutput = AVPlayerItemVideoOutput(pixelBufferAttributes: videoOutputOptions)
playerItem = AVPlayerItem(asset: asset)
if let item = playerItem {
item.addObserver(self, forKeyPath: "status", options: .initial, context: videoContext)
item.addObserver(self, forKeyPath: "loadedTimeRanges", options: [.new, .old], context: videoContext)
NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didFailedToPlayToEnd), name: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime, object: nil)
if let output = videoOutput {
item.add(output)
item.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithm.varispeed
assetPlayer = AVPlayer(playerItem: item)
if let player = assetPlayer {
player.rate = playerRate
}
addPeriodicalObserver()
if let playView = playerView, let layer = playView.layer as? AVPlayerLayer {
layer.player = assetPlayer
print("player created")
}
}
}
}
}
private func addPeriodicalObserver() {
let timeInterval = CMTimeMake(value: 1, timescale: 1)
if let player = assetPlayer {
player.addPeriodicTimeObserver(forInterval: timeInterval, queue: DispatchQueue.main, using: { (time) in
self.playerDidChangeTime(time: time)
})
}
}
private func playerDidChangeTime(time:CMTime) {
if let player = assetPlayer {
let timeNow = CMTimeGetSeconds(player.currentTime())
let progress = timeNow / assetDuration
delegate?.didUpdateProgress(progress: progress)
}
}
@objc private func playerItemDidReachEnd() {
delegate?.didFinishPlayItem()
if let player = assetPlayer {
player.seek(to: CMTime.zero)
if autoRepeatPlay == true {
play()
}
}
}
@objc private func didFailedToPlayToEnd() {
delegate?.didFailPlayToEnd()
}
private func playerDidChangeStatus(status:AVPlayer.Status) {
if status == .failed {
print("Failed to load video")
} else if status == .readyToPlay, let player = assetPlayer {
volume = player.volume
delegate?.readyToPlay()
if autoPlay == true && player.rate == 0.0 {
play()
}
}
}
private func moviewPlayerLoadedTimeRangeDidUpdated(ranges:Array<NSValue>) {
var maximum:TimeInterval = 0
for value in ranges {
let range:CMTimeRange = value.timeRangeValue
let currentLoadedTimeRange = CMTimeGetSeconds(range.start) + CMTimeGetSeconds(range.duration)
if currentLoadedTimeRange > maximum {
maximum = currentLoadedTimeRange
}
}
let progress:Double = assetDuration == 0 ? 0.0 : Double(maximum) / assetDuration
delegate?.downloadedProgress(progress: progress)
}
deinit {
cleanUp()
}
private func initialSetupWithURL(url:NSURL) {
let options = [AVURLAssetPreferPreciseDurationAndTimingKey : true]
urlAsset = AVURLAsset(url: url as URL, options: options)
}
// MARK: - Observations
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == videoContext {
if let key = keyPath {
if key == "status", let player = assetPlayer {
playerDidChangeStatus(status: player.status)
} else if key == "loadedTimeRanges", let item = playerItem {
moviewPlayerLoadedTimeRangeDidUpdated(ranges: item.loadedTimeRanges)
}
}
}
}
}
Usage:
用法:
private var playerView: PlayerView = PlayerView()
private var videoPlayer:VideoPlayer?
and inside viewDidLoad()
:
和里面viewDidLoad()
:
view.addSubview(playerView)
preparePlayer()
// set Constraints (if you do it purely in code)
playerView.translatesAutoresizingMaskIntoConstraints = false
playerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 10.0).isActive = true
playerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10.0).isActive = true
playerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10.0).isActive = true
playerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 10.0).isActive = true
private func preparePlayer() {
if let filePath = Bundle.main.path(forResource: "my video", ofType: "mp4") {
let fileURL = NSURL(fileURLWithPath: filePath)
videoPlayer = VideoPlayer(urlAsset: fileURL, view: playerView)
if let player = videoPlayer {
player.playerRate = 0.67
}
}
}