如何检测正在WKWebView中播放的电影? [英] How to detect a movie being played in a WKWebView?
问题描述
我想知道是否可以检测到在WKWebView中播放的电影?
I'd like to know wether it's possible to detect a movie being played in the WKWebView?
另外我想知道打开的确切网址流?
Additionally I'd like to know the exact URL of the opened stream?
推荐答案
由于这个问题的解决方案需要大量的研究和不同的方法,我想记录下来,供其他人遵循我的想法。 如果您只是对最终解决方案感兴趣,请寻找一些精美的标题。
Since the solution(s) to this question required a lot of research and different approaches, I'd like to document it here for others to follow my thoughts. If you're just interested in the final solution, look for some fancy headings.
我开始使用的应用程序非常简单。这是一个单视图应用程序导入 WebKit
并打开一个 WKWebView
,其中包含一些 NSURL
:
The app I started with, was pretty simple. It's a Single-View Application that imports WebKit
and opens a WKWebView
with some 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)
}
}
该URL包含一个受JavaScript保护的视频。 我还没看过这个视频,这只是我发现的第一个。请记住添加
NSAppTransportSecurity
和NSAllowsArbitraryLoads
到你的Info.plist
,否则你会看到一个空白页面。
The URL includes a video that is (kind of) protected by JavaScript. I really haven't seen the video yet, it was just the first I discovered. Remember to add
NSAppTransportSecurity
andNSAllowsArbitraryLoads
to yourInfo.plist
or you will see a blank page.
WKNavigationDelegate
WKNavigationDelegate
不会通知您正在播放的视频。因此,设置 webView.navigationDelegate = self
并实施协议将无法为您带来所需的结果。
WKNavigationDelegate
The WKNavigationDelegate
won't notify you about a video being played. So setting webView.navigationDelegate = self
and implementing the protocol won't bring you the desired results.
我认为必须有像 SomeVideoPlayerDidOpen
这样的事件。不幸的是没有,但它可能有一个 SomeViewDidOpen
事件,所以我开始检查视图层次结构:
I assumed that there must be an event like SomeVideoPlayerDidOpen
. Unfortunately there wasn't any, but it might have a SomeViewDidOpen
event, so I started inspecting the view hierarchy:
UIWindow
UIWindow
WKWebView
WKScrollView
...
...
UIWindow
UIWindow
UIView
AVPlayerView
UITransitionView
UIView
UIView
UIView
...
UIView
...
AVTouchIgnoringView
...
正如预期的那样,会有额外的 UIWindow
已添加哪些可能有一个事件,而且它确实有它!
As expected there will be an additional UIWindow
added which might have an event and hell yes it does have!
我扩展 viewDidAppear:
添加一个新的观察者:
I extended viewDidAppear:
by adding a new observer:
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)")
}
正如我们预期的那样,它会返回我们之前检查过的视图层次结构。但遗憾的是,似乎稍后将创建 AVPlayerView
。
As expected it returns the view hierarchy as we inspected earlier. But unfortunately it seems like the AVPlayerView
will be created later.
如果您信任您的应用程序只有 UIWindow
它将打开媒体播放器,此时你已经完成了。但是这个解决方案不会让我晚上睡觉,所以让我们更深入...
If you trust your application that the only UIWindow
it'll open is the media player, you're finished at this point. But this solution wouldn't let me sleep at night, so let's go deeper...
我们需要收到有关 AVPlayerView
被添加到此无名 UIView
的通知。很明显, AVPlayerView
必须是 UIView
的子类,但由于Apple没有正式记录,我检查了< a href =https://github.com/nst/iOS-Runtime-Headers/blob/master/Frameworks/AVKit.framework/AVPlayerView.h =noreferrer> iOS运行时标题 AVPlayerView
,它绝对 UIView
。
We need to get notified about the AVPlayerView
being added to this nameless UIView
. It seems pretty obvious that AVPlayerView
must be a subclass of UIView
but since it's not officially documented by Apple I checked the iOS Runtime Headers for AVPlayerView
and it definitely is a UIView
.
现在我们知道 AVPlayerView
是 UIView
的子类,它可能会添加到无名 c $ c> UIView
通过调用 addSubview:
。因此,我们必须收到有关添加的视图的通知。不幸的是, UIView
没有提供观察此事件的事件。但它 调用一个名为 didAddSubview:
的方法,这可能非常方便。
Now that we know that AVPlayerView
is a subclass of UIView
it will probably added to the nameless UIView
by calling addSubview:
. So we'd have to get notified about a view that was added. Unfortunately UIView
doesn't provide an event for this to be observed. But it does call a method called didAddSubview:
which could be very handy.
所以,让我们检查一下 AVPlayerView
将被添加到我们的应用程序的某个地方并发送通知:
So let's check wether a AVPlayerView
will be added somewhere in our application and 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")
}
更好的通知注入
由于 AVPlayer视图
不会被删除但只是取消分配我们将不得不重写我们的代码并向 AVPlayerViewController
注入一些通知。这样我们就可以获得尽可能多的通知,例如: PlayerWillAppear
和 PlayerWillDisappear
:
Better notification injection
Since the AVPlayerView
won't get removed but only deallocated we'll have to rewrite our code a little bit and inject some notifications to the AVPlayerViewController
. That way we'll have as many notifications as we want, e.g.: 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 are good to go:
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")
}
视频的网址
我花了几个小时挖掘一些iOS Runtime Headers来猜测我在哪里可以找到指向视频的URL,但我无法找到它。当我深入研究WebKit本身的一些源文件时,我不得不放弃并接受没有简单方法来做它,虽然我相信它隐藏在某个地方并且可以到达,但很可能只是付出了很多努力。
URL of the video
I spent a couple of hours digging some iOS Runtime Headers to guess where I could find the URL pointing to the video, but I couldn't manage to find it. When I was digging into some source files of WebKit itself, I had to give up and accept that there's no easy way to do it, although I believe it's somewhere hidden and can be reached, but most likely only with a lot of effort.
这篇关于如何检测正在WKWebView中播放的电影?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!