ios 如何从 UIWebView 嵌入式 YouTube 视频播放接收 NSNotifications

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8518719/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 15:53:34  来源:igfitidea点击:

How to receive NSNotifications from UIWebView embedded YouTube video playback

iosyoutubempmovieplayercontrollernsnotificationcentermpmovieplayer

提问by Voloda2

I didn't received any notifications for MPMoviePlayerController. What am I doing wrong?

我没有收到任何通知MPMoviePlayerController。我究竟做错了什么?

I use following logic.

我使用以下逻辑。

I'm begining to play youtube video in UIWebView. UIWebViewcalls a standard MPMoviePlayerController. I don't control MPMoviePlayerControllerbecause I didn't instantiate MPMoviePlayerController.

我开始在UIWebView. UIWebView调用一个标准MPMoviePlayerController。我不控制,MPMoviePlayerController因为我没有实例化MPMoviePlayerController.

I run youtube's clip with autoplay (1 second delay):

我使用自动播放运行 youtube 的剪辑(1 秒延迟):

[self performSelector:@selector(touchInView:) withObject:b afterDelay:1];

My code is:

我的代码是:

- (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];
}

UPDATE:I'm creating application that plays youtube's video. You can run playlist and you will see first video. When first video has ended, second video begins play automatically and so on.

更新:我正在创建播放 youtube 视频的应用程序。您可以运行播放列表,您将看到第一个视频。当第一个视频结束时,第二个视频开始自动播放,依此类推。

I need to support ios 4.1 and above.

我需要支持 ios 4.1 及更高版本。

UPDATE2:@H2CO3 I'm trying to use your url-scheme, but it don't works. Delegate method didn't called on exit event. I added my html url to log. It is:

UPDATE2:@H2CO3 我正在尝试使用您的 url-scheme,但它不起作用。没有在退出事件上调用委托方法。我添加了我的 html url 来记录。这是:

<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
}

UPDATE3@Till, I cann't caught UIMoviePlayerControllerDidExitFullscreenNotification, but I found MPAVControllerItemPlaybackDidEndNotification. MPAVControllerItemPlaybackDidEndNotification appears when playback video is ended.

UPDATE3@Till,我无法捕捉到 UIMoviePlayerControllerDidExitFullscreenNotification,但我发现了 MPAVControllerItemPlaybackDidEndNotification。MPAVControllerItemPlaybackDidEndNotification 在播放视频结束时出现。

But I don't understand how do I catch onDone notifications?

但我不明白如何捕捉 onDone 通知?

回答by Till

There are no documented notifications sent by the UIWebViewembedded movie player.

UIWebView嵌入式电影播放器没有发送记录通知。

In fact, the closed implementation used within the UIWebViewdoes differ from the public MPMoviePlayerControllerin many aspects (e.g. DRM).

事实上,内部使用的封闭实现UIWebView确实MPMoviePlayerController在许多方面(例如 DRM)与公众不同。

The most important classes used for playing video content within that UIWebVieware called MPAVControllerand UIMoviePlayerController. The latter one makes the player appear like the MPMoviePlayerControllerfullscreen interface.

用于在其中播放视频内容的最重要的类UIWebView称为MPAVControllerUIMoviePlayerController。后者使播放器看起来像MPMoviePlayerController全屏界面。

In case you dare to risk a rejection by Apple, there are actually ways to still achieve what you are looking for.

如果您敢于冒被 Apple 拒绝的风险,实际上仍有一些方法可以实现您的目标。

NOTEThis is not documented and is subject to break on each and every new iOS release. It does however work on iOS4.3, 5.0 and 5.01, 5.1 and 6.0 and it maywork on other versions as well.

注意这没有记录并且可能会在每个新的 iOS 版本中中断。然而,它确实适用于 iOS4.3、5.0 和 5.01、5.1 和 6.0,并且适用于其他版本。

I am not able to test this solution on iOS 4.1 and 4.2, so that is up to you to do. I highly suspect that it will work.

我无法在 iOS 4.1 和 4.2 上测试此解决方案,所以这取决于您。我高度怀疑它会起作用。



Fullscreen State

全屏状态

If, for example you are intending to react upon the user tapping the DONEbutton, you may be able to do it this way:

例如,如果您打算对用户点击“完成”按钮做出反应,您可以这样做:

UPDATEThe old version of this answer recommended to useUIMoviePlayerControllerDidExitFullscreenNotificationwhereas this new version (updated for iOS6) recommends usingUIMoviePlayerControllerWillExitFullscreenNotification.

更新建议使用此答案的旧版本,UIMoviePlayerControllerDidExitFullscreenNotification而此新版本(针对 iOS6 更新)建议使用UIMoviePlayerControllerWillExitFullscreenNotification.

C-Language Level:

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 Level:

Objective-C 级别:

- (void)playerWillExitFullscreen:(NSNotification *)notification
{
    //do something...
}

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(playerWillExitFullscreen:)
                                             name:@"UIMoviePlayerControllerWillExitFullscreenNotification" 
                                           object:nil];

