Skip to content

Instantly share code, notes, and snippets.

@lanserxt
Last active November 25, 2025 14:11
Show Gist options
  • Select an option

  • Save lanserxt/77780783adfa179c7a009042b9cf5a95 to your computer and use it in GitHub Desktop.

Select an option

Save lanserxt/77780783adfa179c7a009042b9cf5a95 to your computer and use it in GitHub Desktop.
iOS 26: SpeechRecognizer example
//
// SpeechTranscribeView.swift
// WWDC25Demo
//
// Created by Anton Gubarenko on 20.07.2025.
//
import SwiftUI
import Playgrounds
import Speech
/// Demo View for SpeechRecognizer
struct SpeechTranscribeView: View {
@State private var transcribedText: AttributedString = ""
var body: some View {
VStack {
Text("Transcribe speech")
ScrollView(.vertical, content: {
Text(transcribedText)
.frame(maxHeight: .infinity)
.multilineTextAlignment(.leading)
})
Spacer()
}.task{
do {
try await transcribeFile()
} catch {
print(error)
}
}
}
/// Main recognition task
private func transcribeFile() async throws {
var finalStr: AttributedString = ""
let fileUrl = try loadAudioFile(named: "dragon_sample", withExtension: "mp3")
let locale = Locale(identifier: "it_IT")
let transcriber = transcriber(for: locale)
print("transcriber ready")
try await ensureModel(transcriber: transcriber, locale: locale)
print("ensureModel ready")
let analyzer = SpeechAnalyzer(modules: [transcriber])
if let lastSample = try await analyzer.analyzeSequence(from: AVAudioFile(forReading: fileUrl)) {
try await analyzer.finalizeAndFinish(through: lastSample)
} else {
await analyzer.cancelAndFinishNow()
}
for try await recResponse in transcriber.results {
if recResponse.isFinal {
finalStr.append(recResponse.text)
}
}
transcribedText = finalStr
}
/// Loading audio file
/// - Parameters:
/// - name: file name
/// - ext: extension
/// - Returns: URL of file
private func loadAudioFile(named name: String, withExtension ext: String) throws -> URL {
guard let url = Bundle.main.url(forResource: name, withExtension: ext) else {
throw NSError(domain: "SpeechAnalyzerExample", code: 1, userInfo: [NSLocalizedDescriptionKey: "Audio file not found in bundle."])
}
return url
}
/// Transcriber for locale
/// - Parameter locale: locale to recognize
/// - Returns: Transcriber
private func transcriber(for locale: Locale) -> SpeechTranscriber {
SpeechTranscriber(locale: locale,
transcriptionOptions: [],
reportingOptions: [.volatileResults],
attributeOptions: [.audioTimeRange])
}
/// Chekc that model exists
/// - Parameters:
/// - transcriber: transcriber to use for this model
/// - locale: locale to recognize
private func ensureModel(transcriber: SpeechTranscriber, locale: Locale) async throws {
guard await supported(locale: locale) else {
throw NSError(domain: "SpeechAnalyzerExample", code: 1, userInfo: [NSLocalizedDescriptionKey: "Locale not supported"])
}
if await installed(locale: locale) {
return
} else {
try await downloadIfNeeded(for: transcriber)
}
}
/// Is locale supported
/// - Parameter locale: locale to recognize
/// - Returns: true if supported, fasle - else
private func supported(locale: Locale) async -> Bool {
print(SpeechTranscriber.isAvailable)
guard SpeechTranscriber.isAvailable else {return false}
let supported = await DictationTranscriber.supportedLocales
print("Have locales \(supported)")
return supported.map { $0.identifier(.bcp47) }.contains(locale.identifier(.bcp47))
}
/// Is locale file installed locally
/// - Parameter locale: locale to recognize
/// - Returns: true is installed locally
private func installed(locale: Locale) async -> Bool {
let installed = await Set(SpeechTranscriber.installedLocales)
return installed.map { $0.identifier(.bcp47) }.contains(locale.identifier(.bcp47))
}
/// Download module assets
/// - Parameter module: module to use
private func downloadIfNeeded(for module: SpeechTranscriber) async throws {
if let downloader = try await AssetInventory.assetInstallationRequest(supporting: [module]) {
try await downloader.downloadAndInstall()
}
}
}
#Preview {
SpeechTranscribeView()
}
@JoeFerrucci
Copy link

The sample mp3 would be nice ๐Ÿ™‚

@lanserxt
Copy link
Author

lanserxt commented Nov 9, 2025

The sample mp3 would be nice ๐Ÿ™‚

Sure, https://drive.google.com/file/d/1FyOyRYeFNWaxpRNmdcqVgLqQWbw6a302/view

@lanserxt
Copy link
Author

Unified logic + SpeechDetector added

//
//  SpeechTranscribeView.swift
//  WWDC25Demo
//
//  Created by Anton Gubarenko on 20.07.2025.
//

import SwiftUI
import Playgrounds
import Speech

/// Demo View for SpeechRecognizer
struct SpeechTranscribeView: View {
    
    @State private var transcribedText: AttributedString = ""
    
