web-dev-qa-db-de.com

So empfangen Sie NSNotifications von der eingebetteten YouTube-Videowiedergabe von UIWebView

Ich habe keine Benachrichtigungen für MPMoviePlayerController erhalten. Was mache ich falsch?

Ich benutze folgende Logik.

Ich fange an, YouTube-Videos in UIWebView abzuspielen. UIWebView ruft einen Standard MPMoviePlayerController auf. Ich kontrolliere MPMoviePlayerController nicht, weil ich MPMoviePlayerController nicht instanziiert habe.

Ich starte den YouTube-Clip mit Autoplay (1 Sekunde Verzögerung):

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

Mein Code ist:

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

PDATE: Ich erstelle eine Anwendung, die das YouTube-Video abspielt. Du kannst eine Wiedergabeliste starten und du wirst das erste Video sehen. Wenn das erste Video beendet ist, wird das zweite Video automatisch wiedergegeben und so weiter.

Ich muss ios 4.1 und höher unterstützen.

PDATE2: @ H2CO3 Ich versuche, Ihr URL-Schema zu verwenden, aber es funktioniert nicht. Delegate-Methode wurde beim Exit-Ereignis nicht aufgerufen. Ich habe meine HTML-URL zum Log hinzugefügt. Es ist:

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

PDATE @Till, ich kann UIMoviePlayerControllerDidExitFullscreenNotification nicht abfangen, aber ich habe MPAVControllerItemPlaybackDidEndNotification gefunden. MPAVControllerItemPlaybackDidEndNotification wird angezeigt, wenn die Videowiedergabe beendet wird.

Aber ich verstehe nicht, wie ich onDone-Benachrichtigungen abfange.

51
Voloda2

Vom eingebetteten Movie Player UIWebView werden keine dokumentierten Benachrichtigungen gesendet.

Tatsächlich unterscheidet sich die in der UIWebView verwendete geschlossene Implementierung in vielen Aspekten von der öffentlichen MPMoviePlayerController (z. B. DRM).

Die wichtigsten Klassen für die Wiedergabe von Videoinhalten in dieser UIWebView werden als MPAVController und UIMoviePlayerController bezeichnet. Letzteres lässt den Player wie die Vollbild-Oberfläche MPMoviePlayerController erscheinen.

Falls Sie es wagen, eine Ablehnung durch Apple zu riskieren, gibt es tatsächlich Möglichkeiten, das zu erreichen, wonach Sie suchen.

NOTE Dies ist nicht dokumentiert und kann bei jeder neuen iOS-Version geändert werden. Es funktioniert jedoch auf iOS4.3, 5.0 und 5.01, 5.1 und 6.0 und es kann auch auf anderen Versionen funktionieren.

Ich kann diese Lösung unter iOS 4.1 und 4.2 nicht testen, das liegt also an Ihnen. Ich vermute sehr, dass es funktionieren wird.


Vollbildmodus

Wenn Sie beispielsweise darauf reagieren möchten, dass der Benutzer auf die Schaltfläche DONE tippt, können Sie dies möglicherweise folgendermaßen tun:

UPDATE In der alten Version dieser Antwort wird empfohlen UIMoviePlayerControllerDidExitFullscreenNotification, während diese neue Version (aktualisiert für iOS6) die Verwendung von UIMoviePlayerControllerWillExitFullscreenNotification empfiehlt.

C-Sprachlevel:

void PlayerWillExitFullscreen (CFNotificationCenterRef center,
                 void *observer,
                 CFStringRef name,
                 const void *object,
                 CFDictionaryRef userInfo)
{
    //do something...
}

CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), 
    NULL, 
    PlayerWillExitFullscreen, 
    CFSTR("UIMoviePlayerControllerWillExitFullscreenNotification"), 
    NULL,  
    CFNotificationSuspensionBehaviorDeliverImmediately);

Ziel-C-Level:

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

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

