mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Mute switch monitoring
This commit is contained in:
parent
e316f0521b
commit
f59df97175
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -601,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:
|
||||
@ -1169,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,7 @@ final class StoryItemContentComponent: Component {
|
||||
streamVideo: .story,
|
||||
loopVideo: true,
|
||||
enableSound: true,
|
||||
beginWithAmbientSound: environment.sharedState.useAmbientMode,
|
||||
beginWithAmbientSound: component.useAmbientMode,
|
||||
mixWithOthers: true,
|
||||
useLargeThumbnail: false,
|
||||
autoFetchFullSizeThumbnail: false,
|
||||
@ -216,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
|
||||
}
|
||||
@ -605,7 +617,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
|
||||
func enterAmbientMode() {
|
||||
func enterAmbientMode(ambient: Bool) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
@ -613,7 +625,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)
|
||||
@ -1027,7 +1039,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
|
||||
@ -2163,7 +2176,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"
|
||||
|
@ -115,7 +115,7 @@ final class OverlayInstantVideoNode: OverlayMediaItemNode {
|
||||
self.videoNode.playOnceWithSound(playAndRecord: playAndRecord)
|
||||
}
|
||||
|
||||
func continueWithOverridingAmbientMode() {
|
||||
func continueWithOverridingAmbientMode(isAmbient: Bool) {
|
||||
}
|
||||
|
||||
func pause() {
|
||||
|
@ -422,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)
|
||||
}
|
||||
@ -475,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) {
|
||||
|
@ -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) {
|
||||
|
@ -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