AudioPlayer 音频播放器

SwiftUI 与 AVFoundation 相结合,让您可以在应用程序中轻松创建多功能音频播放器。

1. AVFoundation 框架

使用音频播放器,首先要导入 AVFoundation ,这是苹果用于操作音频和视频内容的框架。

import AVFoundation

2. 创建音频控制器

新建一个 AudioPlayerViewModel 类,充当 SwiftUI 视图和 AVAudioPlayer 实例之间的桥梁。它处理 AVAudioPlayer 的初始化并管理播放状态,如果音频文件丢失或音频播放器无法实例化,该类还会记录相应的错误消息。并且可以控制音频的播放和暂停。


class AudioPlayerViewModel: ObservableObject {

	// 声明一个音频播放器,AVAudioPlayer 是用于音频播放的类
  var audioPlayer: AVAudioPlayer?

  @Published var isPlaying = false

	// 初始化:将播放器和音频绑定
  init() {
	    if let sound = Bundle.main.path(forResource: "PocketCyclopsLvl1", ofType: "mp3") {
		      do {
			         self.audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: sound))
		      } catch {
				       print("AVAudioPlayer could not be instantiated.")
		      }
	    } else {
		      print("Audio file could not be found.")
	    }
  }

	// 通过控制器去控制音频的播放暂停
  func playOrPause() {
	    guard let player = audioPlayer else { return }
	    if player.isPlaying {
		      player.pause()
		      isPlaying = false
	    } else {
		      player.play()
		      isPlaying = true
	    }
	}
	
}

3. 与视图结合

在具体视图中,将 AudioPlayerViewModel 实例化为 @State ,确保该实例在 ContentView 的生命周期内持续存在。

提供一个按钮,点击该按钮时,会调用视图模型上的 playOrPause 方法,从而控制音频播放。按钮内的图像会动态变化以反映当前播放状态(音频暂停时显示播放图标,或音频播放时显示暂停图标)。

struct ContentView: View {

	// 创建音频控制器的实例
  @State var audioPlayerViewModel = AudioPlayerViewModel()

  var body: some View {
    VStack {
      Button(action: {
        audioPlayerViewModel.playOrPause()
      }) {
        Image(systemName: audioPlayerViewModel.isPlaying ? "pause.circle" : "play.circle")
          .resizable()
          .frame(width: 64, height: 64)
      }
    }
  }
}

在 SwiftUI 中添加音效

// 首先导入 AVFoundation 框架,其中包括 AVAudioPlayer ,一个用于从文件或内存播放音频的类
import AVFoundation

// ContentView 结构体包括两个用于控制声音播放的 Button 视图和一个用于选择音效的 Picker 视图
struct ContentView: View {

  @State private var player: AVAudioPlayer?
  @State private var selectedSound: String = "bounce"

  let soundNames = ["bounce", "button", "crawler_die", "crawler_jump", "flyerattack", "flyercloseeye", "flyerdie"]

  var body: some View {
    VStack {
      Picker(selection: $selectedSound, label: Text("Select Sound")) {
        ForEach(soundNames, id: \\.self) {
          Text($0)
        }
      }
      .padding()

      Button(action: {
        self.playSound()
      }) {
        Text("Play Sound")
      }
    }
  }

  func playSound() {
    guard let soundURL = Bundle.main.url(forResource: selectedSound, withExtension: "wav") else {
      return
    }
    do {
      player = try AVAudioPlayer(contentsOf: soundURL)
    } catch {
      print("Failed to load the sound: \\(error)")
    }
    player?.play()
  }
  
}


VideoPlayer 视频播放器

VideoPlayer 视图可以从任何 URL(本地或远程)播放电影。

1. AVKit 框架

VideoPlayer 来自于 AVKit 框架,因此在尝试之前应该确保导入该框架:

import AVKit

2. 创建 VideoPlayer

假设您的应用程序包中有 video.mp4 并且想要播放它,可以使用以下代码:

// 尝试在主应用程序包中查找名为“BookTrailer.m4v”的文件
let videoURL: URL? = Bundle.main.url(forResource: "BookTrailer", withExtension: "m4v")

VStack {
    if let url = videoURL {
		    // 调用播放器
        VideoPlayer(player: AVPlayer(url: url))
    } else {
        Text("Video not found")
    }
}
.frame(height: 400)

    
// 如果想播放远程视频,请使用其远程 URL:
VideoPlayer(player: 
		AVPlayer(
				url:  URL(string: "<https://yoursite.com/video.mp4>")!
		)
)
.frame(height: 400)

