From a5af24b53b6085cfb5547592c29155bc10e71f9e Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 15 Aug 2024 16:29:16 -0700 Subject: [PATCH] Revert "[Video] Download videos" (#4945) --- bskyweb/cmd/bskyweb/server.go | 3 - bskyweb/static/robots.txt | 1 - .../hlsdownload/ExpoHLSDownloadModule.kt | 35 --- .../hlsdownload/HLSDownloadView.kt | 141 ------------ .../expo-module.config.json | 4 +- modules/expo-bluesky-swiss-army/index.ts | 10 +- .../HLSDownload/ExpoHLSDownloadModule.swift | 31 --- .../ios/HLSDownload/HLSDownloadView.swift | 148 ------------ .../src/HLSDownload/index.native.tsx | 39 ---- .../src/HLSDownload/index.tsx | 22 -- .../src/HLSDownload/types.ts | 10 - package.json | 4 - src/Navigation.tsx | 6 - src/components/VideoDownloadScreen.native.tsx | 4 - src/components/VideoDownloadScreen.tsx | 215 ------------------ src/lib/routes/types.ts | 1 - src/routes.ts | 1 - src/view/screens/Storybook/index.tsx | 46 +--- yarn.lock | 29 --- 19 files changed, 3 insertions(+), 747 deletions(-) delete mode 100644 modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/hlsdownload/ExpoHLSDownloadModule.kt delete mode 100644 modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/hlsdownload/HLSDownloadView.kt delete mode 100644 modules/expo-bluesky-swiss-army/ios/HLSDownload/ExpoHLSDownloadModule.swift delete mode 100644 modules/expo-bluesky-swiss-army/ios/HLSDownload/HLSDownloadView.swift delete mode 100644 modules/expo-bluesky-swiss-army/src/HLSDownload/index.native.tsx delete mode 100644 modules/expo-bluesky-swiss-army/src/HLSDownload/index.tsx delete mode 100644 modules/expo-bluesky-swiss-army/src/HLSDownload/types.ts delete mode 100644 src/components/VideoDownloadScreen.native.tsx delete mode 100644 src/components/VideoDownloadScreen.tsx diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index 01f1a875..fdef01ce 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -256,9 +256,6 @@ func serve(cctx *cli.Context) error { e.GET("/profile/:handleOrDID/post/:rkey/liked-by", server.WebGeneric) e.GET("/profile/:handleOrDID/post/:rkey/reposted-by", server.WebGeneric) - // video download - e.GET("/video-download", server.WebGeneric) - // starter packs e.GET("/starter-pack/:handleOrDID/:rkey", server.WebStarterPack) e.GET("/start/:handleOrDID/:rkey", server.WebStarterPack) diff --git a/bskyweb/static/robots.txt b/bskyweb/static/robots.txt index d785755a..4f8510d1 100644 --- a/bskyweb/static/robots.txt +++ b/bskyweb/static/robots.txt @@ -7,4 +7,3 @@ # be ok. User-Agent: * Allow: / -Disallow: /video-download diff --git a/modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/hlsdownload/ExpoHLSDownloadModule.kt b/modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/hlsdownload/ExpoHLSDownloadModule.kt deleted file mode 100644 index 786b84e4..00000000 --- a/modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/hlsdownload/ExpoHLSDownloadModule.kt +++ /dev/null @@ -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) - } - } - } -} diff --git a/modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/hlsdownload/HLSDownloadView.kt b/modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/hlsdownload/HLSDownloadView.kt deleted file mode 100644 index 5f3082a8..00000000 --- a/modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/hlsdownload/HLSDownloadView.kt +++ /dev/null @@ -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>, - val onError: ViewEventCallback>, -) { - @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" - } -} diff --git a/modules/expo-bluesky-swiss-army/expo-module.config.json b/modules/expo-bluesky-swiss-army/expo-module.config.json index 04411ecf..4cdc11e9 100644 --- a/modules/expo-bluesky-swiss-army/expo-module.config.json +++ b/modules/expo-bluesky-swiss-army/expo-module.config.json @@ -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" ] } } diff --git a/modules/expo-bluesky-swiss-army/index.ts b/modules/expo-bluesky-swiss-army/index.ts index 67dc6ee6..2cf4f36c 100644 --- a/modules/expo-bluesky-swiss-army/index.ts +++ b/modules/expo-bluesky-swiss-army/index.ts @@ -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} diff --git a/modules/expo-bluesky-swiss-army/ios/HLSDownload/ExpoHLSDownloadModule.swift b/modules/expo-bluesky-swiss-army/ios/HLSDownload/ExpoHLSDownloadModule.swift deleted file mode 100644 index a9b445e4..00000000 --- a/modules/expo-bluesky-swiss-army/ios/HLSDownload/ExpoHLSDownloadModule.swift +++ /dev/null @@ -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) - } - } - } -} diff --git a/modules/expo-bluesky-swiss-army/ios/HLSDownload/HLSDownloadView.swift b/modules/expo-bluesky-swiss-army/ios/HLSDownload/HLSDownloadView.swift deleted file mode 100644 index 591c0933..00000000 --- a/modules/expo-bluesky-swiss-army/ios/HLSDownload/HLSDownloadView.swift +++ /dev/null @@ -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? -} diff --git a/modules/expo-bluesky-swiss-army/src/HLSDownload/index.native.tsx b/modules/expo-bluesky-swiss-army/src/HLSDownload/index.native.tsx deleted file mode 100644 index 92f26192..00000000 --- a/modules/expo-bluesky-swiss-army/src/HLSDownload/index.native.tsx +++ /dev/null @@ -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 - style: StyleProp - } -> = requireNativeViewManager('ExpoHLSDownload') - -export default class HLSDownloadView extends React.PureComponent { - private nativeRef: React.RefObject = React.createRef() - - constructor(props: HLSDownloadViewProps) { - super(props) - } - - static isAvailable(): boolean { - return NativeModule.isAvailable() - } - - async startDownloadAsync(sourceUrl: string): Promise { - return await this.nativeRef.current.startDownloadAsync(sourceUrl) - } - - render() { - return ( - - ) - } -} diff --git a/modules/expo-bluesky-swiss-army/src/HLSDownload/index.tsx b/modules/expo-bluesky-swiss-army/src/HLSDownload/index.tsx deleted file mode 100644 index 93c50497..00000000 --- a/modules/expo-bluesky-swiss-army/src/HLSDownload/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' - -import {NotImplementedError} from '../NotImplemented' -import {HLSDownloadViewProps} from './types' - -export default class HLSDownloadView extends React.PureComponent { - constructor(props: HLSDownloadViewProps) { - super(props) - } - - static isAvailable(): boolean { - return false - } - - async startDownloadAsync(sourceUrl: string): Promise { - throw new NotImplementedError({sourceUrl}) - } - - render() { - return null - } -} diff --git a/modules/expo-bluesky-swiss-army/src/HLSDownload/types.ts b/modules/expo-bluesky-swiss-army/src/HLSDownload/types.ts deleted file mode 100644 index 6a474d28..00000000 --- a/modules/expo-bluesky-swiss-army/src/HLSDownload/types.ts +++ /dev/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 -} diff --git a/package.json b/package.json index 088f2faf..a4523d98 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,6 @@ "@emoji-mart/react": "^1.1.1", "@expo/html-elements": "^0.4.2", "@expo/webpack-config": "^19.0.0", - "@ffmpeg/ffmpeg": "^0.12.10", - "@ffmpeg/util": "^0.12.1", "@floating-ui/dom": "^1.6.3", "@floating-ui/react-dom": "^2.0.8", "@formatjs/intl-locale": "^4.0.0", @@ -145,7 +143,6 @@ "expo-web-browser": "~13.0.3", "fast-text-encoding": "^1.0.6", "history": "^5.3.0", - "hls-parser": "^0.13.3", "hls.js": "^1.5.11", "js-sha256": "^0.9.0", "jwt-decode": "^4.0.0", @@ -227,7 +224,6 @@ "@testing-library/react-native": "^11.5.2", "@tsconfig/react-native": "^2.0.3", "@types/he": "^1.1.2", - "@types/hls-parser": "^0.8.7", "@types/jest": "^29.4.0", "@types/lodash.chunk": "^4.2.7", "@types/lodash.debounce": "^4.0.7", diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 0d151427..79856879 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -50,7 +50,6 @@ import { StarterPackScreenShort, } from '#/screens/StarterPack/StarterPackScreen' import {Wizard} from '#/screens/StarterPack/Wizard' -import {VideoDownloadScreen} from '#/components/VideoDownloadScreen' import {Referrer} from '../modules/expo-bluesky-swiss-army' import {init as initAnalytics} from './lib/analytics/analytics' import {useWebScrollRestoration} from './lib/hooks/useWebScrollRestoration' @@ -365,11 +364,6 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { getComponent={() => Wizard} options={{title: title(msg`Edit your starter pack`), requireAuth: true}} /> - VideoDownloadScreen} - options={{title: title(msg`Download video`)}} - /> ) } diff --git a/src/components/VideoDownloadScreen.native.tsx b/src/components/VideoDownloadScreen.native.tsx deleted file mode 100644 index a1f6466f..00000000 --- a/src/components/VideoDownloadScreen.native.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export function VideoDownloadScreen() { - // @TODO redirect - return null -} diff --git a/src/components/VideoDownloadScreen.tsx b/src/components/VideoDownloadScreen.tsx deleted file mode 100644 index 3169d265..00000000 --- a/src/components/VideoDownloadScreen.tsx +++ /dev/null @@ -1,215 +0,0 @@ -import React from 'react' -import {parse} from 'hls-parser' -import {MasterPlaylist, MediaPlaylist, Variant} from 'hls-parser/types' - -interface PostMessageData { - action: 'progress' | 'error' - messageStr?: string - messageFloat?: number -} - -function postMessage(data: PostMessageData) { - // @ts-expect-error safari webview only - if (window?.webkit) { - // @ts-expect-error safari webview only - window.webkit.messageHandlers.onMessage.postMessage(JSON.stringify(data)) - // @ts-expect-error android webview only - } else if (AndroidInterface) { - // @ts-expect-error android webview only - AndroidInterface.onMessage(JSON.stringify(data)) - } -} - -function createSegementUrl(originalUrl: string, newFile: string) { - const parts = originalUrl.split('/') - parts[parts.length - 1] = newFile - return parts.join('/') -} - -export function VideoDownloadScreen() { - const ffmpegRef = React.useRef(null) - const fetchFileRef = React.useRef(null) - - const [dataUrl, setDataUrl] = React.useState(null) - - const load = React.useCallback(async () => { - const ffmpegLib = await import('@ffmpeg/ffmpeg') - const ffmpeg = new ffmpegLib.FFmpeg() - ffmpegRef.current = ffmpeg - - const ffmpegUtilLib = await import('@ffmpeg/util') - fetchFileRef.current = ffmpegUtilLib.fetchFile - - const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm' - - await ffmpeg.load({ - coreURL: await ffmpegUtilLib.toBlobURL( - `${baseURL}/ffmpeg-core.js`, - 'text/javascript', - ), - wasmURL: await ffmpegUtilLib.toBlobURL( - `${baseURL}/ffmpeg-core.wasm`, - 'application/wasm', - ), - }) - }, []) - - const createMp4 = React.useCallback(async (videoUrl: string) => { - // Get the master playlist and find the best variant - const masterPlaylistRes = await fetch(videoUrl) - const masterPlaylistText = await masterPlaylistRes.text() - const masterPlaylist = parse(masterPlaylistText) as MasterPlaylist - - // If URL given is not a master playlist, we probably cannot handle this. - if (!masterPlaylist.isMasterPlaylist) { - postMessage({ - action: 'error', - messageStr: 'A master playlist was not found in the provided playlist.', - }) - return - } - - // Figure out what the best quality is. These should generally be in order, but we'll check them all just in case - let bestVariant: Variant | undefined - for (const variant of masterPlaylist.variants) { - if (!bestVariant || variant.bandwidth > bestVariant.bandwidth) { - bestVariant = variant - } - } - - // Should only happen if there was no variants at all given to us. Mostly for types. - if (!bestVariant) { - postMessage({ - action: 'error', - messageStr: 'No variants were found in the provided master playlist.', - }) - return - } - - const urlParts = videoUrl.split('/') - urlParts[urlParts.length - 1] = bestVariant?.uri - const bestVariantUrl = urlParts.join('/') - - // Download and parse m3u8 - const hlsFileRes = await fetch(bestVariantUrl) - const hlsPlainText = await hlsFileRes.text() - const playlist = parse(hlsPlainText) as MediaPlaylist - - // This one shouldn't be a master playlist - again just for types really - if (playlist.isMasterPlaylist) { - postMessage({ - action: 'error', - messageStr: 'An unknown error has occurred.', - }) - return - } - - const ffmpeg = ffmpegRef.current - - // Get the correctly ordered file names. We need to remove the tracking info from the end of the file name - const segments = playlist.segments.map(segment => { - return segment.uri.split('?')[0] - }) - - // Download each segment - let error: string | null = null - let completed = 0 - await Promise.all( - playlist.segments.map(async segment => { - const uri = createSegementUrl(bestVariantUrl, segment.uri) - const filename = segment.uri.split('?')[0] - - const res = await fetch(uri) - if (!res.ok) { - error = 'Failed to download playlist segment.' - } - - const blob = await res.blob() - try { - await ffmpeg.writeFile(filename, await fetchFileRef.current(blob)) - } catch (e: unknown) { - error = 'Failed to write file.' - } finally { - completed++ - const progress = completed / playlist.segments.length - postMessage({ - action: 'progress', - messageFloat: progress, - }) - } - }), - ) - - // Do something if there was an error - if (error) { - postMessage({ - action: 'error', - messageStr: error, - }) - return - } - - // Put the segments together - await ffmpeg.exec([ - '-i', - `concat:${segments.join('|')}`, - '-c:v', - 'copy', - 'output.mp4', - ]) - - const fileData = await ffmpeg.readFile('output.mp4') - const blob = new Blob([fileData.buffer], {type: 'video/mp4'}) - const dataUrl = await new Promise(resolve => { - const reader = new FileReader() - reader.onloadend = () => resolve(reader.result as string) - reader.onerror = () => resolve(null) - reader.readAsDataURL(blob) - }) - return dataUrl - }, []) - - const download = React.useCallback( - async (videoUrl: string) => { - await load() - const mp4Res = await createMp4(videoUrl) - - if (!mp4Res) { - postMessage({ - action: 'error', - messageStr: 'An error occurred while creating the MP4.', - }) - return - } - - setDataUrl(mp4Res) - }, - [createMp4, load], - ) - - React.useEffect(() => { - const url = new URL(window.location.href) - const videoUrl = url.searchParams.get('videoUrl') - - if (!videoUrl) { - postMessage({action: 'error', messageStr: 'No video URL provided'}) - } else { - setDataUrl(null) - download(videoUrl) - } - }, [download]) - - if (!dataUrl) return null - - return ( - - ) -} diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index 77e7266a..0cc83b47 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -50,7 +50,6 @@ export type CommonNavigatorParams = { StarterPackShort: {code: string} StarterPackWizard: undefined StarterPackEdit: {rkey?: string} - VideoDownload: undefined } export type BottomTabNavigatorParams = CommonNavigatorParams & { diff --git a/src/routes.ts b/src/routes.ts index bda2d98e..c9e23e08 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -48,5 +48,4 @@ export const router = new Router({ StarterPack: '/starter-pack/:name/:rkey', StarterPackShort: '/starter-pack-short/:code', StarterPackWizard: '/starter-pack/create', - VideoDownload: '/video-download', }) diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx index c6da6331..71dbe883 100644 --- a/src/view/screens/Storybook/index.tsx +++ b/src/view/screens/Storybook/index.tsx @@ -1,17 +1,12 @@ import React from 'react' import {ScrollView, View} from 'react-native' -import {deleteAsync} from 'expo-file-system' -import {saveToLibraryAsync} from 'expo-media-library' import {useSetThemePrefs} from '#/state/shell' -import {useVideoLibraryPermission} from 'lib/hooks/usePermissions' -import {isIOS, isWeb} from 'platform/detection' +import {isWeb} from 'platform/detection' import {CenteredView} from '#/view/com/util/Views' -import * as Toast from 'view/com/util/Toast' import {ListContained} from 'view/screens/Storybook/ListContained' import {atoms as a, ThemeProvider, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' -import {HLSDownloadView} from '../../../../modules/expo-bluesky-swiss-army' import {Breakpoints} from './Breakpoints' import {Buttons} from './Buttons' import {Dialogs} from './Dialogs' @@ -38,49 +33,10 @@ function StorybookInner() { const t = useTheme() const {setColorMode, setDarkTheme} = useSetThemePrefs() const [showContainedList, setShowContainedList] = React.useState(false) - const hlsDownloadRef = React.useRef(null) - - const {requestVideoAccessIfNeeded} = useVideoLibraryPermission() return ( - { - const uri = e.nativeEvent.uri - const permsRes = await requestVideoAccessIfNeeded() - if (!permsRes) return - - await saveToLibraryAsync(uri) - try { - deleteAsync(uri) - } catch (err) { - console.error('Failed to delete file', err) - } - Toast.show('Video saved to library') - }} - onStart={() => console.log('Download is starting')} - onError={e => console.log(e.nativeEvent.message)} - onProgress={e => console.log(e.nativeEvent.progress)} - /> - {!showContainedList ? ( <> diff --git a/yarn.lock b/yarn.lock index 28308d95..cd0508d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3925,23 +3925,6 @@ resolved "https://registry.yarnpkg.com/@fastify/deepmerge/-/deepmerge-1.3.0.tgz#8116858108f0c7d9fd460d05a7d637a13fe3239a" integrity sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A== -"@ffmpeg/ffmpeg@^0.12.10": - version "0.12.10" - resolved "https://registry.yarnpkg.com/@ffmpeg/ffmpeg/-/ffmpeg-0.12.10.tgz#e3cce21f21f11f33dfc1ec1d5ad5694f4a3073c9" - integrity sha512-lVtk8PW8e+NUzGZhPTWj2P1J4/NyuCrbDD3O9IGpSeLYtUZKBqZO8CNj1WYGghep/MXoM8e1qVY1GztTkf8YYQ== - dependencies: - "@ffmpeg/types" "^0.12.2" - -"@ffmpeg/types@^0.12.2": - version "0.12.2" - resolved "https://registry.yarnpkg.com/@ffmpeg/types/-/types-0.12.2.tgz#bc7eef321ae50225c247091f1f23fd3087c6aa1d" - integrity sha512-NJtxwPoLb60/z1Klv0ueshguWQ/7mNm106qdHkB4HL49LXszjhjCCiL+ldHJGQ9ai2Igx0s4F24ghigy//ERdA== - -"@ffmpeg/util@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@ffmpeg/util/-/util-0.12.1.tgz#98afa20d7b4c0821eebdb205ddcfa5d07b0a4f53" - integrity sha512-10jjfAKWaDyb8+nAkijcsi9wgz/y26LOc1NKJradNMyCIl6usQcBbhkjX5qhALrSBcOy6TOeksunTYa+a03qNQ== - "@floating-ui/core@^1.0.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1" @@ -8024,13 +8007,6 @@ resolved "https://registry.yarnpkg.com/@types/he/-/he-1.2.0.tgz#3845193e597d943bab4e61ca5d7f3d8fc3d572a3" integrity sha512-uH2smqTN4uGReAiKedIVzoLUAXIYLBTbSofhx3hbNqj74Ua6KqFsLYszduTrLCMEAEAozF73DbGi/SC1bzQq4g== -"@types/hls-parser@^0.8.7": - version "0.8.7" - resolved "https://registry.yarnpkg.com/@types/hls-parser/-/hls-parser-0.8.7.tgz#26360493231ed8606ebe995976c63c69c3982657" - integrity sha512-3ry9V6i/uhSbNdvBUENAqt2p5g+xKIbjkr5Qv4EaXe7eIJnaGQntFZalRLQlKoEop381a0LwUr2qNKKlxQC4TQ== - dependencies: - "@types/node" "*" - "@types/html-minifier-terser@^6.0.0": version "6.1.0" resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" @@ -13480,11 +13456,6 @@ history@^5.3.0: dependencies: "@babel/runtime" "^7.7.6" -hls-parser@^0.13.3: - version "0.13.3" - resolved "https://registry.yarnpkg.com/hls-parser/-/hls-parser-0.13.3.tgz#5f7a305629cf462bbf16a4d080e03e0be714f1fe" - integrity sha512-DXqW7bwx9j2qFcAXS/LBJTDJWitxknb6oUnsnTvECHrecPvPbhRgIu45OgNDUU6gpwKxMJx40SHRRUUhdIM2gA== - hls.js@^1.5.11: version "1.5.11" resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-1.5.11.tgz#3941347df454983859ae8c75fe19e8818719a826"