mirror of
https://github.com/cranci1/Sora.git
synced 2026-03-11 17:45:37 +00:00
Merge branch 'dev'
This commit is contained in:
commit
a77b57e3d2
7 changed files with 147 additions and 74 deletions
|
|
@ -25,6 +25,7 @@
|
|||
<string>infuse</string>
|
||||
<string>vlc</string>
|
||||
<string>nplayer-https</string>
|
||||
<string>senplayer</string>
|
||||
</array>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
|
|
|
|||
95
Sora/Utils/ContinueWatching/DownloadManager.swift
Normal file
95
Sora/Utils/ContinueWatching/DownloadManager.swift
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// DownloadManager.swift
|
||||
// Sulfur
|
||||
//
|
||||
// Created by Francesco on 29/04/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AVKit
|
||||
import AVFoundation
|
||||
|
||||
class DownloadManager: NSObject, ObservableObject {
|
||||
@Published var activeDownloads: [(URL, Double)] = []
|
||||
@Published var localPlaybackURL: URL?
|
||||
|
||||
private var assetDownloadURLSession: AVAssetDownloadURLSession!
|
||||
private var activeDownloadTasks: [URLSessionTask: URL] = [:]
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
initializeDownloadSession()
|
||||
loadLocalContent()
|
||||
}
|
||||
|
||||
private func initializeDownloadSession() {
|
||||
let configuration = URLSessionConfiguration.background(withIdentifier: "hls-downloader")
|
||||
assetDownloadURLSession = AVAssetDownloadURLSession(
|
||||
configuration: configuration,
|
||||
assetDownloadDelegate: self,
|
||||
delegateQueue: .main
|
||||
)
|
||||
}
|
||||
|
||||
func downloadAsset(from url: URL) {
|
||||
let asset = AVURLAsset(url: url)
|
||||
let task = assetDownloadURLSession.makeAssetDownloadTask(
|
||||
asset: asset,
|
||||
assetTitle: "Offline Video",
|
||||
assetArtworkData: nil,
|
||||
options: nil
|
||||
)
|
||||
|
||||
task?.resume()
|
||||
activeDownloadTasks[task!] = url
|
||||
}
|
||||
|
||||
private func loadLocalContent() {
|
||||
guard let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
|
||||
|
||||
do {
|
||||
let contents = try FileManager.default.contentsOfDirectory(
|
||||
at: documents,
|
||||
includingPropertiesForKeys: nil,
|
||||
options: .skipsHiddenFiles
|
||||
)
|
||||
|
||||
if let localURL = contents.first(where: { $0.pathExtension == "movpkg" }) {
|
||||
localPlaybackURL = localURL
|
||||
}
|
||||
} catch {
|
||||
print("Error loading local content: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DownloadManager: AVAssetDownloadDelegate {
|
||||
func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
|
||||
activeDownloadTasks.removeValue(forKey: assetDownloadTask)
|
||||
localPlaybackURL = location
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||
guard let error = error else { return }
|
||||
print("Download error: \(error.localizedDescription)")
|
||||
activeDownloadTasks.removeValue(forKey: task)
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession,
|
||||
assetDownloadTask: AVAssetDownloadTask,
|
||||
didLoad timeRange: CMTimeRange,
|
||||
totalTimeRangesLoaded loadedTimeRanges: [NSValue],
|
||||
timeRangeExpectedToLoad: CMTimeRange) {
|
||||
|
||||
guard let url = activeDownloadTasks[assetDownloadTask] else { return }
|
||||
let progress = loadedTimeRanges
|
||||
.map { $0.timeRangeValue.duration.seconds / timeRangeExpectedToLoad.duration.seconds }
|
||||
.reduce(0, +)
|
||||
|
||||
if let index = activeDownloads.firstIndex(where: { $0.0 == url }) {
|
||||
activeDownloads[index].1 = progress
|
||||
} else {
|
||||
activeDownloads.append((url, progress))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2038,15 +2038,14 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
}
|
||||
]
|
||||
|
||||
let resetDelayAction = UIAction(title: "Reset Timing") { [weak self] _ in
|
||||
let resetDelayAction = UIAction(title: "Reset Delay") { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
SubtitleSettingsManager.shared.update { settings in settings.subtitleDelay = 0.0 }
|
||||
self.subtitleDelay = 0.0
|
||||
self.loadSubtitleSettings()
|
||||
DropManager.shared.showDrop(title: "Subtitle Timing Reset", subtitle: "", duration: 0.5, icon: UIImage(systemName: "clock.arrow.circlepath"))
|
||||
}
|
||||
|
||||
let delayMenu = UIMenu(title: "Subtitle Timing", children: delayActions + [resetDelayAction])
|
||||
let delayMenu = UIMenu(title: "Subtitle Delay", children: delayActions + [resetDelayAction])
|
||||
|
||||
let subtitleOptionsMenu = UIMenu(title: "Subtitle Options", children: [
|
||||
subtitlesToggleAction, colorMenu, fontSizeMenu, shadowMenu, backgroundMenu, paddingMenu, delayMenu
|
||||
|
|
|
|||
|
|
@ -2,82 +2,46 @@
|
|||
// DownloadView.swift
|
||||
// Sulfur
|
||||
//
|
||||
// Created by Francesco on 12/03/25.
|
||||
// Created by Francesco on 29/04/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DownloadItem: Identifiable {
|
||||
let id = UUID()
|
||||
let title: String
|
||||
let episode: Int
|
||||
let type: String
|
||||
var progress: Double
|
||||
var status: String
|
||||
}
|
||||
|
||||
class DownloadViewModel: ObservableObject {
|
||||
@Published var downloads: [DownloadItem] = []
|
||||
|
||||
init() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateStatus(_:)), name: .DownloadManagerStatusUpdate, object: nil)
|
||||
}
|
||||
|
||||
@objc func updateStatus(_ notification: Notification) {
|
||||
guard let info = notification.userInfo,
|
||||
let title = info["title"] as? String,
|
||||
let episode = info["episode"] as? Int,
|
||||
let type = info["type"] as? String,
|
||||
let status = info["status"] as? String,
|
||||
let progress = info["progress"] as? Double else { return }
|
||||
|
||||
if let index = downloads.firstIndex(where: { $0.title == title && $0.episode == episode }) {
|
||||
downloads[index] = DownloadItem(title: title, episode: episode, type: type, progress: progress, status: status)
|
||||
} else {
|
||||
let newDownload = DownloadItem(title: title, episode: episode, type: type, progress: progress, status: status)
|
||||
downloads.append(newDownload)
|
||||
}
|
||||
}
|
||||
}
|
||||
import AVKit
|
||||
|
||||
struct DownloadView: View {
|
||||
@StateObject var viewModel = DownloadViewModel()
|
||||
@StateObject private var viewModel = DownloadManager()
|
||||
@State private var hlsURL = "https://test-streams.mux.dev/x36xhzz/url_6/193039199_mp4_h264_aac_hq_7.m3u8"
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List(viewModel.downloads) { download in
|
||||
HStack(spacing: 16) {
|
||||
Image(systemName: iconName(for: download))
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30)
|
||||
.foregroundColor(.accentColor)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("\(download.title) - Episode \(download.episode)")
|
||||
.font(.headline)
|
||||
|
||||
ProgressView(value: download.progress)
|
||||
.progressViewStyle(LinearProgressViewStyle(tint: .accentColor))
|
||||
.frame(height: 8)
|
||||
|
||||
Text(download.status)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
VStack {
|
||||
TextField("Enter HLS URL", text: $hlsURL)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
.padding()
|
||||
|
||||
Button("Download Stream") {
|
||||
viewModel.downloadAsset(from: URL(string: hlsURL)!)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.padding()
|
||||
|
||||
List(viewModel.activeDownloads, id: \.0) { (url, progress) in
|
||||
VStack(alignment: .leading) {
|
||||
Text(url.absoluteString)
|
||||
ProgressView(value: progress)
|
||||
.progressViewStyle(LinearProgressViewStyle())
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink("Play Offline Content") {
|
||||
if let url = viewModel.localPlaybackURL {
|
||||
VideoPlayer(player: AVPlayer(url: url))
|
||||
} else {
|
||||
Text("No offline content available")
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.navigationTitle("Downloads")
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
|
||||
func iconName(for download: DownloadItem) -> String {
|
||||
if download.type == "hls" {
|
||||
return download.status.lowercased().contains("converting") ? "arrow.triangle.2.circlepath.circle.fill" : "checkmark.circle.fill"
|
||||
} else {
|
||||
return download.progress >= 1.0 ? "checkmark.circle.fill" : "arrow.down.circle.fill"
|
||||
.navigationTitle("HLS Downloader")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -859,6 +859,8 @@ struct MediaInfoView: View {
|
|||
scheme = "outplayer://\(url)"
|
||||
case "nPlayer":
|
||||
scheme = "nplayer-\(url)"
|
||||
case "SenPlayer":
|
||||
scheme = "SenPlayer://x-callback-url/play?url=\(url)"
|
||||
case "Default":
|
||||
let videoPlayerViewController = VideoPlayerViewController(module: module)
|
||||
videoPlayerViewController.streamUrl = url
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ struct SettingsViewPlayer: View {
|
|||
@AppStorage("doubleTapSeekEnabled") private var doubleTapSeekEnabled: Bool = false
|
||||
@AppStorage("skipIntroOutroVisible") private var skipIntroOutroVisible: Bool = true
|
||||
|
||||
private let mediaPlayers = ["Default", "VLC", "OutPlayer", "Infuse", "nPlayer", "Sora"]
|
||||
private let mediaPlayers = ["Default", "VLC", "OutPlayer", "Infuse", "nPlayer", "SenPlayer", "Sora"]
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
130217CC2D81C55E0011EFF5 /* DownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130217CB2D81C55E0011EFF5 /* DownloadView.swift */; };
|
||||
130C6BFA2D53AB1F00DC1432 /* SettingsViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */; };
|
||||
13103E8B2D58E028000F0673 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13103E8A2D58E028000F0673 /* View.swift */; };
|
||||
13103E8E2D58E04A000F0673 /* SkeletonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13103E8D2D58E04A000F0673 /* SkeletonCell.swift */; };
|
||||
131270172DC13A010093AA9C /* DownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131270162DC13A010093AA9C /* DownloadManager.swift */; };
|
||||
131270192DC13A3C0093AA9C /* DownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131270182DC13A3C0093AA9C /* DownloadView.swift */; };
|
||||
131845F92D47C62D00CA7A54 /* SettingsViewGeneral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131845F82D47C62D00CA7A54 /* SettingsViewGeneral.swift */; };
|
||||
1327FBA72D758CEA00FC6689 /* Analytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1327FBA62D758CEA00FC6689 /* Analytics.swift */; };
|
||||
1327FBA92D758DEA00FC6689 /* UIDevice+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1327FBA82D758DEA00FC6689 /* UIDevice+Model.swift */; };
|
||||
|
|
@ -69,11 +70,12 @@
|
|||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
130217CB2D81C55E0011EFF5 /* DownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadView.swift; sourceTree = "<group>"; };
|
||||
130C6BF82D53A4C200DC1432 /* Sora.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Sora.entitlements; sourceTree = "<group>"; };
|
||||
130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewData.swift; sourceTree = "<group>"; };
|
||||
13103E8A2D58E028000F0673 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = "<group>"; };
|
||||
13103E8D2D58E04A000F0673 /* SkeletonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonCell.swift; sourceTree = "<group>"; };
|
||||
131270162DC13A010093AA9C /* DownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadManager.swift; sourceTree = "<group>"; };
|
||||
131270182DC13A3C0093AA9C /* DownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadView.swift; sourceTree = "<group>"; };
|
||||
131845F82D47C62D00CA7A54 /* SettingsViewGeneral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewGeneral.swift; sourceTree = "<group>"; };
|
||||
1327FBA62D758CEA00FC6689 /* Analytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Analytics.swift; sourceTree = "<group>"; };
|
||||
1327FBA82D758DEA00FC6689 /* UIDevice+Model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+Model.swift"; sourceTree = "<group>"; };
|
||||
|
|
@ -171,6 +173,13 @@
|
|||
path = SkeletonCells;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
131270152DC139CD0093AA9C /* DownloadManager */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
path = DownloadManager;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1327FBA52D758CEA00FC6689 /* Analytics */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -226,7 +235,7 @@
|
|||
1399FAD22D3AB34F00E97C31 /* SettingsView */,
|
||||
133F55B92D33B53E00E08EEA /* LibraryView */,
|
||||
133D7C7C2D2BE2630075467E /* SearchView.swift */,
|
||||
130217CB2D81C55E0011EFF5 /* DownloadView.swift */,
|
||||
131270182DC13A3C0093AA9C /* DownloadView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -257,6 +266,7 @@
|
|||
133D7C852D2BE2640075467E /* Utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
131270152DC139CD0093AA9C /* DownloadManager */,
|
||||
13C0E5E82D5F85DD00E7F619 /* ContinueWatching */,
|
||||
13103E8C2D58E037000F0673 /* SkeletonCells */,
|
||||
13DC0C442D302C6A00D0F966 /* MediaPlayer */,
|
||||
|
|
@ -364,6 +374,7 @@
|
|||
children = (
|
||||
13C0E5E92D5F85EA00E7F619 /* ContinueWatchingManager.swift */,
|
||||
13C0E5EB2D5F85F800E7F619 /* ContinueWatchingItem.swift */,
|
||||
131270162DC13A010093AA9C /* DownloadManager.swift */,
|
||||
);
|
||||
path = ContinueWatching;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -521,6 +532,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
135CCBE22D4D1138008B9C0E /* SettingsViewPlayer.swift in Sources */,
|
||||
131270172DC13A010093AA9C /* DownloadManager.swift in Sources */,
|
||||
1327FBA72D758CEA00FC6689 /* Analytics.swift in Sources */,
|
||||
13DC0C462D302C7500D0F966 /* VideoPlayer.swift in Sources */,
|
||||
1359ED142D76F49900C13034 /* finTopView.swift in Sources */,
|
||||
|
|
@ -558,8 +570,8 @@
|
|||
133D7C942D2BE2640075467E /* JSController.swift in Sources */,
|
||||
133D7C922D2BE2640075467E /* URLSession.swift in Sources */,
|
||||
133D7C912D2BE2640075467E /* SettingsViewModule.swift in Sources */,
|
||||
131270192DC13A3C0093AA9C /* DownloadView.swift in Sources */,
|
||||
13E62FC22DABC5830007E259 /* Trakt-Login.swift in Sources */,
|
||||
130217CC2D81C55E0011EFF5 /* DownloadView.swift in Sources */,
|
||||
133F55BB2D33B55100E08EEA /* LibraryManager.swift in Sources */,
|
||||
13E62FC42DABC58C0007E259 /* Trakt-Token.swift in Sources */,
|
||||
133D7C8E2D2BE2640075467E /* LibraryView.swift in Sources */,
|
||||
|
|
|
|||
Loading…
Reference in a new issue