mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-13 13:00:40 +00:00
progress saving
This commit is contained in:
parent
2a1c19528f
commit
317a7afd96
7 changed files with 175 additions and 70 deletions
|
|
@ -17,7 +17,6 @@
|
|||
132417A12D1319E800B4F2D2 /* ModuleStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417992D1319E800B4F2D2 /* ModuleStruct.swift */; };
|
||||
132417A22D1319E800B4F2D2 /* ModulesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1324179A2D1319E800B4F2D2 /* ModulesManager.swift */; };
|
||||
132417A32D1319E800B4F2D2 /* NormalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1324179C2D1319E800B4F2D2 /* NormalPlayer.swift */; };
|
||||
132417A42D1319E800B4F2D2 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1324179D2D1319E800B4F2D2 /* PlayerView.swift */; };
|
||||
132417B82D131A0600B4F2D2 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417A72D131A0600B4F2D2 /* SearchView.swift */; };
|
||||
132417B92D131A0600B4F2D2 /* SearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417A82D131A0600B4F2D2 /* SearchResultsView.swift */; };
|
||||
132417BA2D131A0600B4F2D2 /* SettingsAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417AB2D131A0600B4F2D2 /* SettingsAboutView.swift */; };
|
||||
|
|
@ -33,6 +32,9 @@
|
|||
132417C42D131A0600B4F2D2 /* AnimeInfoExtraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417B72D131A0600B4F2D2 /* AnimeInfoExtraction.swift */; };
|
||||
132417CF2D131B7400B4F2D2 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 132417CE2D131B7400B4F2D2 /* SwiftSoup */; };
|
||||
132417D22D131C5300B4F2D2 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 132417D12D131C5300B4F2D2 /* Kingfisher */; };
|
||||
132417D52D13240200B4F2D2 /* EpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417D42D13240200B4F2D2 /* EpisodeCell.swift */; };
|
||||
132417D72D13242400B4F2D2 /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417D62D13242400B4F2D2 /* CircularProgressBar.swift */; };
|
||||
132417D92D1328B900B4F2D2 /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417D82D1328B900B4F2D2 /* VideoPlayerView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
|
@ -47,7 +49,6 @@
|
|||
132417992D1319E800B4F2D2 /* ModuleStruct.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModuleStruct.swift; sourceTree = "<group>"; };
|
||||
1324179A2D1319E800B4F2D2 /* ModulesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModulesManager.swift; sourceTree = "<group>"; };
|
||||
1324179C2D1319E800B4F2D2 /* NormalPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NormalPlayer.swift; sourceTree = "<group>"; };
|
||||
1324179D2D1319E800B4F2D2 /* PlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = "<group>"; };
|
||||
132417A72D131A0600B4F2D2 /* SearchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
|
||||
132417A82D131A0600B4F2D2 /* SearchResultsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchResultsView.swift; sourceTree = "<group>"; };
|
||||
132417AB2D131A0600B4F2D2 /* SettingsAboutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAboutView.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -62,6 +63,9 @@
|
|||
132417B62D131A0600B4F2D2 /* AnimeInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimeInfoView.swift; sourceTree = "<group>"; };
|
||||
132417B72D131A0600B4F2D2 /* AnimeInfoExtraction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimeInfoExtraction.swift; sourceTree = "<group>"; };
|
||||
132417C52D131AA500B4F2D2 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
132417D42D13240200B4F2D2 /* EpisodeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeCell.swift; sourceTree = "<group>"; };
|
||||
132417D62D13242400B4F2D2 /* CircularProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = "<group>"; };
|
||||
132417D82D1328B900B4F2D2 /* VideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
|
@ -97,8 +101,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
132417C52D131AA500B4F2D2 /* Info.plist */,
|
||||
132417A52D131A0600B4F2D2 /* Views */,
|
||||
132417912D1319E800B4F2D2 /* Utils */,
|
||||
132417A52D131A0600B4F2D2 /* Views */,
|
||||
132417832D13198000B4F2D2 /* SoraApp.swift */,
|
||||
132417852D13198000B4F2D2 /* ContentView.swift */,
|
||||
132417872D13198200B4F2D2 /* Assets.xcassets */,
|
||||
|
|
@ -163,8 +167,8 @@
|
|||
1324179B2D1319E800B4F2D2 /* Player */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
132417D82D1328B900B4F2D2 /* VideoPlayerView.swift */,
|
||||
1324179C2D1319E800B4F2D2 /* NormalPlayer.swift */,
|
||||
1324179D2D1319E800B4F2D2 /* PlayerView.swift */,
|
||||
);
|
||||
path = Player;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -223,12 +227,22 @@
|
|||
132417B52D131A0600B4F2D2 /* AnimeViews */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
132417D32D1323F500B4F2D2 /* EpisodeCell */,
|
||||
132417B62D131A0600B4F2D2 /* AnimeInfoView.swift */,
|
||||
132417B72D131A0600B4F2D2 /* AnimeInfoExtraction.swift */,
|
||||
);
|
||||
path = AnimeViews;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
132417D32D1323F500B4F2D2 /* EpisodeCell */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
132417D42D13240200B4F2D2 /* EpisodeCell.swift */,
|
||||
132417D62D13242400B4F2D2 /* CircularProgressBar.swift */,
|
||||
);
|
||||
path = EpisodeCell;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
|
@ -310,7 +324,7 @@
|
|||
132417BB2D131A0600B4F2D2 /* SettingsIUView.swift in Sources */,
|
||||
132417C42D131A0600B4F2D2 /* AnimeInfoExtraction.swift in Sources */,
|
||||
132417B82D131A0600B4F2D2 /* SearchView.swift in Sources */,
|
||||
132417A42D1319E800B4F2D2 /* PlayerView.swift in Sources */,
|
||||
132417D92D1328B900B4F2D2 /* VideoPlayerView.swift in Sources */,
|
||||
1324179F2D1319E800B4F2D2 /* Notification.swift in Sources */,
|
||||
132417BD2D131A0600B4F2D2 /* SettingsModuleView.swift in Sources */,
|
||||
132417BC2D131A0600B4F2D2 /* SettingsLogsView.swift in Sources */,
|
||||
|
|
@ -318,6 +332,7 @@
|
|||
132417862D13198000B4F2D2 /* ContentView.swift in Sources */,
|
||||
132417C22D131A0600B4F2D2 /* LibraryView.swift in Sources */,
|
||||
132417A32D1319E800B4F2D2 /* NormalPlayer.swift in Sources */,
|
||||
132417D72D13242400B4F2D2 /* CircularProgressBar.swift in Sources */,
|
||||
132417C02D131A0600B4F2D2 /* HomeView.swift in Sources */,
|
||||
132417BF2D131A0600B4F2D2 /* SettingView.swift in Sources */,
|
||||
132417C32D131A0600B4F2D2 /* AnimeInfoView.swift in Sources */,
|
||||
|
|
@ -328,6 +343,7 @@
|
|||
132417C12D131A0600B4F2D2 /* LibraryManager.swift in Sources */,
|
||||
132417BA2D131A0600B4F2D2 /* SettingsAboutView.swift in Sources */,
|
||||
1324179E2D1319E800B4F2D2 /* MiruDataStruct.swift in Sources */,
|
||||
132417D52D13240200B4F2D2 /* EpisodeCell.swift in Sources */,
|
||||
132417A02D1319E800B4F2D2 /* HistoryManager.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,40 +0,0 @@
|
|||
//
|
||||
// PlayerView.swift
|
||||
// Sora
|
||||
//
|
||||
// Created by Francesco on 18/12/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import AVKit
|
||||
|
||||
class VideoPlayerViewController: UIViewController {
|
||||
var player: AVPlayer?
|
||||
var playerViewController: AVPlayerViewController?
|
||||
|
||||
var streamUrl: String?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
guard let streamUrl = streamUrl, let url = URL(string: streamUrl) else {
|
||||
return
|
||||
}
|
||||
|
||||
player = AVPlayer(url: url)
|
||||
playerViewController = AVPlayerViewController()
|
||||
playerViewController?.player = player
|
||||
|
||||
if let playerViewController = playerViewController {
|
||||
playerViewController.view.frame = self.view.frame
|
||||
self.view.addSubview(playerViewController.view)
|
||||
self.addChild(playerViewController)
|
||||
playerViewController.didMove(toParent: self)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
player?.play()
|
||||
}
|
||||
}
|
||||
78
Sora/Utils/Player/VideoPlayerView.swift
Normal file
78
Sora/Utils/Player/VideoPlayerView.swift
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// VideoPlayerView.swift
|
||||
// Sora
|
||||
//
|
||||
// Created by Francesco on 18/12/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import AVKit
|
||||
|
||||
class VideoPlayerViewController: UIViewController {
|
||||
var player: AVPlayer?
|
||||
var playerViewController: AVPlayerViewController?
|
||||
var timeObserverToken: Any?
|
||||
var streamUrl: String?
|
||||
var fullUrl: String = ""
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
guard let streamUrl = streamUrl, let url = URL(string: streamUrl) else {
|
||||
return
|
||||
}
|
||||
|
||||
player = AVPlayer(url: url)
|
||||
playerViewController = AVPlayerViewController()
|
||||
playerViewController?.player = player
|
||||
addPeriodicTimeObserver(fullURL: fullUrl)
|
||||
|
||||
if let playerViewController = playerViewController {
|
||||
playerViewController.view.frame = self.view.frame
|
||||
self.view.addSubview(playerViewController.view)
|
||||
self.addChild(playerViewController)
|
||||
playerViewController.didMove(toParent: self)
|
||||
}
|
||||
|
||||
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(fullUrl)")
|
||||
if lastPlayedTime > 0 {
|
||||
let seekTime = CMTime(seconds: lastPlayedTime, preferredTimescale: 1)
|
||||
self.player?.seek(to: seekTime) { _ in
|
||||
self.player?.play()
|
||||
}
|
||||
} else {
|
||||
self.player?.play()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
player?.play()
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
if let timeObserverToken = timeObserverToken {
|
||||
player?.removeTimeObserver(timeObserverToken)
|
||||
self.timeObserverToken = nil
|
||||
}
|
||||
}
|
||||
|
||||
func addPeriodicTimeObserver(fullURL: String) {
|
||||
guard let player = self.player else { return }
|
||||
|
||||
let interval = CMTime(seconds: 1.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { time in
|
||||
guard let currentItem = player.currentItem,
|
||||
currentItem.duration.seconds.isFinite else {
|
||||
return
|
||||
}
|
||||
|
||||
let currentTime = time.seconds
|
||||
let duration = currentItem.duration.seconds
|
||||
|
||||
UserDefaults.standard.set(currentTime, forKey: "lastPlayedTime_\(fullURL)")
|
||||
UserDefaults.standard.set(duration, forKey: "totalTime_\(fullURL)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,31 +7,9 @@
|
|||
|
||||
import AVKit
|
||||
import SwiftUI
|
||||
import SwiftSoup
|
||||
import Kingfisher
|
||||
import SafariServices
|
||||
|
||||
struct EpisodeCell: View {
|
||||
let episode: String
|
||||
let episodeID: Int
|
||||
let imageUrl: String
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
KFImage(URL(string: "https://cdn.discordapp.com/attachments/1218851049625092138/1318941731349332029/IMG_5081.png?ex=676427b5&is=6762d635&hm=923252d3448fda337f52c964f1428538095cbd018e36a6cfb21d01918e071c9d&"))
|
||||
.resizable()
|
||||
.aspectRatio(16/9, contentMode: .fill)
|
||||
.frame(width: 100, height: 56)
|
||||
.cornerRadius(8)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("Episode \(episodeID + 1)")
|
||||
.font(.headline)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AnimeInfoView: View {
|
||||
let module: ModuleStruct
|
||||
let anime: SearchResult
|
||||
|
|
@ -44,7 +22,7 @@ struct AnimeInfoView: View {
|
|||
@State var isLoading: Bool = true
|
||||
@State var showFullSynopsis: Bool = false
|
||||
|
||||
@AppStorage("externalPlayer") private var externalPlayer: String = "default"
|
||||
@AppStorage("externalPlayer") private var externalPlayer: String = "Default"
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
|
|
@ -152,9 +130,14 @@ struct AnimeInfoView: View {
|
|||
.fontWeight(.bold)
|
||||
|
||||
ForEach(episodes.indices, id: \.self) { index in
|
||||
EpisodeCell(episode: episodes[index], episodeID: index, imageUrl: anime.imageUrl)
|
||||
let episodeURL = "\(module.module[0].details.baseURL)\(episodes[index])"
|
||||
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(episodeURL)")
|
||||
let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(episodeURL)")
|
||||
let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0
|
||||
|
||||
EpisodeCell(episode: episodes[index], episodeID: index, imageUrl: anime.imageUrl, progress: progress)
|
||||
.onTapGesture {
|
||||
fetchEpisodeStream(urlString: "\(module.module[0].details.baseURL)\(episodes[index])")
|
||||
fetchEpisodeStream(urlString: episodeURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -194,6 +177,7 @@ struct AnimeInfoView: View {
|
|||
DispatchQueue.main.async {
|
||||
let videoPlayerViewController = VideoPlayerViewController()
|
||||
videoPlayerViewController.streamUrl = streamUrl
|
||||
videoPlayerViewController.fullUrl = fullURL
|
||||
videoPlayerViewController.modalPresentationStyle = .fullScreen
|
||||
Logger.shared.log("Opening video player with url: \(streamUrl)")
|
||||
|
||||
|
|
|
|||
31
Sora/Views/AnimeViews/EpisodeCell/CircularProgressBar.swift
Normal file
31
Sora/Views/AnimeViews/EpisodeCell/CircularProgressBar.swift
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// CircularProgressBar.swift
|
||||
// Sora
|
||||
//
|
||||
// Created by Francesco on 18/12/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CircularProgressBar: View {
|
||||
var progress: Double
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Circle()
|
||||
.stroke(lineWidth: 5.0)
|
||||
.opacity(0.3)
|
||||
.foregroundColor(Color.accentColor)
|
||||
|
||||
Circle()
|
||||
.trim(from: 0.0, to: CGFloat(min(progress, 1.0)))
|
||||
.stroke(style: StrokeStyle(lineWidth: 5.0, lineCap: .round, lineJoin: .round))
|
||||
.foregroundColor(Color.accentColor)
|
||||
.rotationEffect(Angle(degrees: 270.0))
|
||||
.animation(.linear, value: progress)
|
||||
|
||||
Text(String(format: "%.0f%%", min(progress, 1.0) * 100.0))
|
||||
.font(.system(size: 12))
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Sora/Views/AnimeViews/EpisodeCell/EpisodeCell.swift
Normal file
36
Sora/Views/AnimeViews/EpisodeCell/EpisodeCell.swift
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// EpisodeCell.swift
|
||||
// Sora
|
||||
//
|
||||
// Created by Francesco on 18/12/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct EpisodeCell: View {
|
||||
let episode: String
|
||||
let episodeID: Int
|
||||
let imageUrl: String
|
||||
let progress: Double
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
KFImage(URL(string: "https://cdn.discordapp.com/attachments/1218851049625092138/1318941731349332029/IMG_5081.png?ex=676427b5&is=6762d635&hm=923252d3448fda337f52c964f1428538095cbd018e36a6cfb21d01918e071c9d&"))
|
||||
.resizable()
|
||||
.aspectRatio(16/9, contentMode: .fill)
|
||||
.frame(width: 100, height: 56)
|
||||
.cornerRadius(8)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("Episode \(episodeID + 1)")
|
||||
.font(.headline)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
CircularProgressBar(progress: progress)
|
||||
.frame(width: 40, height: 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue