Skip to content

Commit fec0836

Browse files
committed
feat: add transcription preview on iOS
1 parent 5eab140 commit fec0836

File tree

7 files changed

+108
-40
lines changed

7 files changed

+108
-40
lines changed

ios/Runner.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
1414
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
1515
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
16+
AD72C7632BBB1C3C00B50833 /* WhisperKitRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD72C7622BBB1C3C00B50833 /* WhisperKitRunner.swift */; };
1617
ADB650B7270B3C4300B98C66 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ADB650B6270B3C4300B98C66 /* HealthKit.framework */; };
1718
ADFF30672BB4FE6300B8F8C8 /* WhisperKit in Frameworks */ = {isa = PBXBuildFile; productRef = ADFF30662BB4FE6300B8F8C8 /* WhisperKit */; };
1819
FD4BDDFBB85CAB6754042A0B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E08CACAFD56E1EF268BBD8C5 /* Pods_Runner.framework */; };
@@ -48,6 +49,7 @@
4849
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
4950
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
5051
AD0869BB271438AF00225DF9 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Base.xcconfig; path = Flutter/Base.xcconfig; sourceTree = "<group>"; };
52+
AD72C7622BBB1C3C00B50833 /* WhisperKitRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhisperKitRunner.swift; sourceTree = "<group>"; };
5153
ADAEC2742738A8F200996377 /* RunnerRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerRelease.entitlements; sourceTree = "<group>"; };
5254
ADB650B5270B3C4300B98C66 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
5355
ADB650B6270B3C4300B98C66 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; };
@@ -124,6 +126,7 @@
124126
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
125127
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
126128
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
129+
AD72C7622BBB1C3C00B50833 /* WhisperKitRunner.swift */,
127130
);
128131
path = Runner;
129132
sourceTree = "<group>";
@@ -312,6 +315,7 @@
312315
isa = PBXSourcesBuildPhase;
313316
buildActionMask = 2147483647;
314317
files = (
318+
AD72C7632BBB1C3C00B50833 /* WhisperKitRunner.swift in Sources */,
315319
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
316320
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
317321
);

ios/Runner/AppDelegate.swift

+1-33
Original file line numberDiff line numberDiff line change
@@ -14,39 +14,7 @@ import WhisperKit
1414
}
1515

1616
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
17-
18-
let transcriptionChannel = FlutterMethodChannel(
19-
name: "lotti/transcribe",
20-
binaryMessenger: controller.binaryMessenger)
21-
22-
23-
transcriptionChannel.setMethodCallHandler { (call, result) in
24-
switch call.method {
25-
case "transcribe":
26-
guard let args = call.arguments as? [String: Any] else { return }
27-
let audioFilePath = args["audioFilePath"] as! String
28-
29-
Task {
30-
let model = "small"
31-
let pipe = try? await WhisperKit(model: model, verbose: true, prewarm: true)
32-
33-
let transcription = try? await pipe!.transcribe(
34-
audioPath: audioFilePath,
35-
decodeOptions: DecodingOptions(
36-
task: DecodingTask.transcribe,
37-
usePrefillPrompt: false
38-
))
39-
40-
let text = transcription?.text
41-
let language = transcription?.language
42-
43-
let data = [language, model, text]
44-
result(data)
45-
}
46-
default:
47-
result(FlutterMethodNotImplemented)
48-
}
49-
}
17+
let _ = WhisperKitRunner(binaryMessenger: controller.binaryMessenger)
5018

5119
GeneratedPluginRegistrant.register(with: self)
5220
return super.application(application, didFinishLaunchingWithOptions: launchOptions)

ios/Runner/WhisperKitRunner.swift

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import Foundation
2+
import Flutter
3+
import WhisperKit
4+
5+
public class WhisperKitRunner: NSObject, FlutterStreamHandler {
6+
let transcriptionChannelName = "lotti/transcribe"
7+
let transcriptionProgressChannelName = "lotti/transcribe-progress"
8+
let model = "small"
9+
10+
private var eventSink: FlutterEventSink?
11+
private let transcriptionChannel: FlutterMethodChannel
12+
private let transcriptionProgressChannel: FlutterEventChannel
13+
14+
private var whisperKit: WhisperKit?
15+
16+
init(binaryMessenger: FlutterBinaryMessenger) {
17+
transcriptionChannel = FlutterMethodChannel(
18+
name: transcriptionChannelName,
19+
binaryMessenger: binaryMessenger)
20+
21+
transcriptionProgressChannel = FlutterEventChannel(name: transcriptionProgressChannelName,
22+
binaryMessenger: binaryMessenger)
23+
24+
super.init()
25+
26+
transcriptionChannel.setMethodCallHandler { (call, result) in
27+
switch call.method {
28+
case "transcribe":
29+
guard let args = call.arguments as? [String: Any] else { return }
30+
let audioFilePath = args["audioFilePath"] as! String
31+
32+
Task {
33+
if (self.whisperKit == nil) {
34+
if (self.eventSink != nil) {
35+
self.eventSink!(["initializing model...", ""])
36+
}
37+
38+
self.whisperKit = try? await WhisperKit(model: self.model,
39+
verbose: true,
40+
prewarm: true)
41+
}
42+
43+
let transcription = try? await self.whisperKit!.transcribe(
44+
audioPath: audioFilePath,
45+
decodeOptions: DecodingOptions(
46+
task: DecodingTask.transcribe,
47+
usePrefillPrompt: false
48+
),
49+
callback: self.sendTranscriptionProgressEvent
50+
)
51+
52+
let text = transcription?.text
53+
let language = transcription?.language
54+
55+
let data = [language, self.model, text]
56+
result(data)
57+
}
58+
default:
59+
result(FlutterMethodNotImplemented)
60+
}
61+
}
62+
63+
transcriptionProgressChannel.setStreamHandler(self)
64+
}
65+
66+
private func sendTranscriptionProgressEvent(progress: TranscriptionProgress)->Bool? {
67+
guard let eventSink = eventSink else {
68+
return nil
69+
}
70+
71+
eventSink([progress.text,
72+
progress.timings.pipelineStart.formatted()])
73+
return nil
74+
}
75+
76+
public func onListen(withArguments arguments: Any?,
77+
eventSink: @escaping FlutterEventSink) -> FlutterError? {
78+
self.eventSink = eventSink
79+
return nil
80+
}
81+
82+
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
83+
eventSink = nil
84+
return nil
85+
}
86+
}

lib/widgets/audio/audio_recorder.dart

+12-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import 'package:flutter/material.dart';
22
import 'package:flutter_bloc/flutter_bloc.dart';
33
import 'package:lotti/blocs/audio/recorder_cubit.dart';
44
import 'package:lotti/blocs/audio/recorder_state.dart';
5+
import 'package:lotti/database/database.dart';
56
import 'package:lotti/get_it.dart';
67
import 'package:lotti/services/nav_service.dart';
78
import 'package:lotti/themes/theme.dart';
9+
import 'package:lotti/utils/consts.dart';
810
import 'package:lotti/widgets/audio/transcription_progress_modal.dart';
911
import 'package:lotti/widgets/audio/vu_meter.dart';
1012
import 'package:visibility_detector/visibility_detector.dart';
@@ -31,8 +33,16 @@ class AudioRecorderWidget extends StatelessWidget {
3133

3234
Future<void> stop() async {
3335
await cubit.stop();
34-
if (!context.mounted) return;
35-
await TranscriptionProgressModal.show(context);
36+
37+
final autoTranscribe = await getIt<JournalDb>().getConfigFlag(
38+
autoTranscribeFlag,
39+
);
40+
41+
if (autoTranscribe) {
42+
if (!context.mounted) return;
43+
await TranscriptionProgressModal.show(context);
44+
}
45+
3646
getIt<NavService>().beamBack();
3747
}
3848

macos/Runner/MainFlutterWindow.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ class MainFlutterWindow: NSWindow {
1313

1414
super.awakeFromNib()
1515

16-
let _ = WhisperKitRunner(flutterEngine: flutterViewController.engine)
16+
let _ = WhisperKitRunner(binaryMessenger: flutterViewController.engine.binaryMessenger)
1717
}
1818
}

macos/Runner/WhisperKitRunner.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ public class WhisperKitRunner: NSObject, FlutterStreamHandler {
1313

1414
private var whisperKit: WhisperKit?
1515

16-
init(flutterEngine: FlutterEngine) {
16+
init(binaryMessenger: FlutterBinaryMessenger) {
1717
transcriptionChannel = FlutterMethodChannel(
1818
name: transcriptionChannelName,
19-
binaryMessenger: flutterEngine.binaryMessenger)
19+
binaryMessenger: binaryMessenger)
2020

2121
transcriptionProgressChannel = FlutterEventChannel(name: transcriptionProgressChannelName,
22-
binaryMessenger: flutterEngine.binaryMessenger)
22+
binaryMessenger: binaryMessenger)
2323

2424
super.init()
2525

pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: lotti
22
description: Achieve your goals and keep your data private with Lotti.
33
publish_to: 'none'
4-
version: 0.9.443+2424
4+
version: 0.9.443+2425
55

66
msix_config:
77
display_name: LottiApp

0 commit comments

Comments
 (0)