diff --git a/submodules/AccountContext/Sources/UniversalVideoNode.swift b/submodules/AccountContext/Sources/UniversalVideoNode.swift index 42a0601204..bea26c38aa 100644 --- a/submodules/AccountContext/Sources/UniversalVideoNode.swift +++ b/submodules/AccountContext/Sources/UniversalVideoNode.swift @@ -23,7 +23,7 @@ public protocol UniversalVideoContentNode: AnyObject { func setSoundEnabled(_ value: Bool) func seek(_ timestamp: Double) func playOnceWithSound(playAndRecord: Bool, seek: MediaPlayerSeek, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) - func continueWithOverridingAmbientMode() + func continueWithOverridingAmbientMode(isAmbient: Bool) func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) func setContinuePlayingWithoutSoundOnLostAudioSession(_ value: Bool) @@ -284,10 +284,10 @@ public final class UniversalVideoNode: ASDisplayNode { }) } - public func continueWithOverridingAmbientMode() { + public func continueWithOverridingAmbientMode(isAmbient: Bool) { self.manager.withUniversalVideoContent(id: self.content.id, { contentNode in if let contentNode = contentNode { - contentNode.continueWithOverridingAmbientMode() + contentNode.continueWithOverridingAmbientMode(isAmbient: isAmbient) } }) } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 652f25fffd..b1d91b9467 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1646,7 +1646,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController for filter in filters { if filter.id == filterId, case let .filter(_, title, _, data) = filter { - if let filterPeersAreMuted { + if let filterPeersAreMuted, filterPeersAreMuted.peerIds.count <= 200 { items.append(.action(ContextMenuActionItem(text: filterPeersAreMuted.areMuted ? strongSelf.presentationData.strings.ChatList_ContextUnmuteAll : strongSelf.presentationData.strings.ChatList_ContextMuteAll, textColor: .primary, badge: nil, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: filterPeersAreMuted.areMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { c, f in diff --git a/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift b/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift index da4e6c316b..60791e2cfe 100644 --- a/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift +++ b/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift @@ -188,7 +188,7 @@ private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyCompone convertedType = .recordWithOthers } default: - convertedType = .play + convertedType = .play(mixWithOthers: false) } let disposable = legacyContext.sharedContext.mediaManager.audioSession.push(audioSessionType: convertedType, once: true, activate: { _ in activated?() diff --git a/submodules/MediaPlayer/Sources/MediaPlayer.swift b/submodules/MediaPlayer/Sources/MediaPlayer.swift index e1f07f6f70..acaf62405f 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayer.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayer.swift @@ -125,6 +125,7 @@ private final class MediaPlayerContext { private let fetchAutomatically: Bool private var playAndRecord: Bool private var ambient: Bool + private var mixWithOthers: Bool private var keepAudioSessionWhilePaused: Bool private var continuePlayingWithoutSoundOnLostAudioSession: Bool private let storeAfterDownload: (() -> Void)? @@ -149,7 +150,7 @@ private final class MediaPlayerContext { private var stoppedAtEnd = false - init(queue: Queue, audioSessionManager: ManagedAudioSession, playerStatus: Promise, audioLevelPipe: ValuePipe, postbox: Postbox, userLocation: MediaResourceUserLocation, userContentType: MediaResourceUserContentType, resourceReference: MediaResourceReference, tempFilePath: String?, streamable: MediaPlayerStreaming, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, playAndRecord: Bool, ambient: Bool, keepAudioSessionWhilePaused: Bool, continuePlayingWithoutSoundOnLostAudioSession: Bool, storeAfterDownload: (() -> Void)? = nil, isAudioVideoMessage: Bool) { + init(queue: Queue, audioSessionManager: ManagedAudioSession, playerStatus: Promise, audioLevelPipe: ValuePipe, postbox: Postbox, userLocation: MediaResourceUserLocation, userContentType: MediaResourceUserContentType, resourceReference: MediaResourceReference, tempFilePath: String?, streamable: MediaPlayerStreaming, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, playAndRecord: Bool, ambient: Bool, mixWithOthers: Bool, keepAudioSessionWhilePaused: Bool, continuePlayingWithoutSoundOnLostAudioSession: Bool, storeAfterDownload: (() -> Void)? = nil, isAudioVideoMessage: Bool) { assert(queue.isCurrent()) self.queue = queue @@ -169,6 +170,7 @@ private final class MediaPlayerContext { self.fetchAutomatically = fetchAutomatically self.playAndRecord = playAndRecord self.ambient = ambient + self.mixWithOthers = mixWithOthers self.keepAudioSessionWhilePaused = keepAudioSessionWhilePaused self.continuePlayingWithoutSoundOnLostAudioSession = continuePlayingWithoutSoundOnLostAudioSession self.storeAfterDownload = storeAfterDownload @@ -402,7 +404,7 @@ private final class MediaPlayerContext { self.audioRenderer = nil let queue = self.queue - renderer = MediaPlayerAudioRenderer(audioSession: .manager(self.audioSessionManager), forAudioVideoMessage: self.isAudioVideoMessage, playAndRecord: self.playAndRecord, ambient: self.ambient, forceAudioToSpeaker: self.forceAudioToSpeaker, baseRate: self.baseRate, audioLevelPipe: self.audioLevelPipe, updatedRate: { [weak self] in + renderer = MediaPlayerAudioRenderer(audioSession: .manager(self.audioSessionManager), forAudioVideoMessage: self.isAudioVideoMessage, playAndRecord: self.playAndRecord, ambient: self.ambient, mixWithOthers: self.mixWithOthers, forceAudioToSpeaker: self.forceAudioToSpeaker, baseRate: self.baseRate, audioLevelPipe: self.audioLevelPipe, updatedRate: { [weak self] in queue.async { if let strongSelf = self { strongSelf.tick() @@ -481,7 +483,7 @@ private final class MediaPlayerContext { self.lastStatusUpdateTimestamp = nil if self.enableSound { let queue = self.queue - let renderer = MediaPlayerAudioRenderer(audioSession: .manager(self.audioSessionManager), forAudioVideoMessage: self.isAudioVideoMessage, playAndRecord: self.playAndRecord, ambient: self.ambient, forceAudioToSpeaker: self.forceAudioToSpeaker, baseRate: self.baseRate, audioLevelPipe: self.audioLevelPipe, updatedRate: { [weak self] in + let renderer = MediaPlayerAudioRenderer(audioSession: .manager(self.audioSessionManager), forAudioVideoMessage: self.isAudioVideoMessage, playAndRecord: self.playAndRecord, ambient: self.ambient, mixWithOthers: self.mixWithOthers, forceAudioToSpeaker: self.forceAudioToSpeaker, baseRate: self.baseRate, audioLevelPipe: self.audioLevelPipe, updatedRate: { [weak self] in queue.async { if let strongSelf = self { strongSelf.tick() @@ -599,10 +601,27 @@ private final class MediaPlayerContext { self.stoppedAtEnd = false } - fileprivate func continueWithOverridingAmbientMode() { - if self.ambient { + fileprivate func continueWithOverridingAmbientMode(isAmbient: Bool) { + if !isAmbient { self.ambient = false + var loadedState: MediaPlayerLoadedState? + switch self.state { + case .empty: + break + case let .playing(currentLoadedState): + loadedState = currentLoadedState + case let .paused(currentLoadedState): + loadedState = currentLoadedState + case .seeking: + break + } + if let loadedState = loadedState { + let timestamp = CMTimeGetSeconds(CMTimebaseGetTime(loadedState.controlTimebase.timebase)) + self.seek(timestamp: timestamp, action: .play) + } + } else { + self.ambient = true var loadedState: MediaPlayerLoadedState? switch self.state { case .empty: @@ -1136,10 +1155,10 @@ public final class MediaPlayer { } } - public init(audioSessionManager: ManagedAudioSession, postbox: Postbox, userLocation: MediaResourceUserLocation, userContentType: MediaResourceUserContentType, resourceReference: MediaResourceReference, tempFilePath: String? = nil, streamable: MediaPlayerStreaming, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool = false, enableSound: Bool, baseRate: Double = 1.0, fetchAutomatically: Bool, playAndRecord: Bool = false, ambient: Bool = false, keepAudioSessionWhilePaused: Bool = false, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, storeAfterDownload: (() -> Void)? = nil, isAudioVideoMessage: Bool = false) { + public init(audioSessionManager: ManagedAudioSession, postbox: Postbox, userLocation: MediaResourceUserLocation, userContentType: MediaResourceUserContentType, resourceReference: MediaResourceReference, tempFilePath: String? = nil, streamable: MediaPlayerStreaming, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool = false, enableSound: Bool, baseRate: Double = 1.0, fetchAutomatically: Bool, playAndRecord: Bool = false, ambient: Bool = false, mixWithOthers: Bool = false, keepAudioSessionWhilePaused: Bool = false, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, storeAfterDownload: (() -> Void)? = nil, isAudioVideoMessage: Bool = false) { let audioLevelPipe = self.audioLevelPipe self.queue.async { - let context = MediaPlayerContext(queue: self.queue, audioSessionManager: audioSessionManager, playerStatus: self.statusValue, audioLevelPipe: audioLevelPipe, postbox: postbox, userLocation: userLocation, userContentType: userContentType, resourceReference: resourceReference, tempFilePath: tempFilePath, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, playAutomatically: playAutomatically, enableSound: enableSound, baseRate: baseRate, fetchAutomatically: fetchAutomatically, playAndRecord: playAndRecord, ambient: ambient, keepAudioSessionWhilePaused: keepAudioSessionWhilePaused, continuePlayingWithoutSoundOnLostAudioSession: continuePlayingWithoutSoundOnLostAudioSession, storeAfterDownload: storeAfterDownload, isAudioVideoMessage: isAudioVideoMessage) + let context = MediaPlayerContext(queue: self.queue, audioSessionManager: audioSessionManager, playerStatus: self.statusValue, audioLevelPipe: audioLevelPipe, postbox: postbox, userLocation: userLocation, userContentType: userContentType, resourceReference: resourceReference, tempFilePath: tempFilePath, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, playAutomatically: playAutomatically, enableSound: enableSound, baseRate: baseRate, fetchAutomatically: fetchAutomatically, playAndRecord: playAndRecord, ambient: ambient, mixWithOthers: mixWithOthers, keepAudioSessionWhilePaused: keepAudioSessionWhilePaused, continuePlayingWithoutSoundOnLostAudioSession: continuePlayingWithoutSoundOnLostAudioSession, storeAfterDownload: storeAfterDownload, isAudioVideoMessage: isAudioVideoMessage) self.contextRef = Unmanaged.passRetained(context) } } @@ -1167,10 +1186,10 @@ public final class MediaPlayer { } } - public func continueWithOverridingAmbientMode() { + public func continueWithOverridingAmbientMode(isAmbient: Bool) { self.queue.async { if let context = self.contextRef?.takeUnretainedValue() { - context.continueWithOverridingAmbientMode() + context.continueWithOverridingAmbientMode(isAmbient: isAmbient) } } } diff --git a/submodules/MediaPlayer/Sources/MediaPlayerAudioRenderer.swift b/submodules/MediaPlayer/Sources/MediaPlayerAudioRenderer.swift index 7d1c46f697..5f2043188c 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayerAudioRenderer.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayerAudioRenderer.swift @@ -238,6 +238,7 @@ private final class AudioPlayerRendererContext { var audioSessionControl: ManagedAudioSessionControl? let playAndRecord: Bool let ambient: Bool + let mixWithOthers: Bool var forceAudioToSpeaker: Bool { didSet { if self.forceAudioToSpeaker != oldValue { @@ -251,7 +252,7 @@ private final class AudioPlayerRendererContext { } } - init(controlTimebase: CMTimebase, audioSession: MediaPlayerAudioSessionControl, forAudioVideoMessage: Bool, playAndRecord: Bool, useVoiceProcessingMode: Bool, ambient: Bool, forceAudioToSpeaker: Bool, baseRate: Double, audioLevelPipe: ValuePipe, updatedRate: @escaping () -> Void, audioPaused: @escaping () -> Void) { + init(controlTimebase: CMTimebase, audioSession: MediaPlayerAudioSessionControl, forAudioVideoMessage: Bool, playAndRecord: Bool, useVoiceProcessingMode: Bool, ambient: Bool, mixWithOthers: Bool, forceAudioToSpeaker: Bool, baseRate: Double, audioLevelPipe: ValuePipe, updatedRate: @escaping () -> Void, audioPaused: @escaping () -> Void) { assert(audioPlayerRendererQueue.isCurrent()) self.audioSession = audioSession @@ -267,6 +268,7 @@ private final class AudioPlayerRendererContext { self.playAndRecord = playAndRecord self.useVoiceProcessingMode = useVoiceProcessingMode self.ambient = ambient + self.mixWithOthers = mixWithOthers self.audioStreamDescription = audioRendererNativeStreamDescription() @@ -370,7 +372,7 @@ private final class AudioPlayerRendererContext { if self.paused { self.paused = false - self.startAudioUnit() + self.acquireAudioSession() } } @@ -384,6 +386,69 @@ private final class AudioPlayerRendererContext { } } + private func acquireAudioSession() { + switch self.audioSession { + case let .manager(manager): + self.audioSessionDisposable.set(manager.push(audioSessionType: self.ambient ? .ambient : (self.playAndRecord ? .playWithPossiblePortOverride : .play(mixWithOthers: self.mixWithOthers)), outputMode: self.forceAudioToSpeaker ? .speakerIfNoHeadphones : .system, once: self.ambient, manualActivate: { [weak self] control in + audioPlayerRendererQueue.async { + if let strongSelf = self { + strongSelf.audioSessionControl = control + if !strongSelf.paused { + control.setup() + control.setOutputMode(strongSelf.forceAudioToSpeaker ? .speakerIfNoHeadphones : .system) + control.activate({ _ in + audioPlayerRendererQueue.async { + if let strongSelf = self, !strongSelf.paused { + strongSelf.audioSessionAcquired() + } + } + }) + } + } + } + }, deactivate: { [weak self] temporary in + return Signal { subscriber in + audioPlayerRendererQueue.async { + if let strongSelf = self { + strongSelf.audioSessionControl = nil + if !temporary { + strongSelf.audioPaused() + strongSelf.stop() + } + subscriber.putCompletion() + } + } + + return EmptyDisposable + } + }, headsetConnectionStatusChanged: { [weak self] value in + audioPlayerRendererQueue.async { + if let strongSelf = self, !value { + strongSelf.audioPaused() + } + } + })) + case let .custom(request): + self.audioSessionDisposable.set(request(MediaPlayerAudioSessionCustomControl(activate: { [weak self] in + audioPlayerRendererQueue.async { + if let strongSelf = self { + if !strongSelf.paused { + strongSelf.audioSessionAcquired() + } + } + } + }, deactivate: { [weak self] in + audioPlayerRendererQueue.async { + if let strongSelf = self { + strongSelf.audioSessionControl = nil + strongSelf.audioPaused() + strongSelf.stop() + } + } + }))) + } + } + private func startAudioUnit() { assert(audioPlayerRendererQueue.isCurrent()) @@ -538,72 +603,13 @@ private final class AudioPlayerRendererContext { self.equalizerAudioUnit = equalizerAudioUnit self.outputAudioUnit = outputAudioUnit } - - switch self.audioSession { - case let .manager(manager): - self.audioSessionDisposable.set(manager.push(audioSessionType: self.ambient ? .ambient : (self.playAndRecord ? .playWithPossiblePortOverride : .play), outputMode: self.forceAudioToSpeaker ? .speakerIfNoHeadphones : .system, once: self.ambient, manualActivate: { [weak self] control in - audioPlayerRendererQueue.async { - if let strongSelf = self { - strongSelf.audioSessionControl = control - if !strongSelf.paused { - control.setup() - control.setOutputMode(strongSelf.forceAudioToSpeaker ? .speakerIfNoHeadphones : .system) - control.activate({ _ in - audioPlayerRendererQueue.async { - if let strongSelf = self, !strongSelf.paused { - strongSelf.audioSessionAcquired() - } - } - }) - } - } - } - }, deactivate: { [weak self] temporary in - return Signal { subscriber in - audioPlayerRendererQueue.async { - if let strongSelf = self { - strongSelf.audioSessionControl = nil - if !temporary { - strongSelf.audioPaused() - strongSelf.stop() - } - subscriber.putCompletion() - } - } - - return EmptyDisposable - } - }, headsetConnectionStatusChanged: { [weak self] value in - audioPlayerRendererQueue.async { - if let strongSelf = self, !value { - strongSelf.audioPaused() - } - } - })) - case let .custom(request): - self.audioSessionDisposable.set(request(MediaPlayerAudioSessionCustomControl(activate: { [weak self] in - audioPlayerRendererQueue.async { - if let strongSelf = self { - if !strongSelf.paused { - strongSelf.audioSessionAcquired() - } - } - } - }, deactivate: { [weak self] in - audioPlayerRendererQueue.async { - if let strongSelf = self { - strongSelf.audioSessionControl = nil - strongSelf.audioPaused() - strongSelf.stop() - } - } - }))) - } } private func audioSessionAcquired() { assert(audioPlayerRendererQueue.isCurrent()) + self.startAudioUnit() + if let audioGraph = self.audioGraph { let startTime = CFAbsoluteTimeGetCurrent() @@ -821,7 +827,7 @@ public final class MediaPlayerAudioRenderer { private let audioClock: CMClock public let audioTimebase: CMTimebase - public init(audioSession: MediaPlayerAudioSessionControl, forAudioVideoMessage: Bool = false, playAndRecord: Bool, useVoiceProcessingMode: Bool = false, ambient: Bool, forceAudioToSpeaker: Bool, baseRate: Double, audioLevelPipe: ValuePipe, updatedRate: @escaping () -> Void, audioPaused: @escaping () -> Void) { + public init(audioSession: MediaPlayerAudioSessionControl, forAudioVideoMessage: Bool = false, playAndRecord: Bool, useVoiceProcessingMode: Bool = false, ambient: Bool, mixWithOthers: Bool, forceAudioToSpeaker: Bool, baseRate: Double, audioLevelPipe: ValuePipe, updatedRate: @escaping () -> Void, audioPaused: @escaping () -> Void) { var audioClock: CMClock? CMAudioClockCreate(allocator: nil, clockOut: &audioClock) if audioClock == nil { @@ -834,7 +840,7 @@ public final class MediaPlayerAudioRenderer { self.audioTimebase = audioTimebase! audioPlayerRendererQueue.async { - let context = AudioPlayerRendererContext(controlTimebase: audioTimebase!, audioSession: audioSession, forAudioVideoMessage: forAudioVideoMessage, playAndRecord: playAndRecord, useVoiceProcessingMode: useVoiceProcessingMode, ambient: ambient, forceAudioToSpeaker: forceAudioToSpeaker, baseRate: baseRate, audioLevelPipe: audioLevelPipe, updatedRate: updatedRate, audioPaused: audioPaused) + let context = AudioPlayerRendererContext(controlTimebase: audioTimebase!, audioSession: audioSession, forAudioVideoMessage: forAudioVideoMessage, playAndRecord: playAndRecord, useVoiceProcessingMode: useVoiceProcessingMode, ambient: ambient, mixWithOthers: mixWithOthers, forceAudioToSpeaker: forceAudioToSpeaker, baseRate: baseRate, audioLevelPipe: audioLevelPipe, updatedRate: updatedRate, audioPaused: audioPaused) self.contextRef = Unmanaged.passRetained(context) } } diff --git a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift index ea61bbd1a4..1953ba14d4 100644 --- a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift +++ b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift @@ -319,7 +319,7 @@ public func playSound(context: AccountContext, notificationSoundList: Notificati return Signal { subscriber in var currentPlayer: AudioPlayerWrapper? var deactivateImpl: (() -> Void)? - let session = context.sharedContext.mediaManager.audioSession.push(audioSessionType: .play, activate: { _ in + let session = context.sharedContext.mediaManager.audioSession.push(audioSessionType: .play(mixWithOthers: true), activate: { _ in Queue.mainQueue().async { let filePath = fileNameForNotificationSound(account: context.account, notificationSoundList: notificationSoundList, sound: sound, defaultSound: defaultSound) diff --git a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift index 31a2bd5c58..a238e1e8d4 100644 --- a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift +++ b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift @@ -16,7 +16,7 @@ func managedAudioSessionLog(_ what: @autoclosure () -> String) { public enum ManagedAudioSessionType: Equatable { case ambient - case play + case play(mixWithOthers: Bool) case playWithPossiblePortOverride case record(speaker: Bool, withOthers: Bool) case voiceCall @@ -766,23 +766,25 @@ public final class ManagedAudioSession: NSObject { managedAudioSessionLog("ManagedAudioSession setting category for \(type) (native: \(nativeCategory)) activateNow: \(activateNow)") var options: AVAudioSession.CategoryOptions = [] switch type { - case .play: - break - case .ambient: + case let .play(mixWithOthers): + if mixWithOthers { options.insert(.mixWithOthers) - case .playWithPossiblePortOverride: - if case .playAndRecord = nativeCategory { - options.insert(.allowBluetoothA2DP) - } - case .voiceCall, .videoCall: - options.insert(.allowBluetooth) + } + case .ambient: + options.insert(.mixWithOthers) + case .playWithPossiblePortOverride: + if case .playAndRecord = nativeCategory { options.insert(.allowBluetoothA2DP) - options.insert(.mixWithOthers) - case .record: - options.insert(.allowBluetooth) - case .recordWithOthers: - options.insert(.allowBluetoothA2DP) - options.insert(.mixWithOthers) + } + case .voiceCall, .videoCall: + options.insert(.allowBluetooth) + options.insert(.allowBluetoothA2DP) + options.insert(.mixWithOthers) + case .record: + options.insert(.allowBluetooth) + case .recordWithOthers: + options.insert(.allowBluetoothA2DP) + options.insert(.mixWithOthers) } managedAudioSessionLog("ManagedAudioSession setting category and options") let mode: AVAudioSession.Mode @@ -796,11 +798,24 @@ public final class ManagedAudioSession: NSObject { default: mode = .default } + + switch type { + case .play(mixWithOthers: true), .ambient: + try AVAudioSession.sharedInstance().setActive(false) + default: + break + } + try AVAudioSession.sharedInstance().setCategory(nativeCategory, options: options) try AVAudioSession.sharedInstance().setMode(mode) if AVAudioSession.sharedInstance().categoryOptions != options { - managedAudioSessionLog("ManagedAudioSession resetting options") - try AVAudioSession.sharedInstance().setCategory(nativeCategory, options: options) + switch type { + case .voiceCall, .videoCall, .recordWithOthers: + managedAudioSessionLog("ManagedAudioSession resetting options") + try AVAudioSession.sharedInstance().setCategory(nativeCategory, options: options) + default: + break + } } } catch let error { managedAudioSessionLog("ManagedAudioSession setup error \(error)") diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 116472263a..ed0b1e120e 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -898,7 +898,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.audioOutputStatePromise.set(.single(([], .speaker))) } - self.audioSessionDisposable = audioSession.push(audioSessionType: self.isStream ? .play : .voiceCall, activateImmediately: true, manualActivate: { [weak self] control in + self.audioSessionDisposable = audioSession.push(audioSessionType: self.isStream ? .play(mixWithOthers: false) : .voiceCall, activateImmediately: true, manualActivate: { [weak self] control in Queue.mainQueue().async { if let strongSelf = self { strongSelf.updateSessionState(internalState: strongSelf.internalState, audioSessionControl: control) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift index f0086070d8..3be6fffff7 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift @@ -70,7 +70,7 @@ extension TelegramUser { var storiesHidden: Bool? if !isMin { - storiesHidden = (flags2 & (1 << 5)) != 0 + storiesHidden = (flags2 & (1 << 3)) != 0 } var botInfo: BotUserInfo? diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index b6765eeeb0..e117e190be 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -19,6 +19,7 @@ import simd import VolumeButtons import TooltipUI import ChatEntityKeyboardInputNode +import notify func hasFirstResponder(_ view: UIView) -> Bool { if view.isFirstResponder { @@ -32,6 +33,49 @@ func hasFirstResponder(_ view: UIView) -> Bool { return false } +private final class MuteMonitor { + private let updated: (Bool) -> Void + + private var token: Int32 = NOTIFY_TOKEN_INVALID + private(set) var currentValue: Bool = false + + init(updated: @escaping (Bool) -> Void) { + self.updated = updated + + let status = notify_register_dispatch("com.apple.springboard.ringerstate", &self.token, DispatchQueue.main, { [weak self] value in + guard let self else { + return + } + let value = self.refresh() + if self.currentValue != value { + self.currentValue = value + self.updated(value) + } + }) + let _ = status + //print("Notify status: \(status)") + + self.currentValue = self.refresh() + } + + private func refresh() -> Bool { + var state: UInt64 = 0 + if self.token != NOTIFY_TOKEN_INVALID { + let status = notify_get_state(self.token, &state) + let _ = status + //print("Notify refresh status: \(status)") + } + + return state != 0 + } + + deinit { + if self.token != NOTIFY_TOKEN_INVALID { + notify_cancel(self.token) + } + } +} + private final class StoryLongPressRecognizer: UILongPressGestureRecognizer { var shouldBegin: ((UITouch) -> Bool)? var updateIsTracking: ((Bool) -> Void)? @@ -189,7 +233,20 @@ private final class StoryContainerScreenComponent: Component { private var transitionCloneMasterView: UIView private var volumeButtonsListener: VolumeButtonsListener? - private let volumeButtonsListenerShouldBeActive = ValuePromise(false, ignoreRepeated: true) + private let contentWantsVolumeButtonMonitoring = ValuePromise(false, ignoreRepeated: true) + private let isMuteSwitchOnPromise = ValuePromise(false, ignoreRepeated: true) + private let volumeButtonsListenerShouldBeActive = Promise() + private var volumeButtonsListenerShouldBeActiveDisposable: Disposable? + + private var isMuteSwitchOn: Bool = false + private var muteMonitor: MuteMonitor? + + private var audioMode: StoryContentItem.AudioMode = .ambient { + didSet { + self.audioModePromise.set(self.audioMode) + } + } + private let audioModePromise = ValuePromise(.ambient, ignoreRepeated: true) private let inputMediaNodeDataPromise = Promise() @@ -287,6 +344,70 @@ private final class StoryContainerScreenComponent: Component { let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) self.backgroundEffectView.addGestureRecognizer(tapGestureRecognizer) + + let muteMonitor = MuteMonitor(updated: { [weak self] isMuteSwitchOn in + Queue.mainQueue().async { + guard let self else { + return + } + if self.isMuteSwitchOn != isMuteSwitchOn { + let changedToOff = self.isMuteSwitchOn && !isMuteSwitchOn + + self.isMuteSwitchOn = isMuteSwitchOn + + self.isMuteSwitchOnPromise.set(self.isMuteSwitchOn) + + if changedToOff { + switch self.audioMode { + case .on: + self.audioMode = .ambient + for (_, itemSetView) in self.visibleItemSetViews { + if let componentView = itemSetView.view.view as? StoryItemSetContainerComponent.View { + componentView.enterAmbientMode(ambient: !self.isMuteSwitchOn) + } + } + default: + break + } + } + + self.state?.updated(transition: .immediate) + } + } + }) + self.muteMonitor = muteMonitor + self.isMuteSwitchOn = muteMonitor.currentValue + self.isMuteSwitchOnPromise.set(self.isMuteSwitchOn) + + self.volumeButtonsListenerShouldBeActiveDisposable = (combineLatest(queue: .mainQueue(), + self.contentWantsVolumeButtonMonitoring.get(), + self.isMuteSwitchOnPromise.get(), + self.audioModePromise.get() + ) + |> map { contentWantsVolumeButtonMonitoring, isMuteSwitchOn, audioMode -> Bool in + if !contentWantsVolumeButtonMonitoring { + return false + } + switch audioMode { + case .ambient: + if isMuteSwitchOn { + return false + } else { + return true + } + case .on: + return false + case .off: + return true + } + } + |> distinctUntilChanged).start(next: { [weak self] enable in + guard let self else { + return + } + self.volumeButtonsListenerShouldBeActive.set(.single(enable)) + self.updateVolumeButtonMonitoring() + }) } required init?(coder: NSCoder) { @@ -295,6 +416,7 @@ private final class StoryContainerScreenComponent: Component { deinit { self.contentUpdatedDisposable?.dispose() + self.volumeButtonsListenerShouldBeActiveDisposable?.dispose() } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { @@ -665,17 +787,24 @@ private final class StoryContainerScreenComponent: Component { private func updateVolumeButtonMonitoring() { if self.volumeButtonsListener == nil { let buttonAction = { [weak self] in - guard let self, self.storyItemSharedState.useAmbientMode else { + guard let self else { return } - self.storyItemSharedState.useAmbientMode = false - self.volumeButtonsListenerShouldBeActive.set(false) + switch self.audioMode { + case .off, .ambient: + break + case .on: + return + } + self.audioMode = .on for (_, itemSetView) in self.visibleItemSetViews { if let componentView = itemSetView.view.view as? StoryItemSetContainerComponent.View { componentView.leaveAmbientMode() } } + + self.state?.updated(transition: .immediate) } self.volumeButtonsListener = VolumeButtonsListener( shouldBeActive: self.volumeButtonsListenerShouldBeActive.get(), @@ -740,12 +869,7 @@ private final class StoryContainerScreenComponent: Component { } self.focusedItem.set(focusedItemId) - if self.storyItemSharedState.useAmbientMode { - self.volumeButtonsListenerShouldBeActive.set(isVideo) - if isVideo { - self.updateVolumeButtonMonitoring() - } - } + self.contentWantsVolumeButtonMonitoring.set(isVideo) if component.content.stateValue?.slice == nil { self.environment?.controller()?.dismiss() @@ -898,6 +1022,8 @@ private final class StoryContainerScreenComponent: Component { metrics: environment.metrics, deviceMetrics: environment.deviceMetrics, isProgressPaused: isProgressPaused || i != focusedIndex, + isAudioMuted: self.audioMode == .off || (self.audioMode == .ambient && !self.isMuteSwitchOn), + useAmbientMode: self.audioMode == .ambient, hideUI: (i == focusedIndex && (self.itemSetPanState?.didBegin == false || self.itemSetPinchState != nil)), visibilityFraction: 1.0 - abs(panFraction + cubeAdditionalRotationFraction), isPanning: self.itemSetPanState?.didBegin == true, @@ -995,25 +1121,31 @@ private final class StoryContainerScreenComponent: Component { return } - if self.storyItemSharedState.useAmbientMode { - self.storyItemSharedState.useAmbientMode = false - self.volumeButtonsListenerShouldBeActive.set(false) - + switch self.audioMode { + case .ambient: + self.audioMode = .on for (_, itemSetView) in self.visibleItemSetViews { if let componentView = itemSetView.view.view as? StoryItemSetContainerComponent.View { componentView.leaveAmbientMode() } } - } else { - self.storyItemSharedState.useAmbientMode = true - self.volumeButtonsListenerShouldBeActive.set(true) - + case .on: + self.audioMode = .off for (_, itemSetView) in self.visibleItemSetViews { if let componentView = itemSetView.view.view as? StoryItemSetContainerComponent.View { - componentView.enterAmbientMode() + componentView.enterAmbientMode(ambient: !self.isMuteSwitchOn) + } + } + case .off: + self.audioMode = .on + for (_, itemSetView) in self.visibleItemSetViews { + if let componentView = itemSetView.view.view as? StoryItemSetContainerComponent.View { + componentView.leaveAmbientMode() } } } + + self.state?.updated(transition: .immediate) }, keyboardInputData: self.inputMediaNodeDataPromise.get(), sharedViewListsContext: self.sharedViewListsContext @@ -1320,6 +1452,7 @@ public class StoryContainerScreen: ViewControllerComponentContainer { deinit { self.context.sharedContext.hasPreloadBlockingContent.set(.single(false)) + self.focusedItemPromise.set(.single(nil)) } override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift index bb1b66aa7a..fdf0210e1e 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift @@ -13,9 +13,13 @@ public final class StoryContentItem: Equatable { } } + public enum AudioMode { + case ambient + case on + case off + } + public final class SharedState { - public var useAmbientMode: Bool = true - public init() { } } @@ -30,7 +34,7 @@ public final class StoryContentItem: Equatable { open func leaveAmbientMode() { } - open func enterAmbientMode() { + open func enterAmbientMode(ambient: Bool) { } open var videoPlaybackPosition: Double? { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index 58b3916ae8..5fae3ded54 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -29,11 +29,13 @@ final class StoryItemContentComponent: Component { let context: AccountContext let peer: EnginePeer let item: EngineStoryItem + let useAmbientMode: Bool - init(context: AccountContext, peer: EnginePeer, item: EngineStoryItem) { + init(context: AccountContext, peer: EnginePeer, item: EngineStoryItem, useAmbientMode: Bool) { self.context = context self.peer = peer self.item = item + self.useAmbientMode = useAmbientMode } static func ==(lhs: StoryItemContentComponent, rhs: StoryItemContentComponent) -> Bool { @@ -118,7 +120,7 @@ final class StoryItemContentComponent: Component { return } - guard let component = self.component, let environment = self.environment, let currentMessageMedia = self.currentMessageMedia else { + guard let component = self.component, let currentMessageMedia = self.currentMessageMedia else { return } @@ -137,7 +139,8 @@ final class StoryItemContentComponent: Component { streamVideo: .story, loopVideo: true, enableSound: true, - beginWithAmbientSound: environment.sharedState.useAmbientMode, + beginWithAmbientSound: component.useAmbientMode, + mixWithOthers: true, useLargeThumbnail: false, autoFetchFullSizeThumbnail: false, tempFilePath: nil, @@ -215,13 +218,17 @@ final class StoryItemContentComponent: Component { override func leaveAmbientMode() { if let videoNode = self.videoNode { videoNode.setSoundEnabled(true) - videoNode.continueWithOverridingAmbientMode() + videoNode.continueWithOverridingAmbientMode(isAmbient: false) } } - override func enterAmbientMode() { + override func enterAmbientMode(ambient: Bool) { if let videoNode = self.videoNode { - videoNode.setSoundEnabled(false) + if ambient { + videoNode.continueWithOverridingAmbientMode(isAmbient: true) + } else { + videoNode.setSoundEnabled(false) + } } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 2ff2eb5bb8..89e14434f7 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -85,6 +85,8 @@ public final class StoryItemSetContainerComponent: Component { public let metrics: LayoutMetrics public let deviceMetrics: DeviceMetrics public let isProgressPaused: Bool + public let isAudioMuted: Bool + public let useAmbientMode: Bool public let hideUI: Bool public let visibilityFraction: CGFloat public let isPanning: Bool @@ -115,6 +117,8 @@ public final class StoryItemSetContainerComponent: Component { metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isProgressPaused: Bool, + isAudioMuted: Bool, + useAmbientMode: Bool, hideUI: Bool, visibilityFraction: CGFloat, isPanning: Bool, @@ -144,6 +148,8 @@ public final class StoryItemSetContainerComponent: Component { self.metrics = metrics self.deviceMetrics = deviceMetrics self.isProgressPaused = isProgressPaused + self.isAudioMuted = isAudioMuted + self.useAmbientMode = useAmbientMode self.hideUI = hideUI self.visibilityFraction = visibilityFraction self.isPanning = isPanning @@ -192,6 +198,12 @@ public final class StoryItemSetContainerComponent: Component { if lhs.isProgressPaused != rhs.isProgressPaused { return false } + if lhs.isAudioMuted != rhs.isAudioMuted { + return false + } + if lhs.useAmbientMode != rhs.useAmbientMode { + return false + } if lhs.hideUI != rhs.hideUI { return false } @@ -612,7 +624,7 @@ public final class StoryItemSetContainerComponent: Component { self.state?.updated(transition: .immediate) } - func enterAmbientMode() { + func enterAmbientMode(ambient: Bool) { guard let component = self.component else { return } @@ -620,7 +632,7 @@ public final class StoryItemSetContainerComponent: Component { return } if let itemView = visibleItem.view.view as? StoryContentItem.View { - itemView.enterAmbientMode() + itemView.enterAmbientMode(ambient: ambient) } self.state?.updated(transition: .immediate) @@ -1034,7 +1046,8 @@ public final class StoryItemSetContainerComponent: Component { component: AnyComponent(StoryItemContentComponent( context: component.context, peer: component.slice.peer, - item: item.storyItem + item: item.storyItem, + useAmbientMode: component.useAmbientMode )), environment: { itemEnvironment @@ -2197,7 +2210,7 @@ public final class StoryItemSetContainerComponent: Component { } let soundImage: String - if isSilentVideo || component.storyItemSharedState.useAmbientMode { + if isSilentVideo || component.isAudioMuted { soundImage = "Stories/SoundOff" } else { soundImage = "Stories/SoundOn" @@ -2270,7 +2283,7 @@ public final class StoryItemSetContainerComponent: Component { } } - if component.slice.item.storyItem.isCloseFriends && component.slice.peer.id != component.context.account.peerId { + if component.slice.item.storyItem.isCloseFriends { let closeFriendIcon: ComponentView var closeFriendIconTransition = transition if let current = self.closeFriendIcon { @@ -2296,10 +2309,16 @@ public final class StoryItemSetContainerComponent: Component { guard let closeFriendIconView = self.closeFriendIcon?.view else { return } + let tooltipText: String + if component.slice.peer.id == component.context.account.peerId { + tooltipText = "Only people from your close friends list will see this story." + } else { + tooltipText = "You are seeing this story because you have\nbeen added to \(component.slice.peer.compactDisplayTitle)'s list of close friends." + } let tooltipScreen = TooltipScreen( account: component.context.account, sharedContext: component.context.sharedContext, - text: .plain(text: "You are seeing this story because you have\nbeen added to \(component.slice.peer.compactDisplayTitle)'s list of close friends."), style: .default, location: TooltipScreen.Location.point(closeFriendIconView.convert(closeFriendIconView.bounds, to: self).offsetBy(dx: 1.0, dy: 6.0), .top), displayDuration: .manual, shouldDismissOnTouch: { _, _ in + text: .plain(text: tooltipText), style: .default, location: TooltipScreen.Location.point(closeFriendIconView.convert(closeFriendIconView.bounds, to: self).offsetBy(dx: 1.0, dy: 6.0), .top), displayDuration: .manual, shouldDismissOnTouch: { _, _ in return .dismiss(consume: true) } ) @@ -3010,13 +3029,17 @@ public final class StoryItemSetContainerComponent: Component { let targetController = component.context.sharedContext.makeMyStoriesController(context: component.context, isArchive: false) - var viewControllers = navigationController.viewControllers - if let index = viewControllers.firstIndex(where: { $0 === controller }) { - viewControllers.insert(targetController, at: index) + if "".isEmpty { + navigationController.pushViewController(targetController) } else { - viewControllers.append(targetController) + var viewControllers = navigationController.viewControllers + if let index = viewControllers.firstIndex(where: { $0 === controller }) { + viewControllers.insert(targetController, at: index) + } else { + viewControllers.append(targetController) + } + navigationController.setViewControllers(viewControllers, animated: true) } - navigationController.setViewControllers(viewControllers, animated: true) } func navigateToPeer(peer: EnginePeer, chat: Bool, messageId: EngineMessage.Id? = nil) { @@ -3038,29 +3061,35 @@ public final class StoryItemSetContainerComponent: Component { guard let controller, let navigationController else { return } - var viewControllers = navigationController.viewControllers - if let index = viewControllers.firstIndex(where: { $0 === controller }) { - viewControllers.insert(chatController, at: index) + if "".isEmpty { + navigationController.pushViewController(chatController) } else { - viewControllers.append(chatController) + var viewControllers = navigationController.viewControllers + if let index = viewControllers.firstIndex(where: { $0 === controller }) { + viewControllers.insert(chatController, at: index) + } else { + viewControllers.append(chatController) + } + navigationController.setViewControllers(viewControllers, animated: animated) } - navigationController.setViewControllers(viewControllers, animated: animated) })) } else { guard let chatController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { return } - var viewControllers = navigationController.viewControllers - if let index = viewControllers.firstIndex(where: { $0 === controller }) { - viewControllers.insert(chatController, at: index) + if "".isEmpty { + navigationController.pushViewController(chatController) } else { - viewControllers.append(chatController) + var viewControllers = navigationController.viewControllers + if let index = viewControllers.firstIndex(where: { $0 === controller }) { + viewControllers.insert(chatController, at: index) + } else { + viewControllers.append(chatController) + } + navigationController.setViewControllers(viewControllers, animated: true) } - navigationController.setViewControllers(viewControllers, animated: true) } - - controller.dismissWithoutTransitionOut() } private func openStoryEditing() { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 0ac722e66b..f61e854c25 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -2270,25 +2270,28 @@ final class StoryItemSetContainerSendMessage { return } - controller.dismissWithoutTransitionOut() - switch navigation { case let .chat(_, subject, peekData): if let navigationController = controller.navigationController as? NavigationController { if case let .channel(channel) = peerId, channel.flags.contains(.isForum) { + controller.dismissWithoutTransitionOut() component.context.sharedContext.navigateToForumChannel(context: component.context, peerId: peerId.id, navigationController: navigationController) } else { component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peerId), subject: subject, keepStack: .always, peekData: peekData, pushController: { [weak controller, weak navigationController] chatController, animated, completion in guard let controller, let navigationController else { return } - var viewControllers = navigationController.viewControllers - if let index = viewControllers.firstIndex(where: { $0 === controller }) { - viewControllers.insert(chatController, at: index) + if "".isEmpty { + navigationController.pushViewController(chatController) } else { - viewControllers.append(chatController) + var viewControllers = navigationController.viewControllers + if let index = viewControllers.firstIndex(where: { $0 === controller }) { + viewControllers.insert(chatController, at: index) + } else { + viewControllers.append(chatController) + } + navigationController.setViewControllers(viewControllers, animated: animated) } - navigationController.setViewControllers(viewControllers, animated: animated) })) } } diff --git a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift index 9c564f72cd..7481e34dab 100644 --- a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift +++ b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift @@ -214,7 +214,7 @@ final class ManagedAudioRecorderContext { } return ActionDisposable { } - }), playAndRecord: true, ambient: false, forceAudioToSpeaker: false, baseRate: 1.0, audioLevelPipe: ValuePipe(), updatedRate: { + }), playAndRecord: true, ambient: false, mixWithOthers: false, forceAudioToSpeaker: false, baseRate: 1.0, audioLevelPipe: ValuePipe(), updatedRate: { }, audioPaused: {}) self.toneRenderer = toneRenderer diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 45887387ca..5eb4115e02 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -14,6 +14,7 @@ import ChatPresentationInterfaceState import AttachmentUI import ForumCreateTopicScreen import LegacyInstantVideoController +import StoryContainerScreen public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParams) { if case let .peer(peer) = params.chatLocation, case let .channel(channel) = peer, channel.flags.contains(.isForum) { @@ -149,12 +150,16 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam } let resolvedKeepStack: Bool switch params.keepStack { - case .default: - resolvedKeepStack = params.context.sharedContext.immediateExperimentalUISettings.keepChatNavigationStack - case .always: + case .default: + if params.navigationController.viewControllers.contains(where: { $0 is StoryContainerScreen }) { resolvedKeepStack = true - case .never: - resolvedKeepStack = false + } else { + resolvedKeepStack = params.context.sharedContext.immediateExperimentalUISettings.keepChatNavigationStack + } + case .always: + resolvedKeepStack = true + case .never: + resolvedKeepStack = false } if resolvedKeepStack { if let pushController = params.pushController { diff --git a/submodules/TelegramUI/Sources/OverlayInstantVideoNode.swift b/submodules/TelegramUI/Sources/OverlayInstantVideoNode.swift index 4e5a7ea0e4..13a9f4ab20 100644 --- a/submodules/TelegramUI/Sources/OverlayInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/OverlayInstantVideoNode.swift @@ -115,7 +115,7 @@ final class OverlayInstantVideoNode: OverlayMediaItemNode { self.videoNode.playOnceWithSound(playAndRecord: playAndRecord) } - func continueWithOverridingAmbientMode() { + func continueWithOverridingAmbientMode(isAmbient: Bool) { } func pause() { diff --git a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift index 2e14c0c15e..545a773150 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift @@ -37,6 +37,7 @@ public final class NativeVideoContent: UniversalVideoContent { public let loopVideo: Bool public let enableSound: Bool public let beginWithAmbientSound: Bool + public let mixWithOthers: Bool public let baseRate: Double let fetchAutomatically: Bool let onlyFullSizeThumbnail: Bool @@ -54,7 +55,7 @@ public final class NativeVideoContent: UniversalVideoContent { let displayImage: Bool let hasSentFramesToDisplay: (() -> Void)? - public init(id: NativeVideoContentId, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, imageReference: ImageMediaReference? = nil, streamVideo: MediaPlayerStreaming = .none, loopVideo: Bool = false, enableSound: Bool = true, beginWithAmbientSound: Bool = false, baseRate: Double = 1.0, fetchAutomatically: Bool = true, onlyFullSizeThumbnail: Bool = false, useLargeThumbnail: Bool = false, autoFetchFullSizeThumbnail: Bool = false, startTimestamp: Double? = nil, endTimestamp: Double? = nil, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor = .white, tempFilePath: String? = nil, isAudioVideoMessage: Bool = false, captureProtected: Bool = false, hintDimensions: CGSize? = nil, storeAfterDownload: (() -> Void)?, displayImage: Bool = true, hasSentFramesToDisplay: (() -> Void)? = nil) { + public init(id: NativeVideoContentId, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, imageReference: ImageMediaReference? = nil, streamVideo: MediaPlayerStreaming = .none, loopVideo: Bool = false, enableSound: Bool = true, beginWithAmbientSound: Bool = false, mixWithOthers: Bool = false, baseRate: Double = 1.0, fetchAutomatically: Bool = true, onlyFullSizeThumbnail: Bool = false, useLargeThumbnail: Bool = false, autoFetchFullSizeThumbnail: Bool = false, startTimestamp: Double? = nil, endTimestamp: Double? = nil, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor = .white, tempFilePath: String? = nil, isAudioVideoMessage: Bool = false, captureProtected: Bool = false, hintDimensions: CGSize? = nil, storeAfterDownload: (() -> Void)?, displayImage: Bool = true, hasSentFramesToDisplay: (() -> Void)? = nil) { self.id = id self.nativeId = id self.userLocation = userLocation @@ -78,6 +79,7 @@ public final class NativeVideoContent: UniversalVideoContent { self.loopVideo = loopVideo self.enableSound = enableSound self.beginWithAmbientSound = beginWithAmbientSound + self.mixWithOthers = mixWithOthers self.baseRate = baseRate self.fetchAutomatically = fetchAutomatically self.onlyFullSizeThumbnail = onlyFullSizeThumbnail @@ -97,7 +99,7 @@ public final class NativeVideoContent: UniversalVideoContent { } public func makeContentNode(postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { - return NativeVideoContentNode(postbox: postbox, audioSessionManager: audioSession, userLocation: self.userLocation, fileReference: self.fileReference, imageReference: self.imageReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, beginWithAmbientSound: self.beginWithAmbientSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically, onlyFullSizeThumbnail: self.onlyFullSizeThumbnail, useLargeThumbnail: self.useLargeThumbnail, autoFetchFullSizeThumbnail: self.autoFetchFullSizeThumbnail, startTimestamp: self.startTimestamp, endTimestamp: self.endTimestamp, continuePlayingWithoutSoundOnLostAudioSession: self.continuePlayingWithoutSoundOnLostAudioSession, placeholderColor: self.placeholderColor, tempFilePath: self.tempFilePath, isAudioVideoMessage: self.isAudioVideoMessage, captureProtected: self.captureProtected, hintDimensions: self.hintDimensions, storeAfterDownload: self.storeAfterDownload, displayImage: self.displayImage, hasSentFramesToDisplay: self.hasSentFramesToDisplay) + return NativeVideoContentNode(postbox: postbox, audioSessionManager: audioSession, userLocation: self.userLocation, fileReference: self.fileReference, imageReference: self.imageReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, beginWithAmbientSound: self.beginWithAmbientSound, mixWithOthers: self.mixWithOthers, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically, onlyFullSizeThumbnail: self.onlyFullSizeThumbnail, useLargeThumbnail: self.useLargeThumbnail, autoFetchFullSizeThumbnail: self.autoFetchFullSizeThumbnail, startTimestamp: self.startTimestamp, endTimestamp: self.endTimestamp, continuePlayingWithoutSoundOnLostAudioSession: self.continuePlayingWithoutSoundOnLostAudioSession, placeholderColor: self.placeholderColor, tempFilePath: self.tempFilePath, isAudioVideoMessage: self.isAudioVideoMessage, captureProtected: self.captureProtected, hintDimensions: self.hintDimensions, storeAfterDownload: self.storeAfterDownload, displayImage: self.displayImage, hasSentFramesToDisplay: self.hasSentFramesToDisplay) } public func isEqual(to other: UniversalVideoContent) -> Bool { @@ -120,6 +122,7 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent private let fileReference: FileMediaReference private let enableSound: Bool private let beginWithAmbientSound: Bool + private let mixWithOthers: Bool private let loopVideo: Bool private let baseRate: Double private let audioSessionManager: ManagedAudioSession @@ -177,13 +180,14 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent private let hasSentFramesToDisplay: (() -> Void)? - init(postbox: Postbox, audioSessionManager: ManagedAudioSession, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, imageReference: ImageMediaReference?, streamVideo: MediaPlayerStreaming, loopVideo: Bool, enableSound: Bool, beginWithAmbientSound: Bool, baseRate: Double, fetchAutomatically: Bool, onlyFullSizeThumbnail: Bool, useLargeThumbnail: Bool, autoFetchFullSizeThumbnail: Bool, startTimestamp: Double?, endTimestamp: Double?, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor, tempFilePath: String?, isAudioVideoMessage: Bool, captureProtected: Bool, hintDimensions: CGSize?, storeAfterDownload: (() -> Void)? = nil, displayImage: Bool, hasSentFramesToDisplay: (() -> Void)?) { + init(postbox: Postbox, audioSessionManager: ManagedAudioSession, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, imageReference: ImageMediaReference?, streamVideo: MediaPlayerStreaming, loopVideo: Bool, enableSound: Bool, beginWithAmbientSound: Bool, mixWithOthers: Bool, baseRate: Double, fetchAutomatically: Bool, onlyFullSizeThumbnail: Bool, useLargeThumbnail: Bool, autoFetchFullSizeThumbnail: Bool, startTimestamp: Double?, endTimestamp: Double?, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor, tempFilePath: String?, isAudioVideoMessage: Bool, captureProtected: Bool, hintDimensions: CGSize?, storeAfterDownload: (() -> Void)? = nil, displayImage: Bool, hasSentFramesToDisplay: (() -> Void)?) { self.postbox = postbox self.userLocation = userLocation self.fileReference = fileReference self.placeholderColor = placeholderColor self.enableSound = enableSound self.beginWithAmbientSound = beginWithAmbientSound + self.mixWithOthers = mixWithOthers self.loopVideo = loopVideo self.baseRate = baseRate self.audioSessionManager = audioSessionManager @@ -194,7 +198,7 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent self.imageNode = TransformImageNode() - self.player = MediaPlayer(audioSessionManager: audioSessionManager, postbox: postbox, userLocation: userLocation, userContentType: MediaResourceUserContentType(file: fileReference.media), resourceReference: fileReference.resourceReference(fileReference.media.resource), tempFilePath: tempFilePath, streamable: streamVideo, video: true, preferSoftwareDecoding: false, playAutomatically: false, enableSound: enableSound, baseRate: baseRate, fetchAutomatically: fetchAutomatically, ambient: beginWithAmbientSound, continuePlayingWithoutSoundOnLostAudioSession: continuePlayingWithoutSoundOnLostAudioSession, storeAfterDownload: storeAfterDownload, isAudioVideoMessage: isAudioVideoMessage) + self.player = MediaPlayer(audioSessionManager: audioSessionManager, postbox: postbox, userLocation: userLocation, userContentType: MediaResourceUserContentType(file: fileReference.media), resourceReference: fileReference.resourceReference(fileReference.media.resource), tempFilePath: tempFilePath, streamable: streamVideo, video: true, preferSoftwareDecoding: false, playAutomatically: false, enableSound: enableSound, baseRate: baseRate, fetchAutomatically: fetchAutomatically, ambient: beginWithAmbientSound, mixWithOthers: mixWithOthers, continuePlayingWithoutSoundOnLostAudioSession: continuePlayingWithoutSoundOnLostAudioSession, storeAfterDownload: storeAfterDownload, isAudioVideoMessage: isAudioVideoMessage) var actionAtEndImpl: (() -> Void)? if enableSound && !loopVideo { @@ -418,7 +422,7 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent func setSoundEnabled(_ value: Bool) { assert(Queue.mainQueue().isCurrent()) if value { - self.player.playOnceWithSound(playAndRecord: true, seek: .none) + self.player.playOnceWithSound(playAndRecord: false, seek: .none) } else { self.player.continuePlayingWithoutSound(seek: .none) } @@ -471,8 +475,8 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent self.player.setForceAudioToSpeaker(forceAudioToSpeaker) } - func continueWithOverridingAmbientMode() { - self.player.continueWithOverridingAmbientMode() + func continueWithOverridingAmbientMode(isAmbient: Bool) { + self.player.continueWithOverridingAmbientMode(isAmbient: isAmbient) } func setBaseRate(_ baseRate: Double) { diff --git a/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift index 76ead9b753..9cf34654b6 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift @@ -388,7 +388,7 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .buffering(initial: true, whilePlaying: true, progress: 0.0, display: true), soundEnabled: true)) } if !self.hasAudioSession { - self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play, activate: { [weak self] _ in + self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play(mixWithOthers: false), activate: { [weak self] _ in self?.hasAudioSession = true self?.player.play() }, deactivate: { [weak self] _ in @@ -430,7 +430,7 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte func playOnceWithSound(playAndRecord: Bool, seek: MediaPlayerSeek, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) { } - func continueWithOverridingAmbientMode() { + func continueWithOverridingAmbientMode(isAmbient: Bool) { } func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) { diff --git a/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift index ddbf795d69..e473711c37 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift @@ -224,7 +224,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: self.approximateDuration, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: self.seekId, status: .buffering(initial: true, whilePlaying: true, progress: 0.0, display: true), soundEnabled: true)) } if !self.hasAudioSession { - self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play, activate: { [weak self] _ in + self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play(mixWithOthers: false), activate: { [weak self] _ in self?.hasAudioSession = true self?.player.play() }, deactivate: { [weak self] _ in @@ -267,7 +267,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent func playOnceWithSound(playAndRecord: Bool, seek: MediaPlayerSeek, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) { } - func continueWithOverridingAmbientMode() { + func continueWithOverridingAmbientMode(isAmbient: Bool) { } func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) { diff --git a/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift index 5fa917db02..17acbf9b5b 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift @@ -164,7 +164,7 @@ final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoContentNode { } } - func continueWithOverridingAmbientMode() { + func continueWithOverridingAmbientMode(isAmbient: Bool) { } func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) {