AVPlayer
3つ目のテーマは音声再生についてです.簡単な音楽プレーヤーを作って核心概念を学ぶ
クリティカルオブジェクトのクリーンアップ
クリティカルオブジェクトのクリーンアップ
AVPlayerItem
音声ファイルをオブジェクト化します.assetまたはurlを使用してinitできます.
AVPlayer
実際の音声再生キャラクタのオブジェクトとして機能します.再生や停止など、音声再生に関する方法はいくつかあります.
ベース(再生、一時停止)
UI準備
十分なボタン再生ボタンと一時停止ボタンしか追加されていません.各ボタンは、再生および一時停止を実行するコレクタに接続されています.import UIKit
class VC3: UIViewController {
// MARK: Properties
let player = AVPlayer()
let playButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "play.fill"), for: .normal)
button.tintColor = .black
button.widthAnchor.constraint(equalToConstant: 50).isActive = true
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
button.contentVerticalAlignment = .fill
button.contentHorizontalAlignment = .fill
button.addTarget(self, action: #selector(play), for: .touchUpInside)
return button
}()
let pauseButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "pause.fill"), for: .normal)
button.tintColor = .black
button.widthAnchor.constraint(equalToConstant: 50).isActive = true
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
button.contentVerticalAlignment = .fill
button.contentHorizontalAlignment = .fill
button.addTarget(self, action: #selector(pause), for: .touchUpInside)
return button
}()
// MARK: LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
}
// MARK: Selector
@objc func play() {
}
@objc func pause() {
}
// MARK: Helpers
func configureUI() {
view.backgroundColor = .white
view.addSubview(playButton)
playButton.translatesAutoresizingMaskIntoConstraints = false
playButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: -50).isActive = true
playButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
view.addSubview(pauseButton)
pauseButton.translatesAutoresizingMaskIntoConstraints = false
pauseButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 50).isActive = true
pauseButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
}
}
AVPlayer ItemをAVPlayerに入れる
サウンドファイルを束に配置します.前述したように、AVPlayerItemはurlまたはassetを使用してinitすることができる.ここではurlでinitを初期化しました.
AVPlayer ItemをAVPlayerの代替現在のItemメソッドとして指定します.func configurePlayer() {
guard let url = Bundle.main.url(forResource: "sound", withExtension: "mp3") else {
print("Failed to load sound")
return
}
let item = AVPlayerItem(url: url)
player.replaceCurrentItem(with: item)
}
再生、一時停止
再生と一時停止は簡単です.AVPlayerのPlay()とPause()メソッドを使用します.@objc func play() {
player.play()
}
@objc func pause() {
player.pause()
}
詳細(再生時間の表示とナビゲート)
UI準備
上のUIにナビゲーション用のスライダと現在の時刻と合計時刻を表示するラベルを追加します.
スライダは2つの関数を接続します.1つ目はisSeeking変数を切り替えるコレクタで、ナビゲーションの開始と終了時にそれぞれナビゲーション中であることを示します.2つ目は、スライダの値が変更されたときに実際のナビゲーションを実行するコレクタです.import UIKit
import AVFoundation
let buttonSize: CGFloat = 30
class VC3: UIViewController {
// MARK: Properties
let player = AVPlayer()
let playButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "play.fill"), for: .normal)
button.tintColor = .black
button.widthAnchor.constraint(equalToConstant: buttonSize).isActive = true
button.heightAnchor.constraint(equalToConstant: buttonSize).isActive = true
button.contentVerticalAlignment = .fill
button.contentHorizontalAlignment = .fill
button.addTarget(self, action: #selector(play), for: .touchUpInside)
return button
}()
let pauseButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "pause.fill"), for: .normal)
button.tintColor = .black
button.widthAnchor.constraint(equalToConstant: buttonSize).isActive = true
button.heightAnchor.constraint(equalToConstant: buttonSize).isActive = true
button.contentVerticalAlignment = .fill
button.contentHorizontalAlignment = .fill
button.addTarget(self, action: #selector(pause), for: .touchUpInside)
return button
}()
let timeSlider: UISlider = {
let slider = UISlider()
slider.thumbTintColor = .black
slider.addTarget(self, action: #selector(toggleIsSeeking), for: .editingDidBegin)
slider.addTarget(self, action: #selector(toggleIsSeeking), for: .editingDidEnd)
slider.addTarget(self, action: #selector(seek(sender:)), for: .valueChanged)
return slider
}()
var isSeeking: Bool = false
let currentTimeLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 20)
label.text = "현재 시간"
return label
}()
let fullTimeLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 20)
label.text = "전체 시간"
return label
}()
// MARK: LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
configurePlayer()
}
// MARK: Selector
@objc func play() {
player.play()
}
@objc func pause() {
player.pause()
}
@objc func seek() {
}
@objc func toggleIsSeeking() {
isSeeking.toggle()
}
// MARK: Helpers
func configureUI() {
view.backgroundColor = .white
view.addSubview(playButton)
playButton.translatesAutoresizingMaskIntoConstraints = false
playButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: -30).isActive = true
playButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
view.addSubview(pauseButton)
pauseButton.translatesAutoresizingMaskIntoConstraints = false
pauseButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 30).isActive = true
pauseButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
view.addSubview(timeSlider)
timeSlider.translatesAutoresizingMaskIntoConstraints = false
timeSlider.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 10).isActive = true
timeSlider.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10).isActive = true
timeSlider.bottomAnchor.constraint(equalTo: playButton.topAnchor, constant: -20).isActive = true
view.addSubview(currentTimeLabel)
currentTimeLabel.translatesAutoresizingMaskIntoConstraints = false
currentTimeLabel.leftAnchor.constraint(equalTo: timeSlider.leftAnchor).isActive = true
currentTimeLabel.bottomAnchor.constraint(equalTo: timeSlider.topAnchor, constant: -10).isActive = true
view.addSubview(fullTimeLabel)
fullTimeLabel.translatesAutoresizingMaskIntoConstraints = false
fullTimeLabel.rightAnchor.constraint(equalTo: timeSlider.rightAnchor).isActive = true
fullTimeLabel.bottomAnchor.constraint(equalTo: timeSlider.topAnchor, constant: -10).isActive = true
}
func configurePlayer() {
guard let url = Bundle.main.url(forResource: "sound", withExtension: "mp3") else {
print("Failed to load sound")
return
}
let item = AVPlayerItem(url: url)
player.replaceCurrentItem(with: item)
}
}
ナビゲーションの実装
AVPlayerはseekと呼ばれる方法を提供する.ただし,この方法はCMTimeオブジェクトをパラメータとして受け入れる.
CMTimeオブジェクトを簡単に紹介し、時間を点数と見なします.分子が大きいほど時間の量が多くなり,分母が大きいほど時間の精度が高くなる.(これはCMのコアメディア、すなわちAVFoundationの上部にあるメディアに関するモジュールです.)
既定では、スライダの位置は0~1の数字で表されます.スライダの値を使用してタイムゾーンを計算できます.AVPlayerItemの再生時間の合計長はduration属性によって決定される.CMTimeとして表示され、秒と呼ばれる2つの値に変換することで計算できます.
前回計算したDouble値をCMTimeに再変換してメソッドのパラメータとして渡せばよい!@objc func seek(sender: UISlider) {
// ✅ 현재 아이템을 가져온다 (없으면 함수 종료)
guard let currentItem = player.currentItem else { return }
// ✅ 슬라이더의 위치를 가져와서 이동할 시간으로 바꾼다.
let position = Double(sender.value)
// 👉 slide의 현재 위치 (0 ~ 1)를 Float -> Double
let seconds = position * currentItem.duration.seconds
// 👉 이동할 시간대를 계산
let time = CMTime(seconds: seconds, preferredTimescale: 100)
// 👉 CMTime 객체로 변경, 100은 소수점 아래 2째자리 까지만 쓰겠다는 뜻 (분모)
// ✅ 해당 시간대로 이동
player.seek(to: time)
}
🚫 CMTime init注意事項!
CMTimeには複数のイニシエータがあります.次の2つのinitializerの意味は異なり、よく見て使用する必要があります.CMTime(value: 100, timescale: 100)
//👉 100 / 100 즉 1초를 의미함.
CMTime(seconds: 100, preferredTimescale: 100)
//👉 100초를 분모 100으로 나타낸다 즉 10000 / 100을 의미함.
リアルタイムで再生時間を更新
傍観者を取り付ける
AVPlayerオブジェクトは、モジュールを特定の時間に実行するために傍観者を追加することができる.時間間隔を最初のパラメータで受信します.
次に、どのDispatchQueueがエンクロージャを実行するかをパラメータ形式で受信します.UIに関連するタスクを行うので、ホームチーム列で実行しましょう.
最後のパラメータは、実行するモジュールです.ループ参照を防止するために,弱いselfを用いた.func configureObserver() {
let interval = CMTime(seconds: 1, preferredTimescale: 100)
player.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main) { [weak self] _ in
self?.updateTime()
}
}
UI更新関数
オプティカル(光学式)ドライブのエンクロージャで動作するエンクロージャを実装します.現在の再生時間とAVPlayerItemの完全な時間を取得し、ラベル上のテキストにそれぞれ割り当てます.
最後のナビゲーションなしでスライダのvalueを更新します.(ナビゲーション中に更新すると、ナビゲーション中にスライダの値が変更されるため、ナビゲートできません.)func updateTime() {
let currentTime = self.player.currentItem?.currentTime().seconds ?? 0
let totalTime = self.player.currentItem?.duration.seconds ?? 0
self.currentTimeLabel.text = "\(Int(currentTime))"
self.fullTimeLabel.text = "\(Int(totalTime))"
if !isSeeking {
self.timeSlider.value = Float(currentTime / totalTime)
}
}
結果
また、現在の再生時間は1秒ごとに更新され、スライダでナビゲートできることもわかります.
Reference
この問題について(AVPlayer), 我々は、より多くの情報をここで見つけました
https://velog.io/@comdongsam/3.-AVPlayer
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
UI準備
十分なボタン再生ボタンと一時停止ボタンしか追加されていません.各ボタンは、再生および一時停止を実行するコレクタに接続されています.
import UIKit
class VC3: UIViewController {
// MARK: Properties
let player = AVPlayer()
let playButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "play.fill"), for: .normal)
button.tintColor = .black
button.widthAnchor.constraint(equalToConstant: 50).isActive = true
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
button.contentVerticalAlignment = .fill
button.contentHorizontalAlignment = .fill
button.addTarget(self, action: #selector(play), for: .touchUpInside)
return button
}()
let pauseButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "pause.fill"), for: .normal)
button.tintColor = .black
button.widthAnchor.constraint(equalToConstant: 50).isActive = true
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
button.contentVerticalAlignment = .fill
button.contentHorizontalAlignment = .fill
button.addTarget(self, action: #selector(pause), for: .touchUpInside)
return button
}()
// MARK: LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
}
// MARK: Selector
@objc func play() {
}
@objc func pause() {
}
// MARK: Helpers
func configureUI() {
view.backgroundColor = .white
view.addSubview(playButton)
playButton.translatesAutoresizingMaskIntoConstraints = false
playButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: -50).isActive = true
playButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
view.addSubview(pauseButton)
pauseButton.translatesAutoresizingMaskIntoConstraints = false
pauseButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 50).isActive = true
pauseButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
}
}
AVPlayer ItemをAVPlayerに入れる
サウンドファイルを束に配置します.前述したように、AVPlayerItemはurlまたはassetを使用してinitすることができる.ここではurlでinitを初期化しました.
AVPlayer ItemをAVPlayerの代替現在のItemメソッドとして指定します.
func configurePlayer() {
guard let url = Bundle.main.url(forResource: "sound", withExtension: "mp3") else {
print("Failed to load sound")
return
}
let item = AVPlayerItem(url: url)
player.replaceCurrentItem(with: item)
}
再生、一時停止
再生と一時停止は簡単です.AVPlayerのPlay()とPause()メソッドを使用します.
@objc func play() {
player.play()
}
@objc func pause() {
player.pause()
}
詳細(再生時間の表示とナビゲート)
UI準備
上のUIにナビゲーション用のスライダと現在の時刻と合計時刻を表示するラベルを追加します.
スライダは2つの関数を接続します.1つ目はisSeeking変数を切り替えるコレクタで、ナビゲーションの開始と終了時にそれぞれナビゲーション中であることを示します.2つ目は、スライダの値が変更されたときに実際のナビゲーションを実行するコレクタです.import UIKit
import AVFoundation
let buttonSize: CGFloat = 30
class VC3: UIViewController {
// MARK: Properties
let player = AVPlayer()
let playButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "play.fill"), for: .normal)
button.tintColor = .black
button.widthAnchor.constraint(equalToConstant: buttonSize).isActive = true
button.heightAnchor.constraint(equalToConstant: buttonSize).isActive = true
button.contentVerticalAlignment = .fill
button.contentHorizontalAlignment = .fill
button.addTarget(self, action: #selector(play), for: .touchUpInside)
return button
}()
let pauseButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "pause.fill"), for: .normal)
button.tintColor = .black
button.widthAnchor.constraint(equalToConstant: buttonSize).isActive = true
button.heightAnchor.constraint(equalToConstant: buttonSize).isActive = true
button.contentVerticalAlignment = .fill
button.contentHorizontalAlignment = .fill
button.addTarget(self, action: #selector(pause), for: .touchUpInside)
return button
}()
let timeSlider: UISlider = {
let slider = UISlider()
slider.thumbTintColor = .black
slider.addTarget(self, action: #selector(toggleIsSeeking), for: .editingDidBegin)
slider.addTarget(self, action: #selector(toggleIsSeeking), for: .editingDidEnd)
slider.addTarget(self, action: #selector(seek(sender:)), for: .valueChanged)
return slider
}()
var isSeeking: Bool = false
let currentTimeLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 20)
label.text = "현재 시간"
return label
}()
let fullTimeLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 20)
label.text = "전체 시간"
return label
}()
// MARK: LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
configurePlayer()
}
// MARK: Selector
@objc func play() {
player.play()
}
@objc func pause() {
player.pause()
}
@objc func seek() {
}
@objc func toggleIsSeeking() {
isSeeking.toggle()
}
// MARK: Helpers
func configureUI() {
view.backgroundColor = .white
view.addSubview(playButton)
playButton.translatesAutoresizingMaskIntoConstraints = false
playButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: -30).isActive = true
playButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
view.addSubview(pauseButton)
pauseButton.translatesAutoresizingMaskIntoConstraints = false
pauseButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 30).isActive = true
pauseButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
view.addSubview(timeSlider)
timeSlider.translatesAutoresizingMaskIntoConstraints = false
timeSlider.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 10).isActive = true
timeSlider.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10).isActive = true
timeSlider.bottomAnchor.constraint(equalTo: playButton.topAnchor, constant: -20).isActive = true
view.addSubview(currentTimeLabel)
currentTimeLabel.translatesAutoresizingMaskIntoConstraints = false
currentTimeLabel.leftAnchor.constraint(equalTo: timeSlider.leftAnchor).isActive = true
currentTimeLabel.bottomAnchor.constraint(equalTo: timeSlider.topAnchor, constant: -10).isActive = true
view.addSubview(fullTimeLabel)
fullTimeLabel.translatesAutoresizingMaskIntoConstraints = false
fullTimeLabel.rightAnchor.constraint(equalTo: timeSlider.rightAnchor).isActive = true
fullTimeLabel.bottomAnchor.constraint(equalTo: timeSlider.topAnchor, constant: -10).isActive = true
}
func configurePlayer() {
guard let url = Bundle.main.url(forResource: "sound", withExtension: "mp3") else {
print("Failed to load sound")
return
}
let item = AVPlayerItem(url: url)
player.replaceCurrentItem(with: item)
}
}
ナビゲーションの実装
AVPlayerはseekと呼ばれる方法を提供する.ただし,この方法はCMTimeオブジェクトをパラメータとして受け入れる.
CMTimeオブジェクトを簡単に紹介し、時間を点数と見なします.分子が大きいほど時間の量が多くなり,分母が大きいほど時間の精度が高くなる.(これはCMのコアメディア、すなわちAVFoundationの上部にあるメディアに関するモジュールです.)
既定では、スライダの位置は0~1の数字で表されます.スライダの値を使用してタイムゾーンを計算できます.AVPlayerItemの再生時間の合計長はduration属性によって決定される.CMTimeとして表示され、秒と呼ばれる2つの値に変換することで計算できます.
前回計算したDouble値をCMTimeに再変換してメソッドのパラメータとして渡せばよい!@objc func seek(sender: UISlider) {
// ✅ 현재 아이템을 가져온다 (없으면 함수 종료)
guard let currentItem = player.currentItem else { return }
// ✅ 슬라이더의 위치를 가져와서 이동할 시간으로 바꾼다.
let position = Double(sender.value)
// 👉 slide의 현재 위치 (0 ~ 1)를 Float -> Double
let seconds = position * currentItem.duration.seconds
// 👉 이동할 시간대를 계산
let time = CMTime(seconds: seconds, preferredTimescale: 100)
// 👉 CMTime 객체로 변경, 100은 소수점 아래 2째자리 까지만 쓰겠다는 뜻 (분모)
// ✅ 해당 시간대로 이동
player.seek(to: time)
}
🚫 CMTime init注意事項!
CMTimeには複数のイニシエータがあります.次の2つのinitializerの意味は異なり、よく見て使用する必要があります.CMTime(value: 100, timescale: 100)
//👉 100 / 100 즉 1초를 의미함.
CMTime(seconds: 100, preferredTimescale: 100)
//👉 100초를 분모 100으로 나타낸다 즉 10000 / 100을 의미함.
リアルタイムで再生時間を更新
傍観者を取り付ける
AVPlayerオブジェクトは、モジュールを特定の時間に実行するために傍観者を追加することができる.時間間隔を最初のパラメータで受信します.
次に、どのDispatchQueueがエンクロージャを実行するかをパラメータ形式で受信します.UIに関連するタスクを行うので、ホームチーム列で実行しましょう.
最後のパラメータは、実行するモジュールです.ループ参照を防止するために,弱いselfを用いた.func configureObserver() {
let interval = CMTime(seconds: 1, preferredTimescale: 100)
player.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main) { [weak self] _ in
self?.updateTime()
}
}
UI更新関数
オプティカル(光学式)ドライブのエンクロージャで動作するエンクロージャを実装します.現在の再生時間とAVPlayerItemの完全な時間を取得し、ラベル上のテキストにそれぞれ割り当てます.
最後のナビゲーションなしでスライダのvalueを更新します.(ナビゲーション中に更新すると、ナビゲーション中にスライダの値が変更されるため、ナビゲートできません.)func updateTime() {
let currentTime = self.player.currentItem?.currentTime().seconds ?? 0
let totalTime = self.player.currentItem?.duration.seconds ?? 0
self.currentTimeLabel.text = "\(Int(currentTime))"
self.fullTimeLabel.text = "\(Int(totalTime))"
if !isSeeking {
self.timeSlider.value = Float(currentTime / totalTime)
}
}
結果
また、現在の再生時間は1秒ごとに更新され、スライダでナビゲートできることもわかります.
Reference
この問題について(AVPlayer), 我々は、より多くの情報をここで見つけました
https://velog.io/@comdongsam/3.-AVPlayer
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
import UIKit
import AVFoundation
let buttonSize: CGFloat = 30
class VC3: UIViewController {
// MARK: Properties
let player = AVPlayer()
let playButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "play.fill"), for: .normal)
button.tintColor = .black
button.widthAnchor.constraint(equalToConstant: buttonSize).isActive = true
button.heightAnchor.constraint(equalToConstant: buttonSize).isActive = true
button.contentVerticalAlignment = .fill
button.contentHorizontalAlignment = .fill
button.addTarget(self, action: #selector(play), for: .touchUpInside)
return button
}()
let pauseButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "pause.fill"), for: .normal)
button.tintColor = .black
button.widthAnchor.constraint(equalToConstant: buttonSize).isActive = true
button.heightAnchor.constraint(equalToConstant: buttonSize).isActive = true
button.contentVerticalAlignment = .fill
button.contentHorizontalAlignment = .fill
button.addTarget(self, action: #selector(pause), for: .touchUpInside)
return button
}()
let timeSlider: UISlider = {
let slider = UISlider()
slider.thumbTintColor = .black
slider.addTarget(self, action: #selector(toggleIsSeeking), for: .editingDidBegin)
slider.addTarget(self, action: #selector(toggleIsSeeking), for: .editingDidEnd)
slider.addTarget(self, action: #selector(seek(sender:)), for: .valueChanged)
return slider
}()
var isSeeking: Bool = false
let currentTimeLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 20)
label.text = "현재 시간"
return label
}()
let fullTimeLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 20)
label.text = "전체 시간"
return label
}()
// MARK: LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
configurePlayer()
}
// MARK: Selector
@objc func play() {
player.play()
}
@objc func pause() {
player.pause()
}
@objc func seek() {
}
@objc func toggleIsSeeking() {
isSeeking.toggle()
}
// MARK: Helpers
func configureUI() {
view.backgroundColor = .white
view.addSubview(playButton)
playButton.translatesAutoresizingMaskIntoConstraints = false
playButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: -30).isActive = true
playButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
view.addSubview(pauseButton)
pauseButton.translatesAutoresizingMaskIntoConstraints = false
pauseButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 30).isActive = true
pauseButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
view.addSubview(timeSlider)
timeSlider.translatesAutoresizingMaskIntoConstraints = false
timeSlider.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 10).isActive = true
timeSlider.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10).isActive = true
timeSlider.bottomAnchor.constraint(equalTo: playButton.topAnchor, constant: -20).isActive = true
view.addSubview(currentTimeLabel)
currentTimeLabel.translatesAutoresizingMaskIntoConstraints = false
currentTimeLabel.leftAnchor.constraint(equalTo: timeSlider.leftAnchor).isActive = true
currentTimeLabel.bottomAnchor.constraint(equalTo: timeSlider.topAnchor, constant: -10).isActive = true
view.addSubview(fullTimeLabel)
fullTimeLabel.translatesAutoresizingMaskIntoConstraints = false
fullTimeLabel.rightAnchor.constraint(equalTo: timeSlider.rightAnchor).isActive = true
fullTimeLabel.bottomAnchor.constraint(equalTo: timeSlider.topAnchor, constant: -10).isActive = true
}
func configurePlayer() {
guard let url = Bundle.main.url(forResource: "sound", withExtension: "mp3") else {
print("Failed to load sound")
return
}
let item = AVPlayerItem(url: url)
player.replaceCurrentItem(with: item)
}
}
@objc func seek(sender: UISlider) {
// ✅ 현재 아이템을 가져온다 (없으면 함수 종료)
guard let currentItem = player.currentItem else { return }
// ✅ 슬라이더의 위치를 가져와서 이동할 시간으로 바꾼다.
let position = Double(sender.value)
// 👉 slide의 현재 위치 (0 ~ 1)를 Float -> Double
let seconds = position * currentItem.duration.seconds
// 👉 이동할 시간대를 계산
let time = CMTime(seconds: seconds, preferredTimescale: 100)
// 👉 CMTime 객체로 변경, 100은 소수점 아래 2째자리 까지만 쓰겠다는 뜻 (분모)
// ✅ 해당 시간대로 이동
player.seek(to: time)
}
CMTime(value: 100, timescale: 100)
//👉 100 / 100 즉 1초를 의미함.
CMTime(seconds: 100, preferredTimescale: 100)
//👉 100초를 분모 100으로 나타낸다 즉 10000 / 100을 의미함.
func configureObserver() {
let interval = CMTime(seconds: 1, preferredTimescale: 100)
player.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main) { [weak self] _ in
self?.updateTime()
}
}
func updateTime() {
let currentTime = self.player.currentItem?.currentTime().seconds ?? 0
let totalTime = self.player.currentItem?.duration.seconds ?? 0
self.currentTimeLabel.text = "\(Int(currentTime))"
self.fullTimeLabel.text = "\(Int(totalTime))"
if !isSeeking {
self.timeSlider.value = Float(currentTime / totalTime)
}
}
Reference
この問題について(AVPlayer), 我々は、より多くの情報をここで見つけました https://velog.io/@comdongsam/3.-AVPlayerテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol