diff --git a/modules/expo-bluesky-swiss-army/expo-module.config.json b/modules/expo-bluesky-swiss-army/expo-module.config.json index 1111f8a0be..4a3851db20 100644 --- a/modules/expo-bluesky-swiss-army/expo-module.config.json +++ b/modules/expo-bluesky-swiss-army/expo-module.config.json @@ -1,7 +1,7 @@ { "platforms": ["ios", "tvos", "android", "web"], "ios": { - "modules": ["ExpoBlueskySharedPrefsModule", "ExpoBlueskyReferrerModule"] + "modules": ["ExpoBlueskySharedPrefsModule", "ExpoBlueskyReferrerModule", "ExpoHLSDownloadModule"] }, "android": { "modules": [ diff --git a/modules/expo-bluesky-swiss-army/ios/HLSDownload/DownloadManager.swift b/modules/expo-bluesky-swiss-army/ios/HLSDownload/DownloadManager.swift new file mode 100644 index 0000000000..6c8d0f2467 --- /dev/null +++ b/modules/expo-bluesky-swiss-army/ios/HLSDownload/DownloadManager.swift @@ -0,0 +1,50 @@ +// +// DownloadManager.swift +// ExpoBlueskySwissArmy +// +// Created by Hailey on 7/29/24. +// + +class DownloadManager { + static let shared = DownloadManager() + + private let downloads: NSMapTable = NSMapTable(keyOptions: .weakMemory, valueOptions: .weakMemory) + + func add(_ hlsDownload: HLSDownload) { + let key = hlsDownload.sourceUrl.absoluteString as NSString + downloads.setObject(hlsDownload, forKey: key) + } + + func remove(string: String) { + let key = string as NSString + downloads.removeObject(forKey: key) + } + + func remove(hlsDownload: HLSDownload) { + let key = hlsDownload.sourceUrl.absoluteString as NSString + downloads.setObject(hlsDownload, forKey: key) + } + + func getForSource(url: URL) -> HLSDownload? { + let key = url.absoluteString as NSString + return downloads.object(forKey: key) + } + + func getForSource(string: String) -> HLSDownload? { + let key = string as NSString + return downloads.object(forKey: key) + } + + func cancel(url: URL) { + let download = self.getForSource(url: url) + download?.cancel() + } + + func cancelAll() { + let downloadsEnumerator = downloads.objectEnumerator() + while let download = downloadsEnumerator?.nextObject() { + let download = download as? HLSDownload + download?.cancel() + } + } +} diff --git a/modules/expo-bluesky-swiss-army/ios/HLSDownload/ExpoHLSDownloadModule.swift b/modules/expo-bluesky-swiss-army/ios/HLSDownload/ExpoHLSDownloadModule.swift new file mode 100644 index 0000000000..3afb849f20 --- /dev/null +++ b/modules/expo-bluesky-swiss-army/ios/HLSDownload/ExpoHLSDownloadModule.swift @@ -0,0 +1,28 @@ +// +// ExpoHLSDownloadModule.swift +// DoubleConversion +// +// Created by Hailey on 7/29/24. +// + +import ExpoModulesCore + +class ExpoHLSDownloadModule: Module { + public func definition() -> ModuleDefinition { + Name("ExpoHLSDownloadModule") + + AsyncFunction("downloadAsync") { (sourceUrl: URL, progressCb: JavaScriptFunction, promise: Promise) in + let hlsDownload = HLSDownload(sourceUrl: sourceUrl) + let downloadTask = Task { + let outUrl = await hlsDownload.download { (progress: Float) in + try? progressCb.call(progress) + } + promise.resolve(outUrl) + } + } + + AsyncFunction("cancelDownloadAsync") { (sourceUrl: URL) in + DownloadManager.shared.cancel(url: sourceUrl) + } + } +} diff --git a/modules/expo-bluesky-swiss-army/ios/HLSDownload/HLSDownload.swift b/modules/expo-bluesky-swiss-army/ios/HLSDownload/HLSDownload.swift new file mode 100644 index 0000000000..1574faebe6 --- /dev/null +++ b/modules/expo-bluesky-swiss-army/ios/HLSDownload/HLSDownload.swift @@ -0,0 +1,64 @@ +// +// HLSDownload.swift +// ExpoBlueskySwissArmy +// +// Created by Hailey on 7/29/24. +// + +import Foundation +import AVKit + +class HLSDownload { + private let avAsset: AVAsset + private var exportSession: AVAssetExportSession? + let sourceUrl: URL + + init (sourceUrl: URL) { + self.sourceUrl = sourceUrl + self.avAsset = AVAsset(url: sourceUrl) + } + + func download(progress: @escaping(Float) -> Void) async -> URL? { + guard let exportSession = AVAssetExportSession(asset: self.avAsset, presetName: AVAssetExportPresetHighestQuality) else { + // @TODO return an error for the user here + print("Export session error") + return nil + } + + let outDir = FileManager().urls(for: .cachesDirectory, in: .userDomainMask)[0] + guard let outUrl = URL(string: "\(outDir.absoluteString)\(ProcessInfo.processInfo.globallyUniqueString).mp4") else { + print("oops failed to make a url") + return nil + } + + print(outUrl.absoluteString) + + exportSession.outputFileType = AVFileType.mp4 + exportSession.outputURL = outUrl + + let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (_: Timer) in + // Do something here + } + + self.exportSession = exportSession + await exportSession.export() + + // Update with final progress + progress(1) + // Stop calling the callback + timer.invalidate() + + return outUrl + } + + func progress() -> Float { + guard let exportSession = self.exportSession else { + return 0 + } + return exportSession.progress + } + + func cancel() { + // Cancel the download + } +} diff --git a/modules/expo-bluesky-swiss-army/src/HLSDownload/index.native.ts b/modules/expo-bluesky-swiss-army/src/HLSDownload/index.native.ts new file mode 100644 index 0000000000..85d3691d99 --- /dev/null +++ b/modules/expo-bluesky-swiss-army/src/HLSDownload/index.native.ts @@ -0,0 +1,11 @@ +import {requireNativeModule} from 'expo' + +const NativeModule = requireNativeModule('ExpoHLSDownload') + +export async function downloadAsync(sourceUrl: string): Promise { + return NativeModule.downloadAsync(sourceUrl) +} + +export async function cancelAsync(sourceUrl: string): Promise { + return NativeModule.cancelAsync(sourceUrl) +} diff --git a/modules/expo-bluesky-swiss-army/src/HLSDownload/index.ts b/modules/expo-bluesky-swiss-army/src/HLSDownload/index.ts new file mode 100644 index 0000000000..013516384b --- /dev/null +++ b/modules/expo-bluesky-swiss-army/src/HLSDownload/index.ts @@ -0,0 +1,9 @@ +import {NotImplementedError} from '../NotImplemented' + +export function downloadAsync(sourceUrl: string): Promise { + throw new NotImplementedError({sourceUrl}) +} + +export function cancelAsync(sourceUrl: string): Promise { + throw new NotImplementedError({sourceUrl}) +}