berict/Tapad

View on GitHub
app/src/main/java/com/bedrock/padder/model/sound/Sound.java

Summary

Maintainability
B
4 hrs
Test Coverage
package com.bedrock.padder.model.sound;

import android.media.MediaMetadataRetriever;
import android.media.SoundPool;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

public class Sound {

    final String TAG = "Sound";

    boolean isLooping = false;
    boolean isPlaying = false;

    private int playingThreadCount = 0;

    boolean canLoop = true;

    private int soundPoolId = 0;

    private int streamId = 0;

    private int duration = -1;

    private long startTime = -1;

    private SoundPool soundPool;

    private SoundListener listener;

    private Sound sound = this;

    private Handler handler;

    public Sound(SoundPool soundPool, String path, MediaMetadataRetriever mmr) {
        this.soundPool = soundPool;
        this.duration = getDurationFromFile(path, mmr);
        this.load(path);
        handler = new Handler(Looper.getMainLooper());
    }

    public Sound() {
        // empty initializer for null pads
        this.soundPool = null;
        handler = new Handler(Looper.getMainLooper());
    }

    private int getDurationFromFile(String path, MediaMetadataRetriever mmr) {
        if (path != null) {
            try {
                mmr.setDataSource(path);
                int duration = Integer.parseInt(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
                if (duration > 0) {
                    if (duration > 8000) {
                        Log.w(TAG, "Sounds longer than 8 seconds will have cutting issues");
                    }
                    return duration;
                } else {
                    Log.e(TAG, "MediaMetadataRetriever didn't receive duration from file metadata");
                    return 500;
                }
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
                return 500;
            }
        } else {
            return 500;
        }
    }

    void play() {
        try {
            Log.i(TAG, "Attempted to play " + soundPoolId);
            if (isLooping && canLoop) {
                canLoop = false;
                streamId = soundPool.play(soundPoolId, 1, 1, 1, -1, 1);
            } else {
                streamId = soundPool.play(soundPoolId, 1, 1, 1, 0, 1);
            }

            playingThreadCount++;

            startTime = System.currentTimeMillis();

            if (listener != null) {
                isPlaying = true;
                listener.onSoundStart(sound, playingThreadCount);

                if (isLooping) {
                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            listener.onSoundLoop(sound);
                            handler.postDelayed(this, getDuration());
                        }
                    }, getDuration());
                } else {
                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            isPlaying = false;
                            playingThreadCount--;
                            listener.onSoundEnd(sound, playingThreadCount);
                        }
                    }, getDuration());
                }
            }
        } catch (NullPointerException e) {
            Log.e(TAG, "Sound was not initialized");
            e.getMessage();
        }
    }

    void stop() {
        canLoop = true;
        try {
            if (streamId != 0) {
                soundPool.stop(streamId);
                Log.i("Sound", "Stopped sound " + streamId);
            } else {
                throw new NullStreamException();
            }

            // remove loop callbacks
            handler.removeCallbacksAndMessages(null);
            if (listener != null) {
                isPlaying = false;
                playingThreadCount--;
                listener.onSoundStop(this, (int) getCurrentPosition(), getCurrentCompletion());
            }
        } catch (NullStreamException e) {
            if (isPlaying) {
                Log.e("Sound", "Error on stopping sound " + streamId);
                e.printStackTrace();
            }
        } catch (NullPointerException e) {
            Log.e(TAG, "Sound was not initialized");
            e.printStackTrace();
        }
    }

    void loop() {
        if (this.isLooping == false) {
            // was not looping
            this.isLooping = true;
            play();
        } else {
            // stop looping
            this.isLooping = false;
            stop();
        }
    }

    void loop(boolean isLoop) {
        if (isLoop == true) {
            this.isLooping = true;
            play();
        } else {
            this.isLooping = false;
            stop();
        }
    }

    void load(String path) {
        if (path != null) {
            this.soundPoolId = soundPool.load(path, 1);
            Log.d(TAG, "Sound [" + path + "] length is " + duration + "ms");
        }
    }

    void unload() {
        if (soundPoolId != 0 && soundPool != null) {
            this.soundPool.unload(soundPoolId);
            soundPoolId = 0;
            streamId = 0;
        }
    }

    int getDuration() {
        return duration;
    }

    long getCurrentPosition() {
        return System.currentTimeMillis() - startTime;
    }

    float getCurrentCompletion() {
        return (int) getCurrentPosition() / (float) duration;
    }

    void setRate(float rate) {
        try {
            if (streamId != 0) {
                soundPool.setRate(streamId, rate);
                // change the duration to the corresponding rate
                duration = (int) (duration / rate);
            } else {
                throw new NullStreamException();
            }
        } catch (NullStreamException e) {
            e.getMessage();
        }
    }

    public void setSoundListener(SoundListener listener) {
        this.listener = listener;
    }

    private class NullStreamException extends Exception {
        NullStreamException() {
            super("streamID is not initialized");
        }
    }

    public interface SoundListener {

        void onSoundStart(Sound sound, int playingThreadCount);

        void onSoundEnd(Sound sound, int playingThreadCount);

        void onSoundStop(Sound sound, int position, float completion);

        void onSoundLoop(Sound sound);
    }
}