mirror of
https://github.com/cranci1/Sora.git
synced 2026-03-28 21:38:44 +00:00
test, idk if it compiles tbh
This commit is contained in:
parent
a07b43a750
commit
e6b766ab94
5 changed files with 45 additions and 200 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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]()
|
||||
|
|
|
|||
|
|
@ -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 */,
|
||||
|
|
|
|||
Loading…
Reference in a new issue