|
@@ -0,0 +1,601 @@
|
|
|
|
+//
|
|
|
|
+// TSEditAudioVideoBaseVC.swift
|
|
|
|
+// AIRingtone
|
|
|
|
+//
|
|
|
|
+// Created by 100Years on 2025/3/26.
|
|
|
|
+//
|
|
|
|
+
|
|
|
|
+class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelegate {
|
|
|
|
+
|
|
|
|
+ var titleName:String = "Edit".localized
|
|
|
|
+ override func createView() {
|
|
|
|
+
|
|
|
|
+ setPageTitle(titleName)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ var nameLabel: UILabel!
|
|
|
|
+ var nameButton: UIButton!
|
|
|
|
+
|
|
|
|
+ var trackContentView: UIView!
|
|
|
|
+ var trackBgView: UIView!
|
|
|
|
+
|
|
|
|
+ var timeLabel: UILabel!
|
|
|
|
+ var cutButton: UIButton!
|
|
|
|
+ var playButton: UIButton!
|
|
|
|
+ var undoButton: UIButton!
|
|
|
|
+
|
|
|
|
+ var fadeinSlider: UISlider!
|
|
|
|
+ var fadeinTimeLabel: UILabel!
|
|
|
|
+ var fadeoutSlider: UISlider!
|
|
|
|
+ var fadeoutTimeLabel: UILabel!
|
|
|
|
+
|
|
|
|
+ var doneButton: UIButton!
|
|
|
|
+
|
|
|
|
+ var dragLeftView: UIView!
|
|
|
|
+ var startTimeLabel: UILabel!
|
|
|
|
+ var dragRightView: UIView!
|
|
|
|
+ var endTimeLabel: UILabel!
|
|
|
|
+ var progressLine: UIView!
|
|
|
|
+ weak var nameInputTextField: UITextField?
|
|
|
|
+
|
|
|
|
+ var trackView: ZHWaveformView?
|
|
|
|
+
|
|
|
|
+ private lazy var player = TSBusinessAudioPlayer()
|
|
|
|
+ private lazy var audioTool = AudioTool()
|
|
|
|
+
|
|
|
|
+ var ringModel: TSRingModel?
|
|
|
|
+ lazy var operationCache: [TSRingModel] = []
|
|
|
|
+ var tempMp3Path: String?
|
|
|
|
+ var needClearTemp : Bool = false
|
|
|
|
+ override func viewDidLoad() {
|
|
|
|
+ super.viewDidLoad()
|
|
|
|
+
|
|
|
|
+ view.backgroundColor = .clear
|
|
|
|
+// saveButton.set { [weak self] in
|
|
|
|
+// self?.saveButtonClick()
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+ if let ring = ringModel {
|
|
|
|
+ operationCache.append(ring)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ setupUI()
|
|
|
|
+
|
|
|
|
+ let leftPanRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.leftPanRecognizer(sender:)))
|
|
|
|
+ dragLeftView.addGestureRecognizer(leftPanRecognizer)
|
|
|
|
+ dragLeftView.isUserInteractionEnabled = true
|
|
|
|
+
|
|
|
|
+ let rightPanRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.rightPanRecognizer(sender:)))
|
|
|
|
+ dragRightView.addGestureRecognizer(rightPanRecognizer)
|
|
|
|
+ dragRightView.isUserInteractionEnabled = true
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ override func viewDidDisappear(_ animated: Bool) {
|
|
|
|
+ super.viewDidDisappear(animated)
|
|
|
|
+
|
|
|
|
+ if needClearTemp {
|
|
|
|
+ deleteTempFiles()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ func deleteTempFiles() {
|
|
|
|
+ if let tempMp3Path = tempMp3Path {
|
|
|
|
+ let mp3Dir = NSString(string: tempMp3Path).deletingLastPathComponent
|
|
|
|
+ if FileManager.default.isDeletableFile(atPath: mp3Dir) {
|
|
|
|
+ let fileUrl = URL(fileURLWithPath: mp3Dir)
|
|
|
|
+ do {
|
|
|
|
+ try FileManager.default.removeItem(at: fileUrl)
|
|
|
|
+ } catch {
|
|
|
|
+ dePrint("删除文件失败")
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ override func viewWillDisappear(_ animated: Bool) {
|
|
|
|
+ super.viewWillDisappear(animated)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ override func viewWillAppear(_ animated: Bool) {
|
|
|
|
+ super.viewWillAppear(animated)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ lazy var previousBounds: CGRect = .zero
|
|
|
|
+ override func viewDidLayoutSubviews() {
|
|
|
|
+ super.viewDidLayoutSubviews()
|
|
|
|
+
|
|
|
|
+ if previousBounds == .zero {
|
|
|
|
+ previousBounds = view.bounds
|
|
|
|
+ DispatchQueue.main.async {
|
|
|
|
+ self.reloadTrackView()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ deinit {
|
|
|
|
+ dePrint("RingEditViewController 销毁了")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ func reloadTrackView() {
|
|
|
|
+ guard let current = ringModel else {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ var ringFilePath: String?
|
|
|
|
+ if let fileName = current.fileName,
|
|
|
|
+ let filePath = RingDownloadManager.shared.filePath(with: fileName),
|
|
|
|
+ FileManager.default.fileExists(atPath: filePath) {
|
|
|
|
+ ringFilePath = filePath
|
|
|
|
+ } else if let filePath = RingDownloadManager.shared.filePath(for: current.fileUrl),
|
|
|
|
+ FileManager.default.fileExists(atPath: filePath) {
|
|
|
|
+ ringFilePath = filePath
|
|
|
|
+ }
|
|
|
|
+ guard let ringFilePath = ringFilePath else {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ tempMp3Path = ringFilePath
|
|
|
|
+
|
|
|
|
+ trackView?.removeFromSuperview()
|
|
|
|
+ startCropRate = 0
|
|
|
|
+ endCropRate = 1.0
|
|
|
|
+ dragLeftView.centerX = trackContentView.x
|
|
|
|
+ dragRightView.centerX = trackContentView.frame.maxX
|
|
|
|
+
|
|
|
|
+ let url = URL(fileURLWithPath: ringFilePath)
|
|
|
|
+
|
|
|
|
+ let waveform = ZHWaveformView(
|
|
|
|
+ frame: CGRect(x: 0, y: 20, width: trackContentView.width, height: trackContentView.height - 40),
|
|
|
|
+ fileURL: url
|
|
|
|
+ )
|
|
|
|
+ waveform.backgroundColor = .clear
|
|
|
|
+
|
|
|
|
+ // color
|
|
|
|
+ waveform.beginningPartColor = .white.withAlphaComponent(0.2)
|
|
|
|
+ waveform.endPartColor = .white.withAlphaComponent(0.2)
|
|
|
|
+ waveform.wavesColor = "#55A0E9".uiColor
|
|
|
|
+
|
|
|
|
+ // 0 ~ 1
|
|
|
|
+ waveform.trackScale = 0.2
|
|
|
|
+
|
|
|
|
+ waveform.waveformDelegate = self
|
|
|
|
+ waveform.croppedDelegate = self
|
|
|
|
+
|
|
|
|
+ trackContentView.insertSubview(waveform, at: 0)
|
|
|
|
+ trackView = waveform
|
|
|
|
+
|
|
|
|
+ timeLabel.text = current.duration.mmss
|
|
|
|
+ undoButton.isEnabled = operationCache.count > 1
|
|
|
|
+ cutButton.isEnabled = false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ func setupUI() {
|
|
|
|
+ nameLabel.text = ringModel?.title ?? ""
|
|
|
|
+
|
|
|
|
+ doneButton.titleLabel?.numberOfLines = 2
|
|
|
|
+ doneButton.titleLabel?.textAlignment = .center
|
|
|
|
+ doneButton.setTitle("Export to GarageBand\nSet Ringtones".localized, for: .normal)
|
|
|
|
+ doneButton.setGradient(colors: AppTheme.gradientColors, index: 0)
|
|
|
|
+
|
|
|
|
+ fadeinSlider.setThumbImage(UIImage(named: "ic-ring-edit-slider"), for: .normal)
|
|
|
|
+ fadeinSlider.addTarget(self, action: #selector(sliderBeginTap(_:)), for: .touchDown)
|
|
|
|
+ fadeinSlider.addTarget(self, action: #selector(sliderEndTap(_:)), for: .touchUpInside)
|
|
|
|
+ fadeinSlider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
|
|
|
|
+
|
|
|
|
+ fadeoutSlider.setThumbImage(UIImage(named: "ic-ring-edit-slider"), for: .normal)
|
|
|
|
+ fadeoutSlider.addTarget(self, action: #selector(sliderBeginTap(_:)), for: .touchDown)
|
|
|
|
+ fadeoutSlider.addTarget(self, action: #selector(sliderEndTap(_:)), for: .touchUpInside)
|
|
|
|
+ fadeoutSlider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ lazy var fadeInDuration: Int = 0 {
|
|
|
|
+ didSet {
|
|
|
|
+ fadeinTimeLabel.text = "\(fadeInDuration)s"
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ lazy var fadeOutDuration: Int = 0 {
|
|
|
|
+ didSet {
|
|
|
|
+ fadeoutTimeLabel.text = "\(fadeOutDuration)s"
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @objc func sliderBeginTap(_ slider: UISlider) {
|
|
|
|
+ suspendPlay()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @objc func sliderEndTap(_ slider: UISlider) {
|
|
|
|
+ autoPlayAfterMove()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @objc func sliderValueChanged(_ slider: UISlider) {
|
|
|
|
+// print("---\(slider.value)")
|
|
|
|
+ if player.state == .playing {
|
|
|
|
+ player.pause()
|
|
|
|
+ }
|
|
|
|
+ if slider == fadeinSlider {
|
|
|
|
+ fadeInDuration = Int(slider.value.rounded(.toNearestOrEven))
|
|
|
|
+ } else if slider == fadeoutSlider {
|
|
|
|
+ fadeOutDuration = Int(slider.value.rounded(.toNearestOrEven))
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ lazy var startCropRate: CGFloat = 0 {
|
|
|
|
+ didSet {
|
|
|
|
+ let time = startDuration
|
|
|
|
+ cutButton.isEnabled = true
|
|
|
|
+
|
|
|
|
+// print("---start: \(time)")
|
|
|
|
+ startTimeLabel.text = time.mmss
|
|
|
|
+ timeLabel.text = (endDuration - startDuration).mmss
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 裁剪起始时间
|
|
|
|
+ var startDuration: Double {
|
|
|
|
+ guard let model = ringModel else {
|
|
|
|
+ return 0
|
|
|
|
+ }
|
|
|
|
+ return startCropRate * model.duration
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ lazy var previousLeftX: CGFloat = 0
|
|
|
|
+
|
|
|
|
+ var startMinCenterX: CGFloat {
|
|
|
|
+ return trackContentView.x
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var startMaxCenterX: CGFloat {
|
|
|
|
+ guard let model = ringModel else {
|
|
|
|
+ return 0
|
|
|
|
+ }
|
|
|
|
+ let ratio = (endCropRate * model.duration - 10) / model.duration
|
|
|
|
+ return ratio * trackContentView.width + trackContentView.x
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var endMinCenterX: CGFloat {
|
|
|
|
+ guard let model = ringModel else {
|
|
|
|
+ return 0
|
|
|
|
+ }
|
|
|
|
+ let ratio = (startCropRate * model.duration + 10) / model.duration
|
|
|
|
+ return ratio * trackContentView.width + trackContentView.x
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var endMaxCenterX: CGFloat {
|
|
|
|
+ return trackContentView.frame.maxX
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 拖拽时被暂停,拖拽结束后,继续播放
|
|
|
|
+ lazy var isSuspendByAction = false
|
|
|
|
+ @objc private func leftPanRecognizer(sender: UIPanGestureRecognizer) {
|
|
|
|
+ let limitMinCenterX: CGFloat = startMinCenterX
|
|
|
|
+ let limitMaxCenterX: CGFloat = startMaxCenterX
|
|
|
|
+ guard limitMaxCenterX > limitMinCenterX else {
|
|
|
|
+ if sender.state == .began {
|
|
|
|
+ THUD.toast("No less than 10s".localized())
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if sender.state == .began {
|
|
|
|
+ suspendPlay()
|
|
|
|
+ } else if sender.state == .changed {
|
|
|
|
+ // 修改位置
|
|
|
|
+ let newPoint = sender.translation(in: trackBgView)
|
|
|
|
+ var center = dragLeftView.center
|
|
|
|
+ center.x = previousLeftX + newPoint.x
|
|
|
|
+ guard center.x > limitMinCenterX,
|
|
|
|
+ center.x < limitMaxCenterX else {
|
|
|
|
+ // 越界,拖不动了, 最少10s
|
|
|
|
+ if endDuration - startDuration < 11 {
|
|
|
|
+ THUD.toast("No less than 10s".localized())
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ dragLeftView.center = center
|
|
|
|
+
|
|
|
|
+ } else if sender.state == .ended || sender.state == .failed {
|
|
|
|
+ previousLeftX = dragLeftView.center.x
|
|
|
|
+ autoPlayAfterMove()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 边界校验
|
|
|
|
+ if dragLeftView.centerX < limitMinCenterX {
|
|
|
|
+ dragLeftView.centerX = limitMinCenterX
|
|
|
|
+ }
|
|
|
|
+ if dragLeftView.centerX > limitMaxCenterX {
|
|
|
|
+ dragLeftView.centerX = limitMaxCenterX
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let position = dragLeftView.centerX - trackContentView.x
|
|
|
|
+ trackView?.updateLeftCroppedPosition(position)
|
|
|
|
+ startCropRate = position / trackContentView.width
|
|
|
|
+ progressLine.centerX = dragLeftView.centerX
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ lazy var endCropRate: CGFloat = 1.0 {
|
|
|
|
+ didSet {
|
|
|
|
+ cutButton.isEnabled = true
|
|
|
|
+
|
|
|
|
+ let time = endDuration
|
|
|
|
+// print("---end: \(time)")
|
|
|
|
+ endTimeLabel.text = time.mmss
|
|
|
|
+ timeLabel.text = (endDuration - startDuration).mmss
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var endDuration: Double {
|
|
|
|
+ guard let model = ringModel else {
|
|
|
|
+ return 0
|
|
|
|
+ }
|
|
|
|
+ return endCropRate * model.duration
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ lazy var previousRightX: CGFloat = 0
|
|
|
|
+ @objc private func rightPanRecognizer(sender: UIPanGestureRecognizer) {
|
|
|
|
+ let limitMinCenterX: CGFloat = endMinCenterX
|
|
|
|
+ let limitMaxCenterX: CGFloat = endMaxCenterX
|
|
|
|
+ guard limitMaxCenterX > limitMinCenterX else {
|
|
|
|
+ if sender.state == .began {
|
|
|
|
+ // 越界,拖不动了
|
|
|
|
+ THUD.toast("No less than 10s".localized())
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if sender.state == .began {
|
|
|
|
+ suspendPlay()
|
|
|
|
+ } else if sender.state == .changed {
|
|
|
|
+ let newPoint = sender.translation(in: trackBgView)
|
|
|
|
+ var center = dragRightView.center
|
|
|
|
+ center.x = previousRightX + newPoint.x
|
|
|
|
+ guard center.x > limitMinCenterX, center.x < limitMaxCenterX else {
|
|
|
|
+ // 越界,拖不动了, 最少10s
|
|
|
|
+ if endDuration - startDuration < 11 {
|
|
|
|
+ THUD.toast("No less than 10s".localized())
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ dragRightView.center = center
|
|
|
|
+ } else if sender.state == .ended || sender.state == .failed {
|
|
|
|
+ previousRightX = dragRightView.centerX
|
|
|
|
+ autoPlayAfterMove()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 边界校验
|
|
|
|
+ if dragRightView.centerX > limitMaxCenterX {
|
|
|
|
+ dragRightView.centerX = limitMaxCenterX
|
|
|
|
+ }
|
|
|
|
+ if dragRightView.centerX < limitMinCenterX {
|
|
|
|
+ dragRightView.centerX = limitMinCenterX
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let position = dragRightView.centerX - trackContentView.x
|
|
|
|
+ trackView?.updateRightCroppedPosition(position)
|
|
|
|
+ endCropRate = position / trackContentView.width
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @IBAction func buttonClick(_ sender: UIButton) {
|
|
|
|
+ guard let model = ringModel else { return }
|
|
|
|
+
|
|
|
|
+ switch sender {
|
|
|
|
+ case playButton:
|
|
|
|
+ startPlay()
|
|
|
|
+ case undoButton:
|
|
|
|
+ player.pause()
|
|
|
|
+ operationCache.removeLast()
|
|
|
|
+ ringModel = operationCache.last
|
|
|
|
+ reloadTrackView()
|
|
|
|
+ case cutButton:
|
|
|
|
+ startCutAudio { [weak self] newModel, errMsg in
|
|
|
|
+ DispatchQueue.main.async {
|
|
|
|
+ if let newModel = newModel {
|
|
|
|
+ self?.operationCache.append(newModel)
|
|
|
|
+ self?.ringModel = newModel
|
|
|
|
+ self?.reloadTrackView()
|
|
|
|
+ } else {
|
|
|
|
+ THUD.toast(errMsg ?? "Sorry, Edit Failure".localized())
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ case doneButton:
|
|
|
|
+ player.pause()
|
|
|
|
+ startCutAudio { [weak self] result, errMsg in
|
|
|
|
+ DispatchQueue.main.async {
|
|
|
|
+ if let ringModel = result {
|
|
|
|
+ THUD.showLoading()
|
|
|
|
+ RingDownloadManager.shared.shareBand(with: ringModel) { _ in
|
|
|
|
+ DispatchQueue.main.async {
|
|
|
|
+ THUD.hide()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ THUD.toast(errMsg ?? "Sorry, Edit Failure".localized())
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ case nameButton:
|
|
|
|
+ let alertVC = UIAlertController(title: nil, message: "Ringtone Name".localized(), preferredStyle: .alert)
|
|
|
|
+ alertVC.addTextField { textField in
|
|
|
|
+ textField.placeholder = "input name".localized()
|
|
|
|
+ textField.font = UIFont.systemFont(ofSize: 16)
|
|
|
|
+ textField.text = self.ringModel?.title
|
|
|
|
+ self.nameInputTextField = textField
|
|
|
|
+ }
|
|
|
|
+ let ok = UIAlertAction(title: "OK".localized(), style: .default) { [weak self] _ in
|
|
|
|
+ self?.ringModel?.title = self?.nameInputTextField?.text
|
|
|
|
+ self?.nameLabel.text = self?.ringModel?.title
|
|
|
|
+ }
|
|
|
|
+ let cancel = UIAlertAction(title: "Cancel".localized(), style: .cancel)
|
|
|
|
+ alertVC.addAction(cancel)
|
|
|
|
+ alertVC.addAction(ok)
|
|
|
|
+ present(alertVC, animated: true) {
|
|
|
|
+ self.nameInputTextField?.becomeFirstResponder()
|
|
|
|
+ }
|
|
|
|
+ default:
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 拖拽,暂停
|
|
|
|
+ func suspendPlay() {
|
|
|
|
+ if player.state == .playing {
|
|
|
|
+ isSuspendByAction = true
|
|
|
|
+ player.pause()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 停止拖拽,继续播放
|
|
|
|
+ func autoPlayAfterMove() {
|
|
|
|
+// if isSuspendByAction {
|
|
|
|
+ startPlay()
|
|
|
|
+ isSuspendByAction = false
|
|
|
|
+// }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ func startPlay() {
|
|
|
|
+ guard let model = ringModel else { return }
|
|
|
|
+
|
|
|
|
+ playButton.isSelected = !playButton.isSelected
|
|
|
|
+ if player.state == .playing {
|
|
|
|
+ player.pause()
|
|
|
|
+ } else {
|
|
|
|
+ if let playURL = model.playURL {
|
|
|
|
+ player.play(playURL)
|
|
|
|
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
|
|
|
+ self.player.seek(self.startDuration)
|
|
|
|
+ }
|
|
|
|
+ player.set(volum: fadeInDuration > 0 ? 0 : 1)
|
|
|
|
+
|
|
|
|
+ if UIApplication.getSystemVolume() < 0.1 {
|
|
|
|
+ THUD.toast("Please turn up the volume".localized(), shake: true)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 裁剪
|
|
|
|
+ func startCutAudio(completion: ((RingModel?, String?) -> Void)?) {
|
|
|
|
+ guard let model = ringModel,
|
|
|
|
+ let filePath = RingDownloadManager.shared.ringFilePath(for: model),
|
|
|
|
+ let copyModel = model.copy() as? RingModel else {
|
|
|
|
+ completion?(nil, nil)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ let url = URL(fileURLWithPath: filePath)
|
|
|
|
+ let asset = AVAsset(url: url)
|
|
|
|
+
|
|
|
|
+ let formatter = DateFormatter()
|
|
|
|
+ formatter.dateFormat = "yyyyMMddHHmmss"
|
|
|
|
+
|
|
|
|
+ var editName = model.title ?? formatter.string(from: Date())
|
|
|
|
+ editName = sanitizeFilePath(editName)
|
|
|
|
+ if !url.pathExtension.isEmpty {
|
|
|
|
+ editName.append(".\(url.pathExtension)")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ guard let savePath = RingDownloadManager.shared.getCopyToPath(with: editName) else {
|
|
|
|
+ completion?(nil, nil)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ copyModel.duration = endDuration - startDuration
|
|
|
|
+ THUD.showLoading()
|
|
|
|
+ audioTool.startTansformAudio(url: url.path, from: startDuration, to: endDuration, fadeIn: Double(fadeInDuration), fadeOut: Double(fadeOutDuration), savePath: savePath) { filePath, errMsg in
|
|
|
|
+ DispatchQueue.main.async {
|
|
|
|
+ THUD.hide()
|
|
|
|
+ }
|
|
|
|
+ if let filePath = filePath {
|
|
|
|
+ let url = URL(fileURLWithPath: filePath)
|
|
|
|
+ copyModel.title = url.deletingPathExtension().lastPathComponent.removingPercentEncoding
|
|
|
|
+ copyModel.fileName = url.lastPathComponent
|
|
|
|
+ copyModel.size = FileManager.default.getFileSize(url) ?? copyModel.size
|
|
|
|
+ completion?(copyModel, errMsg)
|
|
|
|
+ } else {
|
|
|
|
+ completion?(nil, errMsg)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ func saveButtonClick() {
|
|
|
|
+ guard let current = ringModel else { return }
|
|
|
|
+// guard operationCache.count > 1 else {
|
|
|
|
+// // 只有原音频,意味着未进行裁剪操作,提示
|
|
|
|
+// let alertVC = UIAlertController(title: nil, message: "No audio can be saved. Whether to save the original audio?", preferredStyle: .alert)
|
|
|
|
+// alertVC.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
|
|
|
+// alertVC.addAction(UIAlertAction(title: "Save", style: .default, handler: { [weak self] _ in
|
|
|
|
+// RingDownloadManager.shared.saveEdited(ring: current)
|
|
|
|
+// self?.dismiss(animated: true)
|
|
|
|
+// }))
|
|
|
|
+// present(alertVC, animated: true)
|
|
|
|
+// return
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+ player.pause()
|
|
|
|
+ let dismissHandler: (() -> Void)? = { [weak self] in
|
|
|
|
+ self?.dismiss(animated: true, completion: {
|
|
|
|
+ /// 编辑完也需要小红点提示
|
|
|
|
+ RingDownloadManager.shared.hasUnreadRing = true
|
|
|
|
+ NotificationCenter.default.post(name: .downloadUnreadStateChanged, object: nil)
|
|
|
|
+
|
|
|
|
+ if let _ = UIApplication.topViewController as? MineRingsViewController {
|
|
|
|
+ THUD.toast("Saved successfully".localized(), shake: true)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ SaveSuccessTipsView.show(at: UIApplication.rootViewController?.topController.view) {
|
|
|
|
+ guard let topVC = UIApplication.topViewController else {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ let mineVC = MineRingsViewController()
|
|
|
|
+ mineVC.currentType = .edited
|
|
|
|
+ topVC.navigationController?.pushViewController(mineVC, animated: true)
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+// // 未编辑, 不需要裁剪
|
|
|
|
+// guard startCropRate != 0 || endCropRate != 1.0
|
|
|
|
+// || fadeInDuration != 0 || fadeOutDuration != 0 else {
|
|
|
|
+// // 修改名称,直接保存
|
|
|
|
+// RingDownloadManager.shared.saveEdited(ring: current)
|
|
|
|
+// dismissHandler?()
|
|
|
|
+// return
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+ // 保存音频
|
|
|
|
+ startCutAudio { result, errMsg in
|
|
|
|
+ if let ringModel = result {
|
|
|
|
+ // 裁剪的音频,使用本地文件播放,清空网络url
|
|
|
|
+ ringModel.fileUrl = nil
|
|
|
|
+ RingDownloadManager.shared.saveEdited(ring: ringModel)
|
|
|
|
+ DispatchQueue.main.async {
|
|
|
|
+ dismissHandler?()
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ DispatchQueue.main.async {
|
|
|
|
+ THUD.toast(errMsg ?? "Sorry, Save Failure".localized())
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ override func navigationBarItemClick(_ type: NavigationBarAction) {
|
|
|
|
+ if case .back = type {
|
|
|
|
+ guard operationCache.count <= 1 else {
|
|
|
|
+ let alertVC = UIAlertController(title: nil, message: "As you leave, any changes you have made will not be saved.".localized(), preferredStyle: .alert)
|
|
|
|
+ alertVC.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel))
|
|
|
|
+ alertVC.addAction(UIAlertAction(title: "Leave".localized(), style: .default, handler: { [weak self] _ in
|
|
|
|
+ self?.dismiss(animated: true)
|
|
|
|
+ }))
|
|
|
|
+ present(alertVC, animated: true)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ dismiss(animated: true)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|