如何从 UIWebView 嵌入式 YouTube 视频播放接收 NSNotifications
Posted
技术标签:
【中文标题】如何从 UIWebView 嵌入式 YouTube 视频播放接收 NSNotifications【英文标题】:How to receive NSNotifications from UIWebView embedded YouTube video playback 【发布时间】:2012-01-21 01:37:55 【问题描述】:我没有收到MPMoviePlayerController
的任何通知。我做错了什么?
我使用以下逻辑。
我开始在UIWebView
中播放 youtube 视频。 UIWebView
调用标准 MPMoviePlayerController
。我不控制MPMoviePlayerController
,因为我没有实例化MPMoviePlayerController
。
我使用自动播放(延迟 1 秒)运行 youtube 的剪辑:
[self performSelector:@selector(touchInView:) withObject:b afterDelay:1];
我的代码是:
- (void)viewDidLoad
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadStateDidChange:) name:MPMoviePlayerLoadStateDidChangeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackDidFinish:) name:MPMoviePlayerDidExitFullscreenNotification object:nil];
[self embedYouTube];
- (void)loadStateDidChange:(NSNotification*)notification
NSLog(@"________loadStateDidChange");
- (void)playbackDidFinish:(NSNotification*)notification
NSLog(@"________DidExitFullscreenNotification");
- (void)embedYouTube
CGRect frame = CGRectMake(25, 89, 161, 121);
NSString *urlString = [NSString stringWithString:@"http://www.youtube.com/watch?v=sh29Pm1Rrc0"];
NSString *embedhtml = @"<html><head>\
<body style=\"margin:0\">\
<embed id=\"yt\" src=\"%@\" type=\"application/x-shockwave-flash\" \
width=\"%0.0f\" height=\"%0.0f\"></embed>\
</body></html>";
NSString *html = [NSString stringWithFormat:embedHTML, urlString, frame.size.width, frame.size.height];
UIWebView *videoView = [[UIWebView alloc] initWithFrame:frame];
videoView.delegate = self;
for (id subview in videoView.subviews)
if ([[subview class] isSubclassOfClass: [UIScrollView class]])
((UIScrollView *)subview).bounces = NO;
[videoView loadHTMLString:html baseURL:nil];
[self.view addSubview:videoView];
[videoView release];
- (void)webViewDidFinishLoad:(UIWebView *)_webView
UIButton *b = [self findButtonInView:_webView];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(touchInView:) object:b];
[self performSelector:@selector(touchInView:) withObject:b afterDelay:1];
- (UIButton *)findButtonInView:(UIView *)view
UIButton *button = nil;
if ([view isMemberOfClass:[UIButton class]])
return (UIButton *)view;
if (view.subviews && [view.subviews count] > 0)
for (UIView *subview in view.subviews)
button = [self findButtonInView:subview];
if (button) return button;
return button;
- (void)touchInView:(UIButton*)b
[b sendActionsForControlEvents:UIControlEventTouchUpInside];
更新:我正在创建播放 youtube 视频的应用程序。您可以运行播放列表,您将看到第一个视频。当第一个视频结束时,第二个视频开始自动播放,依此类推。
我需要支持 ios 4.1 及更高版本。
UPDATE2: @H2CO3 我正在尝试使用您的 url 方案,但它不起作用。退出事件时未调用委托方法。我将我的 html 网址添加到日志中。 它是:
<html><head> <body style="margin:0">
<script>function endMovie()
document.location.href="somefakeurlscheme://video-ended";
</script> <embed id="yt" src="http://www.youtube.com/watch?v=sh29Pm1Rrc0"
onended="endMovie()" type="application/x-shockwave-flash"
></embed>
</body></html>
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
if ([[[request URL] absoluteString] hasPrefix:@"somefakeurlscheme://video-ended"])
[self someMethodSupposedToDetectVideoEndedEvent];
return NO; // prevent really loading the URL
return YES; // else load the URL as desired
更新3 @Till,我无法捕获 UIMoviePlayerControllerDidExitFullscreenNotification,但我找到了 MPAVControllerItemPlaybackDidEndNotification。 MPAVControllerItemPlaybackDidEndNotification 在播放视频结束时出现。
但我不明白如何捕捉 onDone 通知?
【问题讨论】:
您最初的假设不正确。UIWebView
不使用标准 MPMoviePlayerController
进行播放。
【参考方案1】:
没有由UIWebView
嵌入式电影播放器发送的记录通知。
事实上,UIWebView
中使用的封闭式实现在许多方面(例如 DRM)与公共MPMoviePlayerController
确实不同。
在UIWebView
中用于播放视频内容的最重要的类称为MPAVController
和UIMoviePlayerController
。后者使播放器看起来像MPMoviePlayerController
全屏界面。
如果您胆敢冒被 Apple 拒绝的风险,实际上仍有一些方法可以实现您的目标。
注意 这没有记录在案,并且在每个新的 iOS 版本中都会中断。但是它确实适用于 iOS4.3、5.0 和 5.01、5.1 和 6.0,并且它可能也适用于其他版本。
我无法在 iOS 4.1 和 4.2 上测试这个解决方案,所以这取决于你。我高度怀疑它会起作用。
全屏状态
例如,如果您打算在用户点击完成按钮时做出反应,您可以这样做:
更新 建议使用此答案的旧版本 UIMoviePlayerControllerDidExitFullscreenNotification
而此新版本(针对 iOS6 更新)建议使用 @987654329 @。
C 语言水平:
void PlayerWillExitFullscreen (CFNotificationCenterRef center,
void *observer,
CFStringRef name,
const void *object,
CFDictionaryRef userInfo)
//do something...
CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(),
NULL,
PlayerWillExitFullscreen,
CFSTR("UIMoviePlayerControllerWillExitFullscreenNotification"),
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
Objective-C 级别:
- (void)playerWillExitFullscreen:(NSNotification *)notification
//do something...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playerWillExitFullscreen:)
name:@"UIMoviePlayerControllerWillExitFullscreenNotification"
object:nil];
我确实起草了 C-Level 和 Objective-C-Level 选项,因为真正了解所有这些的最佳方法是使用 C-Level (CoreFoundation) 函数,如答案末尾所示。如果通知的发送者不使用 Objective-C (NSNotifications),您可能实际上无法使用 NSNotification-mechanics 捕获它们。
播放状态
要检查播放状态,请注意"MPAVControllerPlaybackStateChangedNotification"
(如上所示)并检查可能如下所示的userInfo
:
MPAVControllerNewStateParameter = 1;
MPAVControllerOldStateParameter = 2;
进一步的逆向工程
要进行逆向工程和探索所有发送的通知,请使用以下 sn-p。
void MyCallBack (CFNotificationCenterRef center,
void *observer,
CFStringRef name,
const void *object,
CFDictionaryRef userInfo)
NSLog(@"name: %@", name);
NSLog(@"userinfo: %@", userInfo);
CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(),
NULL,
MyCallBack,
NULL,
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
【讨论】:
标准通知语法也适用于我: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(movieStartedPlaying:) name:@"UIMoviePlayerControllerDidEnterFullscreenNotification" object:nil];同样在 4.0 之前的 iOS 上,电影在它自己的模态控制器中打开并且不会触发这些事件。从好的方面来说,onViewDidAppear 将在底层控制器上被调用。 是否使用了这种方法?如果是,是否获得了应用商店的批准? @Roshit 它肯定会得到批准,因为没有调用私有 API。但话又说回来,他们可能会突然改变主意。 使用未记录的通知肯定是私有 API,但 Apple 不会轻易检测到任何东西。对于播放状态的变化,最好使用 UIMovieViewPlaybackStateDidChangeNotification。 @Till Hi 如果我们想隐藏播放器的默认控件并添加一些自定义控件怎么做?有什么想法吗?【参考方案2】:在 iOS 4.3+ 中,您可以使用 UIMoviePlayerControllerDidEnterFullscreenNotification
和 UIMoviePlayerControllerDidExitFullscreenNotification
通知:
-(void)viewDidLoad
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(youTubeStarted:) name:@"UIMoviePlayerControllerDidEnterFullscreenNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(youTubeFinished:) name:@"UIMoviePlayerControllerDidExitFullscreenNotification" object:nil];
-(void)youTubeStarted:(NSNotification *)notification
// your code here
-(void)youTubeFinished:(NSNotification *)notification
// your code here
【讨论】:
非常感谢,但您知道这是在哪里记录的吗?我找不到任何东西,需要类似于UIMoviePlayerController
Will EnterFullscreenNotification
的东西。
它是无证的。显然现在 iOS 6 中有UIMoviePlayerControllerWillEnterFullscreenNotification
和WillExit
通知,但我还没有测试过这些。请参阅此问题的已接受答案(由 Till 提供),因为他已对其进行了更新,并提供了更详细的解释。
最好的解决方案,但有一个问题,如果我想停止播放器播放视频,那我该怎么做?
那么,iOS8 的解决方案是什么? @RakeshBhatt【参考方案3】:
据我所知,在制作 Cocoa Touch 应用程序时,不依赖 UIWebView(以及 Apple 制作的所有系统类)的实现细节。可能是 UIWebView 的视频播放器不是标准的 MPMoviePlayerController 类,它可能有一个完全不同的委托/通知系统,用户不应该访问它。
我建议你使用 HTML5 元素并检测此标签的“onended”事件:
<html>
<body>
<script>
function endMovie()
// detect the event here
document.location.href="somefakeurlscheme://video-ended";
</script>
<video src="http://youtube.com/watch?v=aiugvdk755f" onended="endMovie()"></video>
</body>
</html>
事实上,通过 endMovie javascript 函数,你可以重定向到一个虚假的 URL,你可以在你的 -webView:shouldStartLoadWithRequest: (UIWebViewDelegate) 方法中捕获该 URL,从而得到视频已经结束的通知:
- (BOOL) webView:(UIWebView *)wv shouldStartLoadWithRequest:(NSURLRequest *)req
if ([[[req URL] absoluteString] hasPrefix:@"somefakeurlscheme://video-ended"])
[self someMethodSupposedToDetectVideoEndedEvent];
return NO; // prevent really loading the URL
return YES; // else load the URL as desired
希望这会有所帮助。
【讨论】:
+1 我喜欢使用 url-scheme 来使这个回调工作的想法。所以现在这完全取决于 UIWebView 是否正确处理onended
。
@H2CO3,您的方法无效。请查看我更新的问题。
@Voloda2 因为你做错了。我告诉过你使用
您好,我必须说这个想法相当出色.. 但是... Youtube 视频不是通过使用您的代码嵌入(不是任何),它们最终都成为一个视频元素通过播放按钮的条纹。所以你不能点击它。 -- 有人找到解决办法了吗?
它不适用于所有 youtube/Vimeo 视频,对吗?只有 HTML 支持的格式?【参考方案4】:
基于@H2CO3 答案,但使用iframe API。这是我让它发挥作用的唯一方法。
这不使用任何私有 API,这使其更具前瞻性。
这是嵌入 Youtube 视频的代码。查看 API 了解更多自定义方法。
<html>
<body>
<!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
<div id="player"></div>
<script>
// 2. This code loads the IFrame Player API code asynchronously.
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// 3. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
var player;
function onYouTubeIframeAPIReady()
player = new YT.Player('player',
height: '480',
width: '640',
videoId: 'aiugvdk755f',
events:
'onStateChange': onPlayerStateChange
);
// 5. The API calls this function when the player's state changes.
function onPlayerStateChange(event)
if (event.data == YT.PlayerState.ENDED)
endedMovie();
function endedMovie()
// detect the event here
document.location.href="somefakeurlscheme://video-ended";
</script>
</body>
</html>
这就是您收到视频结束通知的方式(UIWebViewDelegate 方法)。
- (BOOL) webView:(UIWebView *)wv shouldStartLoadWithRequest:(NSURLRequest *)req
if ([[[req URL] absoluteString] hasPrefix:@"somefakeurlscheme://video-ended"])
[self someMethodSupposedToDetectVideoEndedEvent];
return NO; // prevent really loading the URL
return YES; // else load the URL as desired
【讨论】:
【参考方案5】:在 ViewDidLoad 中添加如下代码
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(VideoExitFullScreen:) name:@"UIMoviePlayerControllerDidExitFullscreenNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(VideoEnterFullScreen:) name:@"UIMoviePlayerControllerDidEnterFullscreenNotification" object:nil];
以下方法用于显示进入/退出全屏的各个过程的消息/功能
- (void)VideoExitFullScreen:(id)sender
// Your respective content/function for Exit from full screen
- (void)VideoEnterFullScreen:(id)sender
// Your respective content/function for Enter to full screen
【讨论】:
【参考方案6】:这适用于我在 iOS 6.1 中,它在收到 AVPlayerItemDidPlayToEndTimeNotification 时隐藏/删除其他窗口:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemEnded:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
...
- (void)playerItemEnded:(NSNotification *)notification
for (UIWindow *window in [[UIApplication sharedApplication] windows])
if (window != self.window)
window.hidden = YES;
【讨论】:
这仅在用户将视频播放到最后时有效。如果他们只播放几秒钟然后按完成,则不会调用。【参考方案7】:[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(youTubeStarted:) name:UIWindowDidBecomeVisibleNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(youTubeFinished:) name:UIWindowDidBecomeHiddenNotification object:nil];
-(void)youTubeStarted:(NSNotification *)notification
// Entered Fullscreen code goes here..
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.fullScreenVideoIsPlaying = YES;
NSLog(@"%f %f",webViewForWebSite.frame.origin.x,webViewForWebSite.frame.origin.y);
-(void)youTubeFinished:(NSNotification *)notification
// Left fullscreen code goes here...
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.fullScreenVideoIsPlaying = NO;
//CODE BELOW FORCES APP BACK TO PORTRAIT ORIENTATION ONCE YOU LEAVE VIDEO.
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait animated:NO];
//present/dismiss viewcontroller in order to activate rotating.
UIViewController *mVC = [[UIViewController alloc] init];
[self presentViewController:mVC animated:NO completion:Nil];
// [self presentModalViewController:mVC animated:NO];
[self dismissViewControllerAnimated:NO completion:Nil];
// [self dismissModalViewControllerAnimated:NO];
【讨论】:
【参考方案8】:对于 iOS8(另外我有一个不是 youtube 视频的嵌入式视频)我可以开始工作的唯一解决方案是捕捉 viewWill/DidLayoutSubviews
中的任何一个,作为额外的奖励你不需要更改 HTML 或使用任何私有 API:
所以基本上:
@property (nonatomic) BOOL showingVideoFromWebView;
...
...
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
if (navigationType == UIWebViewNavigationTypeOther)
//Was "other" in my case... Might be UIWebViewNavigationTypeLinkClicked
self.showingVideoFromWebView = YES;
- (void)viewWillLayoutSubviews
[super viewWillLayoutSubviews];
// Do whatever...
// Note: This will get called both when video is entering fullscreen AND exiting!
self.showingVideoFromWebView = NO;
在我的情况下,我的 web 视图位于 UITableViewCell
内,因此我必须找到一种在单元格和视图控制器之间进行通信的方法,并且还要避免使用 BOOL 标志,我这样做了:
- (BOOL)webView:(UIWebView *)webView shouldStartLoad.....
... if (opening video check....)
[[NSNotificationCenter defaultCenter] addObserverForName:@"webViewEmbedVidChangedState" object:nil queue:nil usingBlock:^(NSNotification *note)
// Do whatever need to be done when the video is either
// entering fullscreen or exiting fullscreen....
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"webViewEmbedVidChangedState" object:nil];
];
- (void)viewWillLayoutSubviews.....
[[NSNotificationCenter defaultCenter] postNotificationName:@"webViewEmbedVidChangedState" object:nil];
【讨论】:
据我记忆,所有解决方案都是不安全的,因此为了使其可靠,只需以我不需要的方式更改逻辑。 这样可以从视频播放器获得暂停通知吗?【参考方案9】:实际上,出于逆向工程目的,您还可以使用 Cocoa API 喜欢
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleNotification:)
name:nil
object:nil];
在这种情况下,您将收到所有通知
【讨论】:
这是一个老问题的迟到的答案。你的答案提供了其他人没有提供的什么? @JAL 正如here提到的,你可以使用Core Foundation对通知进行逆向工程,但我认为大多数iOS开发者更喜欢Foundation =)以上是关于如何从 UIWebView 嵌入式 YouTube 视频播放接收 NSNotifications的主要内容,如果未能解决你的问题,请参考以下文章
iPad SDK:嵌入式 YouTube 电影在 UIWebView 后面播放全屏版本
UIWebView 中的嵌入式 youtube 播放器正在破坏视图层次结构
如何在用户不触摸 UIWebView 本身的情况下启动 YouTube 视频?