progress saving

This commit is contained in:
Francesco 2024-12-18 17:08:32 +01:00
parent 2a1c19528f
commit 317a7afd96
7 changed files with 175 additions and 70 deletions

View file

@ -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;

View file

@ -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()
}
}

View 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)")
}
}
}

View file

@ -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)")

View 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))
}
}
}

View 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)
}
}
}