    var body: some View {
        VStack {
            Text("Transcribe speech")
            ScrollView(.vertical, content: {
                Text(transcribedText)
                    .frame(maxHeight: .infinity)
                    .multilineTextAlignment(.leading)
            })
            Spacer()
        }.task{
            do {
                try await detectVoice()
            } catch {
                print(error)
            }
        }
        
    }
    
    /// Main recognition task
    private func transcribeFile() async throws {
        var finalStr: AttributedString = ""
        let fileUrl = try loadAudioFile(named: "dragon_sample", withExtension: "mp3")
        
        let locale = Locale(identifier: "it_IT")
        
        
        let transcriber = transcriber(for: locale)
        print("transcriber ready")
        try await ensureModel(transcriber: transcriber, locale: locale)
        print("ensureModel ready")
        let analyzer = SpeechAnalyzer(modules: [transcriber])
        
        
        if let lastSample = try await analyzer.analyzeSequence(from: AVAudioFile(forReading: fileUrl)) {
            try await analyzer.finalizeAndFinish(through: lastSample)
        } else {
            await analyzer.cancelAndFinishNow()
        }
        
        for try await recResponse in  transcriber.results {
            if recResponse.isFinal {
                finalStr.append(recResponse.text)
            }
        }
        transcribedText = finalStr
    }
    
    private func detectVoice() async throws {
        var finalStr: AttributedString = ""
        let fileUrl = try loadAudioFile(named: "dragon_sample", withExtension: "mp3")
        
        let locale = Locale(identifier: "it_IT")
        
        let transcriber = transcriber(for: locale)
        let detector = detector(for: locale)
        
        print("detector ready")
        try await ensureModel(transcriber: transcriber, locale: locale)
        try await ensureModel(transcriber: detector, locale: locale)
        print("ensureModel ready")
        let analyzer = SpeechAnalyzer(modules: [detector, transcriber])
        
        
        
        if let lastSample = try await analyzer.analyzeSequence(from: AVAudioFile(forReading: fileUrl)) {
            try await analyzer.finalizeAndFinish(through: lastSample)
        } else {
            await analyzer.cancelAndFinishNow()
        }
        
                for try await recResponse in  transcriber.results {
                    if recResponse.isFinal {
                        finalStr.append(recResponse.text)
                    }
                }
        
//        for try await recResponse in  detector.results {
//            finalStr.append(AttributedString( "Is speach detected: \(recResponse.speechDetected)"))
//        }
        transcribedText = finalStr
    }
    
    /// Loading audio file
    /// - Parameters:
    ///   - name: file name
    ///   - ext: extension
    /// - Returns: URL of file
    private func loadAudioFile(named name: String, withExtension ext: String) throws -> URL {
        guard let url = Bundle.main.url(forResource: name, withExtension: ext) else {
            throw NSError(domain: "SpeechAnalyzerExample", code: 1, userInfo: [NSLocalizedDescriptionKey: "Audio file not found in bundle."])
        }
        return url
    }
    
    /// Transcriber for locale
    /// - Parameter locale: locale to recognize
    /// - Returns: Transcriber
    private func transcriber(for locale: Locale) -> SpeechTranscriber {
        SpeechTranscriber(locale: locale,
                          transcriptionOptions: [],
                          reportingOptions: [.volatileResults],
                          attributeOptions: [.audioTimeRange])
    }
    
    /// Detector for locale
    /// - Parameter locale: locale to recognize
    /// - Returns: detector
    private func detector(for locale: Locale) -> SpeechDetector {
        SpeechDetector(detectionOptions: .init(sensitivityLevel: .medium), reportResults: true)
        
    }
    
    /// Chekc that model exists
    /// - Parameters:
    ///   - transcriber: transcriber to use for this model
    ///   - locale: locale to recognize
    private func ensureModel(transcriber: any SpeechModule, locale: Locale) async throws {
        guard await supported(locale: locale) else {
            throw NSError(domain: "SpeechAnalyzerExample", code: 1, userInfo: [NSLocalizedDescriptionKey: "Locale not supported"])
        }
        
        if await installed(locale: locale) {
            return
        } else {
            try await downloadIfNeeded(for: transcriber)
        }
    }
    
    /// Is locale supported
    /// - Parameter locale: locale to recognize
    /// - Returns: true if supported, fasle - else
    private func supported(locale: Locale) async -> Bool {
        print(SpeechTranscriber.isAvailable)
        guard SpeechTranscriber.isAvailable else {return false}
        let supported = await DictationTranscriber.supportedLocales
        print("Have locales \(supported)")
        return supported.map { $0.identifier(.bcp47) }.contains(locale.identifier(.bcp47))
    }
    
    /// Is locale file installed locally
    /// - Parameter locale: locale to recognize
    /// - Returns: true is installed locally
    private func installed(locale: Locale) async -> Bool {
        let installed = await Set(SpeechTranscriber.installedLocales)
        return installed.map { $0.identifier(.bcp47) }.contains(locale.identifier(.bcp47))
    }
    
    /// Download module assets
    /// - Parameter module: module to use
    private func downloadIfNeeded(for module: any SpeechModule) async throws {
        if let downloader = try await AssetInventory.assetInstallationRequest(supporting: [module]) {
            try await downloader.downloadAndInstall()
        }
    }
}

#Preview {
    SpeechTranscribeView()
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment