在 iOS 中循环播放列表时出现 AVQueuePlayer 内存问题
AVQueuePlayer memory issue while looping a playlist in iOS
在我的 iOS 应用程序中,我正在尝试播放下载到应用程序的文档目录中的视频列表。为了实现该目标,我使用了 AVQueuePlayer。以下是我的代码,它在循环 6/7 次后导致应用程序崩溃。
@interface PlayYTVideoViewController () <NSURLConnectionDataDelegate, UITableViewDataSource, UITableViewDelegate>
{
AVQueuePlayer *avQueuePlayer;
}
- (void)playlistLoop
{
NSLog(@"%s - %d", __PRETTY_FUNCTION__, __LINE__);
lastPlayedVideoNumber = 0;
_loadingVideoLabel.hidden = YES;
avPlayerItemsMutArray = [[NSMutableArray alloc] init];
for (NSString *videoPath in clipUrlsMutArr)
{
NSURL *vidPathUrl = [NSURL fileURLWithPath:videoPath];
AVPlayerItem *avpItem = [AVPlayerItem playerItemWithURL:vidPathUrl];
[avPlayerItemsMutArray addObject:avpItem];
}
avPlayerItemsArray = [avPlayerItemsMutArray copy];
for(AVPlayerItem *item in avPlayerItemsArray)
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemDidPlayToEndTime:) name:AVPlayerItemDidPlayToEndTimeNotification object:item];
}
avQueuePlayer = [AVQueuePlayer queuePlayerWithItems:avPlayerItemsArray];
avQueuePlayer.actionAtItemEnd = AVPlayerActionAtItemEndAdvance;
introVideoLayer = [AVPlayerLayer playerLayerWithPlayer:avQueuePlayer];
introVideoLayer.frame = _mpIntroVideoView.bounds;
[_mpContainerView.layer addSublayer:introVideoLayer];
[avQueuePlayer play];
}
- (void)itemDidPlayToEndTime:(NSNotification *)notification
{
NSLog(@"%s - %d", __PRETTY_FUNCTION__, __LINE__);
AVPlayerItem *endedAVPlayerItem = [notification object];
[endedAVPlayerItem seekToTime:kCMTimeZero];
for (AVPlayerItem *item in avPlayerItemsArray)
{
if (item == endedAVPlayerItem)
{
lastPlayedVideoNumber++;
break;
}
}
[self reloadVideoClipsTable];
if ([endedAVPlayerItem isEqual:[avPlayerItemsArray lastObject]])
{
[self playlistLoop];
}
}
出现内存问题后,我尝试对上面的代码进行一些更改。
我尝试设置 avQueuePlayer 变量 public 并将其设置为强变量
@property (strong, nonatomic) AVQueuePlayer *avQueuePlayer;
通过这样做,我希望 avQueuePlayer 变量保留在内存中,直到我们手动设置为 nil。但这并没有解决问题。
然后我尝试将播放器、相关数组和层设置为 nil 并为新的循环会话重新创建。
if (avPlayerItemsMutArray != nil)
{
avPlayerItemsMutArray = nil;
}
avPlayerItemsMutArray = [[NSMutableArray alloc] init];
if (avPlayerItemsArray != nil)
{
avPlayerItemsArray = nil;
}
avPlayerItemsArray = [avPlayerItemsMutArray copy];
if (avQueuePlayer != nil)
{
avQueuePlayer = nil;
}
avQueuePlayer = [AVQueuePlayer queuePlayerWithItems:avPlayerItemsArray];
if(introVideoLayer != nil)
{
[introVideoLayer removeFromSuperlayer];
introVideoLayer = nil;
}
introVideoLayer = [AVPlayerLayer playerLayerWithPlayer:avQueuePlayer];
但这也无助于解决问题。
接下来我尝试在观察者在新循环中重新初始化之前将其移除
if (avPlayerItemsArray != nil)
{
avPlayerItemsArray = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AVPlayerItemDidPlayToEndTimeNotification
object:nil];
}
但这也无济于事。
接下来我使用 Instrument 找出内存使用情况和泄漏。应用程序崩溃时不超过 18 MB,并且还有超过 200 MB 剩余可用空间。 Instruments 稍微复杂一点,但我仍然没有发现与此代码相关的任何内存泄漏。
实际上错误与 AVQueuePlayer 无关。在我的应用程序中,我在视频播放视图下方的 table 中列出了所有视频。在那个 table 中,每一行都包含我从下面的代码中截取的视频缩略图。
+ (UIImage *)createThumbForVideo:(NSString *)vidFileName
{
NSString *videoFolder = [Video getVideoFolder];
NSString *videoFilePath = [videoFolder stringByAppendingFormat:@"/trickbook/videos/edited/%@",vidFileName];
NSURL *url = [NSURL fileURLWithPath:videoFilePath];
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
AVAssetImageGenerator *generateImg = [[AVAssetImageGenerator alloc] initWithAsset:asset];
generateImg.appliesPreferredTrackTransform = YES;
NSError *error = NULL;
CMTime time = CMTimeMake(1, 65);
CGImageRef refImg = [generateImg copyCGImageAtTime:time actualTime:NULL error:&error];
UIImage *frameImage = [[UIImage alloc] initWithCGImage:refImg];
return frameImage;
}
每次视频剪辑结束播放并且播放列表开始新循环时,我都会更新 table 视图。所以每次我调用上面的方法,这就是内存问题的原因。
作为解决方案,我只为单个视频剪辑调用此方法一次,并将返回的 UIImage 存储在 mutable 数组中。那解决了问题。
问题的标题和标签可能不足以回答,但我认为这值得作为问答存在,而不是删除 post.
在我的 iOS 应用程序中,我正在尝试播放下载到应用程序的文档目录中的视频列表。为了实现该目标,我使用了 AVQueuePlayer。以下是我的代码,它在循环 6/7 次后导致应用程序崩溃。
@interface PlayYTVideoViewController () <NSURLConnectionDataDelegate, UITableViewDataSource, UITableViewDelegate>
{
AVQueuePlayer *avQueuePlayer;
}
- (void)playlistLoop
{
NSLog(@"%s - %d", __PRETTY_FUNCTION__, __LINE__);
lastPlayedVideoNumber = 0;
_loadingVideoLabel.hidden = YES;
avPlayerItemsMutArray = [[NSMutableArray alloc] init];
for (NSString *videoPath in clipUrlsMutArr)
{
NSURL *vidPathUrl = [NSURL fileURLWithPath:videoPath];
AVPlayerItem *avpItem = [AVPlayerItem playerItemWithURL:vidPathUrl];
[avPlayerItemsMutArray addObject:avpItem];
}
avPlayerItemsArray = [avPlayerItemsMutArray copy];
for(AVPlayerItem *item in avPlayerItemsArray)
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemDidPlayToEndTime:) name:AVPlayerItemDidPlayToEndTimeNotification object:item];
}
avQueuePlayer = [AVQueuePlayer queuePlayerWithItems:avPlayerItemsArray];
avQueuePlayer.actionAtItemEnd = AVPlayerActionAtItemEndAdvance;
introVideoLayer = [AVPlayerLayer playerLayerWithPlayer:avQueuePlayer];
introVideoLayer.frame = _mpIntroVideoView.bounds;
[_mpContainerView.layer addSublayer:introVideoLayer];
[avQueuePlayer play];
}
- (void)itemDidPlayToEndTime:(NSNotification *)notification
{
NSLog(@"%s - %d", __PRETTY_FUNCTION__, __LINE__);
AVPlayerItem *endedAVPlayerItem = [notification object];
[endedAVPlayerItem seekToTime:kCMTimeZero];
for (AVPlayerItem *item in avPlayerItemsArray)
{
if (item == endedAVPlayerItem)
{
lastPlayedVideoNumber++;
break;
}
}
[self reloadVideoClipsTable];
if ([endedAVPlayerItem isEqual:[avPlayerItemsArray lastObject]])
{
[self playlistLoop];
}
}
出现内存问题后,我尝试对上面的代码进行一些更改。
我尝试设置 avQueuePlayer 变量 public 并将其设置为强变量
@property (strong, nonatomic) AVQueuePlayer *avQueuePlayer;
通过这样做,我希望 avQueuePlayer 变量保留在内存中,直到我们手动设置为 nil。但这并没有解决问题。
然后我尝试将播放器、相关数组和层设置为 nil 并为新的循环会话重新创建。
if (avPlayerItemsMutArray != nil)
{
avPlayerItemsMutArray = nil;
}
avPlayerItemsMutArray = [[NSMutableArray alloc] init];
if (avPlayerItemsArray != nil)
{
avPlayerItemsArray = nil;
}
avPlayerItemsArray = [avPlayerItemsMutArray copy];
if (avQueuePlayer != nil)
{
avQueuePlayer = nil;
}
avQueuePlayer = [AVQueuePlayer queuePlayerWithItems:avPlayerItemsArray];
if(introVideoLayer != nil)
{
[introVideoLayer removeFromSuperlayer];
introVideoLayer = nil;
}
introVideoLayer = [AVPlayerLayer playerLayerWithPlayer:avQueuePlayer];
但这也无助于解决问题。
接下来我尝试在观察者在新循环中重新初始化之前将其移除
if (avPlayerItemsArray != nil)
{
avPlayerItemsArray = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AVPlayerItemDidPlayToEndTimeNotification
object:nil];
}
但这也无济于事。
接下来我使用 Instrument 找出内存使用情况和泄漏。应用程序崩溃时不超过 18 MB,并且还有超过 200 MB 剩余可用空间。 Instruments 稍微复杂一点,但我仍然没有发现与此代码相关的任何内存泄漏。
实际上错误与 AVQueuePlayer 无关。在我的应用程序中,我在视频播放视图下方的 table 中列出了所有视频。在那个 table 中,每一行都包含我从下面的代码中截取的视频缩略图。
+ (UIImage *)createThumbForVideo:(NSString *)vidFileName
{
NSString *videoFolder = [Video getVideoFolder];
NSString *videoFilePath = [videoFolder stringByAppendingFormat:@"/trickbook/videos/edited/%@",vidFileName];
NSURL *url = [NSURL fileURLWithPath:videoFilePath];
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
AVAssetImageGenerator *generateImg = [[AVAssetImageGenerator alloc] initWithAsset:asset];
generateImg.appliesPreferredTrackTransform = YES;
NSError *error = NULL;
CMTime time = CMTimeMake(1, 65);
CGImageRef refImg = [generateImg copyCGImageAtTime:time actualTime:NULL error:&error];
UIImage *frameImage = [[UIImage alloc] initWithCGImage:refImg];
return frameImage;
}
每次视频剪辑结束播放并且播放列表开始新循环时,我都会更新 table 视图。所以每次我调用上面的方法,这就是内存问题的原因。
作为解决方案,我只为单个视频剪辑调用此方法一次,并将返回的 UIImage 存储在 mutable 数组中。那解决了问题。
问题的标题和标签可能不足以回答,但我认为这值得作为问答存在,而不是删除 post.