如果需要,可以向 VideoPlayer 初始值设定项提供第二个参数闭包,用于添加要在视频上绘制的内容。这将绘制在系统视频控件下方,但可以响应这些控件未捕获的任何事件。

// 例如,这会将文本“水印”放置在视频区域的最顶部:
VideoPlayer(player: AVPlayer(url:  URL(string: "<https://bit.ly/swswift>")!)) {
    VStack {
        Text("Watermark")
            .foregroundStyle(.black)
            .background(.white.opacity(0.7))
        Spacer()
    }
    .frame(width: 400, height: 300)
}

3. 导入设备的视频

用 PhotosPicker 导入视频


在 SwiftUI 中实现视频流

在此示例中,您将利用 SwiftUI 的 VideoPlayer 视图,该视图简化了从远程 URL 流式传输视频的过程。 VideoPlayer 视图环绕 AVPlayerViewController ,提供带有播放控件的成熟视频播放器界面。

import AVKit

struct ContentView: View {
	  var body: some View {
		    NavigationStack {
			      VideoPlayer(player: AVPlayer(url: URL(string: "<https://archive.org/download/four_days_of_gemini_4/four_days_of_gemini_4_512kb.mp4>")!))
		        .navigationTitle("Video Player")
		    }
	  }
}

创建音频和视频的动画可视化

在 SwiftUI 中,为音频和视频创建动画可视化是一件轻而易举的事。您所需要的只是对 Path 和 Shape 协议有基本的了解。

// 创建了一个名为 AnimatedVisualizer 的自定义形状。
// 该形状采用表示音频样本的 CGFloat 值数组。然后使用 path 函数根据提供的音频样本创建可视化效果。
struct AnimatedVisualizer: Shape {

  let audioSamples: [CGFloat]

  func path(in rect: CGRect) -> Path {
    var path = Path()

    let height = rect.height
    let width = rect.width / CGFloat(audioSamples.count)

    for i in 0 ..< audioSamples.count {
      let x = width * CGFloat(i)
      let y = CGFloat(audioSamples[i]) * height

      path.addRect(CGRect(x: x, y: 0, width: width, height: y))
    }

    return path
  }
}

// 然后,在 ContentView 中使用此自定义形状,其中使用 ZStack 将可视化效果添加为背景元素。
// 您还可以添加一些动画,为可视化效果提供炫酷的脉动效果。
struct ContentView: View {

  @State private var audioSamples: [CGFloat] = [0.2, 0.5, 0.8, 0.3, 0.6, 0.9, 0.4, 0.4, 0.4, 0.4]

  var body: some View {
    ZStack {
      AnimatedVisualizer(audioSamples: audioSamples)
        .fill(Color.red)
        .opacity(0.8)
        .animation(Animation.easeInOut(duration: 0.2).repeatForever(autoreverses: true), value: audioSamples)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
    // 在 onAppear 块中,您每 0.2 秒生成随机音频样本以动态更新可视化。
    .onAppear {
      Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { timer in
        self.audioSamples = self.generateAudioSamples()
      }
    }
  }

  func generateAudioSamples() -> [CGFloat] {
    var samples: [CGFloat] = []
    for _ in 0...10 {
      samples.append(CGFloat.random(in: 0...1))
    }
    return samples
  }
  
}


处理播放音视频时的错误

音频和视频播放错误可能源于一系列问题,包括网络连接问题、文件丢失或放错位置以及不受支持的文件格式等。妥善管理这些错误至关重要,每当出现问题时为用户提供清晰且有用的反馈。

要在本地进行测试,请尝试在禁用网络连接的情况下播放视频。然后您应该会看到显示的错误消息。

import AVKit

struct ContentView: View {

  let player = AVPlayer(url: URL(string: "<https://archive.org/download/lunchroom_manners/lunchroom_manners_512kb.mp4>")!)
  
  let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
  
  @State private var isPlaybackLikelyToKeepUp = true

  var body: some View {
    VStack {
      VideoPlayer(player: player)
      if !isPlaybackLikelyToKeepUp {
        Text("Playback Error: Network load is likely to prevent playback from keeping up.")
      }
    }
    .onReceive(timer, perform: { _ in
	      isPlaybackLikelyToKeepUp = player.currentItem?.isPlaybackLikelyToKeepUp ?? true
    })
  }
  
}

AVKit 框架中的 VideoPlayer 视图用于播放视频。 AVPlayerItem 的 isPlaybackLikelyToKeepUp 属性用于确定网络负载是否妨碍视频播放。如果它可能妨碍播放,则会显示一条用户友好的错误消息。

此方法是处理播放错误的简化方法,特别是对于涉及网络问题的场景。根据您的具体需求,可能需要其他操作来处理各种错误类型。