【Swift】動画の時間トリミングスライダーの実装：ABVideoRangeSliderを使って

# はじめに

環境は
Swift4　＋　Xcode9.2

動画の時間範囲を指定するスライダーを自作しようと思うと、とてもめんどくさい。

<img width="427" alt="スクリーンショット 2019-09-07 17.23.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211271/4809d923-7e6a-d67b-77a3-5babc92e6b66.png">

こんな感じのやつです。

出典：https://github.com/AppsBoulevard/ABVideoRangeSlider
（この記事のトピックであるライブラリ、ABVideoRangeSliderのgithubからお借りしました）

調べたところiOSアプリ開発者のためにUIVideoEditorControllerというのが用意されていているのですが、カスタマイズが効かないのが不満。

具体的には

１、先頭時刻/後尾時刻ツマミ（以降ツマミと呼びます）のデフォルト位置が決められない
２、ツマミの選択可能範囲が動画の全時間域になってしまう（ここからここまでの間のみを表示したい！ができない）

というのが不満。（もしもUIVideoEditorControllerでこれらを設定できたら教えていただきたいです！）


ということで、ライブラリの導入を検討しました。

# 先に結論

少し長くなるので、先にまとめを。

・公開ライブラリのAMVideoRangeSliderとABVideoRangeSliderをSwift4で試した
・どちらもSwift3向けで、podにinstallしたあとSwift4用に修正する必要がある
・今回はABVideoRangeSliderを使い、Swift4用に修正したあと、さらにソースをいじった
・理由はコードからインスタンスを作成すると、意図されていないせいかツマミの表示がおかしくなるのと、不満点の二番目を解決するため


# 検討したライブラリ

### AMVideoRangeSlider

まず試したのはAMVideoRangeSlider（https://github.com/iAmrMohamed/AMVideoRangeSlider）。
しかし、重大な問題点が。。（自分の実装のせいだとしたらごめんなさい）

ツマミで先頭時刻/後尾時刻を同じにする、つまり２つのツマミをがっちゃんこした状態にすると、先頭時刻＝後尾時刻にはならなかった！

おそらくツマミの太さを考慮せず、各ツマミの中央位置を使って時刻を算出しているためだと思われます。

こちらのソースコードを修正しても良かったのですが、自分は次のライブラリを（修正して）使いました。

### ABVideoRangeSlider

次にこちらを試しました。（https://github.com/AppsBoulevard/ABVideoRangeSlider）

自分はSwift4環境だったので、podからinstallしたあと、コンパイル時に出るWarningで「fix」ボタンをポチポチしてSwift4用に修正する必要がありました。
ポチポチするだけでは修正されないものも、関数名でググれば新旧の関数比較が出てくるので、そこまで骨の折れる作業ではありませんでした。

# ABVideoRangeSliderをいじる

で、いざ

```swift

let videoRangeSlider = ABVideoRangeSlider()
```

からコードで呼び出したのですが、、、

ツマミの描画がおかしい！



よくよく調べてみると、ツマミの部分は画像（UIImageView）を使っていて、このサイズがおかしいのが原因でした。

で、なぜそんなことが起きたかというと！

動画のサムネイル画像がたくさん詰まっている四角い部分は

```swift

videoRangeSlider.frame = CGRect(...)
```

な感じで指定したframeを反映して描画されますが、ツマミの大きさはインスタンスを作成した時点で（デフォルトの値を使って）作成されてしまうっぽいです。

StoryBoardから呼び出されることのみを想定しているため、このような問題が発生したのではないでしょうか？

だけど俺は、極力コードから呼び出したいんや！


# コードからインスタンスを作成するために

以下はpod installして、さらにソースをSwift4用に修正した後の話です。
ソースファイルは
プロジェクトディレクトリ/Pods/ABVideoRangeSlider/
にあります。


ソースを見てみると

