Как получать NSNotification от встроенного UIWebView воспроизведения видео YouTube

Я не получил никаких уведомлений за MPMoviePlayerController, Что я делаю неправильно?

Я использую следующую логику.

Я начинаю играть на YouTube видео в UIWebView, UIWebView называет стандарт MPMoviePlayerController, Я не контролирую MPMoviePlayerController потому что я не был создан MPMoviePlayerController,

Я запускаю клип на YouTube с автовоспроизведением (задержка 1 секунда):

[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 и выше.

ОБНОВЛЕНИЕ2: @ H2CO3 Я пытаюсь использовать вашу URL-схему, но она не работает. Метод делегата не вызывается при событии выхода. Я добавил свой 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 @ До сих пор я не могу поймать UIMoviePlayerControllerDidExitFullscreenNotification, но я нашел MPAVControllerItemPlaybackDidEndNotification. MPAVControllerItemPlaybackDidEndNotification появляется, когда воспроизведение видео заканчивается.

Но я не понимаю, как я могу ловить onDone уведомления?

9 ответов

Решение

Нет документированных уведомлений, отправленных UIWebView встроенный проигрыватель фильмов.

Фактически, закрытая реализация, используемая в UIWebView действительно отличается от общественности MPMoviePlayerController во многих аспектах (например, DRM).

Самые важные классы, используемые для воспроизведения видео контента в этом UIWebView называются MPAVController а также UIMoviePlayerController, Последний заставляет игрока выглядеть как MPMoviePlayerController полноэкранный интерфейс.

Если вы рискуете отказаться от Apple, на самом деле есть способы добиться того, что вы ищете.

ПРИМЕЧАНИЕ. Это не задокументировано и может быть нарушено при каждом новом выпуске iOS. Однако он работает на iOS4.3, 5.0 и 5.01, 5.1 и 6.0 и может работать на других версиях.

Я не могу протестировать это решение на iOS 4.1 и 4.2, так что вам решать. Я очень подозреваю, что это будет работать.


Полноэкранный режим

Если, например, вы намереваетесь отреагировать на то, что пользователь нажал кнопку " ГОТОВО", вы можете сделать это следующим образом:

ОБНОВЛЕНИЕ Старая версия этого ответа рекомендуется использовать UIMoviePlayerControllerDidExitFullscreenNotification тогда как эта новая версия (обновленная для iOS6) рекомендует использовать UIMoviePlayerControllerWillExitFullscreenNotification,

Уровень C-Language:

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-механику.


Состояние воспроизведения

Для изучения состояния воспроизведения обратите внимание на "MPAVControllerPlaybackStateChangedNotification" (как указано выше) и изучить userInfo который может выглядеть так:

{
    MPAVControllerNewStateParameter = 1;
    MPAVControllerOldStateParameter = 2;
}

Дальнейшая обратная инженерия

Для обратного инжиниринга и изучения всех отправленных уведомлений используйте следующий фрагмент.

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

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

Насколько я знаю, на детали реализации UIWebView (и всех системных классов, созданных Apple) не следует полагаться при создании приложения Cocoa Touch. Возможно, это тот случай, когда видеоплеер 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>

Фактически, из JavaScript-функции endMovie вы можете перенаправить на фиктивный URL-адрес, который вы можете перехватить в своем методе -webView:shouldStartLoadWithRequest: (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
}

Надеюсь это поможет.

Основано на ответе @H2CO3, но с API iframe. Это был единственный способ заставить это работать.

Это не использует какой-либо частный 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
 }

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

Это работает для меня в 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;
        }
    }
}
[[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];

}

Для 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;
}

В моем случае мой веб-вид внутри 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];

На самом деле для целей обратного проектирования вы также можете использовать API Какао, как

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

В этом случае вы будете получать все уведомления

Другие вопросы по тегам