Search code examples
androidaudiomediarecordernosuchmethoderror

No virtual method MediaRecorder.setOutputFile with API < 26?


I am recording audio in my app and I am struggling with the MediaRecorder class in

import android.media.MediaRecorder;

The problem is that the code compiles but does not seem to run on devices with API level < 26 because apparently the the function MediaRecorder.setOutputFormat() does not exists prior to API 26 according to the inline error message in Android Studio. Similarly when I run the code on an emulator with API 24 I get a crash with runtime error:

java.lang.NoSuchMethodError: No virtual method setOutputFile(Ljava/io/File;)V in class Landroid/media/MediaRecorder; or its super classes (declaration of 'android.media.MediaRecorder' appears in /system/framework/framework.jar)

As a quick fix I just increased my minimal SDK level in the gradle file to 26 and everthing works just fine. However, I don't want my app to be restricted to Android 8.0 and higher, because 7.x is still so popular.

So my first question is: How can it be that only one function of the whole MediaRecorder library is not available, how did people use the MediaRecorder before API 26? I cannot to find anything about that online.

Second question: How can I fix the problem, so I can record audio for API level 24+? Do I have to use another library?

This is the part of my Activity where the I am setting up the recorder and start the recording. Again, this works fine when the minimal SDK is 26...

private MediaRecorder recorder;

private void startRecording(){
    if(!recordingActive) {
        recorder = new MediaRecorder();
        recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        recorder.setAudioChannels(1);
        recorder.setOutputFormat(output_formats[currentFormat]);
        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        switch(activeAudioButton) {
            case AUDIO_BUTTON_A:
                recorder.setOutputFile(tempSoundFileA); // <-- 
                break;
            case AUDIO_BUTTON_B:
                recorder.setOutputFile(tempSoundFileB); // <-- 
                break;
        }
        recorder.setOnErrorListener(errorListener);
        recorder.setOnInfoListener(infoListener);
        try {
            recorder.prepare();
            recorder.start();
            recordingActive = true;
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    speakNowTextView.setVisibility(View.VISIBLE);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Here is my gradle file:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.my.app"
        minSdkVersion 24
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:support-v4:28.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.android.support:recyclerview-v7:28.0.0'
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.android.support:preference-v7:28.0.0'
    implementation 'com.android.support:recyclerview-v7:28.0.0'
}

Solution

  • When using an API level below 26, you can pass in a string containing the file path instead. If you specifically need to work with a File object, use file.getAbsolutePath() in Java or file.absolutePath in Kotlin.