How to detect AVplayer and get url of current video from WKWebView? - ios

I'm using below code to extract url from UIWebView: it is working fine but, this same code using for WKWebView it's not working anymore. Can anyone help me? The video playing in WKWebView is Inlineplacyback not in fullscreen.
My code is :
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemBecameCurrent(_:)), name: NSNotification.Name("AVPlayerItemBecameCurrentNotification"), object: nil)
#objc func playerItemBecameCurrent(_ sender : NSNotification){
let playerItem: AVPlayerItem? = sender.object as? AVPlayerItem
if playerItem == nil {
print("player item nil")
return
}
// Break down the AVPlayerItem to get to the path
let asset = playerItem?.asset as? AVURLAsset
let url: URL? = asset?.url
let path = url?.absoluteString
print(path!,"video url")
}
Response URL :
https://r2---sn-po4g5uxa-5hql.googlevideo.com/videoplayback?txp=5531432&sparams=clen%2Cdur%2Cei%2Cgir%2Cid%2Cinitcwndbps%2Cip%2Cipbits%2Citag%2Clmt%2Cmime%2Cmm%2Cmn%2Cms%2Cmv%2Cpcm2%2Cpl%2Cratebypass%2Crequiressl%2Csource%2Cexpire&ip=103.37.181.55&ratebypass=yes&id=o-AM9UWIaxopyYZX4gikGuswG8EMi3dhH_PPBMIqY5cbXj&expire=1554400796&c=MWEB&fvip=4&initcwndbps=481250&ipbits=0&mime=video%2Fmp4&dur=60.093&lmt=1554142002789460&key=yt6&mt=1554379078&itag=18&source=youtube&gir=yes&requiressl=yes&signature=6C68366FC249958BB8E95A5D88074FF8BCB99745.DA113E66DD0B46863BAE52DAA3CAB31FD141F0E5&clen=2708520&mm=31%2C29&mn=sn-po4g5uxa-5hql%2Csn-cvh7knek&ei=vPGlXPOWHIWD8QOO1KBo&ms=au%2Crdu&pcm2=no&pl=24&mv=m&cpn=I9d32bNmeq3kf0jn&cver=2.20190403&ptk=youtube_none&pltype=contentugc
It's video URL not Webpage URL so, please help me how to get this.
Thanks.

This is kind of a hack, but the only way I found to accomplish this.
First set yourself as WKWebView navigation delegate:
self.webView?.navigationDelegate = self
Now listen to all navigation changes, and save the requested url:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let urlStr = navigationAction.request.url?.absoluteString {
//Save presented URL
//Full path can be accessed via self.webview.url
}
decisionHandler(.allow)
}
Now you only need to know when does the new screen become visible, and use the URL you saved (To know the video URL of the new visible screen).
You can do this via listening to UIWindowDidBecomeVisibleNotification notification:
NotificationCenter.default.addObserver(self, selector: #selector(windowDidBecomeVisibleNotification(notif:)), name: NSNotification.Name("UIWindowDidBecomeVisibleNotification"), object: nil)
Then check if the navigation window is not your window, and that means a new screen did open:
#objc func windowDidBecomeVisibleNotification(notif: Notification) {
if let isWindow = notif.object as? UIWindow {
if (isWindow !== self.view.window) {
print("New window did open, check what is the currect URL")
}
}
}

You can try to inject JS in your WKWebView like shown here: https://paulofierro.com/blog/2015/10/12/listening-for-video-playback-within-a-wkwebview

retrieves the full URL from the request property of the navigation action in the webView(_:decidePolicyFor:decisionHandler:) method of WKNavigationDelegate.
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let urlStr = navigationAction.request.url?.absoluteString {
//urlStr is your URL
}
decisionHandler(.allow)
}
also don't forgot to conform protocol
webView.navigationDelegate = self

Using Swift
You can get the html content from url of webview using code below
let docString = webView.stringByEvaluatingJavaScriptFromString("document.documentElement.outerHTML")
This case you'll get the whole html content,
Then look for href links inside the html string
let regex = try! NSRegularExpression(pattern: "<a[^>]+href=\"(.*?)\"[^>]*>")
let range = NSMakeRange(0, docString.characters.count)
let matches = regex.matches(in: docString, range: range)
for match in matches {
let htmlLessString = (docString as NSString).substring(with: match.rangeAt(1))
print(htmlLessString)
}
Check if it is youtube url using
Regular expression: "#https?://(www.)?youtube.com/.[^\s.,"\']+#i"
Another way to achive this
Think outside the box!
You can make an api call to get the url. That seems pretty easy using the web languages like php, .net etc.
The code for getting all urls inside a webpage in PHP (Use whatever language that is okey for you)
$url="http://wwww.somewhere.com";
$data=file_get_contents($url);
$data = strip_tags($data,"<a>");
$d = preg_split("/<\/a>/",$data);
foreach ( $d as $k=>$u ){
if( strpos($u, "<a href=") !== FALSE ){
$u = preg_replace("/.*<a\s+href=\"/sm","",$u);
$u = preg_replace("/\".*/","",$u);
print $u."\n";
}
}
To check one by one if it is youtube url.
$sText = "Check out my latest video here http://www.youtube.com/?123";
preg_match_all('#https?://(www\.)?youtube.com/.[^\s.,"\']+#i', $sText, $aMatches);
var_dump($aMatches);
If you wanted to check if the sample apps are using the same method, get a web debugging proxy and dig on it
Many of the above explanations are taken from other sites.
I Hope it sum up to your need!
Happy coding!

Try this in your ViewController, to add an URL Observer on WKWebView:
override func loadView() {
let webConfig = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfig)
webView.addObserver(self, forKeyPath: "URL", options: .new, context: nil)
view = webView
}
Overriding the observeValue to get url request:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(WKWebView.url) {
let urlRequest:String = webView.url?.absoluteString ?? ""
print(urlRequest)
}
}
Finally... deinit the Observer:
deinit { webView.removeObserver(self, forKeyPath: "URL") }

In WKWebView we need add the configuration for Inlineplacyback is true in WKWebViewConfiguration. If the configuration is set in WKWebView its automatically takes to full screen view.
Below code for reference:
class WebViewController: UIViewController {
lazy var webView: WKWebView! = {
let webView = WKWebView(frame: .zero, configuration: configuration)
webView.translatesAutoresizingMaskIntoConstraints = false
webView.uiDelegate = self
webView.navigationDelegate = self
let request = URLRequest(url: .init(string: "https://developer.apple.com/videos/play/wwdc2020/10188/")!)
webView.load(request)
return webView
}()
lazy var configuration: WKWebViewConfiguration! = {
let configuration = WKWebViewConfiguration()
configuration.allowsInlineMediaPlayback = true
configuration.mediaTypesRequiringUserActionForPlayback = .audio
configuration.allowsPictureInPictureMediaPlayback = true
return configuration
}()
override func loadView() {
super.loadView()
self.view.backgroundColor = .white
self.view.addSubview(self.webView)
// Constraint
NSLayoutConstraint.activate([
self.webView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
self.webView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
self.webView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor),
self.webView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor),
])
}
}
extension WebViewController: WKUIDelegate{
}
extension WebViewController: WKNavigationDelegate{
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse) async -> WKNavigationResponsePolicy {
debugPrint("---------------------------- decidePolicyFor navigationResponse")
return .allow
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: #escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {
debugPrint("---------------------------- decidePolicyFor navigationAction")
decisionHandler(.allow, .init())
}
}
Click here to see sample output video

Related

Making certain links in WKWebView open in Safari, not the Webview

I have a WebView app which contains external links that I wish to have users open within Safari as opposed to the webview itself, on tap. I believe it has something to do with a Navigation Delegate but I am new to iOS Dev and have no idea where to start! Below is my code as it is today. If you can tell me specifically what changes to make and where to put in any code, that would make my life so much easier. Thanks everyone in advance for any help! I think there's a way along the lines of setting a Navigation delegate such that all URL's that start with https://example-root.com/ open normal, in the webview since they are native nav buttons but all other URL's I want to open in safari on tap.
import UIKit
import WebKit
class ViewController: UIViewController {
let webView: WKWebView = {
let prefs = WKWebpagePreferences()
prefs.allowsContentJavaScript = true
let configuration = WKWebViewConfiguration()
configuration.defaultWebpagePreferences = prefs
let webView = WKWebView(frame: .zero, configuration: configuration)
return webView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(webView)
// Do any additional setup after loading the view.
guard let url = URL(string: "https://example-root.com/") else {
return
}
webView.load(URLRequest(url: url))
DispatchQueue.main.asyncAfter(deadline: .now()+5) {
self.webView.evaluateJavaScript("document.body.innerHTML") { result, error in guard lethtml = result as? String, error == nil else {
return
}
}
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
webView.frame = view.bounds
}
}
You're right that you'll need to use the NavigationDelegate to intercept the navigation action. Make your ViewController conform to WKNavigationDelegate and implement the webView(_:decidePolicyFor:decisionHandler:) method:
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url, !url.host.contains("example-root.com") {
UIApplication.shared.open(url)
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
}
Don't forget to set the navigationDelegate property of your WKWebView, you can do this in viewDidLoad with the following:
webView.navigationDelegate = self

Swift 5 Disable Redirection from WKWebView to different Apps

I have a WKWebView inside my app. The problem is that when for example going on https://stockx.com/de-de and the user has the StockX app, the user is redirected to the app.
Is there any way to disable this redirection in genreal for my webView? I couldn't find anything on this... Not too sure if you need code but here is my webView:
lazy var webView: WKWebView = {
let webConfiguration = WKWebViewConfiguration()
let webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
webView.translatesAutoresizingMaskIntoConstraints = false
return webView
}()
Apparently there is this function for an older Swift version:
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: ((WKNavigationActionPolicy) -> Void)) {
print("webView:\(webView) decidePolicyForNavigationAction:\(navigationAction) decisionHandler:\(decisionHandler)")
let app = UIApplication.sharedApplication()
let url = navigationAction.request.URL
let myScheme: NSString = "https"
if (url!.scheme == myScheme) && app.canOpenURL(url!) {
print("redirect detected..")
// intercepting redirect, do whatever you want
app.openURL(url!) // open the original url
decisionHandler(.Cancel)
return
}
decisionHandler(.Allow)
}
But this is no longer working in Swift 5. Any idea how to to this in Swift 5?
So what is happening here it's web app initializing universal link. What you can theoretically do to block this:
Reference to this answer
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: ((WKNavigationActionPolicy) -> Void)) {
print("webView:\(webView) decidePolicyForNavigationAction:\(navigationAction) decisionHandler:\(decisionHandler)")
let app = UIApplication.sharedApplication()
let url = navigationAction.request.URL
let myScheme: NSString = "https"
if (url!.scheme == myScheme) && app.canOpenURL(url!) {
print("redirect detected..")
// intercepting redirect, do whatever you want
app.openURL(url!) // open the original url
decisionHandler(.Cancel)
return
}
decisionHandler(.Allow)
}

How to pass function to webpage using swift?

