test, idk if it compiles tbh

This commit is contained in:
Francesco 2025-06-01 18:32:42 +02:00
parent a07b43a750
commit e6b766ab94
5 changed files with 45 additions and 200 deletions

View file

@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.group-session</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.assets.movies.read-write</key>

View file

@ -7,8 +7,6 @@
import UIKit
import AVKit
import MediaPlayer
import GroupActivities
class VideoPlayerViewController: UIViewController {
let module: ScrapingModule
@ -21,16 +19,12 @@ class VideoPlayerViewController: UIViewController {
var subtitles: String = ""
var aniListID: Int = 0
var headers: [String:String]? = nil
var totalEpisodes: Int = 0
var episodeNumber: Int = 0
var episodeImageUrl: String = ""
var mediaTitle: String = ""
private var currentArtwork: MPMediaItemArtwork?
private var groupSession: GroupSession<WatchTogetherActivity>?
private var messenger: GroupSessionMessenger?
init(module: ScrapingModule) {
self.module = module
super.init(nibName: nil, bundle: nil)
@ -43,10 +37,6 @@ class VideoPlayerViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupSharePlay()
setupNowPlaying()
setupRemoteTransportControls()
guard let streamUrl = streamUrl, let url = URL(string: streamUrl) else {
return
}
@ -60,21 +50,12 @@ class VideoPlayerViewController: UIViewController {
request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Referer")
request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Origin")
}
request.addValue("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36", forHTTPHeaderField: "User-Agent")
let asset = AVURLAsset(url: url, options: ["AVURLAssetHTTPHeaderFieldsKey": request.allHTTPHeaderFields ?? [:]])
let playerItem = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: playerItem)
player?.allowsExternalPlayback = false
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print("Failed to set audio session category: \(error)")
}
playerViewController = NormalPlayer()
playerViewController?.player = player
@ -99,153 +80,6 @@ class VideoPlayerViewController: UIViewController {
}
}
private func setupSharePlay() {
guard let streamUrl = streamUrl else { return }
let activity = WatchTogetherActivity(
streamUrl: streamUrl,
mediaTitle: mediaTitle,
episodeNumber: episodeNumber
)
Task {
switch await activity.prepareForActivation() {
case .activationPreferred:
do {
_ = try await activity.activate()
Logger.shared.log("SharePlay session activated successfully", type: "General")
} catch {
Logger.shared.log("Failed to activate SharePlay: \(error)", type: "Error")
}
case .activationDisabled:
Logger.shared.log("SharePlay activation disabled", type: "General")
case .cancelled:
Logger.shared.log("SharePlay activation cancelled", type: "General")
@unknown default:
Logger.shared.log("SharePlay activation unknown state", type: "Info")
}
}
Task {
for await session in WatchTogetherActivity.sessions() {
configureGroupSession(session)
}
}
}
private func configureGroupSession(_ session: GroupSession<WatchTogetherActivity>) {
groupSession = session
messenger = GroupSessionMessenger(session: session)
session.join()
Task {
guard let messenger = messenger else { return }
for await (timeString, _) in messenger.messages(of: String.self) {
if let seconds = Double(timeString) {
let time = CMTime(seconds: seconds, preferredTimescale: 600)
await handlePlaybackMessage(time)
}
}
}
player?.addPeriodicTimeObserver(
forInterval: CMTime(seconds: 1, preferredTimescale: 600),
queue: .main
) { [weak self] time in
guard let self = self else { return }
Task {
let timeString = String(time.seconds)
try? await self.messenger?.send(timeString)
}
}
}
private func handlePlaybackMessage(_ time: CMTime) async {
await MainActor.run {
guard let player = player else { return }
let currentTime = player.currentTime()
let difference = abs(CMTimeSubtract(time, currentTime).seconds)
if difference > 1.0 {
player.seek(to: time)
}
}
}
private func setupNowPlaying() {
var nowPlayingInfo: [String: Any] = [
MPMediaItemPropertyTitle: mediaTitle,
MPMediaItemPropertyArtist: "Episode \(episodeNumber)",
MPNowPlayingInfoPropertyPlaybackRate: player?.rate ?? 1.0
]
if let player = player, let currentItem = player.currentItem {
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentItem.currentTime().seconds
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = currentItem.duration.seconds
}
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
if let imageUrl = URL(string: episodeImageUrl) {
URLSession.custom.dataTask(with: imageUrl) { [weak self] data, _, _ in
guard let self = self,
let data = data,
let image = UIImage(data: data) else { return }
DispatchQueue.main.async {
self.currentArtwork = MPMediaItemArtwork(boundsSize: image.size) { _ in
return image
}
self.updateNowPlayingInfo()
}
}.resume()
}
let interval = CMTime(seconds: 0.5, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
player?.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] _ in
self?.updateNowPlayingInfo()
}
}
func updateNowPlayingInfo() {
guard let player = player,
let currentItem = player.currentItem else { return }
var nowPlayingInfo: [String: Any] = [
MPMediaItemPropertyTitle: mediaTitle,
MPMediaItemPropertyArtist: "Episode \(episodeNumber)",
MPNowPlayingInfoPropertyElapsedPlaybackTime: currentItem.currentTime().seconds,
MPMediaItemPropertyPlaybackDuration: currentItem.duration.seconds,
MPNowPlayingInfoPropertyPlaybackRate: player.rate
]
if let artwork = currentArtwork {
nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
}
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
private func setupRemoteTransportControls() {
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.addTarget { [weak self] _ in
self?.player?.play()
self?.updateNowPlayingInfo()
return .success
}
commandCenter.pauseCommand.addTarget { [weak self] _ in
self?.player?.pause()
self?.updateNowPlayingInfo()
return .success
}
commandCenter.seekForwardCommand.isEnabled = false
commandCenter.seekBackwardCommand.isEnabled = false
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
player?.play()
@ -254,7 +88,6 @@ class VideoPlayerViewController: UIViewController {
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
groupSession?.leave()
if let playbackSpeed = player?.rate {
UserDefaults.standard.set(playbackSpeed, forKey: "lastPlaybackSpeed")
}
@ -342,11 +175,9 @@ class VideoPlayerViewController: UIViewController {
}
deinit {
groupSession?.leave()
player?.pause()
if let timeObserverToken = timeObserverToken {
player?.removeTimeObserver(timeObserverToken)
}
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
}
}

View file

@ -1,21 +0,0 @@
//
// VideoPlayer.swift
// Sora
//
// Created by Francesco on 27/005/25.
import GroupActivities
struct WatchTogetherActivity: GroupActivity {
let streamUrl: String
let mediaTitle: String
let episodeNumber: Int
var metadata: GroupActivityMetadata {
var metadata = GroupActivityMetadata()
metadata.type = .watchTogether
metadata.title = mediaTitle
metadata.subtitle = "Episode \(episodeNumber)"
return metadata
}
}

View file

@ -20,7 +20,7 @@ struct MediaItem: Identifiable {
struct MediaInfoView: View {
let title: String
let imageUrl: String
@State var imageUrl: String
let href: String
let module: ScrapingModule
@ -77,6 +77,7 @@ struct MediaInfoView: View {
@State private var showRangeInput: Bool = false
@State private var isBulkDownloading: Bool = false
@State private var bulkDownloadProgress: String = ""
@State private var tmdbType: TMDBFetcher.MediaType? = nil
private var isGroupedBySeasons: Bool {
return groupedEpisodes().count > 1
@ -494,10 +495,19 @@ struct MediaInfoView: View {
Label("Open in AniList", systemImage: "link")
}
}
if UserDefaults.standard.string(forKey: "metadataProviders") ?? "TMDB" == "AniList" {
Button(action: {
isMatchingPresented = true
}) {
Label("Match with AniList", systemImage: "magnifyingglass")
}
}
Button(action: {
isMatchingPresented = true
fetchTMDBPosterImageAndSet()
}) {
Label("Match with AniList", systemImage: "magnifyingglass")
Label("Use TMDB Poster Image", systemImage: "photo")
}
Divider()
@ -781,11 +791,13 @@ struct MediaInfoView: View {
private func fetchMetadataIDIfNeeded() {
let provider = UserDefaults.standard.string(forKey: "metadataProviders") ?? "TMDB"
let cleaned = cleanTitle(title)
if provider == "TMDB" {
tmdbID = nil
tmdbFetcher.fetchBestMatchID(for: cleaned) { id, type in
DispatchQueue.main.async {
self.tmdbID = id
self.tmdbType = type
Logger.shared.log("Fetched TMDB ID: \(id ?? -1) (\(type?.rawValue ?? "unknown")) for title: \(cleaned)", type: "Debug")
}
}
@ -805,6 +817,35 @@ struct MediaInfoView: View {
}
}
private func fetchTMDBPosterImageAndSet() {
guard let tmdbID = tmdbID, let tmdbType = tmdbType else { return }
let apiType = tmdbType.rawValue
let urlString = "https://api.themoviedb.org/3/\(apiType)/\(tmdbID)?api_key=738b4edd0a156cc126dc4a4b8aea4aca"
guard let url = URL(string: urlString) else { return }
let tmdbImageWidth = UserDefaults.standard.string(forKey: "tmdbImageWidth") ?? "780"
URLSession.custom.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else { return }
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let posterPath = json["poster_path"] as? String {
let imageUrl: String
if tmdbImageWidth == "original" {
imageUrl = "https://image.tmdb.org/t/p/original\(posterPath)"
} else {
imageUrl = "https://image.tmdb.org/t/p/w\(tmdbImageWidth)\(posterPath)"
}
DispatchQueue.main.async {
self.imageUrl = imageUrl
}
}
} catch {
Logger.shared.log("Failed to parse TMDB poster: \(error.localizedDescription)", type: "Error")
}
}.resume()
}
private func markAllPreviousEpisodesAsWatched(ep: EpisodeLink, inSeason: Bool) {
let userDefaults = UserDefaults.standard
var updates = [String: Double]()

View file

@ -22,7 +22,6 @@
04F08EDF2DE10C1D006B29D9 /* TabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F08EDE2DE10C1A006B29D9 /* TabBar.swift */; };
04F08EE22DE10C40006B29D9 /* TabItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F08EE12DE10C27006B29D9 /* TabItem.swift */; };
04F08EE42DE10D6F006B29D9 /* AllBookmarks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F08EE32DE10D6B006B29D9 /* AllBookmarks.swift */; };
130A810B2DE5F32400614732 /* WatchTogether.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130A810A2DE5F32400614732 /* WatchTogether.swift */; };
130C6BFA2D53AB1F00DC1432 /* SettingsViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */; };
13103E8B2D58E028000F0673 /* ScrollViewBottomPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13103E8A2D58E028000F0673 /* ScrollViewBottomPadding.swift */; };
13103E8E2D58E04A000F0673 /* SkeletonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13103E8D2D58E04A000F0673 /* SkeletonCell.swift */; };
@ -121,7 +120,6 @@
04F08EDE2DE10C1A006B29D9 /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = "<group>"; };
04F08EE12DE10C27006B29D9 /* TabItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabItem.swift; sourceTree = "<group>"; };
04F08EE32DE10D6B006B29D9 /* AllBookmarks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllBookmarks.swift; sourceTree = "<group>"; };
130A810A2DE5F32400614732 /* WatchTogether.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WatchTogether.swift; path = Sora/Utils/MediaPlayer/WatchTogether.swift; sourceTree = SOURCE_ROOT; };
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 /* ScrollViewBottomPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewBottomPadding.swift; sourceTree = "<group>"; };
@ -556,7 +554,6 @@
isa = PBXGroup;
children = (
13EA2BD02D32D97400C1EBD7 /* CustomPlayer */,
130A810A2DE5F32400614732 /* WatchTogether.swift */,
13DC0C452D302C7500D0F966 /* VideoPlayer.swift */,
13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */,
);
@ -720,7 +717,6 @@
buildActionMask = 2147483647;
files = (
135CCBE22D4D1138008B9C0E /* SettingsViewPlayer.swift in Sources */,
130A810B2DE5F32400614732 /* WatchTogether.swift in Sources */,
131270172DC13A010093AA9C /* DownloadManager.swift in Sources */,
1327FBA72D758CEA00FC6689 /* Analytics.swift in Sources */,
13DC0C462D302C7500D0F966 /* VideoPlayer.swift in Sources */,