|
@@ -5,15 +5,14 @@
|
|
|
// Created by 100Years on 2025/3/26.
|
|
|
//
|
|
|
import AVFoundation
|
|
|
-class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelegate {
|
|
|
+class TSEditAudioVideoBaseVC: TSBaseVC {
|
|
|
//#################################### 数据区域 ####################################//
|
|
|
- var titleName:String = "Edit".localized
|
|
|
-
|
|
|
- var ringModel: TSRingModel
|
|
|
+ var titleName:String{
|
|
|
+ "Edit".localized
|
|
|
+ }
|
|
|
var editOriginalURL:URL
|
|
|
|
|
|
- init(ringModel: TSRingModel,editOriginalURL:URL) {
|
|
|
- self.ringModel = ringModel
|
|
|
+ init(editOriginalURL:URL) {
|
|
|
self.editOriginalURL = editOriginalURL
|
|
|
super.init()
|
|
|
}
|
|
@@ -21,240 +20,14 @@ class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelega
|
|
|
@MainActor required init?(coder: NSCoder) {
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
//#################################### TSCustomStackView ####################################//
|
|
|
lazy var cusStackView: TSCustomStackView = {
|
|
|
let cusStackView = TSCustomStackView(axis: .vertical,spacing: 0)
|
|
|
return cusStackView
|
|
|
}()
|
|
|
|
|
|
-
|
|
|
- //#################################### 名字 ####################################//
|
|
|
- lazy var nameLabel: UILabel = {
|
|
|
- let nameLabel = UILabel.createLabel(text: ringModel.title, font: .font(size: 14),textColor: .white.withAlphaComponent(0.8),textAlignment: .center)
|
|
|
- return nameLabel
|
|
|
- }()
|
|
|
-
|
|
|
- weak var nameInputTextField: UITextField?
|
|
|
- lazy var nameButton: UIButton = {
|
|
|
- let nameButton = UIButton.createButton(image: UIImage(named: "edit_field")) { [weak self] in
|
|
|
- guard let self = self else { return }
|
|
|
-
|
|
|
- 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
|
|
|
- guard let self = self else { return }
|
|
|
- nameLabel.text = nameInputTextField?.text
|
|
|
-
|
|
|
- if let text = nameLabel.text{
|
|
|
- ringModel.title = text
|
|
|
- }
|
|
|
- }
|
|
|
- let cancel = UIAlertAction(title: "Cancel".localized, style: .cancel)
|
|
|
- alertVC.addAction(cancel)
|
|
|
- alertVC.addAction(ok)
|
|
|
- present(alertVC, animated: true) {
|
|
|
- self.nameInputTextField?.becomeFirstResponder()
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
- return nameButton
|
|
|
- }()
|
|
|
-
|
|
|
- lazy var nameView: UIView = {
|
|
|
- let nameView = UIView()
|
|
|
-
|
|
|
- let nameContentView = UIView()
|
|
|
- nameView.addSubview(nameContentView)
|
|
|
- nameContentView.snp.makeConstraints { make in
|
|
|
- make.center.equalToSuperview()
|
|
|
- }
|
|
|
-
|
|
|
- nameContentView.addSubview(nameLabel)
|
|
|
- nameContentView.addSubview(nameButton)
|
|
|
-
|
|
|
- nameLabel.snp.makeConstraints { make in
|
|
|
- make.leading.equalToSuperview()
|
|
|
- make.width.greaterThanOrEqualTo(220*kDesignScale)
|
|
|
- make.height.equalTo(23)
|
|
|
- make.top.bottom.equalTo(0)
|
|
|
- }
|
|
|
- nameButton.snp.makeConstraints { make in
|
|
|
- make.trailing.equalToSuperview()
|
|
|
- make.leading.equalTo(nameLabel.snp.trailing)
|
|
|
- make.width.height.equalTo(23)
|
|
|
- make.top.bottom.equalTo(0)
|
|
|
- }
|
|
|
-
|
|
|
- return nameView
|
|
|
- }()
|
|
|
-
|
|
|
- //#################################### track编辑视频 ####################################//\
|
|
|
- let trackContentViewLeft:CGFloat = 24.0
|
|
|
- let trackViewH:CGFloat = 210.0
|
|
|
- lazy var trackViewW:CGFloat = k_ScreenWidth - trackContentViewLeft * 2
|
|
|
- lazy var trackView: ZHWaveformView = {
|
|
|
- let waveform = ZHWaveformView(frame: CGRect(x: 0, y: 0, width: trackViewW, height:trackViewH),fileURL: editOriginalURL)
|
|
|
- waveform.backgroundColor = .clear
|
|
|
- waveform.beginningPartColor = .white.withAlphaComponent(0.2) // color
|
|
|
- waveform.endPartColor = .white.withAlphaComponent(0.2)
|
|
|
- waveform.wavesColor = "#7E57F4".uiColor
|
|
|
- waveform.trackScale = 0.2// 0 ~ 1
|
|
|
- waveform.waveformDelegate = self
|
|
|
- waveform.croppedDelegate = self
|
|
|
-
|
|
|
- return waveform
|
|
|
- }()
|
|
|
-
|
|
|
- lazy var trackContentView: UIView = {
|
|
|
- let trackContentView = UIView(frame: CGRectMake(24, 0, k_ScreenWidth-48, trackViewH+12))
|
|
|
-
|
|
|
- trackContentView.addSubview(trackView)
|
|
|
- trackView.snp.makeConstraints { make in
|
|
|
- make.top.centerX.equalToSuperview()
|
|
|
- make.width.equalTo(trackView.width)
|
|
|
- make.height.equalTo(trackView.height)
|
|
|
- }
|
|
|
- return trackContentView
|
|
|
- }()
|
|
|
-
|
|
|
-
|
|
|
- func creatDragView()->UIView{
|
|
|
- let bgView = UIView()
|
|
|
-
|
|
|
- let view1 = UIView.creatColor(color: .white)
|
|
|
- bgView.addSubview(view1)
|
|
|
- let view2 = UIView.creatColor(color: .white)
|
|
|
- view2.cornerRadius = 6
|
|
|
- bgView.addSubview(view2)
|
|
|
-
|
|
|
- view1.snp.makeConstraints { make in
|
|
|
- make.width.equalTo(2)
|
|
|
- make.height.equalTo(trackViewH)
|
|
|
- make.centerX.equalToSuperview()
|
|
|
- make.top.equalToSuperview()
|
|
|
- }
|
|
|
-
|
|
|
- view2.snp.makeConstraints { make in
|
|
|
- make.top.equalTo(view1.snp.bottom)
|
|
|
- make.width.height.equalTo(12)
|
|
|
- make.bottom.equalToSuperview()
|
|
|
- make.leading.equalTo(12)
|
|
|
- make.trailing.equalTo(-12)
|
|
|
- }
|
|
|
- return bgView
|
|
|
- }
|
|
|
- lazy var dragLeftView: UIView = creatDragView()
|
|
|
- lazy var dragRightView: UIView = creatDragView()
|
|
|
- var progressLine: UIView = UIView.creatColor(color: .themeColor)
|
|
|
-
|
|
|
-
|
|
|
- lazy var startTimeLabel: UILabel = {
|
|
|
- let label = UILabel.createLabel(text: "00:00s",font: .font(size: 14.0),textColor: .lesserText)
|
|
|
- return label
|
|
|
- }()
|
|
|
- var endTimeLabel: UILabel = {
|
|
|
- let label = UILabel.createLabel(text: "10:00s",font: .font(size: 14.0),textColor: .lesserText)
|
|
|
- return label
|
|
|
- }()
|
|
|
- var timeLabel: UILabel = {
|
|
|
- let label = UILabel.createLabel(text: "00:00",font: .font(size: 26.0),textColor: .lesserText)
|
|
|
- return label
|
|
|
- }()
|
|
|
-
|
|
|
-
|
|
|
- lazy var timeView: UIView = {
|
|
|
- let timeView = UIView()
|
|
|
-
|
|
|
- timeView.addSubview(startTimeLabel)
|
|
|
- timeView.addSubview(endTimeLabel)
|
|
|
- timeView.addSubview(timeLabel)
|
|
|
-
|
|
|
- startTimeLabel.snp.makeConstraints { make in
|
|
|
- make.leading.equalTo(16)
|
|
|
- make.top.equalTo(12)
|
|
|
- make.height.equalTo(20)
|
|
|
- }
|
|
|
-
|
|
|
- endTimeLabel.snp.makeConstraints { make in
|
|
|
- make.trailing.equalTo(-16)
|
|
|
- make.top.equalTo(12)
|
|
|
- make.height.equalTo(20)
|
|
|
- }
|
|
|
-
|
|
|
- timeLabel.snp.makeConstraints { make in
|
|
|
- make.centerX.equalToSuperview()
|
|
|
- make.top.equalTo(45)
|
|
|
- make.height.equalTo(26)
|
|
|
- }
|
|
|
-
|
|
|
- return timeView
|
|
|
- }()
|
|
|
-
|
|
|
- lazy var trackBgView: UIView = {
|
|
|
- let trackBgView = UIView()
|
|
|
-
|
|
|
- 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
|
|
|
-
|
|
|
- let gradientImageView = UIImageView.createImageView(imageName: "trackBg_gradient",contentMode: .scaleToFill)
|
|
|
- trackBgView.addSubview(gradientImageView)
|
|
|
- gradientImageView.snp.makeConstraints { make in
|
|
|
- make.leading.top.trailing.equalToSuperview()
|
|
|
- make.height.equalTo(trackViewH)
|
|
|
- }
|
|
|
-
|
|
|
- trackBgView.addSubview(trackContentView)
|
|
|
- trackContentView.snp.makeConstraints { make in
|
|
|
- make.top.equalToSuperview()
|
|
|
- make.leading.equalTo(trackContentView.x)
|
|
|
- make.trailing.equalTo(-trackContentView.x)
|
|
|
- make.height.equalTo(trackViewH)
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- trackBgView.addSubview(dragLeftView)
|
|
|
- dragLeftView.snp.makeConstraints { make in
|
|
|
- make.top.leading.equalToSuperview()
|
|
|
- }
|
|
|
-
|
|
|
- trackBgView.addSubview(dragRightView)
|
|
|
- dragRightView.snp.makeConstraints { make in
|
|
|
- make.top.equalToSuperview()
|
|
|
- make.trailing.equalToSuperview()
|
|
|
- }
|
|
|
- progressLine.isHidden = true
|
|
|
- trackBgView.addSubview(progressLine)
|
|
|
- progressLine.snp.makeConstraints { make in
|
|
|
- make.top.leading.equalToSuperview()
|
|
|
- make.width.equalTo(1)
|
|
|
- make.height.equalTo(trackViewH)
|
|
|
- }
|
|
|
-
|
|
|
- trackBgView.addSubview(timeView)
|
|
|
- timeView.snp.makeConstraints { make in
|
|
|
- make.top.equalTo(trackContentView.bottom)
|
|
|
- make.trailing.leading.equalToSuperview()
|
|
|
- make.height.equalTo(71)
|
|
|
- make.bottom.equalTo(0)
|
|
|
- }
|
|
|
-
|
|
|
- return trackBgView
|
|
|
- }()
|
|
|
-
|
|
|
-
|
|
|
- //#################################### paly按钮 ####################################//
|
|
|
+ //#################################### play按钮 ####################################//
|
|
|
|
|
|
lazy var playButtonView: UIView = {
|
|
|
let playButtonView = UIView(frame: CGRectMake(0, 0, k_ScreenWidth, 18+80+18))
|
|
@@ -277,7 +50,6 @@ class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelega
|
|
|
return playButtonView
|
|
|
}()
|
|
|
|
|
|
-
|
|
|
lazy var playButton: UIButton = {//editaudio_play //editaudio_pause
|
|
|
let playButton = UIButton.createButton(image: UIImage(named: "editaudio_play")){ [weak self] in
|
|
|
guard let self = self else { return }
|
|
@@ -287,58 +59,6 @@ class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelega
|
|
|
return playButton
|
|
|
}()
|
|
|
|
|
|
-
|
|
|
- //#################################### Fade in ####################################//
|
|
|
- lazy var fadeinSliderView: TSEditAudioSliderView = {
|
|
|
- let fadeinSliderView = TSEditAudioSliderView()
|
|
|
- fadeinSliderView.leftLabel.text = "Fade in".localized
|
|
|
- fadeinSliderView.rightLabel.text = "0s"
|
|
|
- fadeinSliderView.slider.maximumValue = 5
|
|
|
-// fadeinSliderView.slider.value = 3
|
|
|
- return fadeinSliderView
|
|
|
- }()
|
|
|
- lazy var fadeinSlider: UISlider = {
|
|
|
- let slider = fadeinSliderView.slider
|
|
|
- slider.addTarget(self, action: #selector(sliderBeginTap(_:)), for: .touchDown)
|
|
|
- slider.addTarget(self, action: #selector(sliderEndTap(_:)), for: .touchUpInside)
|
|
|
- slider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
|
|
|
- return slider
|
|
|
- }()
|
|
|
- //#################################### Fade out ####################################//
|
|
|
-
|
|
|
- lazy var fadeoutSliderView: TSEditAudioSliderView = {
|
|
|
- let fadeoutSliderView = TSEditAudioSliderView()
|
|
|
- fadeoutSliderView.leftLabel.text = "Fade out".localized
|
|
|
- fadeoutSliderView.rightLabel.text = "0s"
|
|
|
- fadeoutSliderView.slider.maximumValue = 5
|
|
|
-// fadeoutSliderView.slider.value = 3
|
|
|
- return fadeoutSliderView
|
|
|
- }()
|
|
|
- lazy var fadeoutSlider: UISlider = {
|
|
|
- let slider = fadeoutSliderView.slider
|
|
|
- slider.addTarget(self, action: #selector(sliderBeginTap(_:)), for: .touchDown)
|
|
|
- slider.addTarget(self, action: #selector(sliderEndTap(_:)), for: .touchUpInside)
|
|
|
- slider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
|
|
|
- return slider
|
|
|
- }()
|
|
|
- //#################################### Output Volume ####################################//
|
|
|
- lazy var outputVolumeSliderView: TSEditAudioSliderView = {
|
|
|
- let outputVolumeSliderView = TSEditAudioSliderView()
|
|
|
- outputVolumeSliderView.leftLabel.text = "Output Volume".localized
|
|
|
- outputVolumeSliderView.rightLabel.text = "100%"
|
|
|
- outputVolumeSliderView.slider.minimumValue = 1.0
|
|
|
- outputVolumeSliderView.slider.maximumValue = 2.0
|
|
|
-// outputVolumeSliderView.slider.value = 1.5
|
|
|
- return outputVolumeSliderView
|
|
|
- }()
|
|
|
- lazy var outputVolumeSlider: UISlider = {
|
|
|
- let slider = outputVolumeSliderView.slider
|
|
|
- slider.addTarget(self, action: #selector(sliderBeginTap(_:)), for: .touchDown)
|
|
|
- slider.addTarget(self, action: #selector(sliderEndTap(_:)), for: .touchUpInside)
|
|
|
- slider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
|
|
|
- return slider
|
|
|
- }()
|
|
|
-
|
|
|
//#################################### 底部保存按钮等 ####################################//
|
|
|
lazy var cutButton: TSAppBtnView = { //保存
|
|
|
let cutButton = TSAppBtnView()
|
|
@@ -360,9 +80,6 @@ class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelega
|
|
|
return doneButton
|
|
|
}()
|
|
|
|
|
|
-
|
|
|
- var undoButton: UIButton!//暂时没用
|
|
|
-
|
|
|
lazy var bottomBtnView: UIView = {
|
|
|
let bottomBtnView = UIView()
|
|
|
|
|
@@ -405,93 +122,8 @@ class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelega
|
|
|
make.top.leading.bottom.trailing.equalTo(0)
|
|
|
make.bottom.equalTo(bottomBtnView.snp.top)
|
|
|
}
|
|
|
- setUpCusStackView()
|
|
|
- }
|
|
|
-
|
|
|
- func setUpCusStackView() {
|
|
|
- cusStackView.addSubviewToStack(nameView)
|
|
|
- nameView.snp.makeConstraints { make in
|
|
|
- make.height.equalTo(56)
|
|
|
- make.width.equalTo(k_ScreenWidth)
|
|
|
- }
|
|
|
- cusStackView.addSubviewToStack(trackBgView)
|
|
|
- trackBgView.snp.makeConstraints { make in
|
|
|
- make.height.equalTo(300)
|
|
|
- make.width.equalTo(k_ScreenWidth)
|
|
|
- }
|
|
|
-
|
|
|
- cusStackView.addSubviewToStack(playButtonView)
|
|
|
- playButtonView.snp.makeConstraints { make in
|
|
|
- make.height.equalTo(playButtonView.height)
|
|
|
- make.width.equalTo(playButtonView.width)
|
|
|
- }
|
|
|
-
|
|
|
- cusStackView.addSubviewToStack(fadeinSliderView)
|
|
|
- fadeinSliderView.snp.makeConstraints { make in
|
|
|
- make.height.equalTo(fadeinSliderView.height)
|
|
|
- }
|
|
|
-
|
|
|
- let spaceView = UIView()
|
|
|
- cusStackView.addSubviewToStack(spaceView)
|
|
|
- spaceView.snp.makeConstraints { make in
|
|
|
- make.height.equalTo(8)
|
|
|
- }
|
|
|
-
|
|
|
- cusStackView.addSubviewToStack(fadeoutSliderView)
|
|
|
- fadeoutSliderView.snp.makeConstraints { make in
|
|
|
- make.height.equalTo(fadeinSliderView.height)
|
|
|
- }
|
|
|
-
|
|
|
- let spaceView1 = UIView()
|
|
|
- cusStackView.addSubviewToStack(spaceView1)
|
|
|
- spaceView1.snp.makeConstraints { make in
|
|
|
- make.height.equalTo(8)
|
|
|
- }
|
|
|
-
|
|
|
- cusStackView.addSubviewToStack(outputVolumeSliderView)
|
|
|
- outputVolumeSliderView.snp.makeConstraints { make in
|
|
|
- make.height.equalTo(fadeinSliderView.height)
|
|
|
- make.bottom.equalToSuperview()
|
|
|
- }
|
|
|
-
|
|
|
- _ = fadeinSlider
|
|
|
- _ = fadeoutSlider
|
|
|
- _ = outputVolumeSlider
|
|
|
- }
|
|
|
-
|
|
|
- override func dealThings() {
|
|
|
-
|
|
|
- dePrint("TSEditAudioVideoBaseVC ringModel = \(String(describing: ringModel.toJSONString())),editOriginalURL = \(editOriginalURL)")
|
|
|
-
|
|
|
- if ringModel.duration == 0, player.duration != 0 {
|
|
|
- ringModel.duration = Int(player.duration)
|
|
|
- }
|
|
|
-
|
|
|
- operationCache.append(ringModel)
|
|
|
- reloadTrackView()
|
|
|
}
|
|
|
|
|
|
- private lazy var player:TSBusinessAudioPlayer = {
|
|
|
- let player = TSBusinessAudioPlayer()
|
|
|
- player.loadLoactionURL(url: self.editOriginalURL)
|
|
|
- player.currentTimeChangedHandle = { [weak self] current,total in
|
|
|
- guard let self = self else { return }
|
|
|
- handlePlayer(progressChanged: current, total: total)
|
|
|
- }
|
|
|
-
|
|
|
- player.stateChangedHandle = { [weak self] state in
|
|
|
- guard let self = self else { return }
|
|
|
- handlePlayer(state: state)
|
|
|
- }
|
|
|
-
|
|
|
- return player
|
|
|
- }()
|
|
|
- private lazy var audioTool = AudioTool()
|
|
|
- private var maxVolume:Float{
|
|
|
- return outputVolume * 0.5
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
lazy var operationCache: [TSRingModel] = []
|
|
|
var tempMp3Path: String?
|
|
|
var needClearTemp : Bool = false
|
|
@@ -531,37 +163,10 @@ class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelega
|
|
|
|
|
|
}
|
|
|
|
|
|
- // 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() {
|
|
|
-
|
|
|
- tempMp3Path = editOriginalURL.path
|
|
|
-
|
|
|
- startCropRate = 0
|
|
|
- endCropRate = 1.0
|
|
|
- dragLeftView.centerX = trackContentView.x
|
|
|
- dragRightView.centerX = trackContentView.frame.maxX
|
|
|
-
|
|
|
-
|
|
|
- timeLabel.text = ringModel.duration.mmss
|
|
|
-// undoButton.isEna bled = operationCache.count > 1
|
|
|
- cutButton.setBtnEnabled(isEnabled: false)
|
|
|
- }
|
|
|
-
|
|
|
override func navBarClickLeftAction() {
|
|
|
TSCustomAlertController.show(in: self, config: TSCustomAlertController.AlertConfig(
|
|
|
message: "As you leave, the changes you have made will be lost.".localized,
|
|
@@ -583,405 +188,18 @@ class TSEditAudioVideoBaseVC: TSBaseVC , ZHCroppedDelegate, ZHWaveformViewDelega
|
|
|
}
|
|
|
))
|
|
|
}
|
|
|
- //#################################### UI 储存属性 ####################################//
|
|
|
- lazy var fadeInDuration: Int = 0 {
|
|
|
- didSet {
|
|
|
- fadeinSliderView.rightLabel.text = "\(fadeInDuration)s"
|
|
|
- }
|
|
|
- }
|
|
|
- lazy var fadeOutDuration: Int = 0 {
|
|
|
- didSet {
|
|
|
- fadeoutSliderView.rightLabel.text = "\(fadeOutDuration)s"
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
- lazy var outputVolume: Float = 1.0 {
|
|
|
- didSet {
|
|
|
- outputVolumeSliderView.rightLabel.text = "\(Int(outputVolume*100.0))%"
|
|
|
- }
|
|
|
- }
|
|
|
- lazy var previousLeftX: CGFloat = 0
|
|
|
- lazy var startCropRate: CGFloat = 0 {
|
|
|
- didSet {
|
|
|
- let time = startDuration
|
|
|
- cutButton.setBtnEnabled(isEnabled: true)
|
|
|
-
|
|
|
- // print("---start: \(time)")
|
|
|
- startTimeLabel.text = time.mmss
|
|
|
- timeLabel.text = (endDuration - startDuration).mmss
|
|
|
- }
|
|
|
- }
|
|
|
- // 拖拽时被暂停,拖拽结束后,继续播放
|
|
|
- lazy var isSuspendByAction = false
|
|
|
- lazy var endCropRate: CGFloat = 1.0 {
|
|
|
- didSet {
|
|
|
- cutButton.setBtnEnabled(isEnabled: true)
|
|
|
- let time = endDuration
|
|
|
- endTimeLabel.text = time.mmss
|
|
|
- timeLabel.text = (endDuration - startDuration).mmss
|
|
|
- }
|
|
|
- }
|
|
|
- lazy var previousRightX: CGFloat = 0
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-extension TSEditAudioVideoBaseVC {
|
|
|
|
|
|
-
|
|
|
-
|
|
|
- @objc func sliderBeginTap(_ slider: UISlider) {
|
|
|
- suspendPlay()
|
|
|
- }
|
|
|
-
|
|
|
- @objc func sliderEndTap(_ slider: UISlider) {
|
|
|
- autoPlayAfterMove()
|
|
|
- }
|
|
|
-
|
|
|
- @objc func sliderValueChanged(_ slider: UISlider) {
|
|
|
- // print("---\(slider.value)")
|
|
|
- if player.isPlaying {
|
|
|
- player.pause()
|
|
|
- }
|
|
|
- if slider == fadeinSlider {
|
|
|
- fadeInDuration = Int(slider.value.rounded(.toNearestOrEven))
|
|
|
- } else if slider == fadeoutSlider {
|
|
|
- fadeOutDuration = Int(slider.value.rounded(.toNearestOrEven))
|
|
|
- } else if slider == outputVolumeSlider {
|
|
|
- outputVolume = slider.value
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- // 裁剪起始时间
|
|
|
- var startDuration: Double {
|
|
|
- return startCropRate * CGFloat(ringModel.duration)
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- var startMinCenterX: CGFloat {
|
|
|
- return trackContentView.x
|
|
|
- }
|
|
|
-
|
|
|
- var startMaxCenterX: CGFloat {
|
|
|
- let ratio = (endCropRate * CGFloat(ringModel.duration) - 10) / CGFloat(ringModel.duration)
|
|
|
- return ratio * trackContentView.width + trackContentView.x
|
|
|
- }
|
|
|
-
|
|
|
- var endMinCenterX: CGFloat {
|
|
|
- let ratio = (startCropRate * CGFloat(ringModel.duration) + 10) / CGFloat(ringModel.duration)
|
|
|
- return ratio * trackContentView.width + trackContentView.x
|
|
|
- }
|
|
|
-
|
|
|
- var endMaxCenterX: CGFloat {
|
|
|
- return trackContentView.frame.maxX
|
|
|
- }
|
|
|
-
|
|
|
- @objc private func leftPanRecognizer(sender: UIPanGestureRecognizer) {
|
|
|
-
|
|
|
- dePrint("TSEditAudioVideoBaseVC leftPanRecognizer=\(sender)")
|
|
|
- let limitMinCenterX: CGFloat = startMinCenterX
|
|
|
- let limitMaxCenterX: CGFloat = startMaxCenterX
|
|
|
- guard limitMaxCenterX > limitMinCenterX else {
|
|
|
- if sender.state == .began {
|
|
|
- dePrint("TSEditAudioVideoBaseVC rightPanRecognizer No less than 10s")
|
|
|
- TSToastShared.showToast(text:"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 {
|
|
|
- TSToastShared.showToast(text:"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
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- var endDuration: Double {
|
|
|
- return endCropRate * CGFloat(CGFloat(ringModel.duration))
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- @objc private func rightPanRecognizer(sender: UIPanGestureRecognizer) {
|
|
|
- dePrint("TSEditAudioVideoBaseVC rightPanRecognizer=\(sender)")
|
|
|
- let limitMinCenterX: CGFloat = endMinCenterX
|
|
|
- let limitMaxCenterX: CGFloat = endMaxCenterX
|
|
|
- guard limitMaxCenterX > limitMinCenterX else {
|
|
|
- if sender.state == .began {
|
|
|
- dePrint("TSEditAudioVideoBaseVC rightPanRecognizer No less than 10s")
|
|
|
- // 越界,拖不动了
|
|
|
- TSToastShared.showToast(text:"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 {
|
|
|
- TSToastShared.showToast(text:"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) {
|
|
|
- let model = ringModel
|
|
|
-
|
|
|
- // 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 {
|
|
|
- // TSToastShared.showToast(text:errMsg ?? "Sorry, Edit Failure".localized)
|
|
|
- // }
|
|
|
- // }
|
|
|
- // }
|
|
|
- // case doneButton:
|
|
|
- // player.pause()
|
|
|
- // startCutAudio { [weak self] result, errMsg in
|
|
|
- // guard let self = self else { return }
|
|
|
- // DispatchQueue.main.async {
|
|
|
- // if let ringModel = result {
|
|
|
- // TSToastShared.showLoading(containerView: self.view)
|
|
|
- //// RingDownloadManager.shared.shareBand(with: ringModel) { _ in
|
|
|
- //// DispatchQueue.main.async {
|
|
|
- //// TSToastShared.hideLoading()
|
|
|
- //// }
|
|
|
- //// }
|
|
|
- // } else {
|
|
|
- // TSToastShared.showToast(text:errMsg ?? "Sorry, Edit Failure".localized)
|
|
|
- // }
|
|
|
- // }
|
|
|
- // }
|
|
|
-
|
|
|
- // default:
|
|
|
- // break
|
|
|
- // }
|
|
|
- }
|
|
|
-
|
|
|
- // 拖拽,暂停
|
|
|
- func suspendPlay() {
|
|
|
- if player.isPlaying {
|
|
|
- isSuspendByAction = true
|
|
|
- player.pause()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 停止拖拽,继续播放
|
|
|
- func autoPlayAfterMove() {
|
|
|
- startPlay()
|
|
|
- isSuspendByAction = false
|
|
|
- }
|
|
|
|
|
|
func startPlay() {
|
|
|
- let model = ringModel
|
|
|
|
|
|
- playButton.isSelected = !playButton.isSelected
|
|
|
- if player.isPlaying {
|
|
|
- player.pause()
|
|
|
- } else {
|
|
|
-
|
|
|
- player.playUrlString(model.audioUrl,localURL: editOriginalURL)
|
|
|
-// DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
|
|
- self.player.seek(to:self.startDuration)
|
|
|
-// }
|
|
|
- player.setVolume(volume: fadeInDuration > 0 ? 0 : maxVolume)
|
|
|
-
|
|
|
- if UIApplication.getSystemVolume() < 0.1 {
|
|
|
- TSToastShared.showToast(text:"Please turn up the volume".localized)
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
func saveButtonClick() {
|
|
|
- handleSaveCutAudio()
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- func handleSaveCutAudio(completion:((TSRingModel?)->Void)? = nil){
|
|
|
- player.pause()
|
|
|
- // 保存音频
|
|
|
- startCutAudio { result, errMsg,savePath in
|
|
|
- if let ringModel = result {
|
|
|
- // 裁剪的音频,使用本地文件播放,清空网络url
|
|
|
- dePrint("TSEditAudioVideoBaseVC saveModel ringModel = \(ringModel.toJSONString())")
|
|
|
- TSMineRintoneHistory.shared.saveModel(model: ringModel)
|
|
|
-
|
|
|
- completion?(ringModel)
|
|
|
- DispatchQueue.main.async {
|
|
|
- self.pop()
|
|
|
- if let window = WindowHelper.getKeyWindow() {
|
|
|
- kSaveSuccesswShared.show(atView:window,text: "Saved in “My Ringtone”".localized) { [weak self] in
|
|
|
- guard let self = self else { return }
|
|
|
- if let vc = WindowHelper.getCurrentViewController(){
|
|
|
- kPushVC(target: vc, modelVC: TSRingDownVC())
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- DispatchQueue.main.async {
|
|
|
- TSToastShared.showToast(text: errMsg ?? "Sorry, Save Failure".localized)
|
|
|
- }
|
|
|
- completion?(nil)
|
|
|
- }
|
|
|
- }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
func setButtonClick() {
|
|
|
- handleSaveCutAudio { model in
|
|
|
- if let ringModel = model {
|
|
|
- let path = ringModel.documentPath.fillDocumentURL
|
|
|
- _ = kPurchaseToolShared.kshareBand(needVip: false, vc: self, fileURL: path, fileName: ringModel.title)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 裁剪
|
|
|
- func startCutAudio(completion: ((TSRingModel?, String?,URL?) -> Void)?) {
|
|
|
- guard let copyModel = ringModel.copy() as? TSRingModel else {
|
|
|
- completion?(nil, nil, nil)
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- let savePath = TSDownloadManager.generateRingSaveLocalURL(name: copyModel.title)
|
|
|
- TSFileManagerTool.checkFolderAndCreate(from: savePath)
|
|
|
|
|
|
- copyModel.duration = Int(endDuration - startDuration)
|
|
|
- TSRingLoadingView.shared.showWindow()
|
|
|
- audioTool.startTansformAudio(url:editOriginalURL.path, from: startDuration, to: endDuration, fadeIn: Double(fadeInDuration), fadeOut: Double(fadeOutDuration),addVolume: Double(outputVolume) ,savePath: savePath.path) { filePath, errMsg in
|
|
|
- DispatchQueue.main.async {
|
|
|
- TSRingLoadingView.shared.remove()
|
|
|
- }
|
|
|
- if let filePath = filePath {
|
|
|
- let url = URL(fileURLWithPath: filePath)
|
|
|
- copyModel.documentPath = filePath.documentLastURLString
|
|
|
-
|
|
|
- if let fileInfo = TSBusinessAudioPlayer.getAudioFileInfo(path: url.path) {
|
|
|
- if let size = fileInfo.sizeInBytes {
|
|
|
- copyModel.size = Int(size)
|
|
|
- }
|
|
|
- if let duration = fileInfo.durationInSeconds {
|
|
|
- copyModel.duration = Int(duration)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- completion?(copyModel, errMsg, savePath)
|
|
|
- } else {
|
|
|
- completion?(nil, errMsg, nil)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-}
|
|
|
-extension TSEditAudioVideoBaseVC {
|
|
|
- func handlePlayer(state: TSBusinessAudioPlayer.PlayerState) {
|
|
|
- playButton.isSelected = player.isPlaying
|
|
|
- progressLine.isHidden = !playButton.isSelected
|
|
|
- if player.currentPlayerState == .play {
|
|
|
- progressLine.centerX = dragLeftView.centerX
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- func handlePlayer(progressChanged current: Double, total: Double) {
|
|
|
- let range = endDuration - startDuration
|
|
|
- let rangeWidth = trackContentView.width * range / total
|
|
|
- let progress = max(0, current - startDuration) / range
|
|
|
-
|
|
|
- if player.isPlaying {
|
|
|
- progressLine.centerX = dragLeftView.centerX + progress * rangeWidth
|
|
|
- }
|
|
|
-
|
|
|
- if current >= endDuration {
|
|
|
- player.pause()
|
|
|
- }
|
|
|
-
|
|
|
- // 淡入
|
|
|
- if fadeInDuration > 0, current - startDuration < Double(fadeInDuration) {
|
|
|
- let fadeProgress = Float(current - startDuration) / Float(fadeInDuration)
|
|
|
- let newVolume = min(fadeProgress * maxVolume * 100, 100.0)
|
|
|
- print("---volume: \(newVolume)")
|
|
|
- player.setVolume(volume:newVolume / 100)
|
|
|
- }
|
|
|
-
|
|
|
- // 淡出
|
|
|
- if fadeOutDuration > 0, current > (endDuration - Double(fadeOutDuration)) {
|
|
|
- let fadeProgress = Float(endDuration - current) / Float(fadeOutDuration)
|
|
|
- let newVolume = max(fadeProgress * maxVolume * 100, 0.0)
|
|
|
- print("---volume: \(newVolume)")
|
|
|
- player.setVolume(volume: newVolume / 100)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- func sanitizeFilePath(_ path: String) -> String {
|
|
|
- let illegalFileNameCharacters = CharacterSet(charactersIn: "/\\?%*|\"<>: ")
|
|
|
- let sanitizedPath = path.components(separatedBy: illegalFileNameCharacters).joined(separator: "_")
|
|
|
- return sanitizedPath
|
|
|
}
|
|
|
}
|