Skip to content

AVKit

Audio/Video functionality for Skip Lite apps.

See what API is included here.

The SkipAV framework provides a small subset of the AVKit and AVFoundation frameworks as well as a SwiftUI.VideoPlayer component for Android based on the androidx.media3 package’s ExoPlayer.

To include this framework in your project, add the following dependency to your Package.swift file:

let package = Package(
name: "my-package",
products: [
.library(name: "MyProduct", targets: ["MyTarget"]),
],
dependencies: [
.package(url: "https://source.skip.tools/skip-av.git", from: "1.0.0"),
],
targets: [
.target(name: "MyTarget", dependencies: [
.product(name: "SkipAV", package: "skip-av")
])
]
)

SkipAV depends on the skip transpiler plugin and the SkipUI package.

SkipAV is part of the core Skip Core Frameworks and is not intended to be imported directly. The transpiler includes import skip.av.* in generated Kotlin for any Swift source that imports the AVKit or AVFoundation frameworks.

import SwiftUI
import AVKit
struct PlayerView: View {
@State var player = AVPlayer(playerItem: AVPlayerItem(url: URL(string: "https://skip.tools/assets/introduction.mov")!))
@State var isPlaying: Bool = false
var body: some View {
VStack {
Button {
isPlaying ? player.pause() : player.play()
isPlaying = !isPlaying
player.seek(to: .zero)
} label: {
Image(systemName: isPlaying ? "stop" : "play")
.padding()
}
VideoPlayer(player: player)
}
}
}

Some AVPlayerItem notifications, such as .didPlayToEndTimeNotification and .timeJumpedNotification, can be handled through the NotificationCenter.

For example:

VideoPlayer(player: player)
.onReceive(NotificationCenter.default.publisher(for: AVPlayerItem.didPlayToEndTimeNotification)) { event in
logger.info("didPlayToEndTimeNotification: \(event)")
}
.onReceive(NotificationCenter.default.publisher(for: AVPlayerItem.timeJumpedNotification)) { event in
logger.info("timeJumpedNotification: \(event)")
}

This framework also supports the ‘AVFoundation.AVAudioRecorder’ and AVFoundation.AVAudioPlayer’ APIs via Android’s MediaRecorder and MediaPlayer. These APIs can be used for audio recording and playback.

import SwiftUI
import AVFoundation
struct AudioPlayground: View {
@State var isRecording: Bool = false
@State var errorMessage: String? = nil
@State var audioRecorder: AVAudioRecorder?
@State var audioPlayer: AVAudioPlayer?
var body: some View {
#if SKIP
let context = androidx.compose.ui.platform.LocalContext.current
#endif
return VStack {
Button(isRecording ? "Stop Recording" : "Start Recording") {
self.isRecording ? self.stopRecording() : self.startRecording()
}
Button("Play Recording") {
try? self.playRecording()
}
if let errorMessage {
Text(errorMessage)
.foregroundColor(.red)
}
}
.padding()
#if SKIP
.onAppear {
requestAudioRecordingPermission(context: context)
}
#endif
}
var captureURL: URL {
get {
#if SKIP
let context = ProcessInfo.processInfo.androidContext
let file = java.io.File(context.filesDir, "recording.m4a")
return URL(fileURLWithPath: file.absolutePath)
#else
return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
.first!.appendingPathComponent("recording.m4a")
#endif
}
}
func startRecording() {
do {
#if !SKIP
setupAudioSession()
#endif
self.audioRecorder = try AVAudioRecorder(url: captureURL, settings: [AVFormatIDKey: Int(kAudioFormatMPEG4AAC), AVSampleRateKey: 12000, AVNumberOfChannelsKey: 1, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue])
} catch {
print(error.localizedDescription)
}
audioRecorder?.record()
isRecording = true
}
func stopRecording() {
isRecording = false
audioRecorder?.stop()
}
func playRecording() throws {
do {
guard FileManager.default.fileExists(atPath: captureURL.path) else {
errorMessage = "Recording file does not exist."
return
}
audioPlayer = try AVAudioPlayer(contentsOf: captureURL)
audioPlayer?.play()
errorMessage = ""
} catch {
logger.error("Could not play audio: \(error.localizedDescription)")
errorMessage = "Could not play audio: \(error.localizedDescription)"
}
}
#if SKIP
func requestAudioRecordingPermission(context: android.content.Context) {
guard let activity = context as? android.app.Activity else {
return
}
// You must also list these permissions in your Manifest.xml
let permissions = listOf(android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
androidx.core.app.ActivityCompat.requestPermissions(activity, permissions.toTypedArray(), 1)
}
#else
func setupAudioSession() {
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(.playAndRecord, mode: .default)
try session.setActive(true)
} catch {
errorMessage = "Failed to setup audio session: \(error.localizedDescription)"
}
}
#endif
}

The following table summarizes SkipAV’s API support on Android. Anything not listed here is likely not supported. Note that in your iOS-only code - i.e. code within #if !SKIP blocks - you can use any Swift API you want. Additionally:

Support levels:

  • ✅ – Full
  • 🟢 – High
  • 🟡 – Medium
  • 🟠 – Low
SupportAPI
🟢
AVAudioPlayer
  • init(contentsOf url: URL) throws
  • init(data: Data) throws
  • func prepareToPlay() -> Bool
  • func play()
  • func pause()
  • func stop()
  • var isPlaying: Bool
  • var duration: TimeInterval
  • var numberOfLoops: Int
  • var volume: Double
  • var rate: Double
  • var currentTime: TimeInterval
  • var url: URL?
  • var data: Data?
🟢
AVAudioRecorder
  • init(url: URL, settings: [String: Any]) throws
  • func prepareToRecord() -> Bool
  • func record()
  • func pause()
  • func stop()
  • func deleteRecording() -> Bool
  • var isRecording: Bool
  • var url: URL
  • var settings: [String: Any]
  • var currentTime: TimeInterval
  • func peakPower(forChannel channelNumber: Int) -> Float
  • func averagePower(forChannel channelNumber: Int) -> Double
🟠
AVPlayer
  • init()
  • init(playerItem: AVPlayerItem?)
  • init(url: URL)
  • func play()
  • func pause()
  • func seek(to time: CMTime)
🟠
AVPlayerItem
  • init(url: URL)
🟡
VideoPlayer
  • init(player: AVPlayer?)

We welcome contributions to SkipAV. The Skip product documentation includes helpful instructions and tips on local Skip library development.