148 lines
4.1 KiB
Swift
148 lines
4.1 KiB
Swift
import ExpoModulesCore
|
|
import WebKit
|
|
|
|
class HLSDownloadView: ExpoView, WKScriptMessageHandler, WKNavigationDelegate, WKDownloadDelegate {
|
|
var webView: WKWebView!
|
|
var downloaderUrl: URL?
|
|
|
|
private var onStart = EventDispatcher()
|
|
private var onError = EventDispatcher()
|
|
private var onProgress = EventDispatcher()
|
|
private var onSuccess = EventDispatcher()
|
|
|
|
private var outputUrl: URL?
|
|
|
|
public required init(appContext: AppContext? = nil) {
|
|
super.init(appContext: appContext)
|
|
|
|
// controller for post message api
|
|
let contentController = WKUserContentController()
|
|
contentController.add(self, name: "onMessage")
|
|
let configuration = WKWebViewConfiguration()
|
|
configuration.userContentController = contentController
|
|
|
|
// create webview
|
|
let webView = WKWebView(frame: .zero, configuration: configuration)
|
|
|
|
// Use these for debugging, to see the webview itself
|
|
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
webView.layer.masksToBounds = false
|
|
webView.backgroundColor = .clear
|
|
webView.contentMode = .scaleToFill
|
|
|
|
webView.navigationDelegate = self
|
|
|
|
self.addSubview(webView)
|
|
self.webView = webView
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
// MARK: - view functions
|
|
|
|
func startDownload(sourceUrl: URL) {
|
|
guard let downloaderUrl = self.downloaderUrl,
|
|
let url = URL(string: "\(downloaderUrl.absoluteString)?videoUrl=\(sourceUrl.absoluteString)") else {
|
|
self.onError([
|
|
"message": "Downloader URL is not set."
|
|
])
|
|
return
|
|
}
|
|
|
|
self.onStart()
|
|
self.webView.load(URLRequest(url: url))
|
|
}
|
|
|
|
// webview message handling
|
|
|
|
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
|
guard let response = message.body as? String,
|
|
let data = response.data(using: .utf8),
|
|
let payload = try? JSONDecoder().decode(WebViewActionPayload.self, from: data) else {
|
|
self.onError([
|
|
"message": "Failed to decode JSON post message."
|
|
])
|
|
return
|
|
}
|
|
|
|
switch payload.action {
|
|
case .progress:
|
|
guard let progress = payload.messageFloat else {
|
|
self.onError([
|
|
"message": "Failed to decode JSON post message."
|
|
])
|
|
return
|
|
}
|
|
self.onProgress([
|
|
"progress": progress
|
|
])
|
|
case .error:
|
|
guard let messageStr = payload.messageStr else {
|
|
self.onError([
|
|
"message": "Failed to decode JSON post message."
|
|
])
|
|
return
|
|
}
|
|
self.onError([
|
|
"message": messageStr
|
|
])
|
|
}
|
|
}
|
|
|
|
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
|
|
guard #available(iOS 14.5, *) else {
|
|
return .cancel
|
|
}
|
|
|
|
if navigationAction.shouldPerformDownload {
|
|
return .download
|
|
} else {
|
|
return .allow
|
|
}
|
|
}
|
|
|
|
// MARK: - wkdownloaddelegate
|
|
|
|
@available(iOS 14.5, *)
|
|
func webView(_ webView: WKWebView, navigationAction: WKNavigationAction, didBecome download: WKDownload) {
|
|
download.delegate = self
|
|
}
|
|
|
|
@available(iOS 14.5, *)
|
|
func webView(_ webView: WKWebView, navigationResponse: WKNavigationResponse, didBecome download: WKDownload) {
|
|
download.delegate = self
|
|
}
|
|
|
|
@available(iOS 14.5, *)
|
|
func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String, completionHandler: @escaping (URL?) -> Void) {
|
|
let directory = NSTemporaryDirectory()
|
|
let fileName = "\(NSUUID().uuidString).mp4"
|
|
let url = NSURL.fileURL(withPathComponents: [directory, fileName])
|
|
|
|
self.outputUrl = url
|
|
completionHandler(url)
|
|
}
|
|
|
|
@available(iOS 14.5, *)
|
|
func downloadDidFinish(_ download: WKDownload) {
|
|
guard let url = self.outputUrl else {
|
|
return
|
|
}
|
|
self.onSuccess([
|
|
"uri": url.absoluteString
|
|
])
|
|
self.outputUrl = nil
|
|
}
|
|
}
|
|
|
|
struct WebViewActionPayload: Decodable {
|
|
enum Action: String, Decodable {
|
|
case progress, error
|
|
}
|
|
|
|
let action: Action
|
|
let messageStr: String?
|
|
let messageFloat: Float?
|
|
}
|