Revert "[Video] Download videos" (#4945)
This commit is contained in:
parent
b6e515c664
commit
a5af24b53b
19 changed files with 3 additions and 747 deletions
|
@ -1,35 +0,0 @@
|
|||
package expo.modules.blueskyswissarmy.hlsdownload
|
||||
|
||||
import android.net.Uri
|
||||
import expo.modules.kotlin.modules.Module
|
||||
import expo.modules.kotlin.modules.ModuleDefinition
|
||||
|
||||
class ExpoHLSDownloadModule : Module() {
|
||||
override fun definition() =
|
||||
ModuleDefinition {
|
||||
Name("ExpoHLSDownload")
|
||||
|
||||
Function("isAvailable") {
|
||||
return@Function true
|
||||
}
|
||||
|
||||
View(HLSDownloadView::class) {
|
||||
Events(
|
||||
arrayOf(
|
||||
"onStart",
|
||||
"onError",
|
||||
"onProgress",
|
||||
"onSuccess",
|
||||
),
|
||||
)
|
||||
|
||||
Prop("downloaderUrl") { view: HLSDownloadView, downloaderUrl: Uri ->
|
||||
view.downloaderUrl = downloaderUrl
|
||||
}
|
||||
|
||||
AsyncFunction("startDownloadAsync") { view: HLSDownloadView, sourceUrl: Uri ->
|
||||
view.startDownload(sourceUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
package expo.modules.blueskyswissarmy.hlsdownload
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.webkit.DownloadListener
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebView
|
||||
import expo.modules.kotlin.AppContext
|
||||
import expo.modules.kotlin.viewevent.EventDispatcher
|
||||
import expo.modules.kotlin.viewevent.ViewEventCallback
|
||||
import expo.modules.kotlin.views.ExpoView
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.URI
|
||||
import java.util.UUID
|
||||
|
||||
class HLSDownloadView(
|
||||
context: Context,
|
||||
appContext: AppContext,
|
||||
) : ExpoView(context, appContext),
|
||||
DownloadListener {
|
||||
private val webView = WebView(context)
|
||||
|
||||
var downloaderUrl: Uri? = null
|
||||
|
||||
private val onStart by EventDispatcher()
|
||||
private val onError by EventDispatcher()
|
||||
private val onProgress by EventDispatcher()
|
||||
private val onSuccess by EventDispatcher()
|
||||
|
||||
init {
|
||||
this.setupWebView()
|
||||
this.addView(this.webView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun setupWebView() {
|
||||
val webSettings = this.webView.settings
|
||||
webSettings.javaScriptEnabled = true
|
||||
webSettings.domStorageEnabled = true
|
||||
|
||||
webView.setDownloadListener(this)
|
||||
webView.addJavascriptInterface(WebAppInterface(this.onProgress, this.onError), "AndroidInterface")
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
this.webView.stopLoading()
|
||||
this.webView.clearHistory()
|
||||
this.webView.removeAllViews()
|
||||
this.webView.destroy()
|
||||
}
|
||||
|
||||
fun startDownload(sourceUrl: Uri) {
|
||||
if (this.downloaderUrl == null) {
|
||||
this.onError(mapOf(ERROR_KEY to "Downloader URL is not set."))
|
||||
return
|
||||
}
|
||||
|
||||
val url = URI("${this.downloaderUrl}?videoUrl=$sourceUrl")
|
||||
this.webView.loadUrl(url.toString())
|
||||
this.onStart(mapOf())
|
||||
}
|
||||
|
||||
override fun onDownloadStart(
|
||||
url: String?,
|
||||
userAgent: String?,
|
||||
contentDisposition: String?,
|
||||
mimeType: String?,
|
||||
contentLength: Long,
|
||||
) {
|
||||
if (url == null) {
|
||||
this.onError(mapOf(ERROR_KEY to "Failed to retrieve download URL from webview."))
|
||||
return
|
||||
}
|
||||
|
||||
val tempDir = context.cacheDir
|
||||
val fileName = "${UUID.randomUUID()}.mp4"
|
||||
val file = File(tempDir, fileName)
|
||||
|
||||
val base64 = url.split(",")[1]
|
||||
val bytes = Base64.decode(base64, Base64.DEFAULT)
|
||||
|
||||
val fos = FileOutputStream(file)
|
||||
try {
|
||||
fos.write(bytes)
|
||||
} catch (e: Exception) {
|
||||
Log.e("FileDownload", "Error downloading file", e)
|
||||
this.onError(mapOf(ERROR_KEY to e.message.toString()))
|
||||
return
|
||||
} finally {
|
||||
fos.close()
|
||||
}
|
||||
|
||||
val uri = Uri.fromFile(file)
|
||||
this.onSuccess(mapOf("uri" to uri.toString()))
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ERROR_KEY = "message"
|
||||
}
|
||||
}
|
||||
|
||||
public class WebAppInterface(
|
||||
val onProgress: ViewEventCallback<Map<String, Any>>,
|
||||
val onError: ViewEventCallback<Map<String, Any>>,
|
||||
) {
|
||||
@JavascriptInterface
|
||||
public fun onMessage(message: String) {
|
||||
val jsonObject = JSONObject(message)
|
||||
val action = jsonObject.getString("action")
|
||||
|
||||
when (action) {
|
||||
"error" -> {
|
||||
val messageStr = jsonObject.get("messageStr")
|
||||
if (messageStr !is String) {
|
||||
this.onError(mapOf(ERROR_KEY to "Failed to decode JSON post message."))
|
||||
return
|
||||
}
|
||||
this.onError(mapOf(ERROR_KEY to messageStr))
|
||||
}
|
||||
"progress" -> {
|
||||
val messageFloat = jsonObject.get("messageFloat")
|
||||
if (messageFloat !is Number) {
|
||||
this.onError(mapOf(ERROR_KEY to "Failed to decode JSON post message."))
|
||||
return
|
||||
}
|
||||
this.onProgress(mapOf(PROGRESS_KEY to messageFloat))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PROGRESS_KEY = "progress"
|
||||
const val ERROR_KEY = "message"
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@
|
|||
"ExpoBlueskySharedPrefsModule",
|
||||
"ExpoBlueskyReferrerModule",
|
||||
"ExpoBlueskyVisibilityViewModule",
|
||||
"ExpoHLSDownloadModule",
|
||||
"ExpoPlatformInfoModule"
|
||||
]
|
||||
},
|
||||
|
@ -14,8 +13,7 @@
|
|||
"expo.modules.blueskyswissarmy.sharedprefs.ExpoBlueskySharedPrefsModule",
|
||||
"expo.modules.blueskyswissarmy.referrer.ExpoBlueskyReferrerModule",
|
||||
"expo.modules.blueskyswissarmy.visibilityview.ExpoBlueskyVisibilityViewModule",
|
||||
"expo.modules.blueskyswissarmy.platforminfo.ExpoPlatformInfoModule",
|
||||
"expo.modules.blueskyswissarmy.hlsdownload.ExpoHLSDownloadModule"
|
||||
"expo.modules.blueskyswissarmy.platforminfo.ExpoPlatformInfoModule"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,7 @@
|
|||
import HLSDownloadView from './src/HLSDownload'
|
||||
import * as PlatformInfo from './src/PlatformInfo'
|
||||
import {AudioCategory} from './src/PlatformInfo/types'
|
||||
import * as Referrer from './src/Referrer'
|
||||
import * as SharedPrefs from './src/SharedPrefs'
|
||||
import VisibilityView from './src/VisibilityView'
|
||||
|
||||
export {
|
||||
AudioCategory,
|
||||
HLSDownloadView,
|
||||
PlatformInfo,
|
||||
Referrer,
|
||||
SharedPrefs,
|
||||
VisibilityView,
|
||||
}
|
||||
export {AudioCategory, PlatformInfo, Referrer, SharedPrefs, VisibilityView}
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
import ExpoModulesCore
|
||||
|
||||
public class ExpoHLSDownloadModule: Module {
|
||||
public func definition() -> ModuleDefinition {
|
||||
Name("ExpoHLSDownload")
|
||||
|
||||
Function("isAvailable") {
|
||||
if #available(iOS 14.5, *) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
View(HLSDownloadView.self) {
|
||||
Events([
|
||||
"onStart",
|
||||
"onError",
|
||||
"onProgress",
|
||||
"onSuccess"
|
||||
])
|
||||
|
||||
Prop("downloaderUrl") { (view: HLSDownloadView, downloaderUrl: URL) in
|
||||
view.downloaderUrl = downloaderUrl
|
||||
}
|
||||
|
||||
AsyncFunction("startDownloadAsync") { (view: HLSDownloadView, sourceUrl: URL) in
|
||||
view.startDownload(sourceUrl: sourceUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
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?
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
import React from 'react'
|
||||
import {StyleProp, ViewStyle} from 'react-native'
|
||||
import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core'
|
||||
|
||||
import {HLSDownloadViewProps} from './types'
|
||||
|
||||
const NativeModule = requireNativeModule('ExpoHLSDownload')
|
||||
const NativeView: React.ComponentType<
|
||||
HLSDownloadViewProps & {
|
||||
ref: React.RefObject<any>
|
||||
style: StyleProp<ViewStyle>
|
||||
}
|
||||
> = requireNativeViewManager('ExpoHLSDownload')
|
||||
|
||||
export default class HLSDownloadView extends React.PureComponent<HLSDownloadViewProps> {
|
||||
private nativeRef: React.RefObject<any> = React.createRef()
|
||||
|
||||
constructor(props: HLSDownloadViewProps) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
static isAvailable(): boolean {
|
||||
return NativeModule.isAvailable()
|
||||
}
|
||||
|
||||
async startDownloadAsync(sourceUrl: string): Promise<void> {
|
||||
return await this.nativeRef.current.startDownloadAsync(sourceUrl)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<NativeView
|
||||
ref={this.nativeRef}
|
||||
style={{height: 0, width: 0}}
|
||||
{...this.props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import {NotImplementedError} from '../NotImplemented'
|
||||
import {HLSDownloadViewProps} from './types'
|
||||
|
||||
export default class HLSDownloadView extends React.PureComponent<HLSDownloadViewProps> {
|
||||
constructor(props: HLSDownloadViewProps) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
static isAvailable(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
async startDownloadAsync(sourceUrl: string): Promise<void> {
|
||||
throw new NotImplementedError({sourceUrl})
|
||||
}
|
||||
|
||||
render() {
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
import {NativeSyntheticEvent} from 'react-native'
|
||||
|
||||
export interface HLSDownloadViewProps {
|
||||
downloaderUrl: string
|
||||
onSuccess: (e: NativeSyntheticEvent<{uri: string}>) => void
|
||||
|
||||
onStart?: () => void
|
||||
onError?: (e: NativeSyntheticEvent<{message: string}>) => void
|
||||
onProgress?: (e: NativeSyntheticEvent<{progress: number}>) => void
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue