この記事で学ぶこと:
プロジェクトで使えるTips集
①を読んでなくても問題ないですが、概念が曖昧というか方は①から読まれるのをおすすめします。👇

Contents 非表示
プロジェクトで使えるTips集
1.1 カスタムプレイヤーUIを作るには?
VideoPlayer
(SwiftUI純正)でも簡単再生はできるけど、
カスタムUI(再生ボタン・スライダー・時間表示など)を作りたいなら、AVPlayer直操作がマスト
import SwiftUI
import AVFoundation
import UIKit
import AVKit
struct CustomPlayerView: View {
@State private var player = AVPlayer(url: URL(string: "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8")!)
@State private var isPlaying = false
var body: some View {
VStack {
VideoPlayerContainer(player: player)
.frame(height: 250)
HStack {
Button(action: {
isPlaying ? player.pause() : player.play()
isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.fill" : "play.fill")
.font(.largeTitle)
}
Spacer()
Button("⏹ Stop") {
player.pause()
player.seek(to: .zero)
isPlaying = false
}
}
.padding()
}
.onDisappear {
player.pause()
}
}
}
struct VideoPlayerContainer: UIViewControllerRepresentable {
let player: AVPlayer
func makeUIViewController(context: Context) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false // カスタムUIにするなら必須
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {}
}
ポイント:
項目 | ポイント |
---|---|
AVPlayerViewController | UIKitから持ってきて再生処理担当 |
showsPlaybackControls = false | 純正UIを非表示にして、SwiftUI側で制御 |
@State isPlaying | UIと連動させてトグル再生を実現 |
1.2 サムネイル取得 (AVAssetImageGenerator)
最小構成:
func generateThumbnail(from url: URL, at seconds: Double) async throws -> UIImage {
let asset = AVAsset(url: url)
let generator = AVAssetImageGenerator(asset: asset)
generator.appliesPreferredTrackTransform = true
let time = CMTime(seconds: seconds, preferredTimescale: 600)
let cgImage = try generator.copyCGImage(at: time, actualTime: nil)
return UIImage(cgImage: cgImage)
}
ポイント:
項目 | ポイント |
---|---|
.appliesPreferredTrackTransform | 動画の回転補正を自動でやってくれる |
copyCGImage | 一発でCGImage取得。非同期にするなら generateCGImagesAsynchronously |
.toleranceBefore/After | 精度を高めたいときのオプション(省略可) |
1.3 音だけ再生 / 映像だけ再生
🎧 音だけ再生
let playerItem = AVPlayerItem(url: audioURL)
playerItem.videoComposition = AVVideoComposition() // 空のビデオで映像オフ
let player = AVPlayer(playerItem: playerItem)
🎬 映像だけ再生(音なし)
player.isMuted = true
ポイント:
モード | 方法 |
---|---|
音だけ | AVAudioPlayer でもOK、または videoComposition を空に |
映像だけ | player.isMuted = true or AVMutableAudioMix でミュート制御 |
1.4 SwiftUI + AVFoundation の組み合わせ例
カメラプレビューをSwiftUIに表示
struct CameraPreviewView: UIViewRepresentable {
let session: AVCaptureSession
func makeUIView(context: Context) -> UIView {
let view = UIView()
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.frame = UIScreen.main.bounds
view.layer.addSublayer(previewLayer)
return view
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
ポイント:
項目 | ポイント |
---|---|
UIViewRepresentable | SwiftUIとUIKitをブリッジする技 |
AVCaptureVideoPreviewLayer | セッションの映像をリアルタイムで表示 |
videoGravity | アスペクト比調整。resizeAspectFill or resizeAspect |
Q&Aまとめ
Q1. 「そもそも再生できない、、、」
💥 よくある原因:
原因 | チェックポイント |
---|---|
.m3u8 or .mp4 のURLが無効 | URL(string:) → nilになってないか? |
ネットが切れてる | オフラインでHLSは無理(ローカルキャッシュしてなければ) |
AVPlayerItemのstatusがfailed | KVOで監視して原因ログを出すべし |
Info.plistでATS制限 | HTTP URLなら NSAppTransportSecurity を緩める必要あり |
動画の形式が非対応 | .webm や非H.264だとアウト |
チェック:
player.currentItem?.observe(\.status, options: [.new]) { item, _ in
switch item.status {
case .readyToPlay:
print("🎉 Ready to play!")
case .failed:
print("❌ Failed:", item.error ?? "unknown error")
default:
break
}
}
HLSの.m3u8
再生はネット接続 + 有効なURLがマスト
Q2. 「録音したデータを保存するには?」
🎤 基本の録音構成:
AVAudioRecorder
を使う- 保存先は
.m4a
(AAC形式)あたりが現実的 prepareToRecord()
→record()
→stop()
の流れ
サンプル:
import AVFoundation
let audioSession = AVAudioSession.sharedInstance()
try? audioSession.setCategory(.playAndRecord, mode: .default)
try? audioSession.setActive(true)
let url = FileManager.default.temporaryDirectory.appendingPathComponent("record.m4a")
let settings: [String: Any] = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
let recorder = try AVAudioRecorder(url: url, settings: settings)
recorder.record()
// 停止時
recorder.stop()
保存後は url.path
をログ出して、共有や再生で使えるようにしておくとGOOD
Q3. 「なぜシークが正確ではないのか」
A.
原因 | 内容 |
---|---|
シークは1フレーム単位で動く | フレームの種類の都合上、完全なピンポイントにはならない |
HLSは.ts単位で飛ぶ | tsが3秒単位とかなので、30秒指定しても29.2秒に着地したりする |
toleranceBefore/After の設定が甘い | 許容誤差がデフォルトだと広くてズレやすい |
精度を高めたいなら:
// ⚠️ .zero にするとシビアすぎて逆に失敗することもあるので注意
let time = CMTime(seconds: 30.0, preferredTimescale: 600)
player.seek(to: time, toleranceBefore: .zero, toleranceAfter: .zero)
// ピンポイントの静止画像が欲しい時はAVAssetImageGeneratorを使用
let asset = AVAsset(url: yourURL)
let generator = AVAssetImageGenerator(asset: asset)
generator.appliesPreferredTrackTransform = true
let image = try generator.copyCGImage(at: time, actualTime: nil)
まとめ
これでAVFoundationのまとめは終了、GPT君にも記事構成を考えてもらったり、自分も多く学びになりました。
コードはやはり手を動かして学ぶことが一番、座学だけでなく座学 + アウトプットでスキルを高めていきましょう。