Playing audio records
This commit is contained in:
parent
59530b62ba
commit
94e4605af3
@ -146,6 +146,8 @@
|
||||
7A99406426E4BFAE002E9CB6 /* VehicleNoteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A99406326E4BFAE002E9CB6 /* VehicleNoteCell.swift */; };
|
||||
7A9FEEC82529AB23001CA50E /* RxRealmDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FEEC72529AB23001CA50E /* RxRealmDataSource.swift */; };
|
||||
7AA514E02D0B75B3001CAC50 /* StorageService+Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA514DF2D0B75B3001CAC50 /* StorageService+Events.swift */; };
|
||||
7AA515D02D9ABCC800EB3418 /* RecordPlayerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA515CF2D9ABCC800EB3418 /* RecordPlayerService.swift */; };
|
||||
7AA515D22D9ABCE600EB3418 /* RecordPlayerServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA515D12D9ABCE600EB3418 /* RecordPlayerServiceProtocol.swift */; };
|
||||
7AA7BC3525A5DFB80053A5D5 /* ExceptionCatcher in Frameworks */ = {isa = PBXBuildFile; productRef = 7A813DC02508C4D900CC93B9 /* ExceptionCatcher */; };
|
||||
7AA7BC3625A5DFB80053A5D5 /* PKHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABDE1C2532F3EB0041AFC6 /* PKHUD */; };
|
||||
7AAAFAD32C4D0FD00050410D /* ACImageSliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAAFAD22C4D0FD00050410D /* ACImageSliderView.swift */; };
|
||||
@ -444,6 +446,8 @@
|
||||
7A99406326E4BFAE002E9CB6 /* VehicleNoteCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleNoteCell.swift; sourceTree = "<group>"; };
|
||||
7A9FEEC72529AB23001CA50E /* RxRealmDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxRealmDataSource.swift; sourceTree = "<group>"; };
|
||||
7AA514DF2D0B75B3001CAC50 /* StorageService+Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StorageService+Events.swift"; sourceTree = "<group>"; };
|
||||
7AA515CF2D9ABCC800EB3418 /* RecordPlayerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordPlayerService.swift; sourceTree = "<group>"; };
|
||||
7AA515D12D9ABCE600EB3418 /* RecordPlayerServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordPlayerServiceProtocol.swift; sourceTree = "<group>"; };
|
||||
7AAAFAD22C4D0FD00050410D /* ACImageSliderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACImageSliderView.swift; sourceTree = "<group>"; };
|
||||
7AAAFAD92C4D1AFE0050410D /* Zoomable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Zoomable.swift; sourceTree = "<group>"; };
|
||||
7AAAFADB2C4D1E130050410D /* ACImageSliderView+Modifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ACImageSliderView+Modifier.swift"; sourceTree = "<group>"; };
|
||||
@ -823,6 +827,7 @@
|
||||
7A45FB362C2706D000618694 /* Services */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7AA515CE2D9ABC9B00EB3418 /* RecordPlayerService */,
|
||||
7ABDA8012D8704C90083C715 /* VehicleRecordService */,
|
||||
7A9519772D80B3B200E69883 /* AudioRecordService */,
|
||||
7AB4E4392D3D3F390006D052 /* VehicleService */,
|
||||
@ -1043,6 +1048,15 @@
|
||||
path = RecordsScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7AA515CE2D9ABC9B00EB3418 /* RecordPlayerService */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7AA515CF2D9ABCC800EB3418 /* RecordPlayerService.swift */,
|
||||
7AA515D12D9ABCE600EB3418 /* RecordPlayerServiceProtocol.swift */,
|
||||
);
|
||||
path = RecordPlayerService;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7AAAFAD12C4D0FB00050410D /* ACImageSlider */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1630,6 +1644,7 @@
|
||||
7A06E0B32C707E13005731AC /* SettingsServiceProtocol.swift in Sources */,
|
||||
7A06E0B52C707E2B005731AC /* SettingsService.swift in Sources */,
|
||||
7AF6D21F2677C1680086EA64 /* Response.swift in Sources */,
|
||||
7AA515D02D9ABCC800EB3418 /* RecordPlayerService.swift in Sources */,
|
||||
7A60D24D2C5A9D4900D13F7B /* LocationService.swift in Sources */,
|
||||
7A60D24F2C5A9DA800D13F7B /* LocationServiceProtocol.swift in Sources */,
|
||||
7A761C07267E8E7F0005F28F /* AnyEncodable.swift in Sources */,
|
||||
@ -1647,6 +1662,7 @@
|
||||
7A60D2512C5A9E4200D13F7B /* GeocoderProtocol.swift in Sources */,
|
||||
7AB4E4382D3D0C5C0006D052 /* VehiclesArchive.swift in Sources */,
|
||||
7A64A21C2C19E87B00284124 /* OsagoDto.swift in Sources */,
|
||||
7AA515D22D9ABCE600EB3418 /* RecordPlayerServiceProtocol.swift in Sources */,
|
||||
7A809F392D66755B00CF1B3C /* Error+Canceled.swift in Sources */,
|
||||
7AF6D21D2677C1680086EA64 /* Osago.swift in Sources */,
|
||||
7A1CF81629A42117007962DA /* Realm.swift in Sources */,
|
||||
|
||||
@ -70,6 +70,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
settingsService: settingsService
|
||||
)
|
||||
container.register(VehicleRecordServiceProtocol.self, instance: vehicleRecordService)
|
||||
container.register(RecordPlayerServiceProtocol.self, instance: RecordPlayerService())
|
||||
}
|
||||
|
||||
func setupRootController(scene: UIScene, openReport number: String?) {
|
||||
|
||||
@ -18,7 +18,7 @@ struct AudioRecordViewModel: Identifiable {
|
||||
var date: String
|
||||
var onPlay: () -> Void
|
||||
|
||||
init(dto: AudioRecordDto, onPlay: @escaping () -> Void) {
|
||||
init(dto: AudioRecordDto, isPlaying: Bool = false, onPlay: @escaping () -> Void) {
|
||||
|
||||
self.id = dto.id
|
||||
self.duration = Formatters.time.string(from: dto.duration)
|
||||
@ -26,7 +26,7 @@ struct AudioRecordViewModel: Identifiable {
|
||||
self.date = Formatters.short.string(
|
||||
from: Date(timeIntervalSince1970: dto.addedDate)
|
||||
)
|
||||
self.isPlaying = false
|
||||
self.isPlaying = isPlaying
|
||||
self.onPlay = onPlay
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,8 @@ final class RecordsCoordinator {
|
||||
let resolver = ServiceContainer.shared
|
||||
let viewModel = RecordsViewModel(
|
||||
recordService: resolver.resolve(VehicleRecordServiceProtocol.self),
|
||||
storageService: resolver.resolve(StorageServiceProtocol.self)
|
||||
storageService: resolver.resolve(StorageServiceProtocol.self),
|
||||
recordPlayer: resolver.resolve(RecordPlayerServiceProtocol.self)
|
||||
)
|
||||
|
||||
let view = RecordsScreen(viewModel: viewModel)
|
||||
|
||||
@ -15,24 +15,31 @@ final class RecordsViewModel: ACHudContainer {
|
||||
|
||||
let recordService: VehicleRecordServiceProtocol
|
||||
let storageService: StorageServiceProtocol
|
||||
let recordPlayer: RecordPlayerServiceProtocol
|
||||
|
||||
var hud: ACHud?
|
||||
var showRecordingAlert: Bool = false
|
||||
var records: [AudioRecordDto] = []
|
||||
var playingRecord: AudioRecordDto?
|
||||
|
||||
var recordModels: [AudioRecordViewModel] {
|
||||
records.map { record in
|
||||
AudioRecordViewModel(dto: record) { [weak self] in
|
||||
return records.map { record in
|
||||
AudioRecordViewModel(
|
||||
dto: record,
|
||||
isPlaying: record.id == playingRecord?.id
|
||||
) { [weak self] in
|
||||
self?.onPlayTapped(record: record)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(recordService: VehicleRecordServiceProtocol,
|
||||
storageService: StorageServiceProtocol) {
|
||||
storageService: StorageServiceProtocol,
|
||||
recordPlayer: RecordPlayerServiceProtocol) {
|
||||
|
||||
self.recordService = recordService
|
||||
self.storageService = storageService
|
||||
self.recordPlayer = recordPlayer
|
||||
}
|
||||
|
||||
func onAppear() async {
|
||||
@ -65,5 +72,17 @@ final class RecordsViewModel: ACHudContainer {
|
||||
}
|
||||
|
||||
func onPlayTapped(record: AudioRecordDto) {
|
||||
do {
|
||||
playingRecord = record
|
||||
try recordPlayer.play(record: record) { [weak self] dto, error in
|
||||
self?.playingRecord = nil
|
||||
if let error {
|
||||
self?.hud = .error(error)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
playingRecord = nil
|
||||
hud = .error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,80 @@
|
||||
//
|
||||
// RecordPlayerService.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 31.03.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
|
||||
public final class RecordPlayerService: NSObject {
|
||||
|
||||
var player: AVAudioPlayer?
|
||||
var record: AudioRecordDto?
|
||||
var onStop: ((AudioRecordDto, Error?) -> Void)?
|
||||
}
|
||||
|
||||
extension RecordPlayerService: RecordPlayerServiceProtocol {
|
||||
|
||||
public func play(record: AudioRecordDto, onStop: ((AudioRecordDto, Error?) -> Void)?) throws {
|
||||
|
||||
player?.stop()
|
||||
|
||||
let url = try FileManager.default.url(for: record.path, in: Constants.audioRecordsFolder)
|
||||
player = try AVAudioPlayer(contentsOf: url)
|
||||
player?.delegate = self
|
||||
|
||||
self.record = record
|
||||
self.onStop = onStop
|
||||
|
||||
// try AVAudioSession.sharedInstance().setCategory(.playback)
|
||||
// try AVAudioSession.sharedInstance().setActive(true)
|
||||
|
||||
print("==== playing started ====")
|
||||
player?.play()
|
||||
}
|
||||
|
||||
public func pause() {
|
||||
|
||||
player?.pause()
|
||||
}
|
||||
|
||||
public var currentPlayingId: TimeInterval? {
|
||||
guard player?.isPlaying == true else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return record?.id
|
||||
}
|
||||
}
|
||||
|
||||
extension RecordPlayerService: AVAudioPlayerDelegate {
|
||||
|
||||
public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
|
||||
guard let record else {
|
||||
return
|
||||
}
|
||||
|
||||
print("==== playing stopped ====")
|
||||
onStop?(record, nil)
|
||||
reset()
|
||||
}
|
||||
|
||||
public func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: (any Error)?) {
|
||||
guard let record else {
|
||||
return
|
||||
}
|
||||
|
||||
print("==== playing error ====")
|
||||
onStop?(record, error)
|
||||
reset()
|
||||
}
|
||||
|
||||
func reset() {
|
||||
|
||||
onStop = nil
|
||||
player = nil
|
||||
record = nil
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
//
|
||||
// RecordPlayerServiceProtocol.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 31.03.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol RecordPlayerServiceProtocol {
|
||||
|
||||
var currentPlayingId: TimeInterval? { get }
|
||||
|
||||
func play(record: AudioRecordDto, onStop: ((AudioRecordDto, Error?) -> Void)?) throws
|
||||
func pause()
|
||||
}
|
||||
@ -95,7 +95,7 @@ extension VehicleRecordService: VehicleRecordServiceProtocol {
|
||||
|
||||
date = Date()
|
||||
let fileName = "recording-\(date.timeIntervalSince1970).m4a"
|
||||
let url = try FileManager.default.url(for: fileName, in: "recordings")
|
||||
let url = try FileManager.default.url(for: fileName, in: Constants.audioRecordsFolder)
|
||||
self.url = url
|
||||
|
||||
try await recordService.startRecording(to: url)
|
||||
|
||||
@ -50,4 +50,6 @@ public enum Constants {
|
||||
|
||||
public static let reportLinkTokenSecret = "#TheTruthIsOutThere"
|
||||
public static let reportLinkBaseURL = "https://auto.aliencat.pro/report.html"
|
||||
|
||||
public static let audioRecordsFolder = "recordings"
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user