I did draft both, C-Level and Objective-C-Level options because the best way to actually find out about all of this is to use C-Level (CoreFoundation) functions as shown at the end of my answer. If the sender of a notification does not use Objective-C (NSNotifications), you mayactually not be able to trap them using the NSNotification-mechanics.

我确实起草了 C-Level 和 Objective-C-Level 选项,因为真正了解所有这些的最佳方法是使用 C-Level (CoreFoundation) 函数,如我的答案末尾所示。如果一个通知的发件人不使用的Objective-C(NSNotifications),你可能实际上没有使用NSNotification力学能够捕获它们。



Playback State

播放状态

For examining the playback state, look out for "MPAVControllerPlaybackStateChangedNotification"(as drafted above) and examine the userInfowhich may look like this:

要检查播放状态,请注意"MPAVControllerPlaybackStateChangedNotification"(如上所述)并检查userInfo可能如下所示的内容:

{
    MPAVControllerNewStateParameter = 1;
    MPAVControllerOldStateParameter = 2;
}


Further Reverse Engineering

进一步逆向工程

For reverse engineering and exploring all the notifications sent, use the following snippet.

对于逆向工程和探索发送的所有通知,请使用以下代码段。

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);

回答by ChrisJP

In iOS 4.3+ you can use the UIMoviePlayerControllerDidEnterFullscreenNotificationand UIMoviePlayerControllerDidExitFullscreenNotificationnotifications:

在 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
}

回答by ChrisJP

As far as I know, the implementation details of UIWebView (and all system classes made by Apple) are not to be relied upon when making a Cocoa Touch application. Maybe it's the case that an UIWebView's video player is nota standard MPMoviePlayerController class and it might have a totally different delegation/notification system, which is not supposed to be accessible by the user.

据我所知,在制作 Cocoa Touch 应用程序时,不依赖 UIWebView(以及 Apple 制作的所有系统类)的实现细节。也许 UIWebView 的视频播放器不是标准的 MPMoviePlayerController 类,它可能有一个完全不同的委托/通知系统,用户不应该访问它。

I suggest you to use the HTML5 element and detect the "onended" event of this tag:

我建议您使用 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>

In fact, from the endMovie JavaScript function, you can redirect to a bogus URL which you can catch in your -webView:shouldStartLoadWithRequest: (UIWebViewDelegate) method thus get notified that the video has ended:

实际上,从 endMovie JavaScript 函数中,您可以重定向到可以在 -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
}

Hope this helps.

希望这可以帮助。

回答by Fábio Oliveira

Based on the @H2CO3 answer but with the iframe API. It was the only way I could make it work.

基于@H2CO3 答案,但使用iframe API。这是我让它发挥作用的唯一方法。

This doesn't use any private API which makes it more future proof.

这不使用任何私有 API,这使其更具未来证明。

Here's the code to embed your Youtube video. Check the API for more ways to customise this.

这是嵌入 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>

And this is how you get notified that the video ended (UIWebViewDelegate method).

这就是您收到视频结束通知的方式(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
 }

回答by Prabhu Natarajan

in ViewDidLoad add the following code

在 ViewDidLoad 中添加以下代码

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(VideoExitFullScreen:) name:@"UIMoviePlayerControllerDidExitFullscreenNotification" object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(VideoEnterFullScreen:) name:@"UIMoviePlayerControllerDidEnterFullscreenNotification" object:nil];

The following methods are for showing the message/functions for respective process of entering/exiting to/from full screen

以下方法用于显示进入/退出全屏/退出全屏的各个过程的消息/功能

- (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
}

回答by sandeepmistry

This works for me in iOS 6.1, it hides/removes other windows when the AVPlayerItemDidPlayToEndTimeNotification is received:

这在 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;
        }
    }
}

回答by Ritesh Arora

[[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];

}

回答by Aviel Gross

For iOS8(Also I have an embedded video that is not a youtube video) the only solution I could get to work was to catch either one of viewWill/DidLayoutSubviews, and as an added bonus you don't need to change the HTML or use any private APIs :

对于 iOS8(我还有一个不是 youtube 视频的嵌入式视频),我可以开始工作的唯一解决方案是捕获其中之一viewWill/DidLayoutSubviews,作为额外的好处,您不需要更改 HTML 或使用任何私有 API :

So basically:

所以基本上:

@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;
}

In my case my web view is inside a UITableViewCellso I had to find a way to communicate between the cell and the view controller, and to also avoid using a BOOL flag I did this:

在我的例子中,我的 web 视图在 a 内,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];

回答by Serg Dort

Actually for the reverse engineering purposes you can also use Cocoa API like

实际上,出于逆向工程的目的,您还可以使用 Cocoa API,例如

   [[NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(handleNotification:)
                                                name:nil
                                              object:nil];

In this case you will receive all notifications

在这种情况下,您将收到所有通知