如何检测WKWebView中正在播放的电影?

我想知道是否有可能在WKWebView中检测正在播放的电影?

另外,我想知道打开的stream的确切的URL?

由于这个问题的解决scheme需要大量的研究和不同的方法,我想在这里logging下来让其他人跟随我的想法。 如果您只是对最终解决scheme感兴趣,请查找一些精美的标题。

我开始使用的应用程序非常简单。 这是一个单视图应用程序,它导入WebKit并用一些NSURL打开一个WKWebView

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

该url包含一个由JavaScript保护的(某种)video。 我真的还没有看到这个video,这是我发现的第一个。 请记住将NSAppTransportSecurityNSAllowsArbitraryLoads添加到Info.plist否则您将看到一个空白页面。

WKNavigationDelegate

WKNavigationDelegate不会通知您正在播放的video。 所以设置webView.navigationDelegate = self并实现协议不会带给你想要的结果。

NSNotificationCenter

我认为必须有像SomeVideoPlayerDidOpen这样的事件。 不幸的是没有任何,但它可能有一个SomeViewDidOpen事件,所以我开始检查视图层次结构:

 UIWindow UIWindow WKWebView WKScrollView ... ... UIWindow UIWindow UIView AVPlayerView UITransitionView UIView UIView UIView ... UIView ... AVTouchIgnoringView ... 

正如所料,将会有一个额外的UIWindow添加可能有一个事件和地狱是的,它确实有!

我扩展了viewDidAppear:通过添加一个新的观察者:

 NSNotificationCenter.defaultCenter().addObserver(self, selector: "windowDidBecomeVisible:", name: UIWindowDidBecomeVisibleNotification, object: nil) 

并添加了相应的方法:

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

如预期的那样,它返回了我们前面检查过的视图层次结构。 但不幸的是,似乎稍后会创buildAVPlayerView

如果你信任你的应用程序,它将打开的唯一的UIWindow是媒体播放器,你完成了这一点。 但是这个解决scheme不会让我在晚上睡觉,所以我们再深入一点。

注射事件

我们需要得到有关被添加到这个无名UIViewAVPlayerView通知。 这似乎很明显, AVPlayerView必须是UIView的子类,但由于它没有正式logging的苹果我检查了AVPlayerView的iOS运行时头 ,它绝对一个UIView

现在我们知道AVPlayerViewUIView一个子类,它可能会通过调用addSubview:来添加到无名的UIView 。 所以我们必须得到关于添加的视图的通知。 不幸的是, UIView没有提供这个事件。 但它确实调用了一个名为didAddSubview:的方法didAddSubview:这可能非常方便。

所以让我们来检查一下AVPlayerView将被添加到我们应用程序的某个地方并发送一个通知:

 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) 

现在我们可以观察通知并收到相应的事件:

 NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerWillOpen:", name: "PlayerWillOpen", object: nil) func playerWillOpen(notification: NSNotification) { print("A Player will be opened now") } 

更好的通知注入

由于AVPlayerView不会被移除,但是只能被解除分配,所以我们不得不重写我们的代码,并向AVPlayerViewController注入一些通知。 这样,我们将有尽可能多的通知,例如: PlayerWillAppearPlayerWillDisappear

 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) 

现在我们可以观察到这两个通知,并很好地去:

  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

我花了几个小时的时间挖掘一些iOS运行时头,猜测哪里可以find指向video的url,但我无法find它。 当我挖掘WebKit本身的一些源文件时,我不得不放弃并接受没有简单的方法来做到这一点,尽pipe我相信这是隐藏的,可以到达的地方,但最有可能的只是付出很大的努力。

我试图添加(注入)一些用户脚本到WKWebView ,这种方法比方法WKWebView有点安全:

这里是相关的代码:

 let contentController = WKUserContentController() if let jsSource = NSBundle.mainBundle().URLForResource("video_play_messenger", withExtension: "js"), let jsSourceString = try? String(contentsOfURL: jsSource) { let userScript = WKUserScript(source: jsSourceString, injectionTime: .AtDocumentEnd, forMainFrameOnly: true) contentController.addUserScript(userScript) contentController.addScriptMessageHandler(self, name: "callbackHandler") } let webConfiguration = WKWebViewConfiguration() webConfiguration.userContentController = contentController webView = WKWebView(frame: CGRect.zero, configuration: webConfiguration) let request = NSURLRequest(URL: NSURL(string: "URL_FOR_VIDEO")!) webView.loadRequest(request) 

对于WKWebView的控制器,遵循WKScriptMessageHandler并实现这个方法:

 func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) { if message.name == "callbackHandler" { if let messageString = message.body as? String where messageString == "VideoIsPlaying" { // Vide is being played } } } 

video_play_messenger.js添加到您的项目中:

 function videoTags() { return document.getElementsByTagName("video"); } function setupVideoPlayingHandler() { try { var videos = videoTags() for (var i = 0; i < videos.length; i++) { videos.item(i).onplaying = function() { webkit.messageHandlers.callbackHandler.postMessage("VideoIsPlaying"); } } } catch (error) { console.log(error); } } function setupVidePlayingListener() { // If we have video tags, setup onplaying handler if (videoTags().length > 0) { setupVideoPlayingHandler(); return } // Otherwise, wait for 100ms and check again. setTimeout(setupVidePlayingListener, 100); } setupVidePlayingListener(); 

参考: http : //www.kinderas.com/technology/2014/6/15/wkwebview-and-javascript-in-ios-8-using-swift