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 */; };
|
7A99406426E4BFAE002E9CB6 /* VehicleNoteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A99406326E4BFAE002E9CB6 /* VehicleNoteCell.swift */; };
|
||||||
7A9FEEC82529AB23001CA50E /* RxRealmDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FEEC72529AB23001CA50E /* RxRealmDataSource.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 */; };
|
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 */; };
|
7AA7BC3525A5DFB80053A5D5 /* ExceptionCatcher in Frameworks */ = {isa = PBXBuildFile; productRef = 7A813DC02508C4D900CC93B9 /* ExceptionCatcher */; };
|
||||||
7AA7BC3625A5DFB80053A5D5 /* PKHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABDE1C2532F3EB0041AFC6 /* PKHUD */; };
|
7AA7BC3625A5DFB80053A5D5 /* PKHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABDE1C2532F3EB0041AFC6 /* PKHUD */; };
|
||||||
7AAAFAD32C4D0FD00050410D /* ACImageSliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAAFAD22C4D0FD00050410D /* ACImageSliderView.swift */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
7AAAFADB2C4D1E130050410D /* ACImageSliderView+Modifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ACImageSliderView+Modifier.swift"; sourceTree = "<group>"; };
|
||||||
@ -823,6 +827,7 @@
|
|||||||
7A45FB362C2706D000618694 /* Services */ = {
|
7A45FB362C2706D000618694 /* Services */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
7AA515CE2D9ABC9B00EB3418 /* RecordPlayerService */,
|
||||||
7ABDA8012D8704C90083C715 /* VehicleRecordService */,
|
7ABDA8012D8704C90083C715 /* VehicleRecordService */,
|
||||||
7A9519772D80B3B200E69883 /* AudioRecordService */,
|
7A9519772D80B3B200E69883 /* AudioRecordService */,
|
||||||
7AB4E4392D3D3F390006D052 /* VehicleService */,
|
7AB4E4392D3D3F390006D052 /* VehicleService */,
|
||||||
@ -1043,6 +1048,15 @@
|
|||||||
path = RecordsScreen;
|
path = RecordsScreen;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
7AA515CE2D9ABC9B00EB3418 /* RecordPlayerService */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
7AA515CF2D9ABCC800EB3418 /* RecordPlayerService.swift */,
|
||||||
|
7AA515D12D9ABCE600EB3418 /* RecordPlayerServiceProtocol.swift */,
|
||||||
|
);
|
||||||
|
path = RecordPlayerService;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
7AAAFAD12C4D0FB00050410D /* ACImageSlider */ = {
|
7AAAFAD12C4D0FB00050410D /* ACImageSlider */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -1630,6 +1644,7 @@
|
|||||||
7A06E0B32C707E13005731AC /* SettingsServiceProtocol.swift in Sources */,
|
7A06E0B32C707E13005731AC /* SettingsServiceProtocol.swift in Sources */,
|
||||||
7A06E0B52C707E2B005731AC /* SettingsService.swift in Sources */,
|
7A06E0B52C707E2B005731AC /* SettingsService.swift in Sources */,
|
||||||
7AF6D21F2677C1680086EA64 /* Response.swift in Sources */,
|
7AF6D21F2677C1680086EA64 /* Response.swift in Sources */,
|
||||||
|
7AA515D02D9ABCC800EB3418 /* RecordPlayerService.swift in Sources */,
|
||||||
7A60D24D2C5A9D4900D13F7B /* LocationService.swift in Sources */,
|
7A60D24D2C5A9D4900D13F7B /* LocationService.swift in Sources */,
|
||||||
7A60D24F2C5A9DA800D13F7B /* LocationServiceProtocol.swift in Sources */,
|
7A60D24F2C5A9DA800D13F7B /* LocationServiceProtocol.swift in Sources */,
|
||||||
7A761C07267E8E7F0005F28F /* AnyEncodable.swift in Sources */,
|
7A761C07267E8E7F0005F28F /* AnyEncodable.swift in Sources */,
|
||||||
@ -1647,6 +1662,7 @@
|
|||||||
7A60D2512C5A9E4200D13F7B /* GeocoderProtocol.swift in Sources */,
|
7A60D2512C5A9E4200D13F7B /* GeocoderProtocol.swift in Sources */,
|
||||||
7AB4E4382D3D0C5C0006D052 /* VehiclesArchive.swift in Sources */,
|
7AB4E4382D3D0C5C0006D052 /* VehiclesArchive.swift in Sources */,
|
||||||
7A64A21C2C19E87B00284124 /* OsagoDto.swift in Sources */,
|
7A64A21C2C19E87B00284124 /* OsagoDto.swift in Sources */,
|
||||||
|
7AA515D22D9ABCE600EB3418 /* RecordPlayerServiceProtocol.swift in Sources */,
|
||||||
7A809F392D66755B00CF1B3C /* Error+Canceled.swift in Sources */,
|
7A809F392D66755B00CF1B3C /* Error+Canceled.swift in Sources */,
|
||||||
7AF6D21D2677C1680086EA64 /* Osago.swift in Sources */,
|
7AF6D21D2677C1680086EA64 /* Osago.swift in Sources */,
|
||||||
7A1CF81629A42117007962DA /* Realm.swift in Sources */,
|
7A1CF81629A42117007962DA /* Realm.swift in Sources */,
|
||||||
|
|||||||
@ -70,6 +70,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||||||
settingsService: settingsService
|
settingsService: settingsService
|
||||||
)
|
)
|
||||||
container.register(VehicleRecordServiceProtocol.self, instance: vehicleRecordService)
|
container.register(VehicleRecordServiceProtocol.self, instance: vehicleRecordService)
|
||||||
|
container.register(RecordPlayerServiceProtocol.self, instance: RecordPlayerService())
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupRootController(scene: UIScene, openReport number: String?) {
|
func setupRootController(scene: UIScene, openReport number: String?) {
|
||||||
|
|||||||
@ -18,7 +18,7 @@ struct AudioRecordViewModel: Identifiable {
|
|||||||
var date: String
|
var date: String
|
||||||
var onPlay: () -> Void
|
var onPlay: () -> Void
|
||||||
|
|
||||||
init(dto: AudioRecordDto, onPlay: @escaping () -> Void) {
|
init(dto: AudioRecordDto, isPlaying: Bool = false, onPlay: @escaping () -> Void) {
|
||||||
|
|
||||||
self.id = dto.id
|
self.id = dto.id
|
||||||
self.duration = Formatters.time.string(from: dto.duration)
|
self.duration = Formatters.time.string(from: dto.duration)
|
||||||
@ -26,7 +26,7 @@ struct AudioRecordViewModel: Identifiable {
|
|||||||
self.date = Formatters.short.string(
|
self.date = Formatters.short.string(
|
||||||
from: Date(timeIntervalSince1970: dto.addedDate)
|
from: Date(timeIntervalSince1970: dto.addedDate)
|
||||||
)
|
)
|
||||||
self.isPlaying = false
|
self.isPlaying = isPlaying
|
||||||
self.onPlay = onPlay
|
self.onPlay = onPlay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,8 @@ final class RecordsCoordinator {
|
|||||||
let resolver = ServiceContainer.shared
|
let resolver = ServiceContainer.shared
|
||||||
let viewModel = RecordsViewModel(
|
let viewModel = RecordsViewModel(
|
||||||
recordService: resolver.resolve(VehicleRecordServiceProtocol.self),
|
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)
|
let view = RecordsScreen(viewModel: viewModel)
|
||||||
|
|||||||
@ -15,24 +15,31 @@ final class RecordsViewModel: ACHudContainer {
|
|||||||
|
|
||||||
let recordService: VehicleRecordServiceProtocol
|
let recordService: VehicleRecordServiceProtocol
|
||||||
let storageService: StorageServiceProtocol
|
let storageService: StorageServiceProtocol
|
||||||
|
let recordPlayer: RecordPlayerServiceProtocol
|
||||||
|
|
||||||
var hud: ACHud?
|
var hud: ACHud?
|
||||||
var showRecordingAlert: Bool = false
|
var showRecordingAlert: Bool = false
|
||||||
var records: [AudioRecordDto] = []
|
var records: [AudioRecordDto] = []
|
||||||
|
var playingRecord: AudioRecordDto?
|
||||||
|
|
||||||
var recordModels: [AudioRecordViewModel] {
|
var recordModels: [AudioRecordViewModel] {
|
||||||
records.map { record in
|
return records.map { record in
|
||||||
AudioRecordViewModel(dto: record) { [weak self] in
|
AudioRecordViewModel(
|
||||||
|
dto: record,
|
||||||
|
isPlaying: record.id == playingRecord?.id
|
||||||
|
) { [weak self] in
|
||||||
self?.onPlayTapped(record: record)
|
self?.onPlayTapped(record: record)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(recordService: VehicleRecordServiceProtocol,
|
init(recordService: VehicleRecordServiceProtocol,
|
||||||
storageService: StorageServiceProtocol) {
|
storageService: StorageServiceProtocol,
|
||||||
|
recordPlayer: RecordPlayerServiceProtocol) {
|
||||||
|
|
||||||
self.recordService = recordService
|
self.recordService = recordService
|
||||||
self.storageService = storageService
|
self.storageService = storageService
|
||||||
|
self.recordPlayer = recordPlayer
|
||||||
}
|
}
|
||||||
|
|
||||||
func onAppear() async {
|
func onAppear() async {
|
||||||
@ -65,5 +72,17 @@ final class RecordsViewModel: ACHudContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func onPlayTapped(record: AudioRecordDto) {
|
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()
|
date = Date()
|
||||||
let fileName = "recording-\(date.timeIntervalSince1970).m4a"
|
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
|
self.url = url
|
||||||
|
|
||||||
try await recordService.startRecording(to: url)
|
try await recordService.startRecording(to: url)
|
||||||
|
|||||||
@ -50,4 +50,6 @@ public enum Constants {
|
|||||||
|
|
||||||
public static let reportLinkTokenSecret = "#TheTruthIsOutThere"
|
public static let reportLinkTokenSecret = "#TheTruthIsOutThere"
|
||||||
public static let reportLinkBaseURL = "https://auto.aliencat.pro/report.html"
|
public static let reportLinkBaseURL = "https://auto.aliencat.pro/report.html"
|
||||||
|
|
||||||
|
public static let audioRecordsFolder = "recordings"
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user