如何从 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 中用于播放视频内容的最重要的类称为MPAVControllerUIMoviePlayerController。后者使播放器看起来像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+ 中,您可以使用 UIMoviePlayerControllerDidEnterFullscreenNotificationUIMoviePlayerControllerDidExitFullscreenNotification 通知:

-(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 中有UIMoviePlayerControllerWillEnterFullscreenNotificationWillExit 通知,但我还没有测试过这些。请参阅此问题的已接受答案(由 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 视频?

如何在 tvOS 上播放 YouTube 内容

在没有全屏的情况下在 UIWebView 中播放 Youtube 视频

UIWebView 嵌入的 Youtube 视频旋转不流畅