Since the solution to this question required a lot of research and different approaches, I would like to document it so that others follow my thoughts. If you're just interested in the final solution, find some fancy headlines.
The application I started with was pretty simple. This is a one-time application that imports WebKit and opens WKWebView using NSURL :
import UIKit import WebKit class ViewController: UIViewController { var webView: WKWebView! override func viewDidAppear(animated: Bool) { webView = WKWebView() view = webView let request = NSURLRequest(URL: NSURL(string: "http://tinas-burger.tumblr.com/post/133991473113")!) webView.loadRequest(request) } }
The URL includes a video that is (view) protected by JavaScript. I really havenβt seen the video yet, it was only the first thing I discovered. Remember to add NSAppTransportSecurity and NSAllowsArbitraryLoads to your Info.plist or you will see a blank page.
WKNavigationDelegate
WKNavigationDelegate will not notify you of the video being played. Therefore, setting webView.navigationDelegate = self and implementing the protocol will not produce the desired results.
NSNotificationCenter
I suggested that there should be an event like SomeVideoPlayerDidOpen . Unfortunately, there were none, but the SomeViewDidOpen event might have, so I started checking the hierarchy of views:
UIWindow UIWindow WKWebView WKScrollView ... ... UIWindow UIWindow UIView AVPlayerView UITransitionView UIView UIView UIView ... UIView ... AVTouchIgnoringView ...
As expected, an additional UIWindow will be added, which can have an event and hell, yes, it really is!
I added viewDidAppear: adding a new observer:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "windowDidBecomeVisible:", name: UIWindowDidBecomeVisibleNotification, object: nil)
And added the appropriate method:
func windowDidBecomeVisible(notification: NSNotification) { for mainWindow in UIApplication.sharedApplication().windows { for mainWindowSubview in mainWindow.subviews { // this will print: // 1: `WKWebView` + `[WKScrollView]` // 2: `UIView` + `[]` print("\(mainWindowSubview) \(mainWindowSubview.subviews)") }
As expected, it returns a hierarchy of views, as we checked before. But, unfortunately, it looks like AVPlayerView will be created later.
If you trust your application that only UIWindow will open it, it is a media player, you will end at this point. But this decision will not allow me to sleep at night, so let go deeper ...
Event injection
We need to receive notification of the addition of AVPlayerView to this nameless UIView . It seems pretty obvious that AVPlayerView should be a subclass of UIView , but since it is not officially documented by Apple, I checked the iOS Runtime Headers for AVPlayerView and this is definitely a UIView .
Now that we know that AVPlayerView is a subclass of UIView , it will probably be added to the unnamed UIView by calling addSubview: Therefore, we need to receive a notification about the added view. Unfortunately, UIView does not provide events for observation. But he calls the didAddSubview: method, which can be very convenient.
So, let's verify that a AVPlayerView will be added somewhere in our application and will send a notification:
let originalDidAddSubviewMethod = class_getInstanceMethod(UIView.self, "didAddSubview:") let originalDidAddSubviewImplementation = method_getImplementation(originalDidAddSubviewMethod) typealias DidAddSubviewCFunction = @convention(c) (AnyObject, Selector, UIView) -> Void let castedOriginalDidAddSubviewImplementation = unsafeBitCast(originalDidAddSubviewImplementation, DidAddSubviewCFunction.self) let newDidAddSubviewImplementationBlock: @convention(block) (AnyObject!, UIView) -> Void = { (view: AnyObject!, subview: UIView) -> Void in castedOriginalDidAddSubviewImplementation(view, "didAddsubview:", subview) if object_getClass(view).description() == "AVPlayerView" { NSNotificationCenter.defaultCenter().postNotificationName("PlayerWillOpen", object: nil) } } let newDidAddSubviewImplementation = imp_implementationWithBlock(unsafeBitCast(newDidAddSubviewImplementationBlock, AnyObject.self)) method_setImplementation(originalDidAddSubviewMethod, newDidAddSubviewImplementation)
Now we can observe the notification and receive the corresponding event:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerWillOpen:", name: "PlayerWillOpen", object: nil) func playerWillOpen(notification: NSNotification) { print("A Player will be opened now") }
Best Notification Injection
Since AVPlayerView not deleted, but only freed, we will have to rewrite our code a bit and enter some notifications in AVPlayerViewController . Thus, we will have as many notifications as we want, for example: PlayerWillAppear and PlayerWillDisappear :
let originalViewWillAppearMethod = class_getInstanceMethod(UIViewController.self, "viewWillAppear:") let originalViewWillAppearImplementation = method_getImplementation(originalViewWillAppearMethod) typealias ViewWillAppearCFunction = @convention(c) (UIViewController, Selector, Bool) -> Void let castedOriginalViewWillAppearImplementation = unsafeBitCast(originalViewWillAppearImplementation, ViewWillAppearCFunction.self) let newViewWillAppearImplementationBlock: @convention(block) (UIViewController!, Bool) -> Void = { (viewController: UIViewController!, animated: Bool) -> Void in castedOriginalViewWillAppearImplementation(viewController, "viewWillAppear:", animated) if viewController is AVPlayerViewController { NSNotificationCenter.defaultCenter().postNotificationName("PlayerWillAppear", object: nil) } } let newViewWillAppearImplementation = imp_implementationWithBlock(unsafeBitCast(newViewWillAppearImplementationBlock, AnyObject.self)) method_setImplementation(originalViewWillAppearMethod, newViewWillAppearImplementation) let originalViewWillDisappearMethod = class_getInstanceMethod(UIViewController.self, "viewWillDisappear:") let originalViewWillDisappearImplementation = method_getImplementation(originalViewWillDisappearMethod) typealias ViewWillDisappearCFunction = @convention(c) (UIViewController, Selector, Bool) -> Void let castedOriginalViewWillDisappearImplementation = unsafeBitCast(originalViewWillDisappearImplementation, ViewWillDisappearCFunction.self) let newViewWillDisappearImplementationBlock: @convention(block) (UIViewController!, Bool) -> Void = { (viewController: UIViewController!, animated: Bool) -> Void in castedOriginalViewWillDisappearImplementation(viewController, "viewWillDisappear:", animated) if viewController is AVPlayerViewController { NSNotificationCenter.defaultCenter().postNotificationName("PlayerWillDisappear", object: nil) } } let newViewWillDisappearImplementation = imp_implementationWithBlock(unsafeBitCast(newViewWillDisappearImplementationBlock, AnyObject.self)) method_setImplementation(originalViewWillDisappearMethod, newViewWillDisappearImplementation)
Now we can observe these two notifications and go well:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerWillAppear:", name: "PlayerWillAppear", object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerWillDisappear:", name: "PlayerWillDisappear", object: nil) func playerWillAppear(notification: NSNotification) { print("A Player will be opened now") } func playerWillDisappear(notification: NSNotification) { print("A Player will be closed now") }
Video url
I spent a couple of hours digging through some of the iOS Runtime headers to figure out where I can find the URL pointing to the video, but I could not find it. When I delved into some of the source files of WebKit itself, I had to refuse and admit that there is no easy way to do this, although I believe that it is hidden somewhere and can be achieved, but most likely only with great difficulty.