I have a situation where I need to capture an event from a button click in a WebPage.
this code is on the webpage on a button click.
okCoolClick = () => {
/*eslint-disable* /
console.log("okay cool ", window.okCool);
if (window.okCool) {
okCool.performClick();
} else {
window.open("some-url");
}
};
Now the Android team is done with the problem, with below code.
commonWebView.addJavascriptInterface(object : Any() {
#JavascriptInterface
fun performClick() {
val ownerId = arguments?.getInt(OWNER_ID)
if (ownerId == -1) {
context?.startActivity<RightNavigationActivity>()
activity?.finishAffinity()
} else {
context?.startActivity<HomeItemListActivity>(
Pair(HomeItemListActivity.INTENT_EXTRA_COMPONENT,
AppConstants.HOME_SUB_LIST_CLUB_OUTLET_LIST),
Pair(HomeItemListActivity.INTENT_EXTRA_CLUB_ID, ownerId),
Pair(HomeItemListActivity.INDUSTRY_ID, -1)
)
activity?.finish()
}
}
}, "okCool")
So, Now I'm left with finding the solution in iOS,
How can I achieve this on my iOS project? Any help or node toward the correct direction would be great.
In ViewDidLoad() add this
let config = WKWebViewConfiguration()
let contentController = WKUserContentController()
contentController.add(self, name: "function")
config.userContentController = contentController
webView = WKWebView(frame: .zero, configuration: config)
add two methods from WKNavigationDelegate delegate:
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: #escaping (WKNavigationResponsePolicy) -> Void) {
return decisionHandler(.allow)
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void)
{
if navigationAction.navigationType == WKNavigationType.linkActivated, let url = navigationAction.request.url{
DeepLink.deepLink(url.absoluteString)
}
return decisionHandler(.allow)
}
Finally add the most important method to get callback from WKScriptMessageHandler delegate :
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage){
let dataFromWeb = message.body //Data Passed from webview
}
Pass data from html like this
var myObj = {"name":"John", "age":30, "car":null};
window.webkit.messageHandlers.function.postMessage(myObj);
"function" name should be same in the above statement and when u add userContentController as a config for your webview.
You can use this WebViewJavascriptBridge for sending messages between Obj-C/Swift and JavaScript.
Also you can do it without using any Third Party read this.

Allow document upload in input file on WKWebView

I have an html input type="file" upload form on a page loaded through wkwebview, and I'm trying to allow the ability to upload common document files (pdf, docx, etc). Currently I have the ability to take pictures and browse for images, but documents are greyed out and unavailable. I'm a web developer, and swift is greek to me, so any help would be greatly appreciated!
Here is my view controller
import UIKit
import WebKit
import IJProgressView
import FirebaseMessaging
class ViewController: UIViewController, WKNavigationDelegate {
let webView = WKWebView()
var initialstart = true
override func viewDidLoad() {
webView.backgroundColor = UIColor(red:0.17, green:0.24, blue:0.31, alpha:1.0)
webView.isOpaque = false
webView.allowsBackForwardNavigationGestures = true
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.receivedUrlFromPushNotification(notification:)), name: NSNotification.Name(rawValue: "ReceivedPushNotification"), object: nil)
// Do any additional setup after loading the view, typically from a nib.
guard let url = URL(string: "https://www.website.com/users/account") else { return};
webView.frame = view.bounds;
webView.navigationDelegate = self;
webView.load(URLRequest(url: url));
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight];
view.addSubview(webView)
}
func receivedUrlFromPushNotification (notification: Notification) {
let notification: [AnyHashable : Any] = notification.userInfo!
let url = URL(string: notification["url"] as! String);
webView.load(URLRequest(url: url!))
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.navigationType == .linkActivated {
if let url = navigationAction.request.url,
let host = url.host, !host.hasPrefix("www.website.com"),
UIApplication.shared.canOpenURL(url) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(url)
} else {
// Fallback on earlier versions
UIApplication.shared.openURL(url)
}
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
} else {
decisionHandler(.allow)
}
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let url = webView.url;
let path = url?.path;
UIApplication.shared.isNetworkActivityIndicatorVisible = false
IJProgressView.shared.hideProgressView()
view.isOpaque = true
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
if (initialstart == true) {
initialstart = false
} else {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
IJProgressView.shared.showProgressView(view)
}
}
}
For OSX, You can try to implement the delegate method
runOpenPanelWithParameters. It is a WKUIDelegate method.
Check the accepted answer for a similar question: Input type=file not working in WebView of OS X application
In my case, after file gets uploaded, the page was getting redirected to the base url.The problem was that I was calling
webview.load(request)
from the viewDidAppear() function.
Moving the call to the viewDidLoad() fixed my problem.
Click here to see more details

WKWebView on link click listener?

Does there exist something like a onLinkClickListener in the WKWebView class? I tried googling it but found nothing, I also found some unanswered questions on stackoverflow of simillar type.
The reason I need a linkClickListener is that, when I click on a link and the page did not load yet, it does not load the website. I also could create a fancy loading screen, when the page is loading with the listener.
You can do it like this
add WKNavigationDelegate to your class
class ViewController: UIViewController, WKNavigationDelegate
set a navigation delegate
yourWKWebview.navigationDelegate = self
after that you will be able to use decidePolicyFor navigationAction function
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.navigationType == WKNavigationType.linkActivated {
print("link")
decisionHandler(WKNavigationActionPolicy.cancel)
return
}
print("no link")
decisionHandler(WKNavigationActionPolicy.allow)
}
Here is the solution you were looking for
Original answer from Bala: https://stackoverflow.com/a/44408807/8661382
Create WkNavigationDelegate to your class:
class ViewController: UIViewController, WKNavigationDelegate {
}
Override the method loadView and add an observer like this:
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
webView.addObserver(self, forKeyPath: "URL", options: [.new, .old], context: nil)
view = webView
}
In viewDidLoad add an url to your webView.:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.setNavigationBarHidden(false, animated: true)
let url = URL(string: "https://www.hackingwithswift.com")!
webView.load(URLRequest(url: url))
webView.allowsBackForwardNavigationGestures = true
}
Finally, most importantly override observerValue method like this:
override func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let newValue = change?[.newKey] as? Int, let oldValue = change?
[.oldKey] as? Int, newValue != oldValue {
//Value Changed
print(change?[.newKey] as Any)
}else{
//Value not Changed
print(change?[.oldKey] as Any)
}
}
This will print the link you click on webView before loading the link.

Resources