AVPlayer & SwiftUI Part 2 : 플레이어 컨트롤

업데이트 : iOS 14에서 SwiftUI의 자체 VideoPlayer 뷰에 대한 5 부를 확인하십시오 !

이 기사는 Xcode 11 베타 3에 대해 작성되었습니다.

에서 내 마지막 기사 나는 AVPlayer를 가진 SwiftUI에서 비디오를 표시하는 방법을 보여 주었다. 사용자에게 비디오를 보여주고 싶다면 사용자가 어떤 방식 으로든 비디오를 제어 할 수 있기를 원할 것입니다. 아마도 재생을 일시 중지하거나 다른 지점을 찾는 것입니다.

따라서 마지막 기사의 끝에서 끝낸 코드를 기반으로 몇 가지 기본 컨트롤을 얻기 위해 수행해야하는 작업을 살펴 ​​보겠습니다.

먼저, 일부 정리

먼저 지난 기사에서 코드를 남겨둔 부분의 단점을 해결하고 싶습니다. PlayerUIView 클래스에는 비디오 용으로 하드 코딩 된 URL이 있었는데, 이는 매우 열악하므로 대신 PlayerView 외부에 플레이어를 생성하여 수정하겠습니다.

PlayerView에 플레이어를 전달하도록 ContentView의 본문을 업데이트합니다.

var body: some View {
  PlayerView(player: AVPlayer(url: URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")!))
}

struct PlayerView: UIViewRepresentable {
  let player: AVPlayer
  func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
}
  func makeUIView(context: Context) -> UIView {
    return PlayerUIView(player: player)
  }
}

init(player: AVPlayer) {
  super.init(frame: .zero)
playerLayer.player = player
  layer.addSublayer(playerLayer)
}

약간의 준비

PlayerView와 PlayerControlsView를 모두 포함하는 PlayerControlsView 및 PlayerContainerView의 몇 가지 새로운 View 구조체를 만들어 시작하겠습니다.

struct PlayerContainerView : View {
  private let player: AVPlayer
  init(player: AVPlayer) {
    self.player = player
  }
  var body: some View {
    VStack {
      PlayerView(player: player)
      PlayerControlsView(player: player)
    }
  }
}

var body: some View {
  PlayerContainerView(player: AVPlayer(url: URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")!))
}

struct PlayerControlsView : View {
  let player: AVPlayer
  var body: some View {
    Text("TODO")
  }
}

재생 / 일시 정지 버튼

재생 / 일시 정지 버튼으로 문제를 해결해 봅시다! 다음으로 PlayerControlsView를 업데이트합니다.

struct PlayerControlsView : View {
  @State var playerPaused = true
  let player: AVPlayer
  var body: some View {
    Button(action: {
      self.playerPaused.toggle()
      if self.playerPaused {
        self.player.pause()
      }
      else {
        self.player.play()
      }
    }) {
      Image(systemName: playerPaused ? "play" : "pause")
    }
  }
}

실행하면 재생 버튼을 탭하면 동영상이 재생되기 시작하고 (상당한 양의 검은 색을 띠고 있습니다. Sintel에게 감사합니다…) 버튼이 일시 중지 아이콘으로 변경됩니다. 다시 탭하면 비디오가 일시 중지되고 재생 아이콘으로 다시 변경됩니다.

추구

다음으로 Slider를 사용하여 사용자가 비디오의 특정 지점을 찾을 수 있도록합니다. 슬라이더의 값을 얻으려면 위치 탐색 상태가 필요하므로이를 PlayerControlsView 구조체의 맨 위에 추가합니다.

@State var seekPos = 0.0

Slider(value: $seekPos, from: 0, through: 1, onEditingChanged: { _ in
  guard let item = self.player.currentItem else {
    return
  }
  let targetTime = self.seekPos * item.duration.seconds
  self.player.seek(to: CMTime(seconds: targetTime, preferredTimescale: 600))
})

클로저에서 플레이어에서 currentItem을 가져온 다음 (있는 경우) 슬라이더의 값 (self.seekPos라고도 함)과 비디오 지속 시간을 기반으로 검색해야하는 시간을 계산합니다. 그런 다음 우리는 탐색을합니다!

이 뷰를 PlayerControlsView로 가져 오려면 HStack을 사용하여 Play / Pause 버튼 옆에 놓아야합니다. 또한 재생 / 일시 중지 버튼의 양쪽과 슬라이더 끝에 패딩을 추가하여 모든 것이 올바르게 보이도록합니다.

struct PlayerControlsView : View {
  @State var playerPaused = true
  @State var seekPos = 0.0
  let player: AVPlayer
  var body: some View {
    HStack {
      Button(action: {
        self.playerPaused.toggle()
        if self.playerPaused {
          self.player.pause()
        }
        else {
          self.player.play()
        }
      }) {
        Image(systemName: playerPaused ? "play" : "pause")
          .padding(.leading, 20)
          .padding(.trailing, 20)
      }
      Slider(value: $seekPos, from: 0, through: 1, onEditingChanged: { _ in
        guard let item = self.player.currentItem else {
          return
        }
 
        let targetTime = self.seekPos * item.duration.seconds
        self.player.seek(to: CMTime(seconds: targetTime, preferredTimescale: 600))
      })
        .padding(.trailing, 20)
    }
  }
}

탐색 표시 줄도 진행 표시 줄이어야합니다.

탐색 바가 정말로 원하는 것은 비디오의 진행 상황에 맞춰 시간을 이동하여 비디오를 얼마나 진행했는지 파악하는 것입니다. 다음과 같이 PlayerControlsView에 대한 이니셜 라이저를 추가하면 충분히 쉽습니다.

init(player: AVPlayer) {
  self.player = player
player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 0.5, preferredTimescale: 600), queue: nil) { time in
    guard let item = self.player.currentItem else {
      return
    }
self.seekPos = time.seconds / item.duration.seconds
  }
}

안타깝게도 컴파일 할 때 '세그먼트 오류 : 11'오류가 발생하며 이는 이상적이지 않습니다. 나는 Apple과 함께 이것을 제기했으며, 아마도 베타에서 이빨이 나는 문제가 있거나 아마도 어리석은 일을하고있을 것입니다 (컴파일러의 충돌은 확실히 옳지 않습니다!).

기사를 끝내는 가장 좋은 방법은 아니지만 지금은 그게 제가 공유 할 전부입니다!

업데이트 : 파트 3을 지금 사용할 수 있습니다!

Suggested posts

Swift에서 느리게 이미지로드

의존성 주입을 잊지 마세요!

Swift에서 느리게 이미지로드

난이도 : 초급 | 쉬운 | 정상 | 이 기사는 Xcode 12.4 및 Swift 5를 사용하여 개발되었습니다.

코드없이 SwiftUI 디자인 블록 배우기

최고의 SwiftUI 학습 경험을 구축하는 전 Apple 디자이너

코드없이 SwiftUI 디자인 블록 배우기

제 이름은 Sahand Nayebaziz입니다. 저는 Photoshop과 그래픽 디자인으로 시작한 인터페이스 디자이너입니다.