From 76e2b3ed3bce9853cf0601a9ba2797fed4c6be13 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Tue, 27 May 2025 15:23:19 +0200 Subject: [PATCH] idk is this SharePlay? --- Sora/Utils/MediaPlayer/VideoPlayer.swift | 82 ++++++++++++++++++- Sora/Utils/MediaPlayer/WatchTogether.swift | 21 +++++ .../SettingsSubViews/SettingsViewAbout.swift | 4 +- Sulfur.xcodeproj/project.pbxproj | 4 + 4 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 Sora/Utils/MediaPlayer/WatchTogether.swift diff --git a/Sora/Utils/MediaPlayer/VideoPlayer.swift b/Sora/Utils/MediaPlayer/VideoPlayer.swift index 0278201..bff6c99 100644 --- a/Sora/Utils/MediaPlayer/VideoPlayer.swift +++ b/Sora/Utils/MediaPlayer/VideoPlayer.swift @@ -8,6 +8,7 @@ import UIKit import AVKit import MediaPlayer +import GroupActivities class VideoPlayerViewController: UIViewController { let module: ScrapingModule @@ -27,6 +28,8 @@ class VideoPlayerViewController: UIViewController { var mediaTitle: String = "" private var currentArtwork: MPMediaItemArtwork? + private var groupSession: GroupSession? + private var messenger: GroupSessionMessenger? init(module: ScrapingModule) { self.module = module @@ -40,6 +43,7 @@ class VideoPlayerViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + setupSharePlay() setupNowPlaying() setupRemoteTransportControls() @@ -95,6 +99,80 @@ 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) { + 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() { if let imageUrl = URL(string: episodeImageUrl) { URLSession.custom.dataTask(with: imageUrl) { [weak self] data, _, _ in @@ -136,7 +214,7 @@ class VideoPlayerViewController: UIViewController { MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo } - + let interval = CMTime(seconds: 1.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC)) player?.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] _ in self?.updateNowPlayingInfo() @@ -170,6 +248,7 @@ class VideoPlayerViewController: UIViewController { override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) + groupSession?.leave() if let playbackSpeed = player?.rate { UserDefaults.standard.set(playbackSpeed, forKey: "lastPlaybackSpeed") } @@ -269,6 +348,7 @@ class VideoPlayerViewController: UIViewController { } deinit { + groupSession?.leave() player?.pause() if let timeObserverToken = timeObserverToken { player?.removeTimeObserver(timeObserverToken) diff --git a/Sora/Utils/MediaPlayer/WatchTogether.swift b/Sora/Utils/MediaPlayer/WatchTogether.swift new file mode 100644 index 0000000..23d5b6d --- /dev/null +++ b/Sora/Utils/MediaPlayer/WatchTogether.swift @@ -0,0 +1,21 @@ +// +// 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 + } +} diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewAbout.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewAbout.swift index 90c3acb..8919e6b 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewAbout.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewAbout.swift @@ -28,8 +28,8 @@ struct SettingsViewAbout: View { Text("Sora") .font(.title) .bold() - Text("Version \(version)") - .font(.subheadline) + Text("AKA Sulfur") + .font(.caption) .foregroundColor(.secondary) } } diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index 4a3fff4..2811b4d 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 130A810B2DE5F32400614732 /* WatchTogether.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130A810A2DE5F32400614732 /* WatchTogether.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 */; }; @@ -88,6 +89,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 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 = ""; }; 130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewData.swift; sourceTree = ""; }; 13103E8A2D58E028000F0673 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; @@ -447,6 +449,7 @@ isa = PBXGroup; children = ( 13EA2BD02D32D97400C1EBD7 /* CustomPlayer */, + 130A810A2DE5F32400614732 /* WatchTogether.swift */, 13DC0C452D302C7500D0F966 /* VideoPlayer.swift */, 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */, ); @@ -610,6 +613,7 @@ 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 */,