Jak odbierać NSNotifications z UIWebView wbudowane odtwarzanie wideo YouTube

Nie otrzymałem żadnych powiadomień dla MPMoviePlayerController. Co robię źle?

Używam logiki.

Zaczynam odtwarzać wideo na youtube w UIWebView. UIWebView wywołuje standard MPMoviePlayerController. Nie kontroluję MPMoviePlayerController, ponieważ nie tworzyłem MPMoviePlayerController.

Uruchamiam klip na youtube z autoplay (1 sekunda opóźnienia):

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

Mój kod to:

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

Aktualizacja: tworzę aplikację, która odtwarza wideo youtube. Możesz uruchomić playlistę, a zobaczysz najpierw wideo. Po zakończeniu pierwszego filmu, drugie wideo rozpoczyna odtwarzanie automatycznie i tak dalej.

Muszę obsługiwać ios 4.1 i nowsze.

UPDATE2: @H2CO3 próbuję użyć twojego schematu url, ale to nie działa. Metoda Delegate nie wywołała zdarzenia exit. Dodałem mój URL html do logowania. Jest:

<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, nie mogę złapać UIMoviePlayerControllerDidExitFullscreennotification, ale znalazłem MPAVControllerItemPlaybackDidEndnotification. MPAVControllerItemPlaybackDidEndnotification pojawia się po zakończeniu odtwarzania wideo.

Ale nie rozumiem, jak złapać powiadomienia onDone?

Author: Voloda2, 2011-12-15

9 answers

Nie ma udokumentowanych powiadomień wysyłanych przez wbudowany odtwarzacz filmów UIWebView.

W rzeczywistości zamknięta implementacja używana w UIWebView różni się od publicznej MPMoviePlayerController w wielu aspektach (np. DRM).

Najważniejsze klasy używane do odtwarzania treści wideo w obrębie UIWebView to MPAVController i UIMoviePlayerController. Ten ostatni sprawia, że gracz wygląda jak [6]} fullscreen interface.

Jeśli odważysz się zaryzykować odrzucenie przez Apple, istnieją sposoby aby nadal osiągnąć to, czego szukasz.

Uwaga Nie jest to udokumentowane i może ulec uszkodzeniu w każdym nowym wydaniu iOS. Jednak działa na iOS 4.3, 5.0 i 5.01, 5.1 i 6.0 i to Może pracować również na innych wersjach.

Nie jestem w stanie przetestować tego rozwiązania na iOS 4.1 i 4.2, więc to zależy od Ciebie. Podejrzewam, że to zadziała.

Stan Fullscreen

Jeśli na przykład zamierzasz aby zareagować na naciśnięcie przycisku DONE , możesz to zrobić w ten sposób:

Aktualizacja stara wersja tej odpowiedzi zalecała użycie UIMoviePlayerControllerDidExitFullscreenNotification natomiast ta nowa wersja (zaktualizowana dla iOS6) zaleca używanie UIMoviePlayerControllerWillExitFullscreenNotification.

Poziom Języka 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:

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

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

Przygotowałem zarówno opcje na poziomie C, jak i Objective-C, ponieważ najlepszym sposobem, aby dowiedzieć się o tym wszystkim, jest użycie C-Level (CoreFoundation) działa jak pokazano na końcu mojej odpowiedzi. Jeśli nadawca powiadomienia nie używa Objective-C (nsnotifications), możesz nie być w stanie uwięzić ich za pomocą mechaniki NSNotification.


Stan Odtwarzania

Aby sprawdzić stan odtwarzania, zwróć uwagę na "MPAVControllerPlaybackStateChangedNotification" (jak napisano powyżej) i zbadaj userInfo, które mogą wyglądać tak:

{
    MPAVControllerNewStateParameter = 1;
    MPAVControllerOldStateParameter = 2;
}

Dalsza Odwrotność Inżynieria

Do inżynierii odwrotnej i przeglądania wszystkich wysyłanych powiadomień użyj poniższego fragmentu.

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);
 64
Author: Till,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-01 21:33:16

W iOS 4.3+ możesz użyć powiadomień UIMoviePlayerControllerDidEnterFullscreenNotification i 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
}
 32
Author: ChrisJP,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-31 23:41:22

Z tego co wiem, szczegóły implementacji UIWebView (i wszystkich klas systemowych stworzonych przez Apple) nie powinny być brane pod uwagę przy tworzeniu aplikacji Cocoa Touch. Może jest tak, że odtwarzacz wideo UIWebView jest , a nie standardową klasą MPMoviePlayerController i może mieć zupełnie inny system delegacji/powiadomień, który nie powinien być dostępny dla użytkownika.

Proponuję użyć elementu HTML5 i wykryć Zdarzenie " on " tego tag:

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

W rzeczywistości, z funkcji endMovie JavaScript, można przekierować do fałszywego adresu URL, który można złapać w-webView:shouldStartLoadWithRequest: (UIWebViewDelegate) metoda w ten sposób otrzymywać powiadomienia, że film został zakończony:

- (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
}
Mam nadzieję, że to pomoże.
 16
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-18 19:07:50

Na podstawie odpowiedzi @H2CO3, ale z iframe API . To był jedyny sposób, żeby to zadziałało.

To nie używa żadnego prywatnego API, co czyni go bardziej przyszłościowym.

Oto kod do osadzenia filmu na Youtube. Sprawdź interfejs API, aby znaleźć więcej sposobów na dostosowanie tego.

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

I w ten sposób otrzymujesz powiadomienie, że film się skończył (metoda 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
 }
 6
Author: Fábio Oliveira,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-24 14:39:04

W ViewDidLoad Dodaj następujący kod

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

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

Poniższe metody służą do wyświetlania wiadomości / funkcji dla danego procesu wchodzenia/wychodzenia na / z pełnego ekranu

- (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
}
 5
Author: Prabhu Natarajan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-29 05:05:44

To działa u mnie w iOS 6.1, ukrywa / usuwa inne okna po otrzymaniu powiadomienia Avplayeritemdidplaytoendtimen:

[[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;
        }
    }
}
 4
Author: sandeepmistry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-13 16:56:05
[[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];

}
 3
Author: Ritesh Arora,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-07 07:03:48

Dla iOS8 (mam również osadzony film, który nie jest filmem z youtube) jedynym rozwiązaniem, które mogłem dostać się do pracy, było złapanie jednego z viewWill/DidLayoutSubviews, a jako dodatkowy bonus nie musisz zmieniać HTML ani używać żadnych prywatnych API:

Więc zasadniczo:

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

W moim przypadku Mój widok jest wewnątrz UITableViewCell, więc musiałem znaleźć sposób na komunikację między komórką a kontrolerem widoku, a także aby uniknąć używania flagi BOOL zrobiłem to:

- (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];
 1
Author: Aviel Gross,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-27 14:46:06

W rzeczywistości do celów inżynierii odwrotnej można również użyć Cocoa API jak

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

W tym przypadku otrzymasz wszystkie powiadomienia

 1
Author: Serg Dort,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-01 12:40:16