【SwiftUI】VideoPlayerに再生速度変更機能を追加してみた
ビデオプレイヤーを簡単に実装できるAVKitとUIKitのAVPlayerViewController
やSwiftUIのVideoPlayer
ですが、スロー再生機能をつけたいなと思ったのですが既存の機能には無さそうだったので再生速度を変更する機能を追加してみました。
環境
- Xcode 13.3
- iOS 15.5
はじめに
今回はSwiftUIのVideoPlayer
に再生速度を変更するメニューボタンを付ける簡単なものになります。Player自体をフルカスタムして作成するようなものではありません。
作ったもの
VideoPlayer画面 | 再生速度変更メニュー表示 |
---|---|
実装
PlaySpeed
まずは再生速度のrate
と表示ラベル用にenum
のPlaySpeed
を作成します。
今回は、0.25~1.75倍速の幅で用意しました。
enum PlaySpeed: String, CaseIterable, Identifiable { case x0_25 = "0.25倍速" case x0_5 = "0.5倍速" case x0_75 = "0.75倍速" case x1 = "標準" case x1_25 = "1.25倍速" case x1_5 = "1.5倍速" case x1_75 = "1.75倍速" var id: String { rawValue } var rate: Float { switch self { case .x0_25: return 0.25 case .x0_5: return 0.5 case .x0_75: return 0.75 case .x1: return 1 case .x1_25: return 1.25 case .x1_5: return 1.5 case .x1_75: return 1.75 } } var displayTitle: String { return "再生速度・" + self.rawValue } }
AVPlayer
の再生速度を変更するにはrate
に変更したい値を入力する必要がある為、それぞれの倍数にあったrate
を返しています。
ViewModel
SpeedAdjustableVideoPlayerViewModel
というObservableObject
のクラスを作成しました。
import AVKit import Combine class SpeedAdjustableVideoPlayerViewModel: ObservableObject { @Published var player: AVPlayer @Published var playSpeed = PlaySpeed.x1 @Published var displaySpeedTitle = PlaySpeed.x1.displayTitle private var lastPlaySpeed = PlaySpeed.x1 private var cancellable: AnyCancellable? init(player: AVPlayer) { self.player = player observeRate() } func observeRate() { cancellable = player.publisher(for: \.rate).sink(receiveValue: { [weak self] rate in guard let self = self else { return } if rate == 1.0, self.lastPlaySpeed != self.playSpeed { // 停止ボタンを押して、再生を押すと自動的にrateが1.0に戻ってしまい // playSpeedもx1に戻ってしまう為、 // 最後に設定していたSpeedにrateを変更する self.changeRate(into: self.lastPlaySpeed) } }) } func changeRate(into speed: PlaySpeed) { lastPlaySpeed = speed player.rate = speed.rate displaySpeedTitle = speed.displayTitle } }
プロパティ
@Published var player: AVPlayer @Published var playSpeed = PlaySpeed.x1 @Published var displaySpeedTitle = PlaySpeed.x1.displayTitle private var lastPlaySpeed = PlaySpeed.x1 private var cancellable: AnyCancellable?
- player
- パブリッシュする動画を再生する為のAVPlayer
- playSpeed
- パブリッシュする再生速度
- displaySpeedTitle
- パブリッシュする画面表示用の再生速度タイトル
- lastPlaySpeed
- 最後に変更された再生速度
- cancellable
- サブスクライブのキャンセルに必要
init
init(player: AVPlayer) { self.player = player observeRate() }
引数として渡ってきたplayer
を自身のplayer
に代入し、player
のrate
を監視する為にobserveRate()
を呼んでいます。
observeRate()
player
のrate
をsink
で変更がある度に行いたい処理を書いています。
func observeRate() { cancellable = player.publisher(for: \.rate).sink(receiveValue: { [weak self] rate in guard let self = self else { return } if rate == 1.0, self.lastPlaySpeed != self.playSpeed { // 停止ボタンを押して、再生を押すと自動的にrateが1.0に戻ってしまい // playSpeedもx1に戻ってしまう為、 // 最後に設定していたSpeedにrateを変更する self.changeRate(into: self.lastPlaySpeed) } }) }
rate
を変更していた場合でも、VideoPlayer
で停止ボタンが押された際は、rate
が0.0
になり、再生ボタンが押された際は、rate
が1.0
になりました。さらにplaySpeed
も標準に戻ってしまう動きになってしまっていた為、rate
が1.0
に変更された際にlastPlaySpeed
とplaySpeed
が一致しない場合は、lastPlaySpeed
で再生レートをchangeRate(into:)
関数を使用して変更するようにしました。
changeRate(into:)
func changeRate(into speed: PlaySpeed) { lastPlaySpeed = speed player.rate = speed.rate displaySpeedTitle = speed.displayTitle }
引数で渡されたPlaySpeed
を使用して、再生レートと表示用のタイトルを更新。rate
が更新されたのでlastPlaySpeed
の値もspeed
の値で更新しています。
SpeedAdjustableVideoPlayer
再生速度を変更して調整可能なVideoPlayer
のViewです。
import SwiftUI import AVKit struct SpeedAdjustableVideoPlayer: View { @StateObject var viewModel: SpeedAdjustableVideoPlayerViewModel init(player: AVPlayer) { _viewModel = StateObject(wrappedValue: SpeedAdjustableVideoPlayerViewModel(player: player)) } var body: some View { VStack { VideoPlayer(player: viewModel.player) HStack { Spacer() Menu(viewModel.displaySpeedTitle) { ForEach(PlaySpeed.allCases) { speed in Button { viewModel.changeRate(into: speed) } label: { Text(speed.rawValue) } } } } } .background(.black) } }
init
init(player: AVPlayer) { _viewModel = StateObject(wrappedValue: SpeedAdjustableVideoPlayerViewModel(player: player)) }
init
でAVPlayer
を受け取って、そのplayer
をSpeedAdjustableVideoPlayerViewModel
に渡してViewModelを生成しています。
body
シンプルにVStack
でVideoPlayer
と再生速度変更用のMenu
を並べています。
var body: some View { VStack { VideoPlayer(player: viewModel.player) HStack { Spacer() Menu(viewModel.displaySpeedTitle) { ForEach(PlaySpeed.allCases) { speed in Button { viewModel.changeRate(into: speed) } label: { Text(speed.rawValue) } } } } } .background(.black) }
VideoPlayer
の背景色と合わせるために.background
に.black
を設定しています。
ContentView
あとは、SpeedAdjustableVideoPlayer
に再生したいurl
が渡されたAVPlayer
をセットするだけです。
import SwiftUI import AVKit struct ContentView: View { let url = Bundle.main.url(forResource: "sample", withExtension: "mp4")! var body: some View { SpeedAdjustableVideoPlayer(player: AVPlayer(url: url)) } }
おわりに
無事に簡単な再生速度調整機能を追加することが出来ました。
コードはGitHubに載せております。
動画再生するだけならVideoPlayer
だけで再生プレイヤーを表示できるのでとても助かりますね!