AVPlayerLoop not seamlessly looping - Swift 4
One thing to to keep in mind when looping assets is that audio and video tracks can have different offsets and different durations, resulting in 'blips' when looping. Such small differences are quite common in recorded assets.
Iterating over the tracks and printing the time ranges can help to detect such situations: for track in asset.tracks { print( track.mediaType); CMTimeRangeShow( track.timeRange); }
To trim audio and video tracks to equal start times and equal durations, get the common time range of the tracks, and then insert this time range from the original asset into a new AVMutableComposition
. Normally, you also want to preserve properties like the orientation of the video track:
let asset: AVAsset = (your asset initialization here)
let videoTrack: AVAssetTrack = asset.tracks(withMediaType: .video).first!
let audioTrack: AVAssetTrack = asset.tracks(withMediaType: .audio).first!
// calculate common time range of audio and video track
let timeRange: CMTimeRange = CMTimeRangeGetIntersection( (videoTrack.timeRange), (audioTrack.timeRange))
let composition: AVMutableComposition = AVMutableComposition()
try composition.insertTimeRange(timeRange, of: asset, at: kCMTimeZero)
// preserve orientation
composition.tracks(withMediaType: .video).first!.preferredTransform = videoTrack.preferredTransform
Since AVMutableComposition is a subclass of AVAsset, it can be used for AVPlayerLooper
-based looping playback, or exporting with AVAssetExportSession
.
I've put a more complete trimming implementation on github: https://github.com/fluthaus/NHBAVAssetTrimming. It's more robust, handles multiple tracks, preserves more properties and can either be easily integrated in projects or be build as a standalone macOS command line movie trimming utility.
Problem when attempting to loop AVPlayer (userCapturedVideo) seamlessly
I did the same thing a few years back using this piece of code:
var player: AVPlayer!
var playerLayer: AVPlayerLayer!
private func playVideo(name: String) {
guard let path = Bundle.main.path(forResource: name, ofType:"mp4") else { return }
player = AVPlayer(url: NSURL(fileURLWithPath: path) as URL)
playerLayer = AVPlayerLayer(player: player)
self.playerLayer.frame = SOME_BOUNDS
self.player!.play()
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(note:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
}
@objc func playerDidFinishPlaying(note: NSNotification) {
print("Video Finished")
self.playerLayer.removeFromSuperlayer()
playVideo(name: "SOME_NAME")
}
Hope this points you to your desired functionality
Seamless Loop Video
I was able to get this to loop seamless with avlooper.
#import "VideoWindow.h"
#import <AVFoundation/AVFoundation.h>
#import <AVKit/AVKit.h>
@interface VideoWindow ()
@property (strong) IBOutlet AVPlayerView *playerView;
@property (strong) IBOutlet NSWindow *aspectView;
@end
@implementation VideoWindow
{
AVPlayerItem *_playerItem;
AVQueuePlayer *_player;
AVPlayerLooper *_playerLooper;
AVPlayerLayer *_playerLayer;
}
- (void)windowDidLoad {
[super windowDidLoad];
NSString *videoFile = [[NSBundle mainBundle] pathForResource:@"Video2" ofType:@"mp4"];
NSURL *videoURL = [NSURL fileURLWithPath:videoFile];
_playerItem = [AVPlayerItem playerItemWithURL:videoURL];
_player = [AVQueuePlayer queuePlayerWithItems:@[_playerItem]];
_playerLooper = [AVPlayerLooper playerLooperWithPlayer:_player templateItem:_playerItem];
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
_playerLayer.frame = self.playerView.bounds;
_playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.playerView.layer addSublayer:_playerLayer];
[_aspectView setAspectRatio:NSMakeSize(16, 9)];
[_player play];
}
- (void)windowWillClose:(NSNotification *)aNotification {
[_player pause];
[_player seekToTime:kCMTimeZero];
}
@end
Looping a video with AVFoundation AVPlayer?
You can get a Notification when the player ends. Check AVPlayerItemDidPlayToEndTimeNotification
When setting up the player:
ObjC
avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[avPlayer currentItem]];
this will prevent the player to pause at the end.
in the notification:
- (void)playerItemDidReachEnd:(NSNotification *)notification {
AVPlayerItem *p = [notification object];
[p seekToTime:kCMTimeZero];
}
this will rewind the movie.
Don't forget un unregister the notification when releasing the player.
Swift
avPlayer?.actionAtItemEnd = .none
NotificationCenter.default.addObserver(self,
selector: #selector(playerItemDidReachEnd(notification:)),
name: .AVPlayerItemDidPlayToEndTime,
object: avPlayer?.currentItem)
@objc func playerItemDidReachEnd(notification: Notification) {
if let playerItem = notification.object as? AVPlayerItem {
playerItem.seek(to: kCMTimeZero)
}
}
Swift 4+
@objc func playerItemDidReachEnd(notification: Notification) {
if let playerItem = notification.object as? AVPlayerItem {
playerItem.seek(to: CMTime.zero, completionHandler: nil)
}
}
AVAudioPlayer loops are not seamless
Using AVAudioEngine
will give you a lot of flexibility but it's overhead if you don't need anything else but sync your tracks.
In this case you can try to use a single player with AVComposition
containing all your tracks, something like this:
func generateComposition(urls: [URL]) throws -> AVComposition {
let composition = AVMutableComposition()
let audioTracks = urls
.map(AVAsset.init(url:))
.flatMap { $0.tracks(withMediaType: .audio) }
for audioTrack in audioTracks {
guard
let compositionTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
else { continue }
try compositionTrack.insertTimeRange(
audioTrack.timeRange,
of: audioTrack,
at: .zero
)
}
return composition
}
And play it using AVPlayer
:
AVPlayer(playerItem: AVPlayerItem(asset: composition))
Related Topics
How to Debug an iOS Extension (.Appex)
Auto Login Dropbox Account on Core API Without Login Prompt
Uitableview Dynamic Cell Heights Only Correct After Some Scrolling
Uitapgesturerecognizer Tap on Self.View But Ignore Subviews
How to Turn the iPhone Camera Flash On/Off
Cannot Set Searchbar as Firstresponder
Calculating Bearing Between Two Cllocation Points in Swift
How to Add Image in Uitableviewrowaction
Negative Number Modulo in Swift
Dynamic Link Object Has No Url
Type 'Any' Has No Subscript Members (Firebase)
Smart-Search for Parse Usernames in Swift Not Working
Use Didselectrowatindexpath or Prepareforsegue Method for Uitableview
Custom Font Sizing in Xcode 6 Size Classes Not Working Properly with Custom Fonts
Single Function to Dismiss All Open View Controllers
Uicollectionview Header Dynamic Height Using Auto Layout