Tuesday, June 25, 2024
HomeiOS Developmentios - Stopping audio playblack when one other disclosure group is opened...

ios – Stopping audio playblack when one other disclosure group is opened in voice recorder app


I am engaged on a voice recorder app utilizing SwiftUI. I presently have the participant interface in a DisclosureGroup, as so: https://imgur.com/a/GmLvmMh. The audio stops playback whenever you shut the present DisclosureGroup, and in addition just one DisclosureGroup may be opened at a time. Nevertheless, I can not make it in order that the audio stops playback when opening one other DisclosureGroup.

The code for my participant is right here:

import Basis
import AVFoundation
import SwiftUI
import Mix

class Participant: NSObject, ObservableObject, AVAudioPlayerDelegate {
    var participant: AVAudioPlayer?
    let objectWillChange = PassthroughSubject<Void, By no means>()
    
    var isPlaying = false {
        didSet {
            objectWillChange.ship()
        }
    }
    
    @Printed var currentTime: TimeInterval = 0
    non-public var timer: AnyCancellable?

    init(soundURL: URL) throws {
        tremendous.init()
        if FileManager().fileExists(atPath: soundURL.path) {
            do {
                self.participant = attempt AVAudioPlayer(contentsOf: soundURL)
                participant?.prepareToPlay()
                self.participant?.delegate = self
            } catch {
                throw Errors.FailedToPlayURL
            }
        } else {
            print("URL not legitimate!")
        }
    }
    
    func play() {
        self.participant?.play()
        isPlaying = true
        startTimer()
    }
    
    func pause() {
        if isPlaying {
            self.participant?.pause()
            isPlaying = false
            stopTimer()
        }
    }
    
    func cease() {
        participant?.cease()
        isPlaying = false
        resetPlayback()
    }
    
    func search(to time: TimeInterval) {
        participant?.currentTime = time
        currentTime = time
    }
    
    var period: TimeInterval {
        participant?.period ?? 0
    }
    
    non-public func startTimer() {
        timer = Timer.publish(each: 0.1, on: .primary, in: .widespread)
            .autoconnect()
            .sink { [weak self] _ in
                guard let self = self else { return }
                self.currentTime = self.participant?.currentTime ?? 0
                self.objectWillChange.ship()
            }
    }
    
    non-public func stopTimer() {
        timer?.cancel()
        timer = nil
    }
    
    func audioPlayerDidFinishPlaying(_ participant: AVAudioPlayer, efficiently flag: Bool) {
        if flag {
            isPlaying = false
            stopTimer()
            resetPlayback()
        }
    }
    
    non-public func resetPlayback() {
        currentTime = 0
        participant?.currentTime = 0
        objectWillChange.ship()
    }
}

The code for my PlayerViewModel is right here:

import Basis
import Mix

class PlayerViewModel: ObservableObject {
    @Printed var currentTime: TimeInterval = 0
    var participant: Participant
    non-public var cancellables = Set<AnyCancellable>()
    non-public var seekingSubject = PassthroughSubject<TimeInterval, By no means>()

    init(participant: Participant) {
        self.participant = participant
        self.participant.objectWillChange
            .sink { [weak self] in
                self?.currentTime = self?.participant.currentTime ?? 0
            }
            .retailer(in: &cancellables)
        
        seekingSubject
            .throttle(for: .milliseconds(100), scheduler: RunLoop.primary, newest: true)
            .sink { [weak self] time in
                self?.participant.search(to: time)
            }
            .retailer(in: &cancellables)
    }
    
    func play() {
        participant.play()
    }
    
    func pause() {
        participant.pause()
    }
    
    func cease() {
        participant.cease()
    }
    
    func search(to time: TimeInterval) {
        seekingSubject.ship(time)
    }
    
    var period: TimeInterval {
        participant.period
    }
}

Lastly, the code for my PlayerView is right here. At the moment, the best way I am detecting a disclosure group is opened is by the URL it is certain to. This fashion when a disclosure group is opened, opening one other one closes the present one. I attempted placing viewModel.cease() in right here, however to no avail:

import SwiftUI
import AVFoundation

struct PlayerView: View {
    @State var soundURL: URL
    @Binding var openedGroup: URL?
    @State non-public var isOpened: Bool = false

    @StateObject non-public var viewModel: PlayerViewModel
    @State non-public var sliderValue: TimeInterval = 0

    init(soundURL: URL, openedGroup: Binding<URL?>) {
        self._soundURL = State(initialValue: soundURL)
        self._openedGroup = openedGroup
        let participant = attempt? Participant(soundURL: soundURL)
        self._viewModel = StateObject(wrappedValue: PlayerViewModel(participant: participant!))
    }

    var physique: some View {
        DisclosureGroup(isExpanded: Binding(
            get: { self.openedGroup == self.soundURL },
            set: { newValue in
                if newValue {
                    self.openedGroup = self.soundURL
                } else if self.openedGroup == self.soundURL {
                    self.openedGroup = nil
                    viewModel.cease()
                }
            }
        )) {
            VStack {
                Slider(worth: $sliderValue, in: 0...viewModel.period, onEditingChanged: { enhancing in
                    if enhancing {
                        viewModel.pause()
                    } else {
                        viewModel.search(to: sliderValue)
                        viewModel.play()
                    }
                })
                .padding()
                .onChange(of: viewModel.currentTime) {
                    sliderValue = viewModel.currentTime
                }

                HStack {
                    Textual content(timeString(from: viewModel.currentTime))
                    Spacer()
                    Textual content(timeString(from: viewModel.period))
                }
                .padding(.horizontal)

                HStack {
                    Spacer()
                    Picture(systemName: viewModel.participant.isPlaying ? "pause.fill" : "play.fill")
                        .onTapGesture {
                            if viewModel.participant.isPlaying {
                                viewModel.pause()
                            } else {
                                viewModel.play()
                            }
                        }
                    Spacer()
                }
                Spacer()
                FileNameButtonView(soundURL: soundURL)
            }
        } label: {
            Textual content(soundURL.lastPathComponent)
        }
    }

    non-public func timeString(from timeInterval: TimeInterval) -> String {
        let minutes = Int(timeInterval) / 60
        let seconds = Int(timeInterval) % 60
        return String(format: "%02d:%02d", minutes, seconds)
    }
}

If you’d like to try the complete repo, it’s right here: https://github.com/aabagdi/MemoMan/.
Thanks for any assist!



Supply hyperlink

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments