Compare commits
No commits in common. "8322d3a36cd29da543204c7844a49ef25ca575fa" and "1f6644b7036b2baba8d7d86cc2a0881595f8535a" have entirely different histories.
8322d3a36c
...
1f6644b703
|
@ -361,7 +361,6 @@
|
||||||
D6EBF01523C55C0900AE061B /* UIApplication+Scenes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */; };
|
D6EBF01523C55C0900AE061B /* UIApplication+Scenes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */; };
|
||||||
D6EBF01723C55E0D00AE061B /* UISceneSession+MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */; };
|
D6EBF01723C55E0D00AE061B /* UISceneSession+MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */; };
|
||||||
D6EE63FB2551F7F60065485C /* StatusCollapseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EE63FA2551F7F60065485C /* StatusCollapseButton.swift */; };
|
D6EE63FB2551F7F60065485C /* StatusCollapseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EE63FA2551F7F60065485C /* StatusCollapseButton.swift */; };
|
||||||
D6EEDE932C3CF21800E10E51 /* AudioSessionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EEDE922C3CF21800E10E51 /* AudioSessionCoordinator.swift */; };
|
|
||||||
D6F0B12B24A3071C001E48C3 /* MainSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F0B12A24A3071C001E48C3 /* MainSplitViewController.swift */; };
|
D6F0B12B24A3071C001E48C3 /* MainSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F0B12A24A3071C001E48C3 /* MainSplitViewController.swift */; };
|
||||||
D6F0B17524A3A1AA001E48C3 /* MainSidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F0B17424A3A1AA001E48C3 /* MainSidebarViewController.swift */; };
|
D6F0B17524A3A1AA001E48C3 /* MainSidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F0B17424A3A1AA001E48C3 /* MainSidebarViewController.swift */; };
|
||||||
D6F253CF2AC9F86300806D83 /* SearchTokenSuggestionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F253CE2AC9F86300806D83 /* SearchTokenSuggestionCollectionViewCell.swift */; };
|
D6F253CF2AC9F86300806D83 /* SearchTokenSuggestionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F253CE2AC9F86300806D83 /* SearchTokenSuggestionCollectionViewCell.swift */; };
|
||||||
|
@ -804,7 +803,6 @@
|
||||||
D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Scenes.swift"; sourceTree = "<group>"; };
|
D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Scenes.swift"; sourceTree = "<group>"; };
|
||||||
D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISceneSession+MastodonController.swift"; sourceTree = "<group>"; };
|
D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISceneSession+MastodonController.swift"; sourceTree = "<group>"; };
|
||||||
D6EE63FA2551F7F60065485C /* StatusCollapseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCollapseButton.swift; sourceTree = "<group>"; };
|
D6EE63FA2551F7F60065485C /* StatusCollapseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCollapseButton.swift; sourceTree = "<group>"; };
|
||||||
D6EEDE922C3CF21800E10E51 /* AudioSessionCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSessionCoordinator.swift; sourceTree = "<group>"; };
|
|
||||||
D6F0B12A24A3071C001E48C3 /* MainSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSplitViewController.swift; sourceTree = "<group>"; };
|
D6F0B12A24A3071C001E48C3 /* MainSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSplitViewController.swift; sourceTree = "<group>"; };
|
||||||
D6F0B17424A3A1AA001E48C3 /* MainSidebarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSidebarViewController.swift; sourceTree = "<group>"; };
|
D6F0B17424A3A1AA001E48C3 /* MainSidebarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSidebarViewController.swift; sourceTree = "<group>"; };
|
||||||
D6F253CE2AC9F86300806D83 /* SearchTokenSuggestionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTokenSuggestionCollectionViewCell.swift; sourceTree = "<group>"; };
|
D6F253CE2AC9F86300806D83 /* SearchTokenSuggestionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTokenSuggestionCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1609,7 +1607,6 @@
|
||||||
D691296D2BA75ACF005C58ED /* PrivacyInfo.xcprivacy */,
|
D691296D2BA75ACF005C58ED /* PrivacyInfo.xcprivacy */,
|
||||||
D671A6BE299DA96100A81FEA /* Tusker-Bridging-Header.h */,
|
D671A6BE299DA96100A81FEA /* Tusker-Bridging-Header.h */,
|
||||||
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
||||||
D6EEDE922C3CF21800E10E51 /* AudioSessionCoordinator.swift */,
|
|
||||||
D6D79F582A13293200AB2315 /* BackgroundManager.swift */,
|
D6D79F582A13293200AB2315 /* BackgroundManager.swift */,
|
||||||
D69261262BB3BA610023152C /* Box.swift */,
|
D69261262BB3BA610023152C /* Box.swift */,
|
||||||
D61F75B6293C119700C0B37F /* Filterer.swift */,
|
D61F75B6293C119700C0B37F /* Filterer.swift */,
|
||||||
|
@ -2150,7 +2147,6 @@
|
||||||
D61F759B29384F9C00C0B37F /* FilterMO.swift in Sources */,
|
D61F759B29384F9C00C0B37F /* FilterMO.swift in Sources */,
|
||||||
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */,
|
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */,
|
||||||
D6934F342BA8D65A002B1C8D /* ImageGalleryDataSource.swift in Sources */,
|
D6934F342BA8D65A002B1C8D /* ImageGalleryDataSource.swift in Sources */,
|
||||||
D6EEDE932C3CF21800E10E51 /* AudioSessionCoordinator.swift in Sources */,
|
|
||||||
D6C4532D2BCB86AC00E26A0E /* AppearancePrefsView.swift in Sources */,
|
D6C4532D2BCB86AC00E26A0E /* AppearancePrefsView.swift in Sources */,
|
||||||
D64AAE9126C80DC600FC57FB /* ToastView.swift in Sources */,
|
D64AAE9126C80DC600FC57FB /* ToastView.swift in Sources */,
|
||||||
D6BC74862AFC4772000DD603 /* SuggestedProfileCardView.swift in Sources */,
|
D6BC74862AFC4772000DD603 /* SuggestedProfileCardView.swift in Sources */,
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
//
|
|
||||||
// AudioSessionCoordinator.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 7/8/24.
|
|
||||||
// Copyright © 2024 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import AVFoundation
|
|
||||||
|
|
||||||
final class AudioSessionCoordinator {
|
|
||||||
static let shared = AudioSessionCoordinator()
|
|
||||||
|
|
||||||
private init() {}
|
|
||||||
|
|
||||||
private let queue = DispatchQueue(label: "AudioSessionCoordinator", qos: .userInitiated)
|
|
||||||
|
|
||||||
private var videoCount = 0
|
|
||||||
private var gifvCount = 0
|
|
||||||
|
|
||||||
func beginPlayback(mode: Mode, completionHandler: (() -> Void)? = nil) -> Token {
|
|
||||||
let token = Token(mode: mode)
|
|
||||||
queue.async {
|
|
||||||
switch mode {
|
|
||||||
case .video:
|
|
||||||
self.videoCount += 1
|
|
||||||
case .gifv:
|
|
||||||
self.gifvCount += 1
|
|
||||||
}
|
|
||||||
self.update(completionHandler: completionHandler)
|
|
||||||
}
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
|
|
||||||
func endPlayback(token: Token, completionHandler: (() -> Void)? = nil) {
|
|
||||||
// the enqueued block can't retain token, since it may be being dealloc'd right now
|
|
||||||
let mode = token.mode
|
|
||||||
queue.async {
|
|
||||||
switch mode {
|
|
||||||
case .video:
|
|
||||||
self.videoCount -= 1
|
|
||||||
case .gifv:
|
|
||||||
self.gifvCount -= 1
|
|
||||||
}
|
|
||||||
self.update(completionHandler: completionHandler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func update(completionHandler: (() -> Void)?) {
|
|
||||||
let currentCategory = AVAudioSession.sharedInstance().category
|
|
||||||
if videoCount > 0 {
|
|
||||||
try? AVAudioSession.sharedInstance().setCategory(.playback)
|
|
||||||
try? AVAudioSession.sharedInstance().setActive(true)
|
|
||||||
} else if gifvCount > 0 {
|
|
||||||
// if we're transitioning from video to gifv, fully deactivate first
|
|
||||||
// in order to let other (music) apps resume, then activate with the
|
|
||||||
// ambient category to "mix" with others
|
|
||||||
if currentCategory == .playback {
|
|
||||||
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
|
|
||||||
}
|
|
||||||
// only gifv modes requested, allow mixing with others
|
|
||||||
try? AVAudioSession.sharedInstance().setCategory(.ambient)
|
|
||||||
try? AVAudioSession.sharedInstance().setActive(true)
|
|
||||||
} else {
|
|
||||||
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
|
|
||||||
}
|
|
||||||
completionHandler?()
|
|
||||||
}
|
|
||||||
|
|
||||||
final class Token {
|
|
||||||
let mode: Mode
|
|
||||||
|
|
||||||
init(mode: Mode) {
|
|
||||||
self.mode = mode
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
AudioSessionCoordinator.shared.endPlayback(token: self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Mode {
|
|
||||||
case video
|
|
||||||
case gifv
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -29,7 +29,6 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
|
||||||
private var rateObservation: NSKeyValueObservation?
|
private var rateObservation: NSKeyValueObservation?
|
||||||
private var isFirstAppearance = true
|
private var isFirstAppearance = true
|
||||||
private var hideControlsWorkItem: DispatchWorkItem?
|
private var hideControlsWorkItem: DispatchWorkItem?
|
||||||
private var audioSessionToken: AudioSessionCoordinator.Token?
|
|
||||||
|
|
||||||
init(url: URL, caption: String?) {
|
init(url: URL, caption: String?) {
|
||||||
self.url = url
|
self.url = url
|
||||||
|
@ -173,7 +172,10 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
|
||||||
let wasFirstAppearance = isFirstAppearance
|
let wasFirstAppearance = isFirstAppearance
|
||||||
isFirstAppearance = false
|
isFirstAppearance = false
|
||||||
|
|
||||||
audioSessionToken = AudioSessionCoordinator.shared.beginPlayback(mode: .video) {
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
try? AVAudioSession.sharedInstance().setCategory(.playback)
|
||||||
|
try? AVAudioSession.sharedInstance().setActive(true)
|
||||||
|
|
||||||
if wasFirstAppearance {
|
if wasFirstAppearance {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.player.play()
|
self.player.play()
|
||||||
|
@ -185,8 +187,8 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
|
||||||
func galleryContentWillDisappear() {
|
func galleryContentWillDisappear() {
|
||||||
player.pause()
|
player.pause()
|
||||||
|
|
||||||
if let audioSessionToken {
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
AudioSessionCoordinator.shared.endPlayback(token: audioSessionToken)
|
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ class GifvController {
|
||||||
let player: AVPlayer
|
let player: AVPlayer
|
||||||
|
|
||||||
private var isGrayscale = false
|
private var isGrayscale = false
|
||||||
private var audioSessionToken: AudioSessionCoordinator.Token?
|
|
||||||
|
|
||||||
let presentationSizeSubject = PassthroughSubject<CGSize, Never>()
|
let presentationSizeSubject = PassthroughSubject<CGSize, Never>()
|
||||||
private var presentationSizeObservation: NSKeyValueObservation?
|
private var presentationSizeObservation: NSKeyValueObservation?
|
||||||
|
@ -30,9 +29,7 @@ class GifvController {
|
||||||
self.isGrayscale = Preferences.shared.grayscaleImages
|
self.isGrayscale = Preferences.shared.grayscaleImages
|
||||||
|
|
||||||
player.isMuted = true
|
player.isMuted = true
|
||||||
#if os(visionOS)
|
#if !os(visionOS)
|
||||||
player.preventsAutomaticBackgroundingDuringVideoPlayback = false
|
|
||||||
#else
|
|
||||||
player.preventsDisplaySleepDuringVideoPlayback = false
|
player.preventsDisplaySleepDuringVideoPlayback = false
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -44,19 +41,12 @@ class GifvController {
|
||||||
|
|
||||||
func play() {
|
func play() {
|
||||||
if player.rate == 0 {
|
if player.rate == 0 {
|
||||||
audioSessionToken = AudioSessionCoordinator.shared.beginPlayback(mode: .gifv) {
|
player.play()
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.player.play()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func pause() {
|
func pause() {
|
||||||
player.pause()
|
player.pause()
|
||||||
if let audioSessionToken {
|
|
||||||
AudioSessionCoordinator.shared.endPlayback(token: audioSessionToken)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updatePresentationSizeObservation() {
|
private func updatePresentationSizeObservation() {
|
||||||
|
|
|
@ -22,7 +22,6 @@ class GifvPlayerView: UIView {
|
||||||
|
|
||||||
let controller: GifvController
|
let controller: GifvController
|
||||||
private var presentationSizeCancellable: AnyCancellable?
|
private var presentationSizeCancellable: AnyCancellable?
|
||||||
private var wasPlayingWhenSceneBackgrounded = false
|
|
||||||
|
|
||||||
override var intrinsicContentSize: CGSize {
|
override var intrinsicContentSize: CGSize {
|
||||||
controller.item.presentationSize
|
controller.item.presentationSize
|
||||||
|
@ -46,30 +45,4 @@ class GifvPlayerView: UIView {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func willMove(toWindow newWindow: UIWindow?) {
|
|
||||||
super.willMove(toWindow: newWindow)
|
|
||||||
|
|
||||||
if let oldWindow = window,
|
|
||||||
let oldScene = oldWindow.windowScene {
|
|
||||||
NotificationCenter.default.removeObserver(self, name: UIScene.didEnterBackgroundNotification, object: oldScene)
|
|
||||||
NotificationCenter.default.removeObserver(self, name: UIScene.willEnterForegroundNotification, object: oldScene)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let newWindow,
|
|
||||||
let newScene = newWindow.windowScene {
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(sceneDidEnterBackground), name: UIScene.didEnterBackgroundNotification, object: newScene)
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(sceneWillEnterForeground), name: UIScene.willEnterForegroundNotification, object: newScene)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func sceneDidEnterBackground() {
|
|
||||||
wasPlayingWhenSceneBackgrounded = controller.player.rate > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func sceneWillEnterForeground() {
|
|
||||||
if wasPlayingWhenSceneBackgrounded {
|
|
||||||
controller.play()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
final class WeakHolder<T: AnyObject> {
|
class WeakHolder<T: AnyObject> {
|
||||||
weak var object: T?
|
weak var object: T?
|
||||||
|
|
||||||
init(_ object: T?) {
|
init(_ object: T?) {
|
||||||
|
@ -16,7 +16,7 @@ final class WeakHolder<T: AnyObject> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WeakArray<Element: AnyObject>: MutableCollection, RangeReplaceableCollection, RandomAccessCollection, BidirectionalCollection {
|
struct WeakArray<Element: AnyObject>: MutableCollection, RangeReplaceableCollection {
|
||||||
private var array: [WeakHolder<Element>]
|
private var array: [WeakHolder<Element>]
|
||||||
|
|
||||||
var startIndex: Int { array.startIndex }
|
var startIndex: Int { array.startIndex }
|
||||||
|
@ -47,10 +47,6 @@ struct WeakArray<Element: AnyObject>: MutableCollection, RangeReplaceableCollect
|
||||||
return array.index(after: i)
|
return array.index(after: i)
|
||||||
}
|
}
|
||||||
|
|
||||||
func index(before i: Int) -> Int {
|
|
||||||
return array.index(before: i)
|
|
||||||
}
|
|
||||||
|
|
||||||
mutating func replaceSubrange<C>(_ subrange: Range<Int>, with newElements: C) where C : Collection, Self.Element == C.Element {
|
mutating func replaceSubrange<C>(_ subrange: Range<Int>, with newElements: C) where C : Collection, Self.Element == C.Element {
|
||||||
array.replaceSubrange(subrange, with: newElements.map { WeakHolder($0) })
|
array.replaceSubrange(subrange, with: newElements.map { WeakHolder($0) })
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue