【问题标题】:How to receive NSNotifications from UIWebView embedded YouTube video playback如何从 UIWebView 嵌入式 YouTube 视频播放接收 NSNotifications
【发布时间】: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"  
 width="161" height="121"></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 进行播放。

标签: ios youtube mpmovieplayercontroller nsnotificationcenter mpmovieplayer


【解决方案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 =)
        猜你喜欢
        • 2012-08-08
        • 2013-06-14
        • 2011-09-10
        • 1970-01-01
        • 2011-10-08
        • 2014-02-22
        • 2011-05-05
        • 2018-12-16
        • 2018-04-16
        相关资源
        最近更新 更多