Last active
November 25, 2025 14:11
-
-
Save lanserxt/77780783adfa179c7a009042b9cf5a95 to your computer and use it in GitHub Desktop.
iOS 26: SpeechRecognizer example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // 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() | |
| } |
Author
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
Sure, https://drive.google.com/file/d/1FyOyRYeFNWaxpRNmdcqVgLqQWbw6a302/view