Coder Social home page Coder Social logo

skip-av's Introduction

SkipAV

Audio/Video functionality for Skip apps.

See what API is included here.

About

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.

Dependencies

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

SkipAV is part of the core SkipStack 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.

Contributing

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

Examples

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)
        }
    }
}

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
}

API Support

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?)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.