我已经阅读了 StackOverflow 上有关自动播放视频的大部分问题,并且我能够在 UITableView 中自动播放它们,但我遇到的问题很少,如下所述
我想要的是在不使用任何第三方库(如 ASYNCDisplayKit)的情况下像 Facebook 一样自动播放视频的流畅体验。
所有视频 url 均来自 AWSS3 云端 URLS。
我还上传了该问题的视频,以防有人想看。
这是我的完整代码
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
PostViewModel* model = self.posts[indexPath.section];
Post* post = model.post;
PostItems* item = model.items[indexPath.row];
if(item.itemType == nameAndPicture) {
//Removed code as it's not related to question
}
else if(item.itemType == textContent){
//Removed code as it's not related to question
}
else if(item.itemType == images){
//Removed code as it's not related to question
}
else if(item.itemType == videos){
VideoListCell *cell = nil;
cell = (VideoListCell*)[tableView dequeueReusableCellWithIdentifier:kFeedVideoListCellIdentifier forIndexPath:indexPath];
cell.delegate = self;
cell.indexPath = indexPath;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundColor = [UIColor clearColor];
cell.videoThumbnail.image = nil;
[cell setCounter:post.medias.count];
if (post.medias.count > 0) {
MediaItem* item = post.medias[0];
if ([item getMediaType] == VIDEO) {
NSString* thumbnailURL = item.thumbnailUrl;
[cell.videoThumbnail downloadImageWithURL:thumbnailURL andPlaceholderImage:self.timelinePlaceholder indicatorStyle:UIActivityIndicatorViewStyleWhiteLarge cachePolicy:NSURLRequestReturnCacheDataElseLoad andTimeOut:120];
[cell hideVideoAndShowThumbnail];
dispatch_async(dispatch_get_main_queue(), ^{
[cell setMediaItem:item withUserID:post.userId];
});
}
}
cell.clipsToBounds = YES;
return cell;
}
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
//Check if the cell displayed is video cell then try to autoplay the video
if([cell isKindOfClass:[VideoListCell class]]){
VideoListCell* videoCell = (VideoListCell*)cell;
dispatch_async(dispatch_get_main_queue(), ^{
[videoCell hideVideoAndShowThumbnail];
});
PostViewModel* model = self.posts[indexPath.section];
Post* post = model.post;
PostItems* item = model.items[indexPath.row];
if(item.itemType == videos){
videoCell.videoThumbnail.image = nil;
[videoCell setCounter:post.medias.count];
if (post.medias.count > 0) {
MediaItem* item = post.medias[0];
if ([item getMediaType] == VIDEO) {
//dispatch_async(dispatch_get_main_queue(), ^{
NSString* profilePic = item.thumbnailUrl;
[videoCell.videoThumbnail downloadImageWithURL:profilePic andPlaceholderImage:self.timelinePlaceholder indicatorStyle:UIActivityIndicatorViewStyleWhiteLarge cachePolicy:NSURLRequestReturnCacheDataElseLoad andTimeOut:120];
[videoCell setMediaItem:item withUserID:post.userId];
[videoCell playVideo];
}
}
}
}
}
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath {
if([cell isKindOfClass:[VideoListCell class]]){
VideoListCell* videoCell = (VideoListCell*)cell;
[videoCell stopVideo];
videoCell.avLayer = nil;
videoCell.videoPlayer = nil;
[videoCell hideVideoAndShowThumbnail];
}
}
//视频列表单元类
#define kHeight 200
@implementation VideoListCell
- (void)awakeFromNib {
[super awakeFromNib];
UIImage* icon = [[UIImage imageNamed:@"play-icon"] imageTintedWithColor:kSliderDarkYellowColor];
[self.btnPlay setImage:icon forState:UIControlStateNormal];
UIImage* pauseIcon = [[UIImage imageNamed:@"pause-icon"] imageTintedWithColor:kSliderDarkYellowColor];
[self.btnPlay setImage:icon forState:UIControlStateNormal];
[self.btnPlay setImage:pauseIcon forState:UIControlStateSelected];
UITapGestureRecognizer *viewTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapOnView)];
viewTap.numberOfTapsRequired = 1;
self.viewPlayer.userInteractionEnabled = YES;
[self.viewPlayer addGestureRecognizer:viewTap];
self.counterView.hidden = YES;
self.counterView.layer.cornerRadius = 12.0f;
self.counterView.layer.masksToBounds = YES;
//Add Gesture to label
UITapGestureRecognizer *countGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapOnCounterView)];
countGesture.numberOfTapsRequired = 1;
self.counterView.userInteractionEnabled = YES;
[self.counterView addGestureRecognizer:countGesture];
[self.btnFullScreen addTarget:self action:@selector(btnFSTapped:) forControlEvents:UIControlEventTouchUpInside];
self.btnFullScreen.hidden = NO;
UIImage* fullScreenImage = [[UIImage imageNamed:@"fullScreenIcon"] imageTintedWithColor:kSliderDarkYellowColor];
[self.btnFullScreen setImage:fullScreenImage forState:UIControlStateNormal];
}
- (void)showThumbnail:(BOOL)yesOrNo {
self.videoThumbnail.hidden = !yesOrNo;
self.viewForVideo.hidden = yesOrNo;
}
- (void)hideVideoAndShowThumbnail {
[self stopVideo];
[self showThumbnail:YES];
self.btnPlay.selected = NO;
self.isPlaying = NO;
}
- (void)btnFSTapped:(UIButton*)sender {
if (self.delegate && [self.delegate respondsToSelector:@selector(fullScreenButtonTapped:andURL:andPlayer:)]) {
[self.delegate fullScreenButtonTapped:self.indexPath andURL:self.videoURL andPlayer:self.player.player];
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
// if (self.avLayer) {
// [self.avLayer setFrame:CGRectMake(self.viewForVideo.frame.origin.x, self.viewForVideo.frame.origin.y, self.viewForVideo.frame.size.width, self.viewForVideo.frame.size.height)];
// }
}
- (void)initNewPlayerItem {
// Pause the existing video (if there is one)
//[self stopVideo];
if(self.asset){
[self.asset cancelLoading];
}
// First we need to make sure we have a valid URL
if (!self.videoURL) {
return;
}
// Create a new AVAsset from the URL
self.asset = [AVAsset assetWithURL:self.videoURL];
// Now we need an AVPlayerItem to pass to the AVPlayer
AVPlayerItem* item = [[AVPlayerItem alloc] initWithAsset:self.asset];
if(item){
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:item];
}
//[self.player.player replaceCurrentItemWithPlayerItem:item];
// Finally, we set this as the current AVPlayer item
[self.asset loadValuesAsynchronouslyForKeys:@[@"duration"] completionHandler:^{
NSError* error = nil;
AVKeyValueStatus status = [self.asset statusOfValueForKey:@"duration" error:&error];
if (status == AVKeyValueStatusFailed) {
[self.playerSetupLoading stopAnimating];
self.btnPlay.hidden = NO;
self.btnPlay.selected = NO;
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.playerSetupLoading stopAnimating];
[self.player.player replaceCurrentItemWithPlayerItem:item];
self.btnPlay.selected = YES;
self.btnPlay.hidden = YES;
[self showThumbnail:NO];
[self.player.player play];
self.isPlaying = YES;
});
}];
}
- (void)playerItemDidReachEnd:(NSNotification*)notif {
id object = [notif object];
if (object && [object isKindOfClass:[AVPlayerItem class]]) {
AVPlayerItem* item = (AVPlayerItem*)[notif object];
[item seekToTime:kCMTimeZero];
}
//[self stopVideo];
[self showThumbnail:YES];
self.btnPlay.selected = NO;
self.btnPlay.hidden = NO;
}
-(void)prepareForReuse {
// self.videoURL = nil;
// self.videoThumbnail.image = nil;
//[self.player pauseContent];
self.videoThumbnail.image = nil;
if (self.avLayer.superlayer) {
[self.avLayer removeFromSuperlayer];
}
if (self.viewForVideo.subviews.count > 0) {
for (UIView* v in self.viewForVideo.subviews) {
[v removeFromSuperview];
}
}
self.videoURL = nil;
self.player = nil;
self.userID = nil;
self.videoItem = nil;
self.videoPlayer = nil;
self.btnPlay.selected = NO;
[super prepareForReuse];
}
- (void)tapOnView {
//if(self.counterView.hidden){
if (self.delegate && [self.delegate respondsToSelector:@selector(fullScreenButtonTapped:andURL:andPlayer:)]) {
[self.delegate fullScreenButtonTapped:self.indexPath andURL:self.videoURL andPlayer:self.player.player];
}
//}
// else
// {
// if (self.delegate && [self.delegate respondsToSelector:@selector(playVideo:withURL:)]) {
// [self.delegate playVideo:self.indexPath withURL:nil];
// }
// }
}
-(void)tapOnCounterView {
if (self.delegate && [self.delegate respondsToSelector:@selector(playVideo:withURL:)]) {
[self.delegate playVideo:self.indexPath withURL:nil];
}
}
- (void)setCounter:(NSUInteger)count {
if (count > 1) {
self.counterView.hidden = NO;
self.lblCounter.text = [NSString stringWithFormat:@"+%lu more",(unsigned long)count-1];
}
else{
self.counterView.hidden = YES;
}
}
- (IBAction)btnPlayTapped:(id)sender {
//[self playVideo];
//if(self.counterView.hidden){
if(self.btnPlay.selected){
[self stopVideo];
self.btnPlay.selected = NO;
}else{
[self playVideo];
self.btnPlay.selected = YES;
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if(self.player.player.timeControlStatus == AVPlayerTimeControlStatusPlaying){
if(self.btnPlay.hidden){
self.btnPlay.hidden = NO;
}
}
}
- (void)playVideo {
//if (!self.player) {
if ([self.videoObject doesPreSignedURLExpired]) {
//Call API here and update media item object URL
dispatch_async(dispatch_get_main_queue(), ^{
//Call API here
//URL is expired then give a call to our server to generate a new URL
[self generateNewPreSignedURL];
});
}
else{
if (!self.videoURL) {
dispatch_async(dispatch_get_main_queue(), ^{
[self generatePreSignedURLWithVideoThumbnail];
});
}else{
dispatch_async(dispatch_get_main_queue(), ^{
[self setupPlayer];
});
}
}
}
- (void)stopVideo {
if (self.player) {
self.isPlaying = NO;
self.btnPlay.hidden = NO;
[self.player.player pause];
}
}
- (void)setMediaItem:(MediaItem*)item withUserID:(NSNumber*)userId {
self.videoObject = item;
self.userID = userId;
[self showThumbnail:YES];
}
- (void)generatePreSignedURLWithVideoThumbnail {
if (self.videoObject.mediaUrl && [self.videoObject hasPreSignedURL]) {
//Already have pre signed url check if URL is expired
//If URL expired then call our own server to generate a new presigned URL
dispatch_async(dispatch_get_main_queue(), ^{
self.videoURL = [NSURL URLWithString:self.videoObject.mediaUrl];
[self setupPlayer];
});
}
else if(self.videoObject.mediaUrl && [self.videoObject hasPlayListURL]){
AppDelegate* delegate = [AppDelegate applicationDelegate];
AWSS3GetPreSignedURLRequest *getPreSignedURLRequest = [AWSS3GetPreSignedURLRequest new];
getPreSignedURLRequest.bucket = S3BucketName;
getPreSignedURLRequest.key = kS3OutputVideoFileInternalPath(delegate.loggedInUser.userId,[self.videoObject getVideoFolderName],self.videoObject.mediaUrl);
getPreSignedURLRequest.HTTPMethod = AWSHTTPMethodGET;
getPreSignedURLRequest.expires = [NSDate dateWithTimeIntervalSinceNow:Hour*24*5];
[[[AWSS3PreSignedURLBuilder defaultS3PreSignedURLBuilder] getPreSignedURL:getPreSignedURLRequest]
continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(@"Error: %@",task.error);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
self.videoURL = task.result;
[self setupPlayer];
});
}
return nil;
}];
}
else{
//Generate Pre signed URL
AWSS3GetPreSignedURLRequest *getPreSignedURLRequest = [AWSS3GetPreSignedURLRequest new];
getPreSignedURLRequest.bucket = S3BucketName;
getPreSignedURLRequest.key = [kS3InputVideoFilePath(self.userID) stringByAppendingString:self.videoObject.mediaUrl];
getPreSignedURLRequest.HTTPMethod = AWSHTTPMethodGET;
getPreSignedURLRequest.expires = [NSDate dateWithTimeIntervalSinceNow:Hour*24*5];
[[[AWSS3PreSignedURLBuilder defaultS3PreSignedURLBuilder] getPreSignedURL:getPreSignedURLRequest]
continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(@"Error: %@",task.error);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
self.videoURL = task.result;
[self setupPlayer];
});
}
return nil;
}];
}
}
- (void)setupPlayer {
self.btnPlay.hidden = YES;
self.videoItem = nil;
self.videoPlayer = nil;
self.videoItem = [[AVPlayerItem alloc] initWithURL:self.videoURL];
if (self.avLayer.superlayer) {
[self.avLayer removeFromSuperlayer];
}
if (self.viewForVideo.subviews.count > 0) {
for (UIView* v in self.viewForVideo.subviews) {
[v removeFromSuperview];
}
}
self.videoPlayer = [[AVPlayer alloc] initWithPlayerItem:self.videoItem];
self.avLayer = [AVPlayerLayer playerLayerWithPlayer:self.videoPlayer];
self.avLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
self.player = [[AVPlayerViewController alloc] init];
self.player.player = self.videoPlayer;
self.player.videoGravity = AVLayerVideoGravityResizeAspectFill;
self.player.showsPlaybackControls = NO;
// Insert the player into the cell view hierarchy and setup autolayout
self.player.view.translatesAutoresizingMaskIntoConstraints = false;
[self.viewForVideo insertSubview:self.player.view atIndex:0];
//Trailing
NSLayoutConstraint *trailing =[NSLayoutConstraint
constraintWithItem:self.player.view
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.viewForVideo
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:0.f];
//Leading
NSLayoutConstraint *leading = [NSLayoutConstraint
constraintWithItem:self.player.view
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.viewForVideo
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:0.f];
//Bottom
NSLayoutConstraint *bottom =[NSLayoutConstraint
constraintWithItem:self.player.view
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.viewForVideo
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:0.f];
//Height to be fixed for SubView same as AdHeight
NSLayoutConstraint *height = [NSLayoutConstraint
constraintWithItem:self.player.view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:0
constant:kHeight];
//Add constraints to the Parent
[self.viewForVideo addConstraint:trailing];
[self.viewForVideo addConstraint:bottom];
[self.viewForVideo addConstraint:leading];
//Add height constraint to the subview, as subview owns it.
[self.player.view addConstraint:height];
[self initNewPlayerItem];
}
- (void)generateNewPreSignedURL {
if (self.videoObject) {
NSDictionary* postParams = @{kMediaId:self.videoObject.mediaId};
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
TBWebAPIConsumer *web = [TBWebAPIConsumer sharedWebAPIManager];
[web generatePreSignedURL:postParams andCompletionBlock:^(NSError *error, id serverResponse) {
// Do something...
dispatch_async(dispatch_get_main_queue(), ^{
if (error == nil){
//Parse user data here
NSDictionary* data = (NSDictionary*)serverResponse;
if (![data valueForKeyIsNull:@"mediaUrl"]) {
self.videoObject.mediaUrl = [data valueForKey:@"mediaUrl"];
}
if (![data valueForKeyIsNull:@"videoSignedUrlExpiry"]) {
self.videoObject.videoSignedUrlExpiry = [data valueForKey:@"videoSignedUrlExpiry"];
}
[self generatePreSignedURLWithVideoThumbnail];
}
});
}];
});
}
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
有人可以帮我解决这个问题吗?
最佳答案
我有一些建议给你:
不要尝试在滚动期间调用播放/暂停。滚动停止后立即执行此操作:https://gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e
您可以尝试使用这个肮脏的技巧:https://gist.github.com/k06a/66f7815b0325f239411e26f498c75755要对 Apple Review Team 隐藏它,只需使用 UAObfuscateString 库混淆键路径 @"_player.stateDispatchQueue"。
我听说无需肮脏的黑客攻击即可获得平稳的投资返回。我会请我的一个 friend 来回答你的问题。
关于ios - UITableViewCell 打嗝中的自动播放视频,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51787983/
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只