├── .gitignore ├── support-compat-28.0.0.aar ├── requirements.txt ├── audio_recorder.kv ├── helpers.py └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | /.buildozer 2 | /venv 3 | /bin 4 | .idea 5 | __pycache__ -------------------------------------------------------------------------------- /support-compat-28.0.0.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelus33/kivy-audio-recorder/HEAD/support-compat-28.0.0.aar -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | docutils 2 | pygments 3 | # pypiwin32 4 | # kivy_deps.sdl2 5 | # kivy_deps.glew 6 | # kivy_deps.gstreamer 7 | # kivy_deps.angle 8 | pygame 9 | kivy 10 | https://github.com/kivymd/KivyMD/archive/master.zip 11 | pyjnius 12 | buildozer -------------------------------------------------------------------------------- /audio_recorder.kv: -------------------------------------------------------------------------------- 1 | 2 | orientation: 'vertical' 3 | Label: 4 | id: display_label 5 | text: '00:00' 6 | BoxLayout: 7 | size_hint: 1, .2 8 | TextInput: 9 | id: user_input 10 | text: '5' 11 | disabled: duration_switch.active == False # IF SWITCH IS OFF TEXTINPUT IS DISABLED 12 | on_text: root.enforce_numeric() 13 | 14 | Switch: 15 | id: duration_switch 16 | 17 | BoxLayout: 18 | Button: 19 | id: start_button 20 | text: 'Start Recording' 21 | on_release: root.start_recording_clock() 22 | 23 | Button: 24 | id: stop_button 25 | text: 'Stop Recording' 26 | on_release: root.stop_recording() 27 | disabled: True -------------------------------------------------------------------------------- /helpers.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kivy import Logger, platform 3 | 4 | PythonActivity = None 5 | if platform == "android": 6 | PythonActivity = autoclass("org.kivy.android.PythonActivity").mActivity 7 | Context = autoclass('android.content.Context') 8 | ContextCompat = autoclass('android.support.v4.content.ContextCompat') 9 | 10 | 11 | def check_permission(permission, activity=None): 12 | if platform == "android": 13 | activity = PythonActivity 14 | if not activity: 15 | return False 16 | 17 | permission_status = ContextCompat.checkSelfPermission(activity, 18 | permission) 19 | 20 | Logger.info(permission_status) 21 | permission_granted = 0 == permission_status 22 | Logger.info("Permission Status: {}".format(permission_granted)) 23 | return permission_granted 24 | 25 | 26 | def ask_permission(permission): 27 | PythonActivity.requestPermissions([permission]) 28 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kivy import platform 3 | from kivy.app import App 4 | from kivy.clock import Clock 5 | from kivy.lang import Builder 6 | from kivy.uix.boxlayout import BoxLayout 7 | from datetime import datetime 8 | from helpers import check_permission 9 | # only works in build 10 | if platform == "android": 11 | from android.permissions import request_permissions 12 | from android.storage import primary_external_storage_path 13 | 14 | Builder.load_file("audio_recorder.kv") 15 | 16 | 17 | class MyRecorder: 18 | """ Main class for using a Recorder for Android apps """ 19 | required_permissions = [ 20 | "android.permission.WRITE_EXTERNAL_STORAGE", 21 | "android.permission.READ_EXTERNAL_STORAGE", 22 | "android.permission.RECORD_AUDIO", 23 | ] 24 | 25 | def __init__(self): 26 | if not platform == "android": 27 | return 28 | self.MediaRecorder = autoclass('android.media.MediaRecorder') 29 | self.AudioSource = autoclass('android.media.MediaRecorder$AudioSource') 30 | self.OutputFormat = autoclass('android.media.MediaRecorder$OutputFormat') 31 | self.AudioEncoder = autoclass('android.media.MediaRecorder$AudioEncoder') 32 | # 33 | self.ask_permissions() 34 | 35 | if self.check_required_permission(): 36 | self.create_recorder() 37 | 38 | def ask_permissions(self): 39 | request_permissions(self.required_permissions) 40 | 41 | def check_required_permission(self): 42 | permissions = self.required_permissions 43 | # 44 | has_permissions = True 45 | for permission in permissions: 46 | if not check_permission(permission): 47 | has_permissions = False 48 | 49 | return has_permissions 50 | 51 | def create_recorder(self): 52 | now = datetime.now() 53 | dt_string = now.strftime("%d/%m/%Y %H:%M:%S") \ 54 | .replace(" ", "").replace(":", "").replace("/", "") 55 | primary_ext_storage = primary_external_storage_path() 56 | path = primary_ext_storage + '/{}.3gp'.format(dt_string) 57 | # 58 | self.recorder = self.MediaRecorder() 59 | self.recorder.setAudioSource(self.AudioSource.MIC) 60 | self.recorder.setOutputFormat(self.OutputFormat.THREE_GPP) 61 | self.recorder.setOutputFile(path) 62 | self.recorder.setAudioEncoder(self.AudioEncoder.AMR_NB) 63 | self.recorder.prepare() 64 | 65 | def get_recorder(self): 66 | if not hasattr(self, "recorder"): 67 | self.create_recorder() 68 | return self.recorder 69 | 70 | def remove_recorder(self): 71 | delattr(self, "recorder") 72 | 73 | 74 | class AudioApp(App): 75 | def build(self): 76 | return AudioTool() 77 | 78 | # GUI for the example 79 | class AudioTool(BoxLayout): 80 | def __init__(self, **kwargs): 81 | super(AudioTool, self).__init__(**kwargs) 82 | # 83 | self.start_button = self.ids['start_button'] 84 | self.stop_button = self.ids['stop_button'] 85 | self.display_label = self.ids['display_label'] 86 | self.switch = self.ids['duration_switch'] 87 | self.user_input = self.ids['user_input'] 88 | self.recorder = MyRecorder() 89 | 90 | def enforce_numeric(self): 91 | """Make sure the textinput only accepts numbers""" 92 | if self.user_input.text.isdigit() == False: 93 | digit_list = [num for num in self.user_input.text if num.isdigit()] 94 | self.user_input.text = "".join(digit_list) 95 | 96 | def start_recording_clock(self): 97 | recorder = self.recorder 98 | if not recorder.check_required_permission(): 99 | return 100 | self.mins = 0 # Reset the minutes 101 | self.zero = 1 # Reset if the function gets called more than once 102 | self.duration = int(self.user_input.text) # Take the input from the user and convert to a number 103 | Clock.schedule_interval(self.update_display, 1) 104 | self.start_button.disabled = True # Prevents the user from clicking start again which may crash the program 105 | self.stop_button.disabled = False 106 | self.switch.disabled = True # Switch disabled when start is pressed 107 | Clock.schedule_once(self.start_recording) # start the recording 108 | 109 | def start_recording(self, dt): 110 | recorder = self.recorder 111 | if recorder.check_required_permission(): 112 | recorder.get_recorder().start() 113 | else: 114 | recorder.ask_permissions() 115 | 116 | def stop_recording(self): 117 | recorder = self.recorder 118 | if recorder: 119 | Clock.unschedule(self.update_display) 120 | recorder.get_recorder().stop() 121 | recorder.get_recorder().reset() 122 | recorder.get_recorder().release() 123 | # we need to do this in order to make the object reusable 124 | recorder.remove_recorder() 125 | # 126 | Clock.unschedule(self.start_recording) 127 | self.display_label.text = 'Finished Recording!' 128 | self.start_button.disabled = False 129 | self.stop_button.disabled = True 130 | self.switch.disabled = False 131 | 132 | def update_display(self, dt): 133 | if self.switch.active == False: 134 | if self.zero < 60 and len(str(self.zero)) == 1: 135 | self.display_label.text = '0' + str(self.mins) + ':0' + str(self.zero) 136 | self.zero += 1 137 | 138 | elif self.zero < 60 and len(str(self.zero)) == 2: 139 | self.display_label.text = '0' + str(self.mins) + ':' + str(self.zero) 140 | self.zero += 1 141 | 142 | elif self.zero == 60: 143 | self.mins += 1 144 | self.display_label.text = '0' + str(self.mins) + ':00' 145 | self.zero = 1 146 | 147 | elif self.switch.active == True: 148 | if self.duration == 0: # 0 149 | self.display_label.text = 'Recording Finished!' 150 | self.stop_recording() 151 | elif self.duration > 0 and len(str(self.duration)) == 1: # 0-9 152 | self.display_label.text = '00' + ':0' + str(self.duration) 153 | self.duration -= 1 154 | 155 | elif self.duration > 0 and self.duration < 60 and len(str(self.duration)) == 2: # 0-59 156 | self.display_label.text = '00' + ':' + str(self.duration) 157 | self.duration -= 1 158 | 159 | elif self.duration >= 60 and len(str(self.duration % 60)) == 1: # EG 01:07 160 | self.mins = self.duration / 60 161 | self.display_label.text = '0' + str(self.mins) + ':0' + str(self.duration % 60) 162 | self.duration -= 1 163 | 164 | elif self.duration >= 60 and len(str(self.duration % 60)) == 2: # EG 01:17 165 | self.mins = self.duration / 60 166 | self.display_label.text = '0' + str(self.mins) + ':' + str(self.duration % 60) 167 | self.duration -= 1 168 | 169 | 170 | if __name__ == '__main__': 171 | AudioApp().run() 172 | --------------------------------------------------------------------------------