mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
d8c061df1c
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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?()
|
||||
|
@ -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<MediaPlayerStatus>, audioLevelPipe: ValuePipe<Float>, 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<MediaPlayerStatus>, audioLevelPipe: ValuePipe<Float>, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Float>, 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<Float>, 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<Float>, 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<Float>, 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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)")
|
||||
|
@ -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)
|
||||
|
@ -70,7 +70,7 @@ extension TelegramUser {
|
||||
|
||||
var storiesHidden: Bool?
|
||||
if !isMin {
|
||||
storiesHidden = (flags2 & (1 << 5)) != 0
|
||||
storiesHidden = (flags2 & (1 << 3)) != 0
|
||||
}
|
||||
|
||||
var botInfo: BotUserInfo?
|
||||
|
@ -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<Bool>(false, ignoreRepeated: true)
|
||||
private let contentWantsVolumeButtonMonitoring = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private let isMuteSwitchOnPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private let volumeButtonsListenerShouldBeActive = Promise<Bool>()
|
||||
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<StoryContentItem.AudioMode>(.ambient, ignoreRepeated: true)
|
||||
|
||||
private let inputMediaNodeDataPromise = Promise<ChatEntityKeyboardInputNode.InputData>()
|
||||
|
||||
@ -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) {
|
||||
|
@ -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? {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Empty>
|
||||
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() {
|
||||
|
@ -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)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ final class ManagedAudioRecorderContext {
|
||||
}
|
||||
return ActionDisposable {
|
||||
}
|
||||
}), playAndRecord: true, ambient: false, forceAudioToSpeaker: false, baseRate: 1.0, audioLevelPipe: ValuePipe<Float>(), updatedRate: {
|
||||
}), playAndRecord: true, ambient: false, mixWithOthers: false, forceAudioToSpeaker: false, baseRate: 1.0, audioLevelPipe: ValuePipe<Float>(), updatedRate: {
|
||||
}, audioPaused: {})
|
||||
self.toneRenderer = toneRenderer
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -115,7 +115,7 @@ final class OverlayInstantVideoNode: OverlayMediaItemNode {
|
||||
self.videoNode.playOnceWithSound(playAndRecord: playAndRecord)
|
||||
}
|
||||
|
||||
func continueWithOverridingAmbientMode() {
|
||||
func continueWithOverridingAmbientMode(isAmbient: Bool) {
|
||||
}
|
||||
|
||||
func pause() {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -164,7 +164,7 @@ final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
func continueWithOverridingAmbientMode() {
|
||||
func continueWithOverridingAmbientMode(isAmbient: Bool) {
|
||||
}
|
||||
|
||||
func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user