Ich habe sowohl C-Level- als auch Objective-C-Level-Optionen entworfen, da der beste Weg, dies alles herauszufinden, die Verwendung von C-Level-Funktionen (CoreFoundation) ist, wie am Ende meiner Antwort gezeigt. Wenn der Absender einer Benachrichtigung nicht Objective-C (NSNotifications) verwendet, können Sie diese möglicherweise nicht mithilfe der NSNotification-Mechanik abfangen.


Wiedergabestatus

Achten Sie zur Überprüfung des Wiedergabestatus auf "MPAVControllerPlaybackStateChangedNotification" (wie oben entworfen) und untersuchen Sie die userInfo, die wie folgt aussehen kann:

{
    MPAVControllerNewStateParameter = 1;
    MPAVControllerOldStateParameter = 2;
}

Weiteres Reverse Engineering

Verwenden Sie zum Reverse Engineering und Durchsuchen aller gesendeten Benachrichtigungen das folgende 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);
64
Till

Ab iOS 4.3 können Sie die Benachrichtigungen UIMoviePlayerControllerDidEnterFullscreenNotification und UIMoviePlayerControllerDidExitFullscreenNotification verwenden:

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

Soweit mir bekannt ist, sind die Implementierungsdetails von UIWebView (und allen von Apple erstellten Systemklassen) für die Erstellung einer Cocoa Touch-Anwendung nicht maßgeblich. Möglicherweise ist der Video-Player von UIWebView nicht eine standardmäßige MPMoviePlayerController-Klasse und verfügt möglicherweise über ein völlig anderes Delegierungs-/Benachrichtigungssystem, auf das der Benutzer nicht zugreifen kann.

Ich empfehle Ihnen, das HTML5-Element zu verwenden und das "onended" -Ereignis dieses Tags zu erkennen:

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

Tatsächlich können Sie von der JavaScript-Funktion endMovie zu einer gefälschten URL umleiten, die Sie in Ihrer Methode -webView: shouldStartLoadWithRequest: (UIWebViewDelegate) abrufen können, um benachrichtigt zu werden, dass das Video beendet wurde:

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

Hoffe das hilft.

16
user529758

Basierend auf der @ H2CO3-Antwort, aber mit der iframe-API . Nur so konnte ich es zum Laufen bringen.

Hierbei wird keine private API verwendet, die die Zukunftssicherheit erhöht.

Hier ist der Code zum Einbetten deines Youtube-Videos. Weitere Möglichkeiten zum Anpassen finden Sie in der 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>

Und so werden Sie benachrichtigt, dass das Video beendet wurde (UIWebViewDelegate-Methode).

- (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
Fábio Oliveira

fügen Sie in ViewDidLoad den folgenden Code hinzu

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

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

Die folgenden Methoden dienen zum Anzeigen der Meldung/Funktionen für den jeweiligen Vorgang des Ein-/Ausstiegs in den/aus dem Vollbildmodus

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

Dies funktioniert für mich in iOS 6.1, es versteckt/entfernt andere Fenster, wenn die AVPlayerItemDidPlayToEndTimeNotification empfangen wird:

[[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
sandeepmistry
[[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
Ritesh Arora

Eigentlich kann man für das Reverse Engineering auch gerne Cocoa API verwenden

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

In diesem Fall erhalten Sie alle Benachrichtigungen

1
Serg Dort

Für iOS8 (Ich habe auch ein eingebettetes Video, das kein YouTube-Video ist) Die einzige Lösung, die ich zur Arbeit bringen konnte, war, eines von viewWill/DidLayoutSubviews und als zusätzlichen Bonus müssen Sie weder den HTML-Code ändern noch private APIs verwenden:

Also im Grunde genommen:

@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 meinem Fall befindet sich meine Webansicht in einem UITableViewCell, daher musste ich einen Weg finden, um zwischen der Zelle und dem View-Controller zu kommunizieren, und um auch die Verwendung eines BOOL-Flags zu vermeiden:

- (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
Aviel Gross