Mam krótki mp4
plik wideo, który dodałem do mojego obecnego projektu Xcode6 Beta.
Chcę odtworzyć wideo w mojej aplikacji.
Po godzinach poszukiwań nie mogę znaleźć niczego, co by mi pomogło. Czy jest sposób, aby to osiągnąć za pomocą Swift, czy też musisz użyć Objective-C? Czy mogę zostać wskazany we właściwym kierunku? Nie mogę być jedynym, który się nad tym zastanawia.
ios
objective-c
swift
avplayer
avplayerviewcontroller
36 Zgodnie z projektem
źródło
źródło
Odpowiedzi:
Jasne, że możesz używać Swift!
1. Dodanie pliku wideo
Dodaj wideo (nazwijmy to
video.m4v
) do projektu Xcode2. Sprawdzanie, czy plik wideo znajduje się w pakiecie
Otwórz
Project Navigator
cmd+1Następnie wybierz
your project root
>your Target
>Build Phases
>Copy Bundle Resources
.Twój film MUSI być tutaj. Jeśli tak nie jest, dodaj go za pomocą przycisku plusa
3. Kod
Otwórz kontroler widoku i napisz ten kod.
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() } } }
źródło
initializer for conditional binding must have optional type not nsurl
import AVKit
?kolejny przykład Swift 3. Dostarczone rozwiązanie nie zadziałało dla mnie.
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() }
zastosowanie:
playVideo(from: "video.extension")
Uwaga: sprawdź Kopiuj zasoby paczki w fazie budowy, aby upewnić się, że wideo jest dostępne dla projektu.
źródło
Możesz skonfigurować
AVPlayer
w inny sposób, który otwiera się dla pełnego dostosowania ekranu odtwarzacza wideoSwift 2.3
Utwórz
UIView
podklasę do odtwarzania wideo (w zasadzie możesz użyć dowolnegoUIView
obiektu i tylko jest to potrzebneAVPlayerLayer
. Konfiguruję w ten sposób, ponieważ dla mnie jest to znacznie bardziej przejrzyste)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 } } } }
Skonfiguruj swój odtwarzacz
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) } } } } }
Stosowanie:
załóż, że masz widok
@IBOutlet private weak var playerView: PlayerView! private var videoPlayer:VideoPlayer?
i w
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 } } }
Cel C
PlayerView.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
#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
#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
#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
Zasób niezwiązany z przyczyną (plik wideo) powinien mieć ustawione członkostwo docelowe w projekcie
Dodatkowe - link do doskonałego przewodnika Apple Developer
źródło
layer.player = player
(w języku SwiftPlayerView
) nie powinna byćlayer.player = newValue
? Ponieważ chcesz ustawić nową wartość, a nie bieżącą, która jest zerowa.Szybki 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() } }
źródło
ten kod jest konwertowany z odpowiedzi gbk w swift 4
1. w głównym widoku kontrolera:
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 } }
potrzebujesz klasy 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) } } } } }
i klasę PlayerView do tworzenia podglądu wideo:
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 } } } }
źródło
PlayerView dla Swift 4.2
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 } }
źródło
Rozwiązanie GBK w 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 } } } }
Odtwarzacz wideo
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) } } } } }
źródło
Żadne z powyższych nie działało dla mnie Swift 5 dla lokalnego odtwarzacza wideo
po przeczytaniu dokumentacji Apple'a udało mi się stworzyć prosty przykład odtwarzania wideo z lokalnych zasobów
Oto fragment kodu
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() } } }
źródło
Tutaj rozwiązanie dla Swift 5.2
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
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) } } } } }
Stosowanie:
private var playerView: PlayerView = PlayerView() private var videoPlayer:VideoPlayer?
a wewnątrz
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 } } }
źródło