```ABVideoRangeSlider.swift

public class ABVideoRangeSlider: UIView {
   （略）
    override init(frame: CGRect) {
            super.init(frame: frame)
            self.setup()
    }

    required public init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
    }
```

とあり、インスタンス化した時に

```swift
self.setup()
```
メソッドでツマミViewを作成しています。

なので、修正方法としては
１、インスタンス作成時にframeを引数に取らなければいけない仕様にする
２、呼び出し側からframeが設定されてからself.setup()を実行する
あたりが考えられると思います。

できるだけ元のコードに手を加えたくないのであれば、あまりスマートなやり方ではありませんが

```ABVideoRangeSlider.swift

public class ABVideoRangeSlider: UIView {
   （略）
    override init(frame: CGRect) {
            super.init(frame: frame)
            // self.setup()  <- コメントアウト
    }
    （略）
    public func setup(){  // <- publicに変更して呼び出せるようにする
        （略）
```


```swift

let videoRangeSlider = ABVideoRangeSlider()
videoRangeSlider.frame = CGRect(...)
videoRangeSlider.setup()
```
と、すれば解決するはずです。

ただし、実はもう一つ注意するべき点があって、ツマミの縦幅はこれで解決ですが横幅はデフォルトで20と決められています。

```ABVideoRangeSlider.swift

public class ABVideoRangeSlider: UIView {
  （略）
   let indicatorWidth: CGFloat = 20.0
```

全体の横幅に対する比率に変更するなら、

```ABVideoRangeSlider.swift

public class ABVideoRangeSlider: UIView {
  （略）
   var indicatorWidth: CGFloat = 20.0  // <- letをvarにして変更可能に
  （略）
   public func setup(){
        self.indicatorWidth = self.bounds.width * 0.05  // 適当な値に変更
```

で解決。


# 選択できる時間範囲を制限したい

ABVideoRangeSliderで表示される時間幅は、動画の開始時刻（0s)〜終了時刻となっています。

だけど、例えば5s~9sの間だけを表示したい！のであれば、以下の方法で上手くいきます。


```ABVideoRangeSlider.swift

public class ABVideoRangeSlider: UIView {
  （略）
    var beginTimeLimit: Float64!  // 追加
    var endTimeLimit: Float64!    // 追加
  （略）
   // 適当に引数を追加して、表示する開始時刻と終了時刻を指定できるようにする
   public func setVideoURL(videoURL: URL, beginTime: Float64=0, endTime: Float64?=nil){
        var endTimeUsed: Float64
        if endTime == nil {
            endTimeUsed = ABVideoHelper.videoDuration(videoURL: videoURL)
        } else {
            endTimeUsed = endTime!
        }
        self.beginTimeLimit = beginTime
        self.endTimeLimit = endTimeUsed

        // こちらが元のコード：動画の長さをそのまま使っている
        // self.duration = ABVideoHelper.videoDuration(videoURL: videoURL)
        // 変更後：時間幅を計算する
        self.duration = endTimeUsed - beginTime

        // 以下は元のまま
        self.videoURL = videoURL
        self.superview?.layoutSubviews()
        self.updateThumbnails()
   }

   // 以下の２つのメソッドもself.beginTimeLimitとself.endTimeLimitを考慮した計算方法に修正
   private func secondsFromValue(value: CGFloat) -> Float64{
         return duration * Float64((value / 100)) + self.beginTimeLimit
   }
    
   private func valueFromSeconds(seconds: Float) -> CGFloat{
       return CGFloat(Float64(seconds)-self.beginTimeLimit) * 100 / CGFloat(duration)
    }
   
```

これで、指定した時間幅の中のみで範囲指定することができるようになりました！

<img width="291" alt="スクリーンショット 2019-09-07 19.07.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211271/63b33f25-fc0e-2aba-f5d7-0a59cb5c4db3.png">

↑先頭時刻ツマミが左端だけど、0sにならず、指定した3s地点になっている

以上！